From d5a322a35223f357011c7b9ef070f984105034da Mon Sep 17 00:00:00 2001 From: so95 Date: Tue, 10 Dec 2024 10:42:04 +0700 Subject: [PATCH] Theme switch support (#3568) ### What problem does this PR solve? - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: Yingfeng Co-authored-by: Jin Hai --- .gitignore | 4 +- docker/docker-compose-base.yml | 2 +- rag/svr/task_executor.py | 2 +- web/src/app.tsx | 42 +-- web/src/assets/icon/Icon.tsx | 240 ++++++++++++++++++ web/src/assets/svg/api.svg | 2 +- web/src/assets/svg/delete.svg | 2 +- web/src/assets/svg/prompt.svg | 16 +- .../chat-overview-modal/index.less | 1 + .../api-service/embed-modal/index.less | 2 +- web/src/components/editable-cell.tsx | 2 +- .../components/highlight-markdown/index.less | 2 +- .../indented-tree/indented-tree.tsx | 142 ++++++----- .../components/llm-setting-items/index.tsx | 4 +- web/src/components/message-input/index.less | 2 +- web/src/components/message-input/index.tsx | 12 +- .../components/message-item/group-button.tsx | 4 +- web/src/components/message-item/index.less | 13 +- web/src/components/message-item/index.tsx | 12 +- .../components/retrieval-documents/index.tsx | 2 +- web/src/components/theme-provider.tsx | 39 +-- web/src/components/ui/navigation-menu.tsx | 2 +- web/src/layouts/components/header/index.less | 64 ++++- web/src/layouts/components/header/index.tsx | 15 +- .../components/right-toolbar/index.less | 6 +- .../components/right-toolbar/index.tsx | 21 +- web/src/less/mixins.less | 2 +- web/src/less/variable.less | 2 +- web/src/locales/config.ts | 11 +- web/src/locales/en.ts | 4 +- web/src/locales/es.ts | 1 + web/src/locales/id.ts | 3 +- web/src/locales/until.ts | 60 +++++ web/src/locales/vi.ts | 40 ++- web/src/locales/zh-traditional.ts | 1 + .../components/chunk-card/index.less | 3 + .../components/chunk-card/index.tsx | 5 +- .../knowledge-setting/category-panel.tsx | 6 +- .../knowledge-setting/configuration.tsx | 1 + .../components/knowledge-sidebar/index.less | 1 - .../components/knowledge-testing/index.less | 1 - .../testing-control/index.less | 2 +- .../testing-result/index.less | 4 +- .../testing-result/index.tsx | 4 +- web/src/pages/add-knowledge/index.less | 4 +- .../model-setting.tsx | 218 ---------------- web/src/pages/chat/index.less | 12 + web/src/pages/chat/index.tsx | 10 +- web/src/pages/document-viewer/docx/index.less | 3 +- .../pages/flow/canvas/context-menu/index.less | 4 +- web/src/pages/flow/canvas/edge/index.less | 16 ++ web/src/pages/flow/canvas/edge/index.tsx | 7 +- web/src/pages/flow/canvas/node/begin-node.tsx | 13 +- .../flow/canvas/node/categorize-node.tsx | 13 +- .../pages/flow/canvas/node/generate-node.tsx | 13 +- web/src/pages/flow/canvas/node/index.less | 13 +- web/src/pages/flow/canvas/node/index.tsx | 12 +- .../pages/flow/canvas/node/invoke-node.tsx | 12 +- .../pages/flow/canvas/node/keyword-node.tsx | 12 +- web/src/pages/flow/canvas/node/logic-node.tsx | 12 +- .../pages/flow/canvas/node/message-node.tsx | 13 +- web/src/pages/flow/canvas/node/note-node.tsx | 11 +- web/src/pages/flow/canvas/node/popover.tsx | 24 +- .../pages/flow/canvas/node/relevant-node.tsx | 13 +- .../pages/flow/canvas/node/retrieval-node.tsx | 12 +- .../pages/flow/canvas/node/rewrite-node.tsx | 12 +- .../pages/flow/canvas/node/switch-node.tsx | 13 +- .../pages/flow/canvas/node/template-node.tsx | 14 +- web/src/pages/flow/constant.tsx | 25 +- web/src/pages/flow/form/begin-form/index.less | 4 +- web/src/pages/flow/form/components/index.less | 4 +- .../pages/flow/form/invoke-form/index.less | 4 +- web/src/pages/flow/list/flow-card/index.less | 6 +- web/src/pages/knowledge/index.less | 3 +- .../pages/knowledge/knowledge-card/index.less | 28 +- .../pages/knowledge/knowledge-card/index.tsx | 17 +- web/src/pages/login/index.less | 8 +- web/src/pages/search/index.less | 12 +- web/src/pages/search/index.tsx | 4 +- web/src/pages/user-setting/constants.tsx | 18 +- .../setting-locale/TranslationTable.tsx | 68 +++++ .../user-setting/setting-locale/index.tsx | 13 + .../user-setting/setting-model/index.less | 18 ++ .../user-setting/setting-model/index.tsx | 15 +- web/src/routes.ts | 4 + 85 files changed, 1032 insertions(+), 511 deletions(-) create mode 100644 web/src/assets/icon/Icon.tsx create mode 100644 web/src/locales/until.ts create mode 100644 web/src/pages/user-setting/setting-locale/TranslationTable.tsx create mode 100644 web/src/pages/user-setting/setting-locale/index.tsx diff --git a/.gitignore b/.gitignore index c6009b3ebb7..07765cf0e37 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ rag/res/deepdoc sdk/python/ragflow.egg-info/ sdk/python/build/ sdk/python/dist/ -sdk/python/ragflow_sdk.egg-info/ \ No newline at end of file +sdk/python/ragflow_sdk.egg-info/ +huggingface.co/ +nltk_data/ diff --git a/docker/docker-compose-base.yml b/docker/docker-compose-base.yml index 2d482a3ed34..48cfda0600f 100644 --- a/docker/docker-compose-base.yml +++ b/docker/docker-compose-base.yml @@ -74,7 +74,7 @@ services: command: --max_connections=1000 --character-set-server=utf8mb4 - --collation-server=utf8mb4_general_ci + --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password --tls_version="TLSv1.2,TLSv1.3" --init-file /data/application/init.sql diff --git a/rag/svr/task_executor.py b/rag/svr/task_executor.py index e9ea3828064..606bb075716 100644 --- a/rag/svr/task_executor.py +++ b/rag/svr/task_executor.py @@ -260,7 +260,7 @@ def build_chunks(task, progress_callback): def init_kb(row, vector_size: int): idxnm = search.index_name(row["tenant_id"]) - return settings.docStoreConn.createIdx(idxnm, row["kb_id"], vector_size) + return settings.docStoreConn.createIdx(idxnm, row.get("kb_id",""), vector_size) def embedding(docs, mdl, parser_config=None, callback=None): diff --git a/web/src/app.tsx b/web/src/app.tsx index 08161485c07..bbb1d9d7b46 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,7 +1,7 @@ import i18n from '@/locales/config'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { App, ConfigProvider, ConfigProviderProps } from 'antd'; +import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd'; import enUS from 'antd/locale/en_US'; import vi_VN from 'antd/locale/vi_VN'; import zhCN from 'antd/locale/zh_CN'; @@ -14,7 +14,7 @@ import weekOfYear from 'dayjs/plugin/weekOfYear'; import weekYear from 'dayjs/plugin/weekYear'; import weekday from 'dayjs/plugin/weekday'; import React, { ReactNode, useEffect, useState } from 'react'; -import { ThemeProvider } from './components/theme-provider'; +import { ThemeProvider, useTheme } from './components/theme-provider'; import storage from './utils/authorization-util'; dayjs.extend(customParseFormat); @@ -35,7 +35,8 @@ const queryClient = new QueryClient(); type Locale = ConfigProviderProps['locale']; -const RootProvider = ({ children }: React.PropsWithChildren) => { +function Root({ children }: React.PropsWithChildren) { + const { theme: themeragflow } = useTheme(); const getLocale = (lng: string) => AntLanguageMap[lng as keyof typeof AntLanguageMap] ?? enUS; @@ -46,6 +47,28 @@ const RootProvider = ({ children }: React.PropsWithChildren) => { setLocal(getLocale(lng)); }); + return ( + <> + + {children} + + + + ); +} + +const RootProvider = ({ children }: React.PropsWithChildren) => { useEffect(() => { // Because the language is saved in the backend, a token is required to obtain the api. However, the login page cannot obtain the language through the getUserInfo api, so the language needs to be saved in localstorage. const lng = storage.getLanguage(); @@ -57,22 +80,11 @@ const RootProvider = ({ children }: React.PropsWithChildren) => { return ( - - {children} - - + {children} ); }; - export function rootContainer(container: ReactNode) { return {container}; } diff --git a/web/src/assets/icon/Icon.tsx b/web/src/assets/icon/Icon.tsx new file mode 100644 index 00000000000..1d20e3a8423 --- /dev/null +++ b/web/src/assets/icon/Icon.tsx @@ -0,0 +1,240 @@ +import Icon from '@ant-design/icons'; +import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; + +type IconComponentProps = CustomIconComponentProps; +const currentColor = 'currentColor'; +const ApiSvg = () => ( + + + +); +const TeamSvg = () => ( + + + +); +const ProfileSvg = () => ( + + + +); +const PasswordSvg = () => ( + + + +); +const LogOutSvg = () => ( + + + +); +const ModelProviderSvg = () => ( + + + +); +const PromptSvg = () => ( + + + + + + + + + + +); +const WikipediaSvg = () => ( + + + +); +const KeywordSvg = () => ( + + + + +); +const GitHubSvg = () => ( + + + +); +const QWeatherSvg = () => ( + + + +); + +export const ApiIcon = (props: Partial) => ( + +); +export const TeamIcon = (props: Partial) => ( + +); +export const ProfileIcon = (props: Partial) => ( + +); +export const PasswordIcon = (props: Partial) => ( + +); +export const LogOutIcon = (props: Partial) => ( + +); +export const ModelProviderIcon = (props: Partial) => ( + +); +export const PromptIcon = (props: Partial) => ( + +); +export const WikipediaIcon = (props: Partial) => ( + +); +export const KeywordIcon = (props: Partial) => ( + +); +export const GitHubIcon = (props: Partial) => ( + +); +export const QWeatherIcon = (props: Partial) => ( + +); diff --git a/web/src/assets/svg/api.svg b/web/src/assets/svg/api.svg index db088dd74b3..5dd6a8584e3 100644 --- a/web/src/assets/svg/api.svg +++ b/web/src/assets/svg/api.svg @@ -2,5 +2,5 @@ width="24" height="24"> + fill="#667085" p-id="4307"> \ No newline at end of file diff --git a/web/src/assets/svg/delete.svg b/web/src/assets/svg/delete.svg index 1eef9110b61..7e1f0a015cc 100644 --- a/web/src/assets/svg/delete.svg +++ b/web/src/assets/svg/delete.svg @@ -1,5 +1,5 @@ + stroke="#667085" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" /> \ No newline at end of file diff --git a/web/src/assets/svg/prompt.svg b/web/src/assets/svg/prompt.svg index 1a9388d95a5..408c30a9506 100644 --- a/web/src/assets/svg/prompt.svg +++ b/web/src/assets/svg/prompt.svg @@ -2,26 +2,26 @@ width="200" height="200"> + fill="#667085" p-id="4297"> + fill="#667085" p-id="4298"> + fill="#667085" p-id="4299"> + fill="#667085" p-id="4300"> + fill="#667085" p-id="4301"> + fill="#667085" p-id="4302"> + fill="#667085" p-id="4303"> + fill="#667085" p-id="4304"> \ No newline at end of file diff --git a/web/src/components/api-service/chat-overview-modal/index.less b/web/src/components/api-service/chat-overview-modal/index.less index c923c4df745..857bb3adc0c 100644 --- a/web/src/components/api-service/chat-overview-modal/index.less +++ b/web/src/components/api-service/chat-overview-modal/index.less @@ -16,4 +16,5 @@ .apiLinkText { .linkText(); margin: 0 !important; + background-color: rgba(255, 255, 255, 0.1); } diff --git a/web/src/components/api-service/embed-modal/index.less b/web/src/components/api-service/embed-modal/index.less index 5e807d8526e..b2c0cfbba86 100644 --- a/web/src/components/api-service/embed-modal/index.less +++ b/web/src/components/api-service/embed-modal/index.less @@ -4,5 +4,5 @@ .codeText { padding: 10px; - background-color: #e8e8ea; + background-color: #ffffff09; } diff --git a/web/src/components/editable-cell.tsx b/web/src/components/editable-cell.tsx index 85f5610b8d6..c8e53275bd8 100644 --- a/web/src/components/editable-cell.tsx +++ b/web/src/components/editable-cell.tsx @@ -90,7 +90,7 @@ export const EditableCell: React.FC = ({ ) : ( -
+
{children}
); diff --git a/web/src/components/highlight-markdown/index.less b/web/src/components/highlight-markdown/index.less index cfc5f12da72..a250e4a1c25 100644 --- a/web/src/components/highlight-markdown/index.less +++ b/web/src/components/highlight-markdown/index.less @@ -15,5 +15,5 @@ white-space: break-spaces; background-color: rgba(129, 139, 152, 0.12); border-radius: 4px; - color: rgb(31, 35, 40); + color: var(--ant-color-text-base); } diff --git a/web/src/components/indented-tree/indented-tree.tsx b/web/src/components/indented-tree/indented-tree.tsx index fc3cc6a8603..91ee361b22f 100644 --- a/web/src/components/indented-tree/indented-tree.tsx +++ b/web/src/components/indented-tree/indented-tree.tsx @@ -312,80 +312,96 @@ function fallbackRender({ error }: FallbackProps) { const IndentedTree = ({ data, show, style = {} }: IProps) => { const containerRef = useRef(null); const graphRef = useRef(null); + const assignIds = React.useCallback(function assignIds( + node: TreeData, + parentId: string = '', + index = 0, + ) { + if (!node.id) node.id = parentId ? `${parentId}-${index}` : 'root'; + if (node.children) { + node.children.forEach((child, idx) => assignIds(child, node.id, idx)); + } + }, []); - const render = useCallback(async (data: TreeData) => { - const graph: Graph = new Graph({ - container: containerRef.current!, - x: 60, - node: { - type: 'indented', - style: { - size: (d) => [d.id.length * 6 + 10, 20], - labelBackground: (datum) => datum.id === rootId, - labelBackgroundRadius: 0, - labelBackgroundFill: '#576286', - labelFill: (datum) => (datum.id === rootId ? '#fff' : '#666'), - labelText: (d) => d.style?.labelText || d.id, - labelTextAlign: (datum) => (datum.id === rootId ? 'center' : 'left'), - labelTextBaseline: 'top', - color: (datum: any) => { - const depth = graph.getAncestorsData(datum.id, 'tree').length - 1; - return COLORS[depth % COLORS.length] || '#576286'; + const render = useCallback( + async (data: TreeData) => { + const graph: Graph = new Graph({ + container: containerRef.current!, + x: 60, + node: { + type: 'indented', + style: { + size: (d) => [d.id.length * 6 + 10, 20], + labelBackground: (datum) => datum.id === rootId, + labelBackgroundRadius: 0, + labelBackgroundFill: '#576286', + labelFill: (datum) => (datum.id === rootId ? '#fff' : '#666'), + labelText: (d) => d.style?.labelText || d.id, + labelTextAlign: (datum) => + datum.id === rootId ? 'center' : 'left', + labelTextBaseline: 'top', + color: (datum: any) => { + const depth = graph.getAncestorsData(datum.id, 'tree').length - 1; + return COLORS[depth % COLORS.length] || '#576286'; + }, }, - }, - state: { - selected: { - lineWidth: 0, - labelFill: '#40A8FF', - labelBackground: true, - labelFontWeight: 'normal', - labelBackgroundFill: '#e8f7ff', - labelBackgroundRadius: 10, + state: { + selected: { + lineWidth: 0, + labelFill: '#40A8FF', + labelBackground: true, + labelFontWeight: 'normal', + labelBackgroundFill: '#e8f7ff', + labelBackgroundRadius: 10, + }, }, }, - }, - edge: { - type: 'indented', - style: { - radius: 16, - lineWidth: 2, - sourcePort: 'out', - targetPort: 'in', - stroke: (datum: any) => { - const depth = graph.getAncestorsData(datum.source, 'tree').length; - return COLORS[depth % COLORS.length] || 'black'; + edge: { + type: 'indented', + style: { + radius: 16, + lineWidth: 2, + sourcePort: 'out', + targetPort: 'in', + stroke: (datum: any) => { + const depth = graph.getAncestorsData(datum.source, 'tree').length; + return COLORS[depth % COLORS.length] || 'black'; + }, }, }, - }, - layout: { - type: 'indented', - direction: 'LR', - isHorizontal: true, - indent: 40, - getHeight: () => 20, - getVGap: () => 10, - }, - behaviors: [ - 'scroll-canvas', - 'collapse-expand-tree', - { - type: 'click-select', - enable: (event: any) => - event.targetType === 'node' && event.target.id !== rootId, + layout: { + type: 'indented', + direction: 'LR', + isHorizontal: true, + indent: 40, + getHeight: () => 20, + getVGap: () => 10, }, - ], - }); + behaviors: [ + 'scroll-canvas', + 'collapse-expand-tree', + { + type: 'click-select', + enable: (event: any) => + event.targetType === 'node' && event.target.id !== rootId, + }, + ], + }); - if (graphRef.current) { - graphRef.current.destroy(); - } + if (graphRef.current) { + graphRef.current.destroy(); + } - graphRef.current = graph; + graphRef.current = graph; - graph?.setData(treeToGraphData(data)); + assignIds(data); - graph?.render(); - }, []); + graph?.setData(treeToGraphData(data)); + + graph?.render(); + }, + [assignIds], + ); useEffect(() => { if (!isEmpty(data)) { diff --git a/web/src/components/llm-setting-items/index.tsx b/web/src/components/llm-setting-items/index.tsx index 841de4259dd..f665aa0b176 100644 --- a/web/src/components/llm-setting-items/index.tsx +++ b/web/src/components/llm-setting-items/index.tsx @@ -269,7 +269,7 @@ const LlmSettingItems = ({ prefix, formItemLayout = {} }: IProps) => { > @@ -278,7 +278,7 @@ const LlmSettingItems = ({ prefix, formItemLayout = {} }: IProps) => { diff --git a/web/src/components/message-input/index.less b/web/src/components/message-input/index.less index daf07479985..41f382d90da 100644 --- a/web/src/components/message-input/index.less +++ b/web/src/components/message-input/index.less @@ -1,6 +1,6 @@ .messageInputWrapper { margin-right: 20px; - background-color: #f5f5f8; + background-color: rgba(255, 255, 255, 0.1); border-radius: 8px; :global(.ant-input-affix-wrapper) { border-bottom-right-radius: 0; diff --git a/web/src/components/message-input/index.tsx b/web/src/components/message-input/index.tsx index e026581a375..a20147a509b 100644 --- a/web/src/components/message-input/index.tsx +++ b/web/src/components/message-input/index.tsx @@ -27,6 +27,7 @@ import { } from 'antd'; import classNames from 'classnames'; import get from 'lodash/get'; +import { Paperclip } from 'lucide-react'; import { ChangeEventHandler, memo, @@ -36,7 +37,6 @@ import { useState, } from 'react'; import FileIcon from '../file-icon'; -import SvgIcon from '../svg-icon'; import styles from './index.less'; type FileType = Parameters>[0]; @@ -98,7 +98,6 @@ const MessageInput = ({ const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds(); const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod); const conversationIdRef = useRef(conversationId); - const [fileList, setFileList] = useState([]); const handlePreview = async (file: UploadFile) => { @@ -225,14 +224,7 @@ const MessageInput = ({ )} diff --git a/web/src/components/message-item/group-button.tsx b/web/src/components/message-item/group-button.tsx index 2aac4f3b96f..7e656a06489 100644 --- a/web/src/components/message-item/group-button.tsx +++ b/web/src/components/message-item/group-button.tsx @@ -1,3 +1,4 @@ +import { PromptIcon } from '@/assets/icon/Icon'; import CopyToClipboard from '@/components/copy-to-clipboard'; import { useSetModalState } from '@/hooks/common-hooks'; import { IRemoveMessageById } from '@/hooks/logic-hooks'; @@ -12,7 +13,6 @@ import { import { Radio, Tooltip } from 'antd'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import SvgIcon from '../svg-icon'; import FeedbackModal from './feedback-modal'; import { useRemoveMessage, useSendFeedback, useSpeech } from './hooks'; import PromptModal from './prompt-modal'; @@ -70,7 +70,7 @@ export const AssistantGroupButton = ({ )} {prompt && ( - + )} diff --git a/web/src/components/message-item/index.less b/web/src/components/message-item/index.less index a4fe17c8df7..224210e0dd7 100644 --- a/web/src/components/message-item/index.less +++ b/web/src/components/message-item/index.less @@ -6,10 +6,6 @@ .messageItemSectionLeft { width: 80%; } - .messageItemSectionRight { - // width: 80%; - // max-width: 50vw; - } .messageItemContent { display: inline-flex; gap: 20px; @@ -36,10 +32,17 @@ background-color: #e6f4ff; word-break: break-all; } + .messageTextDark { + .chunkText(); + .messageTextBase(); + background-color: #1668dc; + word-break: break-all; + } + .messageUserText { .chunkText(); .messageTextBase(); - background-color: rgb(248, 247, 247); + background-color: rgba(255, 255, 255, 0.3); word-break: break-all; text-align: justify; } diff --git a/web/src/components/message-item/index.tsx b/web/src/components/message-item/index.tsx index bd2db7a4b20..eab098056c3 100644 --- a/web/src/components/message-item/index.tsx +++ b/web/src/components/message-item/index.tsx @@ -18,6 +18,7 @@ import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; import FileIcon from '../file-icon'; import IndentedTreeModal from '../indented-tree/modal'; import NewDocumentLink from '../new-document-link'; +import { useTheme } from '../theme-provider'; import { AssistantGroupButton, UserGroupButton } from './group-button'; import styles from './index.less'; @@ -47,6 +48,7 @@ const MessageItem = ({ regenerateMessage, showLikeButton = true, }: IProps) => { + const { theme } = useTheme(); const isAssistant = item.role === MessageType.Assistant; const isUser = item.role === MessageType.User; const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); @@ -139,7 +141,11 @@ const MessageItem = ({
{ // TODO: - const fileThumbnail = - documentThumbnails[item.id] || documentThumbnails[item.id]; + // const fileThumbnail = + // documentThumbnails[item.id] || documentThumbnails[item.id]; const fileExtension = getExtension(item.name); return ( diff --git a/web/src/components/retrieval-documents/index.tsx b/web/src/components/retrieval-documents/index.tsx index 50344370e12..f73f0f709f7 100644 --- a/web/src/components/retrieval-documents/index.tsx +++ b/web/src/components/retrieval-documents/index.tsx @@ -35,7 +35,7 @@ const RetrievalDocuments = ({ > - {selectedDocumentIds.length ?? 0}/{documents.length} + {selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0} {t('knowledgeDetails.filesSelected')} diff --git a/web/src/components/theme-provider.tsx b/web/src/components/theme-provider.tsx index d6b3eaa1c61..5c9aa1b91a1 100644 --- a/web/src/components/theme-provider.tsx +++ b/web/src/components/theme-provider.tsx @@ -1,6 +1,6 @@ -import { createContext, useContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; -type Theme = 'dark' | 'light' | 'system'; +type Theme = 'dark' | 'light'; type ThemeProviderProps = { children: React.ReactNode; @@ -14,7 +14,7 @@ type ThemeProviderState = { }; const initialState: ThemeProviderState = { - theme: 'system', + theme: 'light', setTheme: () => null, }; @@ -22,7 +22,7 @@ const ThemeProviderContext = createContext(initialState); export function ThemeProvider({ children, - defaultTheme = 'system', + defaultTheme = 'light', storageKey = 'vite-ui-theme', ...props }: ThemeProviderProps) { @@ -32,32 +32,19 @@ export function ThemeProvider({ useEffect(() => { const root = window.document.documentElement; - root.classList.remove('light', 'dark'); - - if (theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') - .matches - ? 'dark' - : 'light'; - - root.classList.add(systemTheme); - return; - } - + localStorage.setItem(storageKey, theme); root.classList.add(theme); - }, [theme]); - - const value = { - theme, - setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme); - setTheme(theme); - }, - }; + }, [storageKey, theme]); return ( - + {children} ); diff --git a/web/src/components/ui/navigation-menu.tsx b/web/src/components/ui/navigation-menu.tsx index ace41472a8c..b2cdae8f6a9 100644 --- a/web/src/components/ui/navigation-menu.tsx +++ b/web/src/components/ui/navigation-menu.tsx @@ -123,6 +123,6 @@ export { NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, - navigationMenuTriggerStyle, NavigationMenuViewport, + navigationMenuTriggerStyle, }; diff --git a/web/src/layouts/components/header/index.less b/web/src/layouts/components/header/index.less index fc72c7d36e4..76f1078a1e8 100644 --- a/web/src/layouts/components/header/index.less +++ b/web/src/layouts/components/header/index.less @@ -48,6 +48,66 @@ color: white; } } + :global(.ant-radio-button-wrapper-checked.dark) { + border-radius: 0px !important; + & a { + color: white; + } + } + :global(.ant-radio-button-wrapper-checked.dark.first) { + border-top-left-radius: 6px !important; + border-bottom-left-radius: 6px !important; + & a { + color: white; + } + } + :global(.ant-radio-button-wrapper-checked.dark.last) { + border-top-right-radius: 6px !important; + border-bottom-right-radius: 6px !important; + & a { + color: white; + } + } +} + +.radioGroupDark { + & label { + height: 40px; + line-height: 40px; + border: 0 !important; + background-color: rgba(249, 249, 249, 0.25); + font-weight: @fontWeight700; + color: rgba(29, 25, 41, 1); + &::before { + display: none !important; + } + } + :global(.ant-radio-button-wrapper-checked) { + border-radius: 6px !important; + & a { + color: white; + } + } + :global(.ant-radio-button-wrapper-checked.dark) { + border-radius: 0px !important; + & a { + color: white; + } + } + :global(.ant-radio-button-wrapper-checked.dark.first) { + border-top-left-radius: 6px !important; + border-bottom-left-radius: 6px !important; + & a { + color: white; + } + } + :global(.ant-radio-button-wrapper-checked.dark.last) { + border-top-right-radius: 6px !important; + border-bottom-right-radius: 6px !important; + & a { + color: white; + } + } } .ant-radio-button-wrapper-checked { @@ -55,6 +115,6 @@ } .radioButtonIcon { vertical-align: middle; - max-width: 16px; - max-height: 16px; + max-width: 15px; + max-height: 15px; } diff --git a/web/src/layouts/components/header/index.tsx b/web/src/layouts/components/header/index.tsx index e5a4954cab5..87a6d3bdc5b 100644 --- a/web/src/layouts/components/header/index.tsx +++ b/web/src/layouts/components/header/index.tsx @@ -10,6 +10,7 @@ import { MouseEventHandler, useCallback, useMemo } from 'react'; import { useLocation } from 'umi'; import Toolbar from '../right-toolbar'; +import { useTheme } from '@/components/theme-provider'; import styles from './index.less'; const { Header } = Layout; @@ -22,7 +23,7 @@ const RagHeader = () => { const { pathname } = useLocation(); const { t } = useTranslate('header'); const appConf = useFetchAppConf(); - + const { theme: themeRag } = useTheme(); const tagsData = useMemo( () => [ { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon }, @@ -78,11 +79,17 @@ const RagHeader = () => { - {tagsData.map((item) => ( - + {tagsData.map((item, index) => ( + { @@ -25,6 +27,8 @@ const handleGithubCLick = () => { const RightToolBar = () => { const { t } = useTranslate('common'); const changeLanguage = useChangeLanguage(); + const { setTheme, theme } = useTheme(); + const { data: { language = 'English' }, } = useFetchUserInfo(); @@ -40,6 +44,13 @@ const RightToolBar = () => { return [...pre!, { type: 'divider' }, cur]; }, []); + const onMoonClick = React.useCallback(() => { + setTheme('light'); + }, [setTheme]); + const onSunClick = React.useCallback(() => { + setTheme('dark'); + }, [setTheme]); + return (
@@ -52,9 +63,13 @@ const RightToolBar = () => { - {/* - - */} + + {theme === 'dark' ? ( + + ) : ( + + )} +
diff --git a/web/src/less/mixins.less b/web/src/less/mixins.less index 7501bdbb29c..078be91827a 100644 --- a/web/src/less/mixins.less +++ b/web/src/less/mixins.less @@ -38,7 +38,7 @@ } tr:nth-child(even) { - background-color: #f2f2f2; + background-color: #f2f2f22a; } } diff --git a/web/src/less/variable.less b/web/src/less/variable.less index 0c883071159..3a180347958 100644 --- a/web/src/less/variable.less +++ b/web/src/less/variable.less @@ -1,7 +1,7 @@ @fontWeight600: 600; @fontWeight700: 700; -@grayBackground: rgba(247, 248, 250, 1); +@grayBackground: rgba(247, 248, 250, 0.1); @gray2: rgba(29, 25, 41, 1); @gray3: rgba(52, 48, 62, 1); @gray8: rgba(165, 163, 169, 1); diff --git a/web/src/locales/config.ts b/web/src/locales/config.ts index a547003f2fc..0da80e594df 100644 --- a/web/src/locales/config.ts +++ b/web/src/locales/config.ts @@ -2,6 +2,7 @@ import i18n from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; +import { createTranslationTable, flattenObject } from './until'; import translation_en from './en'; import translation_es from './es'; import translation_id from './id'; @@ -19,7 +20,15 @@ const resources = { es: translation_es, vi: translation_vi, }; - +const enFlattened = flattenObject(translation_en); +const viFlattened = flattenObject(translation_vi); +const esFlattened = flattenObject(translation_es); +const zhFlattened = flattenObject(translation_zh); +const zh_traditionalFlattened = flattenObject(translation_zh_traditional); +export const translationTable = createTranslationTable( + [enFlattened, viFlattened, esFlattened, zhFlattened, zh_traditionalFlattened], + ['English', 'Vietnamese', 'Spanish', 'zh', 'zh-TRADITIONAL'], +); i18n .use(initReactI18next) .use(LanguageDetector) diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 2526eb324f9..06ec1c56fd7 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -303,9 +303,9 @@ The above is the content you need to summarize.`, randomSeed: 'Random seed', randomSeedMessage: 'Random seed is required', entityTypes: 'Entity types', + vietnamese: 'Vietamese', pageRank: 'Page rank', - pageRankTip: `This is used to boost the relevance score. The relevance score with all the retrieved chunks will plus this number. -When you want to search the given knowledge base at first place, set a higher pagerank score than others.`, + pageRankTip: `This is used to boost the relevance score. The relevance score with all the retrieved chunks will plus this number, When you want to search the given knowledge base at first place, set a higher pagerank score than others.`, }, chunk: { chunk: 'Chunk', diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index c251c494a48..680e6acf062 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -277,6 +277,7 @@ export default { multiTurn: 'Optimización de múltiples turnos', multiTurnTip: 'En conversaciones de múltiples rondas, la consulta a la base de conocimiento se optimiza. El gran modelo se llamará para consumir tokens adicionales.', + description: 'Description of assistant', }, setting: { profile: 'Perfil', diff --git a/web/src/locales/id.ts b/web/src/locales/id.ts index 0ac64fd38e1..2ebe7f4cfcb 100644 --- a/web/src/locales/id.ts +++ b/web/src/locales/id.ts @@ -449,6 +449,7 @@ export default { multiTurnTip: 'Dalam percakapan multi-putaran, kueri ke basis pengetahuan dioptimalkan. Model besar akan dipanggil untuk mengonsumsi token tambahan.', languageSelectionTip: 'Pilih bahasa yang digunakan dalam percakapan.', + description: 'Description of assistant', }, setting: { profile: 'Profil', @@ -748,7 +749,7 @@ export default { bingTip: 'Komponen ini digunakan untuk mendapatkan hasil pencarian dari https://www.bing.com/. Biasanya, ini berfungsi sebagai pelengkap basis pengetahuan. Top N dan Kunci Langganan Bing menentukan jumlah hasil pencarian yang perlu Anda sesuaikan.', apiKey: 'Kunci API', - country: 'Negara&Wilayah', + country: 'Negara', language: 'Bahasa', googleScholar: 'Google Scholar', googleScholarDescription: diff --git a/web/src/locales/until.ts b/web/src/locales/until.ts new file mode 100644 index 00000000000..9934a97570b --- /dev/null +++ b/web/src/locales/until.ts @@ -0,0 +1,60 @@ +type NestedObject = { + [key: string]: string | NestedObject; +}; + +type FlattenedObject = { + [key: string]: string; +}; + +export function flattenObject( + obj: NestedObject, + parentKey: string = '', +): FlattenedObject { + const result: FlattenedObject = {}; + + for (const [key, value] of Object.entries(obj)) { + const newKey = parentKey ? `${parentKey}.${key}` : key; + + if (typeof value === 'object' && value !== null) { + Object.assign(result, flattenObject(value as NestedObject, newKey)); + } else { + result[newKey] = value as string; + } + } + + return result; +} +type TranslationTableRow = { + key: string; + [language: string]: string; +}; + +/** + * Creates a translation table from multiple flattened language objects. + * @param langs - A list of flattened language objects. + * @param langKeys - A list of language identifiers (e.g., 'English', 'Vietnamese'). + * @returns An array representing the translation table. + */ +export function createTranslationTable( + langs: FlattenedObject[], + langKeys: string[], +): TranslationTableRow[] { + const keys = new Set(); + + // Collect all unique keys from the language objects + langs.forEach((lang) => { + Object.keys(lang).forEach((key) => keys.add(key)); + }); + + // Build the table + return Array.from(keys).map((key) => { + const row: TranslationTableRow = { key }; + + langs.forEach((lang, index) => { + const langKey = langKeys[index]; + row[langKey] = lang[key] || ''; // Use empty string if key is missing + }); + + return row; + }); +} diff --git a/web/src/locales/vi.ts b/web/src/locales/vi.ts index bb95ee6f61b..dad864716fa 100644 --- a/web/src/locales/vi.ts +++ b/web/src/locales/vi.ts @@ -34,6 +34,7 @@ export default { pleaseInput: 'Vui lòng nhập', submit: 'Gửi', vietnamese: 'Tiếng việt', + spanish: 'Tiếng Tây Ban Nha', }, login: { login: 'Đăng nhập', @@ -76,6 +77,7 @@ export default { namePlaceholder: 'Vui lòng nhập tên!', doc: 'Tài liệu', searchKnowledgePlaceholder: 'Tìm kiếm', + noMoreData: 'Tất cả chỉ có thế, không còn gì nữa', }, knowledgeDetails: { dataset: 'Dữ liệu', @@ -162,6 +164,7 @@ export default { autoKeywordsTip: `Trích xuất N từ khóa cho mỗi khối để tăng thứ hạng của chúng cho các truy vấn chứa các từ khóa đó. Bạn có thể kiểm tra hoặc cập nhật các từ khóa đã thêm cho một khối từ danh sách khối. Lưu ý rằng các token bổ sung sẽ được tiêu thụ bởi LLM được chỉ định trong 'Cài đặt mô hình hệ thống'.`, autoQuestions: 'Câu hỏi tự động', autoQuestionsTip: `Trích xuất N câu hỏi cho mỗi khối để tăng thứ hạng của chúng cho các truy vấn chứa các câu hỏi đó. Bạn có thể kiểm tra hoặc cập nhật các câu hỏi đã thêm cho một khối từ danh sách khối. Tính năng này sẽ không làm gián đoạn quá trình phân khối nếu xảy ra lỗi, ngoại trừ việc nó có thể thêm kết quả trống vào khối gốc. Lưu ý rằng các token bổ sung sẽ được tiêu thụ bởi LLM được chỉ định trong 'Cài đặt mô hình hệ thống'.`, + delimiterTip: `Hỗ trợ nhiều ký tự phân cách, và các ký tự phân cách nhiều ký tự được bao bọc bởi dấu . Ví dụ: nếu được cấu hình như thế này: "##"; thì văn bản sẽ được phân tách bởi dấu xuống dòng, hai dấu # và dấu chấm phẩy, sau đó được lắp ráp theo kích thước của "số token".`, }, knowledgeConfiguration: { titleDescription: @@ -297,6 +300,9 @@ export default { randomSeed: 'Hạt giống ngẫu nhiên', randomSeedMessage: 'Hạt giống ngẫu nhiên là bắt buộc', entityTypes: 'Loại thực thể', + vietnamese: 'Tiếng Việt', + pageRank: 'Xếp hạng trang', + pageRankTip: `Điều này được sử dụng để tăng điểm liên quan. Điểm liên quan với tất cả các khối được truy xuất sẽ cộng với số này, Khi bạn muốn tìm kiếm cơ sở kiến ​​thức đã cho ở vị trí đầu tiên, hãy đặt điểm "Page Rank" cao hơn những điểm khác.`, }, chunk: { chunk: 'Khối', @@ -316,6 +322,9 @@ export default { ellipse: 'Elip', graph: 'Biểu đồ kiến thức', mind: 'Sơ đồ tư duy', + question: 'Câu hỏi', + questionTip: + 'Nếu có những câu hỏi được đưa ra, việc nhúng phần đó sẽ dựa trên những câu hỏi đó.', }, chat: { newConversation: 'Cuộc trò chuyện mới', @@ -593,6 +602,12 @@ export default { refuse: 'Từ chối', teamMembers: 'Thành viên nhóm', joinedTeams: 'Nhóm đã tham gia', + bedrockModelNameMessage: `Vui lòng nhập tên model của bạn!`, + sureDelete: `Bạn có chắc chắn muốn xóa thành viên này không?`, + quit: `Rời khỏi`, + sureQuit: `Bạn có chắc chắn muốn rời khỏi nhóm mà bạn đã tham gia không?`, + FishAudioAKMessage: `Vui lòng nhập KEY API`, + FishAudioRefIDMessage: `Vui lòng nhập ID của model tham chiếu (để trống để sử dụng model mặc định)`, }, message: { registered: 'Đã đăng ký!', @@ -625,8 +640,8 @@ export default { requestError: 'Lỗi yêu cầu', networkAnomalyDescription: 'Mạng của bạn có sự bất thường và bạn không thể kết nối với máy chủ.', - networkAnomaly: 'bất thường mạng', - hint: 'gợi ý', + networkAnomaly: 'Bất thường mạng', + hint: 'Gợi ý', }, fileManager: { name: 'Tên', @@ -1033,6 +1048,27 @@ export default { optional: 'Tùy chọn', pasteFileLink: 'Dán liên kết tệp', testRun: 'Chạy thử nghiệm', + template: 'Mẫu', + templateDescription: `Thành phần này được sử dụng để sắp chữ đầu ra của nhiều thành phần khác nhau.`, + arXivTip: `Thành phần này được sử dụng để lấy kết quả tìm kiếm từ https://arxiv.org/. Thông thường, nó hoạt động như một phần bổ sung cho cơ sở tri thức. Top N chỉ định số lượng kết quả tìm kiếm bạn cần điều chỉnh.`, + googleTip: `Thành phần này được sử dụng để lấy kết quả tìm kiếm từ https://www.google.com/. Thông thường, nó hoạt động như một phần bổ sung cho cơ sở tri thức. Top N và khóa API SerpApi chỉ định số lượng kết quả tìm kiếm bạn cần điều chỉnh.`, + bingTip: `Thành phần này được sử dụng để lấy kết quả tìm kiếm từ https://www.bing.com/. Thông thường, nó hoạt động như một phần bổ sung cho cơ sở tri thức. Top N và khóa đăng ký Bing chỉ định số lượng kết quả tìm kiếm bạn cần điều chỉnh.`, + gitHubDescription: `Thành phần này được sử dụng để tìm kiếm các kho lưu trữ từ https://github.com/. Top N chỉ định số lượng kết quả tìm kiếm cần điều chỉnh.`, + flow: `Quy trình làm việc`, + emailDescription: 'Gửi email đến địa chỉ đã chỉ định', + toEmail: 'Email người nhận', + smtpServerRequired: 'Vui lòng nhập địa chỉ máy chủ SMTP', + emailContent: 'Nội dung', + smtpServer: 'SMTP Server', + smtpPort: 'SMTP Port', + senderEmailRequired: 'Vui lòng nhập email người gửi', + authCodeRequired: 'Vui lòng nhập mã xác thực', + toEmailRequired: 'Vui lòng nhập email người nhận', + emailContentRequired: 'Vui lòng nhập nội dung email', + emailSentSuccess: 'Email đã được gửi thành công', + emailSentFailed: 'Không gửi được email', + jsonFormatTip: + 'Thành phần thượng nguồn phải cung cấp chuỗi JSON theo định dạng sau:', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index 81d0d5a104b..8c1bcf904a4 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -428,6 +428,7 @@ export default { multiTurnTip: '在多輪對話的中,對去知識庫查詢的問題進行最佳化。會呼叫大模型額外消耗token。', howUseId: '如何使用聊天ID?', + description: '助理描述', }, setting: { profile: '概述', diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less index 02e74426cd4..127a5c790c9 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.less @@ -29,3 +29,6 @@ .cardSelected { background-color: @selectedBackgroundColor; } +.cardSelectedDark { + background-color: #ffffff2f; +} diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx index 5666febc5ff..5934eb309b1 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-card/index.tsx @@ -5,6 +5,7 @@ import classNames from 'classnames'; import DOMPurify from 'dompurify'; import { useEffect, useState } from 'react'; +import { useTheme } from '@/components/theme-provider'; import { ChunkTextMode } from '../../constant'; import styles from './index.less'; @@ -31,6 +32,7 @@ const ChunkCard = ({ }: IProps) => { const available = Number(item.available_int); const [enabled, setEnabled] = useState(false); + const { theme } = useTheme(); const onChange = (checked: boolean) => { setEnabled(checked); @@ -56,7 +58,8 @@ const ChunkCard = ({ return ( diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx index c4eb8ab6508..ba58003c69a 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx @@ -37,16 +37,14 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { {imageList.length > 0 ? ( <> - "{item.title}" {t('methodTitle')} + {`"${item.title}" ${t('methodTitle')}`}

- - "{item.title}" {t('methodExamples')} - + {`"${item.title}" ${t('methodExamples')}`} {t('methodExamplesDescription')} {imageList.map((x) => ( diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index 564ce6e187d..10494e45f79 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -70,6 +70,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { { > - {selectedDocumentIds?.length ?? 0}/{documents.length} + {selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0} {t('filesSelected')} @@ -105,7 +105,7 @@ const TestingResult = ({ handleTesting }: IProps) => { flex={1} className={styles.selectFilesCollapse} > - {chunks.map((x) => ( + {chunks?.map((x) => ( }> {x.img_id && ( diff --git a/web/src/pages/add-knowledge/index.less b/web/src/pages/add-knowledge/index.less index bd8f3ecd377..dc934280cbe 100644 --- a/web/src/pages/add-knowledge/index.less +++ b/web/src/pages/add-knowledge/index.less @@ -6,13 +6,13 @@ flex: 1; overflow-x: auto; height: 100%; - background-color: rgba(247, 248, 250, 1); + background-color: rgba(255, 255, 255, 0.1); padding: 16px 20px 28px 40px; display: flex; flex-direction: column; } .content { - background-color: white; + background-color: rgba(255, 255, 255, 0.1); margin-top: 16px; flex: 1; } diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx index aac34cd2af2..6c0c9d13bd1 100644 --- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx @@ -42,224 +42,6 @@ const ModelSetting = ({ })} > {visible && } - {/* -