From 797052835129139a6fa61c7ce8af638757b3943e Mon Sep 17 00:00:00 2001 From: sdjdd Date: Fri, 19 Jan 2024 12:42:26 +0800 Subject: [PATCH] feat(next/web): check reply language --- next/web/package-lock.json | 21 ++++++++ next/web/package.json | 1 + next/web/script/generateEnv.js | 1 + .../App/Admin/Tickets/Ticket/TicketDetail.tsx | 1 + .../Tickets/Ticket/components/ReplyEditor.tsx | 50 +++++++++++++------ next/web/src/global.d.ts | 1 + 6 files changed, 60 insertions(+), 15 deletions(-) diff --git a/next/web/package-lock.json b/next/web/package-lock.json index c65109794..a7ebb5957 100644 --- a/next/web/package-lock.json +++ b/next/web/package-lock.json @@ -43,6 +43,7 @@ "recoil": "^0.7.6", "remark-gfm": "^3.0.1", "throat": "^6.0.1", + "tinyld": "^1.3.4", "use-query-params": "^2.1.2" }, "devDependencies": { @@ -10253,6 +10254,21 @@ "node": "*" } }, + "node_modules/tinyld": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/tinyld/-/tinyld-1.3.4.tgz", + "integrity": "sha512-u26CNoaInA4XpDU+8s/6Cq8xHc2T5M4fXB3ICfXPokUQoLzmPgSZU02TAkFwFMJCWTjk53gtkS8pETTreZwCqw==", + "bin": { + "tinyld": "bin/tinyld.js", + "tinyld-heavy": "bin/tinyld-heavy.js", + "tinyld-light": "bin/tinyld-light.js" + }, + "engines": { + "node": ">= 12.10.0", + "npm": ">= 6.12.0", + "yarn": ">= 1.20.0" + } + }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -18543,6 +18559,11 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" }, + "tinyld": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/tinyld/-/tinyld-1.3.4.tgz", + "integrity": "sha512-u26CNoaInA4XpDU+8s/6Cq8xHc2T5M4fXB3ICfXPokUQoLzmPgSZU02TAkFwFMJCWTjk53gtkS8pETTreZwCqw==" + }, "tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", diff --git a/next/web/package.json b/next/web/package.json index 9ef4aab49..51bcedb17 100644 --- a/next/web/package.json +++ b/next/web/package.json @@ -46,6 +46,7 @@ "recoil": "^0.7.6", "remark-gfm": "^3.0.1", "throat": "^6.0.1", + "tinyld": "^1.3.4", "use-query-params": "^2.1.2" }, "devDependencies": { diff --git a/next/web/script/generateEnv.js b/next/web/script/generateEnv.js index ffde49db5..1cc0b27e7 100644 --- a/next/web/script/generateEnv.js +++ b/next/web/script/generateEnv.js @@ -8,6 +8,7 @@ const OPTIONAL_ENV_KEYS = [ 'ENABLE_LEANCLOUD_INTEGRATION', 'ALGOLIA_API_KEY', 'ENABLE_USER_CONFIRMATION', + 'ENABLE_REPLY_LANGUAGE_CHECK', ]; const CUSTOM_ENVS = { diff --git a/next/web/src/App/Admin/Tickets/Ticket/TicketDetail.tsx b/next/web/src/App/Admin/Tickets/Ticket/TicketDetail.tsx index 5ea72cfc9..fe6bf73ce 100644 --- a/next/web/src/App/Admin/Tickets/Ticket/TicketDetail.tsx +++ b/next/web/src/App/Admin/Tickets/Ticket/TicketDetail.tsx @@ -163,6 +163,7 @@ export function TicketDetail() { } onOperate={handleOperate} operating={operating} + ticketLanguage={ticket.language} /> diff --git a/next/web/src/App/Admin/Tickets/Ticket/components/ReplyEditor.tsx b/next/web/src/App/Admin/Tickets/Ticket/components/ReplyEditor.tsx index 25fb893f9..375880455 100644 --- a/next/web/src/App/Admin/Tickets/Ticket/components/ReplyEditor.tsx +++ b/next/web/src/App/Admin/Tickets/Ticket/components/ReplyEditor.tsx @@ -1,11 +1,12 @@ import { ClipboardEvent, Dispatch, SetStateAction, useMemo, useRef, useState } from 'react'; import { useDebounce } from 'react-use'; -import { Button, Empty, Input, Radio, Select, Tabs } from 'antd'; +import { Button, Empty, Input, Modal, Radio, Select, Tabs } from 'antd'; import { AiOutlineFile } from 'react-icons/ai'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { uniq } from 'lodash-es'; +import { TicketLanguages } from '@/i18n/locales'; import { useQuickReplies } from '@/api/quick-reply'; import { storage, useCurrentUser } from '@/leancloud'; import { LoadingCover } from '@/components/common'; @@ -13,25 +14,40 @@ import { useHoverMenu } from '@/App/Admin/components/HoverMenu'; import { Uploader, UploaderRef } from '@/App/Admin/components/Uploader'; import { useModal } from './useModal'; -interface ReplyInfo { +interface ReplyData { internal: boolean; content: string; fileIds: string[]; } interface ReplyEditorProps { - onSubmit: (replyInfo: ReplyInfo) => Promise; + onSubmit: (replyData: ReplyData) => Promise; onOperate: (action: string) => void; operating?: boolean; + ticketLanguage?: string; } -export function ReplyEditor({ onSubmit, onOperate, operating }: ReplyEditorProps) { +export function ReplyEditor({ onSubmit, onOperate, operating, ticketLanguage }: ReplyEditorProps) { const [mode, setType] = useState('public'); const [content, setContent] = useState(''); const uploaderRef = useRef(null!); const [submitting, setSubmitting] = useState(false); + const doSubmit = async (content: string, fileIds: string[]) => { + try { + setSubmitting(true); + await onSubmit({ + internal: mode === 'internal', + content, + fileIds, + }); + setContent(''); + uploaderRef.current.reset(); + } finally { + setSubmitting(false); + } + }; const handleSubmit = async () => { if (submitting) { return; @@ -50,18 +66,22 @@ export function ReplyEditor({ onSubmit, onOperate, operating }: ReplyEditorProps return alert('回复内容不能为空'); } - try { - setSubmitting(true); - await onSubmit({ - internal: mode === 'internal', - content: trimedContent, - fileIds, - }); - setContent(''); - uploaderRef.current.reset(); - } finally { - setSubmitting(false); + if (import.meta.env.VITE_ENABLE_REPLY_LANGUAGE_CHECK && trimedContent && ticketLanguage) { + const { detect } = await import('tinyld/light'); + const language = detect(trimedContent); + if (!ticketLanguage.startsWith(language)) { + Modal.confirm({ + title: '回复语言和工单语言不匹配', + content: `工单语言:${TicketLanguages[ticketLanguage]},回复语言:${TicketLanguages[language]}`, + okText: '继续回复', + cancelText: '返回', + onOk: () => doSubmit(trimedContent, fileIds), + }); + return; + } } + + doSubmit(trimedContent, fileIds); }; const [quickReplyModal, toggleQuickReplyModal] = useModal({ diff --git a/next/web/src/global.d.ts b/next/web/src/global.d.ts index 2189c2f7a..49b1bb818 100644 --- a/next/web/src/global.d.ts +++ b/next/web/src/global.d.ts @@ -9,6 +9,7 @@ interface ImportMetaEnv { VITE_ALGOLIA_API_KEY?: string; VITE_ENABLE_TAP_SUPPORT?: string; VITE_ENABLE_USER_CONFIRMATION?: string; + VITE_ENABLE_REPLY_LANGUAGE_CHECK?: string; } declare function docsearch(...args: any[]);