From b63cd1d916ee0f18745080a235847d5b4b20419f Mon Sep 17 00:00:00 2001 From: Cledwyn Lew Date: Wed, 22 Jul 2026 11:25:10 +0800 Subject: [PATCH] 0.10.0 --- package.json | 4 +- pnpm-lock.yaml | 114 ++++++++++-- src/i18n.ts | 35 ++++ src/locales/en-us.json | 18 ++ src/locales/zh-cn.json | 18 ++ src/main.tsx | 1 + .../MessageItem/components/EditActions.tsx | 6 +- .../MessageItem/components/MessageActions.tsx | 168 +++++++++++------- .../components/useMessageActions.ts | 13 +- src/pages/chat/MessageItem/index.tsx | 10 +- src/pages/chat/MessageList.tsx | 2 +- src/pages/chat/MsgSend.tsx | 7 +- src/pages/chat/index.tsx | 55 +++--- 13 files changed, 325 insertions(+), 126 deletions(-) create mode 100644 src/i18n.ts create mode 100644 src/locales/en-us.json create mode 100644 src/locales/zh-cn.json diff --git a/package.json b/package.json index f40394f..a281367 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,18 @@ "dependencies": { "@heroui/react": "2.8.3", "@heroui/theme": "2.4.21", - "@mujian/js-sdk": "0.0.6-beta.69", + "@mujian/js-sdk": "0.0.6-beta.88", "@tailwindcss/vite": "4.1.12", "ahooks": "3.9.5", "axios": "1.11.0", "dayjs": "1.11.18", + "i18next": "^25.8.4", "lodash-es": "4.17.21", "lucide-react": "^0.546.0", "motion": "12.23.12", "react": "19.1.1", "react-dom": "19.1.1", + "react-i18next": "^16.5.4", "react-icons": "5.5.0", "react-markdown": "10.1.0", "react-router": "7.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 738531f..cfa365c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: 2.4.21 version: 2.4.21(tailwindcss@4.1.12) '@mujian/js-sdk': - specifier: 0.0.6-beta.69 - version: 0.0.6-beta.69(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 0.0.6-beta.88 + version: 0.0.6-beta.88(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tailwindcss/vite': specifier: 4.1.12 version: 4.1.12(vite@7.1.2(@types/node@22.13.9)(jiti@2.5.1)(lightningcss@1.30.1)) @@ -29,6 +29,9 @@ importers: dayjs: specifier: 1.11.18 version: 1.11.18 + i18next: + specifier: ^25.8.4 + version: 25.8.4(typescript@5.8.3) lodash-es: specifier: 4.17.21 version: 4.17.21 @@ -44,6 +47,9 @@ importers: react-dom: specifier: 19.1.1 version: 19.1.1(react@19.1.1) + react-i18next: + specifier: ^16.5.4 + version: 16.5.4(i18next@25.8.4(typescript@5.8.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3) react-icons: specifier: 5.5.0 version: 5.5.0(react@19.1.1) @@ -61,7 +67,7 @@ importers: version: 1.0.7(tailwindcss@4.1.12) zustand: specifier: 5.0.8 - version: 5.0.8(@types/react@19.1.10)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + version: 5.0.8(@types/react@19.1.10)(immer@11.1.8)(react@19.1.1)(use-sync-external-store@1.6.0(react@19.1.1)) devDependencies: '@biomejs/biome': specifier: ^2.3.10 @@ -175,6 +181,10 @@ packages: resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -996,8 +1006,8 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - '@mujian/js-sdk@0.0.6-beta.69': - resolution: {integrity: sha512-hdiKHrbq+AngnV9e7woBTqnPhROJaejDgkFhh4IYpgAloIrarWuNbH1ZUvZU2G6Gqn1o4MwkymrXSmDXe9rMQA==} + '@mujian/js-sdk@0.0.6-beta.88': + resolution: {integrity: sha512-i7LqTQd+wyz4RGMZrLjnDrK7wHUzJz7idPXM87IiJtBLin+hZD7z5tLol3mFlLg5QTGwARBSjflpLHaPxD6jKA==} peerDependencies: react: ~19.1.1 react-dom: ~19.1.1 @@ -1877,8 +1887,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.4.2: + resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -2011,9 +2021,23 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + i18next@25.8.4: + resolution: {integrity: sha512-a9A0MnUjKvzjEN/26ZY1okpra9kA8MEwzYEz1BNm+IyxUKPRH6ihf0p7vj8YvULwZHKHl3zkJ6KOt4hewxBecQ==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -2327,6 +2351,22 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-i18next@16.5.4: + resolution: {integrity: sha512-6yj+dcfMncEC21QPhOTsW8mOSO+pzFmT6uvU7XXdvM/Cp38zJkmTeMeKmTrmCMD5ToT79FmiE/mRWiYWcJYW4g==} + peerDependencies: + i18next: '>= 25.6.2' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-icons@5.5.0: resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} peerDependencies: @@ -2522,8 +2562,13 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@13.0.2: + resolution: {integrity: sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==} hasBin: true vfile-message@4.0.3: @@ -2592,6 +2637,10 @@ packages: yaml: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2720,6 +2769,8 @@ snapshots: '@babel/runtime@7.28.3': {} + '@babel/runtime@7.28.6': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -3942,17 +3993,18 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mujian/js-sdk@0.0.6-beta.69(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mujian/js-sdk@0.0.6-beta.88(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@adobe/css-tools': 4.4.4 ahooks: 3.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - dompurify: 3.3.1 + dompurify: 3.4.2 + immer: 11.1.8 lodash-es: 4.17.21 postmate: 1.5.2 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) showdown: 2.1.0 - uuid: 13.0.0 + uuid: 13.0.2 virtua: 0.45.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) transitivePeerDependencies: - solid-js @@ -5124,7 +5176,7 @@ snapshots: dependencies: dequal: 2.0.3 - dompurify@3.3.1: + dompurify@3.4.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -5281,8 +5333,20 @@ snapshots: dependencies: '@types/hast': 3.0.4 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + html-url-attributes@3.0.1: {} + i18next@25.8.4(typescript@5.8.3): + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + typescript: 5.8.3 + + immer@11.1.8: {} + inline-style-parser@0.2.4: {} input-otp@1.4.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): @@ -5678,6 +5742,17 @@ snapshots: react-fast-compare@3.2.2: {} + react-i18next@16.5.4(i18next@25.8.4(typescript@5.8.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3): + dependencies: + '@babel/runtime': 7.28.6 + html-parse-stringify: 3.0.1 + i18next: 25.8.4(typescript@5.8.3) + react: 19.1.1 + use-sync-external-store: 1.6.0(react@19.1.1) + optionalDependencies: + react-dom: 19.1.1(react@19.1.1) + typescript: 5.8.3 + react-icons@5.5.0(react@19.1.1): dependencies: react: 19.1.1 @@ -5905,7 +5980,11 @@ snapshots: dependencies: react: 19.1.1 - uuid@13.0.0: {} + use-sync-external-store@1.6.0(react@19.1.1): + dependencies: + react: 19.1.1 + + uuid@13.0.2: {} vfile-message@4.0.3: dependencies: @@ -5936,14 +6015,17 @@ snapshots: jiti: 2.5.1 lightningcss: 1.30.1 + void-elements@3.1.0: {} + yallist@3.1.1: {} yallist@5.0.0: {} - zustand@5.0.8(@types/react@19.1.10)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)): + zustand@5.0.8(@types/react@19.1.10)(immer@11.1.8)(react@19.1.1)(use-sync-external-store@1.6.0(react@19.1.1)): optionalDependencies: '@types/react': 19.1.10 + immer: 11.1.8 react: 19.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) + use-sync-external-store: 1.6.0(react@19.1.1) zwitch@2.0.4: {} diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..7cd947d --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,35 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import enUs from './locales/en-us.json'; +import zhCn from './locales/zh-cn.json'; + +const resources = { + 'en-US': { translation: enUs }, + 'zh-CN': { translation: zhCn }, +}; + +const defaultLng = (() => { + const paramsLang = new URLSearchParams(window.location.search).get('lang'); + const browserLang = navigator.language.toLowerCase(); + if (paramsLang) { + const langSection = paramsLang.toLowerCase().replace('_', '-').split('-'); + const normalized = langSection[0] + '-' + langSection[1].toUpperCase(); + return normalized; + } + if (browserLang.startsWith('zh')) return 'zh-CN'; + return 'en-US'; +})(); + +i18n.use(initReactI18next).init({ + resources, + lng: defaultLng, + fallbackLng: 'zh-CN', + nonExplicitSupportedLngs: true, + load: 'currentOnly', + interpolation: { + escapeValue: false, + }, +}); + +export default i18n; diff --git a/src/locales/en-us.json b/src/locales/en-us.json new file mode 100644 index 0000000..b8c40f0 --- /dev/null +++ b/src/locales/en-us.json @@ -0,0 +1,18 @@ +{ + "edit": "Edit", + "copy": "Copy", + "refresh": "Refresh", + "continue": "Continue", + "delete": "Delete", + "save": "Save", + "cancel": "Cancel", + "copySuccess": "Copy Success", + "copyFailed": "Copy Failed, Please Try Again.", + "generating": "Generating...", + "sendWithEnter": "Send with Enter", + "newMessageExists": "New Message Exists, Cannot Switch Opening Message", + "assertionFailed": "Assertion Failed: No Opening Message, Cannot Switch Opening Message", + "unknownError": "Oops, Something Went Wrong, Please Refresh the Page and Try Again~", + "saveFailed": "Last Save Message Failed, Please Refresh the Page and Try Again", + "error": "Error, Please Refresh the Page and Try Again" +} diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json new file mode 100644 index 0000000..1574c4a --- /dev/null +++ b/src/locales/zh-cn.json @@ -0,0 +1,18 @@ +{ + "edit": "编辑", + "copy": "复制", + "refresh": "重说", + "continue": "继续", + "delete": "删除", + "save": "保存", + "cancel": "取消", + "copySuccess": "复制成功", + "copyFailed": "复制失败,请重试。", + "generating": "正在生成...", + "sendWithEnter": "按 Enter 发送", + "newMessageExists": "已有新消息,不允许切换开场白", + "assertionFailed": "断言失败:没有开场消息,无法切换开场白", + "unknownError": "哎呀,发生了未知错误,刷新页面再试试吧~", + "saveFailed": "最近一次保存消息失败,请刷新页面后重试", + "error": "出错啦,请刷新页面后重试" +} diff --git a/src/main.tsx b/src/main.tsx index d733267..8e408d3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,7 @@ import '@/styles/global.css'; import { MujianProvider } from '@mujian/js-sdk/react'; import { ReactRouterProvider } from './providers/RouterProvider.tsx'; import '@mujian/js-sdk/react.css'; +import './i18n'; createRoot(document.getElementById('root')!).render( diff --git a/src/pages/chat/MessageItem/components/EditActions.tsx b/src/pages/chat/MessageItem/components/EditActions.tsx index 6f0346b..a0eb247 100644 --- a/src/pages/chat/MessageItem/components/EditActions.tsx +++ b/src/pages/chat/MessageItem/components/EditActions.tsx @@ -1,4 +1,5 @@ import { Button, ButtonGroup, cn } from '@heroui/react'; +import { useTranslation } from 'react-i18next'; import { mjChatCls } from '@/utils/cls'; export type EditActionsProps = { @@ -14,6 +15,7 @@ export const EditActions = ({ onSaveEdit, onCancelEdit, }: EditActionsProps) => { + const { t } = useTranslation(); if (!isEditing) return null; return ( @@ -43,7 +45,7 @@ export const EditActions = ({ size="sm" onPress={onSaveEdit} > - 保存 + {t('save')} diff --git a/src/pages/chat/MessageItem/components/MessageActions.tsx b/src/pages/chat/MessageItem/components/MessageActions.tsx index d412d8d..07cd10a 100644 --- a/src/pages/chat/MessageItem/components/MessageActions.tsx +++ b/src/pages/chat/MessageItem/components/MessageActions.tsx @@ -33,7 +33,7 @@ export type LastMessageActionsProps = { }; export const MessageActions = ({ - // message, + message, isUser, isLastMsg, isFirstMsg, @@ -64,7 +64,7 @@ export const MessageActions = ({
{!isFirstMsg && ( - - - + + - - - - - {(item) => ( - ( + + {item.label} + + )} + + + {message.flags.emptyResponse && + message.flags.saved && + !message.flags.contentFilter && + isLastMsg && ( + )} - - +
)}
@@ -199,6 +217,26 @@ export const MessageActions = ({ )}
+ {message.flags.contentFilter && isLastMsg && ( +
+
+ 检测到截断,是否需要重新生成? +
+ +
+ )} ); }; diff --git a/src/pages/chat/MessageItem/components/useMessageActions.ts b/src/pages/chat/MessageItem/components/useMessageActions.ts index 42374b4..70f759f 100644 --- a/src/pages/chat/MessageItem/components/useMessageActions.ts +++ b/src/pages/chat/MessageItem/components/useMessageActions.ts @@ -1,4 +1,5 @@ import { useContext } from 'react'; +import { useTranslation } from 'react-i18next'; import { PanicContext } from '../../PanicContext'; export type DropdownItem = { @@ -34,30 +35,30 @@ export const useMessageActions = ({ onDelete, }: UseDropdownItemsProps): DropdownItem[] => { const { panicked } = useContext(PanicContext); - + const { t } = useTranslation(); const dropdownItems: DropdownItem[] = [ { key: 'edit', - label: '编辑', + label: t('edit'), onPress: onEditButtonClick, disabled: panicked, }, { key: 'copy', - label: '复制', + label: t('copy'), onPress: onCopyClick, }, ...(isLastMsg ? [ { key: 'regenerate', - label: '重说', + label: t('refresh'), onPress: async () => await onRegenerate?.(), disabled: panicked, }, { key: 'continue', - label: '继续', + label: t('continue'), onPress: async () => await onContinue?.(), disabled: panicked, }, @@ -65,7 +66,7 @@ export const useMessageActions = ({ : []), { key: 'delete', - label: '删除', + label: t('delete'), onPress: onDelete, color: 'danger', className: 'text-danger', diff --git a/src/pages/chat/MessageItem/index.tsx b/src/pages/chat/MessageItem/index.tsx index 659d0f1..dbda5d1 100644 --- a/src/pages/chat/MessageItem/index.tsx +++ b/src/pages/chat/MessageItem/index.tsx @@ -1,6 +1,7 @@ import { addToast, cn } from '@heroui/react'; import { type Message as BaseMessage, useMujian } from '@mujian/js-sdk/react'; import React, { type MouseEventHandler } from 'react'; +import { useTranslation } from 'react-i18next'; import { mjChatCls } from '@/utils/cls'; import { EditActions } from './components/EditActions'; import { MessageActions } from './components/MessageActions'; @@ -83,10 +84,11 @@ export const MessageItem = React.memo((props: MessageItemProps) => { swipes = [], activeSwipeId, - mjv, + // mjv, // sendAt, } = message; + const { t } = useTranslation(); const isUser = role === 'user'; const messageRef = React.useRef(null); // 使用 useRef 获取当前组件的引用 @@ -123,13 +125,13 @@ export const MessageItem = React.memo((props: MessageItemProps) => { await mujian.utils.clipboard.writeText(originalMessage.content); addToast({ color: 'success', - description: '复制成功', + description: t('copySuccess'), timeout: 2000, }); } catch (error) { console.error(error); addToast({ - description: '复制失败,请重试。', + description: t('copyFailed'), }); } }; @@ -185,7 +187,7 @@ export const MessageItem = React.memo((props: MessageItemProps) => { // 按ctrl点击时打印ID if (event.ctrlKey) { event.preventDefault(); - console.log('Official Chat Message ID:', id, mjv); + console.log('Official Chat Message ID:', id, message); } }} > diff --git a/src/pages/chat/MessageList.tsx b/src/pages/chat/MessageList.tsx index 8e64953..457cfc5 100644 --- a/src/pages/chat/MessageList.tsx +++ b/src/pages/chat/MessageList.tsx @@ -60,7 +60,7 @@ export const MessageList = React.memo( prevDataLength.current === data.length && prevLastMsgId.current !== lastMessage?.id; if (shouldRecoverScroll) { - vl.scrollTo(vl.scrollOffset); + vl.scrollTo(vl.scrollOffset + 9999); } prevDataLength.current = data.length; diff --git a/src/pages/chat/MsgSend.tsx b/src/pages/chat/MsgSend.tsx index f1a4f47..09afad3 100644 --- a/src/pages/chat/MsgSend.tsx +++ b/src/pages/chat/MsgSend.tsx @@ -1,7 +1,8 @@ import { Button, Textarea } from '@heroui/react'; import { CircleStopIcon, SendIcon } from 'lucide-react'; -import { mjChatCls } from '@/utils/cls'; import { useContext } from 'react'; +import { useTranslation } from 'react-i18next'; +import { mjChatCls } from '@/utils/cls'; import { PanicContext } from './PanicContext'; interface Props { @@ -31,7 +32,7 @@ export const MsgSend = ({ onChange, }: Props) => { const { panicked } = useContext(PanicContext); - + const { t } = useTranslation(); const isEmptyInput = value.trim().length === 0; const handleSend = () => { @@ -114,7 +115,7 @@ export const MsgSend = ({ maxLength={50000} maxRows={4} minRows={1} - placeholder={running ? '正在生成...' : '按 Enter 发送'} + placeholder={running ? t('generating') : t('sendWithEnter')} radius="lg" size="lg" value={value} diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx index 568eade..c25353d 100644 --- a/src/pages/chat/index.tsx +++ b/src/pages/chat/index.tsx @@ -1,6 +1,7 @@ import { Alert, ScrollShadow, Spinner, ToastProvider } from '@heroui/react'; import { useChat, useMjEngine, useMujian } from '@mujian/js-sdk/react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { useGlobalStore } from '@/store/global'; import { mjChatCls } from '@/utils/cls'; import { MessageList } from './MessageList'; @@ -11,7 +12,7 @@ import { QuickReply } from './QuickReply'; export const Chat = () => { const { init, projectInfo, activePersona } = useGlobalStore(); const mujian = useMujian(); - const [inputValue, setInputValue] = useState(''); + const { t } = useTranslation(); const { messages, status, @@ -24,6 +25,8 @@ export const Chat = () => { editMessage, setSwipe, loadMoreMessage, + input: inputValue, + setInput: setInputValue, } = useChat({ onError: (e) => { console.error(e); @@ -110,15 +113,6 @@ export const Chat = () => { ); } - const errorMessage = - Boolean(error) && - (typeof error === 'object' && - error && - 'message' in error && - typeof error.message === 'string' - ? error.message - : '哎呀,发生了未知错误,刷新页面再试试吧~'); - const toastStyle = ` .toast-region { margin-top: ${paddingTop}px; @@ -128,9 +122,20 @@ export const Chat = () => { status !== 'streaming' && messages.some((m) => m.id.startsWith('not_saved')); + const errorMessage = [ + Boolean(error) && + (typeof error === 'object' && + error && + 'message' in error && + typeof error.message === 'string' + ? error.message + : t('unknownError')), + saveFailed ? t('saveFailed') : '', + ].filter(Boolean); + return (
@@ -151,7 +156,7 @@ export const Chat = () => { )} style={{ backgroundImage: projectInfo - ? `url(${projectInfo.coverImageUrl})` + ? `url(${projectInfo.backgroundImageUrl || projectInfo.coverImageUrl})` : undefined, }} /> @@ -184,19 +189,7 @@ export const Chat = () => { onRegenerate={regenerate} onNeedMore={loadMoreMessage} /> - {saveFailed && ( - - )} - {errorMessage && ( + {errorMessage.length > 0 && ( { description: 'opacity-80 text-xs', }} color="danger" - title="出错啦,请刷新页面后重试" - description={errorMessage} + title={t('error')} + description={ + <> + {errorMessage.map((m) => ( +
{m}
+ ))} + + } variant="bordered" /> )}