diff --git a/web/src/api/knowledgeBase.ts b/web/src/api/knowledgeBase.ts index 63ec80ae..05200221 100644 --- a/web/src/api/knowledgeBase.ts +++ b/web/src/api/knowledgeBase.ts @@ -68,8 +68,8 @@ export const getModelTypeList = async () => { return response as any[]; }; // 获取模型列表 -export const getModelList = async (pageInfo: PageRequest) => { - const response = await request.get(`${apiPrefix}/models`, { ...pageInfo, is_active: true }); +export const getModelList = async (types: string[], pageInfo: PageRequest) => { + const response = await request.get(`${apiPrefix}/models`, { ...pageInfo, type: types?.join(','), is_active: true }); return response as any; }; //获取模型提供者 diff --git a/web/src/api/memory.ts b/web/src/api/memory.ts index 1ec2d7dc..ee71bea8 100644 --- a/web/src/api/memory.ts +++ b/web/src/api/memory.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 14:00:06 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 17:48:01 + * @Last Modified time: 2026-03-31 12:25:53 */ import { request } from '@/utils/request' import type { AxiosRequestConfig } from 'axios' @@ -63,8 +63,8 @@ export const getDashboardData = () => { /****************** User Memory APIs *******************************/ export const userMemoryListUrl = '/dashboard/end_users' -export const getUserMemoryList = () => { - return request.get(userMemoryListUrl) +export const getUserMemoryList = (query?: { keyword?: string }) => { + return request.get(userMemoryListUrl, query) } // User Memory - Total end users export const getTotalEndUsers = () => { diff --git a/web/src/assets/images/common/delete_red_big.svg b/web/src/assets/images/common/delete_red_big.svg new file mode 100644 index 00000000..7751b4e1 --- /dev/null +++ b/web/src/assets/images/common/delete_red_big.svg @@ -0,0 +1,19 @@ + + + 编组 33 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/edit_bg.svg b/web/src/assets/images/common/edit_bg.svg new file mode 100644 index 00000000..4711afa4 --- /dev/null +++ b/web/src/assets/images/common/edit_bg.svg @@ -0,0 +1,17 @@ + + + 编辑 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/edit_bold.svg b/web/src/assets/images/common/edit_bold.svg new file mode 100644 index 00000000..c41984b2 --- /dev/null +++ b/web/src/assets/images/common/edit_bold.svg @@ -0,0 +1,16 @@ + + + 编辑 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/eye.svg b/web/src/assets/images/common/eye.svg new file mode 100644 index 00000000..c7b531b8 --- /dev/null +++ b/web/src/assets/images/common/eye.svg @@ -0,0 +1,16 @@ + + + link-outlined + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/eye_bg.svg b/web/src/assets/images/common/eye_bg.svg new file mode 100644 index 00000000..275c13c2 --- /dev/null +++ b/web/src/assets/images/common/eye_bg.svg @@ -0,0 +1,17 @@ + + + 编辑 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/link.svg b/web/src/assets/images/common/link.svg new file mode 100644 index 00000000..5773d546 --- /dev/null +++ b/web/src/assets/images/common/link.svg @@ -0,0 +1,13 @@ + + + link-outlined + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/common/more.svg b/web/src/assets/images/common/more.svg index 0d4d9cd2..6c24cf52 100644 --- a/web/src/assets/images/common/more.svg +++ b/web/src/assets/images/common/more.svg @@ -1,12 +1,11 @@ - - 更多 - - - - - - + + 卡片1@3x + + + + + diff --git a/web/src/assets/images/common/more_hover.svg b/web/src/assets/images/common/more_hover.svg index 04fc6eb5..d08ba08f 100644 --- a/web/src/assets/images/common/more_hover.svg +++ b/web/src/assets/images/common/more_hover.svg @@ -1,14 +1,23 @@ - - 更多 - - - - - - - - + + 更多@3x + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/images/conversation/analysisEmpty.png b/web/src/assets/images/conversation/analysisEmpty.png index 6d497f31..50adbd82 100644 Binary files a/web/src/assets/images/conversation/analysisEmpty.png and b/web/src/assets/images/conversation/analysisEmpty.png differ diff --git a/web/src/assets/images/menuNew/arrow_t_r.svg b/web/src/assets/images/menuNew/arrow_t_r.svg new file mode 100644 index 00000000..884e46c1 --- /dev/null +++ b/web/src/assets/images/menuNew/arrow_t_r.svg @@ -0,0 +1,16 @@ + + + 编组 51 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/logout_red.svg b/web/src/assets/images/menuNew/logout_red.svg new file mode 100644 index 00000000..7057b974 --- /dev/null +++ b/web/src/assets/images/menuNew/logout_red.svg @@ -0,0 +1,17 @@ + + + 退出 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/settings.svg b/web/src/assets/images/menuNew/settings.svg new file mode 100644 index 00000000..9a64bb29 --- /dev/null +++ b/web/src/assets/images/menuNew/settings.svg @@ -0,0 +1,19 @@ + + + 设置-界面设置 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/userInfo.svg b/web/src/assets/images/menuNew/userInfo.svg new file mode 100644 index 00000000..0e67a919 --- /dev/null +++ b/web/src/assets/images/menuNew/userInfo.svg @@ -0,0 +1,13 @@ + + + 账户 + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/model/bedrock.png b/web/src/assets/images/model/bedrock.png new file mode 100644 index 00000000..a16ee6f7 Binary files /dev/null and b/web/src/assets/images/model/bedrock.png differ diff --git a/web/src/assets/images/model/bedrock.svg b/web/src/assets/images/model/bedrock.svg deleted file mode 100644 index 6a0235af..00000000 --- a/web/src/assets/images/model/bedrock.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/assets/images/model/dashscope.png b/web/src/assets/images/model/dashscope.png index c1aff40e..e57821f0 100644 Binary files a/web/src/assets/images/model/dashscope.png and b/web/src/assets/images/model/dashscope.png differ diff --git a/web/src/assets/images/model/gpustack.png b/web/src/assets/images/model/gpustack.png index b154821d..39d303ae 100644 Binary files a/web/src/assets/images/model/gpustack.png and b/web/src/assets/images/model/gpustack.png differ diff --git a/web/src/assets/images/model/ollama.png b/web/src/assets/images/model/ollama.png new file mode 100644 index 00000000..068d066d Binary files /dev/null and b/web/src/assets/images/model/ollama.png differ diff --git a/web/src/assets/images/model/ollama.svg b/web/src/assets/images/model/ollama.svg deleted file mode 100644 index f8482a96..00000000 --- a/web/src/assets/images/model/ollama.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/assets/images/model/openai.png b/web/src/assets/images/model/openai.png new file mode 100644 index 00000000..db9fabaa Binary files /dev/null and b/web/src/assets/images/model/openai.png differ diff --git a/web/src/assets/images/model/openai.svg b/web/src/assets/images/model/openai.svg deleted file mode 100644 index 70686f9b..00000000 --- a/web/src/assets/images/model/openai.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/assets/images/model/volcano.png b/web/src/assets/images/model/volcano.png index 9aeb3bf3..ba0dce10 100644 Binary files a/web/src/assets/images/model/volcano.png and b/web/src/assets/images/model/volcano.png differ diff --git a/web/src/assets/images/model/xinference.png b/web/src/assets/images/model/xinference.png new file mode 100644 index 00000000..71a4821b Binary files /dev/null and b/web/src/assets/images/model/xinference.png differ diff --git a/web/src/assets/images/model/xinference.svg b/web/src/assets/images/model/xinference.svg deleted file mode 100644 index f5c5f75e..00000000 --- a/web/src/assets/images/model/xinference.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/assets/images/prompt/delete.svg b/web/src/assets/images/prompt/delete.svg new file mode 100644 index 00000000..f413ffa0 --- /dev/null +++ b/web/src/assets/images/prompt/delete.svg @@ -0,0 +1,19 @@ + + + 编组 33 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/prompt/delete_hover.svg b/web/src/assets/images/prompt/delete_hover.svg new file mode 100644 index 00000000..aebdc48c --- /dev/null +++ b/web/src/assets/images/prompt/delete_hover.svg @@ -0,0 +1,20 @@ + + + 编组 33 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/prompt/edit.svg b/web/src/assets/images/prompt/edit.svg new file mode 100644 index 00000000..89668678 --- /dev/null +++ b/web/src/assets/images/prompt/edit.svg @@ -0,0 +1,16 @@ + + + 编辑 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/prompt/edit_bg.svg b/web/src/assets/images/prompt/edit_bg.svg new file mode 100644 index 00000000..4711afa4 --- /dev/null +++ b/web/src/assets/images/prompt/edit_bg.svg @@ -0,0 +1,17 @@ + + + 编辑 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/prompt/eye.svg b/web/src/assets/images/prompt/eye.svg new file mode 100644 index 00000000..df2af1cf --- /dev/null +++ b/web/src/assets/images/prompt/eye.svg @@ -0,0 +1,16 @@ + + + 编辑 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/prompt/eye_bg.svg b/web/src/assets/images/prompt/eye_bg.svg new file mode 100644 index 00000000..275c13c2 --- /dev/null +++ b/web/src/assets/images/prompt/eye_bg.svg @@ -0,0 +1,17 @@ + + + 编辑 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/clear.svg b/web/src/assets/images/workflow/clear.svg new file mode 100644 index 00000000..10502289 --- /dev/null +++ b/web/src/assets/images/workflow/clear.svg @@ -0,0 +1,13 @@ + + + clear-outlined + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/document-extractor.svg b/web/src/assets/images/workflow/document-extractor.svg new file mode 100644 index 00000000..eea39cc6 --- /dev/null +++ b/web/src/assets/images/workflow/document-extractor.svg @@ -0,0 +1,32 @@ + + + 3备份 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/features.svg b/web/src/assets/images/workflow/features.svg new file mode 100644 index 00000000..2ff48584 --- /dev/null +++ b/web/src/assets/images/workflow/features.svg @@ -0,0 +1,15 @@ + + + 参与 + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/return.svg b/web/src/assets/images/workflow/return.svg new file mode 100644 index 00000000..b7cfe153 --- /dev/null +++ b/web/src/assets/images/workflow/return.svg @@ -0,0 +1,17 @@ + + + 退出 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/run.svg b/web/src/assets/images/workflow/run.svg new file mode 100644 index 00000000..5d320106 --- /dev/null +++ b/web/src/assets/images/workflow/run.svg @@ -0,0 +1,13 @@ + + + 编组 31 + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/save.svg b/web/src/assets/images/workflow/save.svg new file mode 100644 index 00000000..681c7633 --- /dev/null +++ b/web/src/assets/images/workflow/save.svg @@ -0,0 +1,17 @@ + + + 保存 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/workflow/variable.svg b/web/src/assets/images/workflow/variable.svg new file mode 100644 index 00000000..cdb8338e --- /dev/null +++ b/web/src/assets/images/workflow/variable.svg @@ -0,0 +1,16 @@ + + + 聊天 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index 34472a2e..ddb25838 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:17 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 13:32:29 + * @Last Modified time: 2026-03-27 14:17:38 */ import { type FC, useRef, useEffect, useState } from 'react' import clsx from 'clsx' @@ -194,7 +194,10 @@ const ChatContent: FC = ({ key={idx} size="small" className="rb:text-[12px]!" - onClick={() => window.open(`/knowledge/${citation.knowledge_id}/document/${citation.document_id}`, '_blank')} + onClick={() => { + const params = new URLSearchParams({ documentId: citation.document_id, parentId: citation.knowledge_id }); + window.open(`/#/knowledge-base/${citation.knowledge_id}/DocumentDetails?${params}`, '_blank'); + }} >{citation.file_name} ))} } diff --git a/web/src/components/Chat/ChatToolbar.tsx b/web/src/components/Chat/ChatToolbar.tsx index 3fbc0e3a..c5db0c4c 100644 --- a/web/src/components/Chat/ChatToolbar.tsx +++ b/web/src/components/Chat/ChatToolbar.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-17 14:22:25 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-23 17:42:38 + * @Last Modified time: 2026-03-27 17:54:47 */ // Toolbar component for chat input area, supporting file upload, audio recording, and variable configuration import { useRef, forwardRef, useImperativeHandle, type ReactNode, useEffect } from 'react' @@ -19,6 +19,7 @@ import type { UploadFileListModalRef } from '@/views/Conversation/types' import type { VariableConfigModalRef } from '@/views/Workflow/types' import type { Variable } from '@/views/Workflow/components/Properties/VariableList/types' import { getFileInfoByUrl } from '@/api/fileStorage'; +import { transform_file_type } from '@/views/Conversation/components/FileUpload' // Exposed methods via ref for parent components to access/set form state export interface ChatToolbarRef { @@ -126,7 +127,7 @@ const ChatToolbar = forwardRef(({ status: 'done', name: file_name, size: file_size, - type: content_type, + type: transform_file_type[content_type] || content_type, } : f) form.setFieldValue('files', updated) onFilesChange?.(updated) diff --git a/web/src/components/DebounceSelect/index.tsx b/web/src/components/DebounceSelect/index.tsx new file mode 100644 index 00000000..ab8379ad --- /dev/null +++ b/web/src/components/DebounceSelect/index.tsx @@ -0,0 +1,106 @@ +import { useRef, useState, useCallback, useEffect, type FC } from 'react'; +import { Select, Spin, Avatar } from 'antd'; +import type { SelectProps, DefaultOptionType } from 'antd/es/select'; + +import { request } from '@/utils/request'; + +interface OptionType { + [key: string]: any; +} + +interface ApiResponse { + items?: T[]; +} + +export interface DebounceSelectProps extends Omit { + /** API endpoint URL — mutually exclusive with fetchOptions */ + url?: string; + /** Extra query params merged with the search keyword */ + params?: Record; + /** Key used as option value */ + valueKey?: string; + /** Key used as option label */ + labelKey?: string; + /** Key name sent to the API for the search keyword */ + searchKey?: string; + /** Custom fetch function — mutually exclusive with url */ + fetchOptions?: (search: string | null) => Promise; + /** Transform raw API items before rendering */ + format?: (items: OptionType[]) => OptionType[]; + debounceTimeout?: number; +} + +const DebounceSelect: FC = ({ + url, + params = { page: 1, pagesize: 20 }, + valueKey = 'value', + labelKey = 'label', + searchKey = 'search', + fetchOptions, + format, + debounceTimeout = 300, + ...props +}) => { + const [fetching, setFetching] = useState(false); + const [options, setOptions] = useState([]); + const fetchRef = useRef(0); + + const timerRef = useRef>(); + + // Load initial options on mount + useEffect(() => { + debounceFetcher(null); + }, []); + + const debounceFetcher = useCallback((keyword: string | null) => { + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => { + fetchRef.current += 1; + const fetchId = fetchRef.current; + setOptions([]); + setFetching(true); + + const promise: Promise = fetchOptions + ? fetchOptions(keyword) + : request + .get>(url!, { ...params, [searchKey]: keyword }) + .then((res) => { + const data: OptionType[] = Array.isArray(res) ? res : res?.items || []; + const formatted = format ? format(data) : data.map((item) => ({ + label: item[labelKey], + value: item[valueKey], + avatar: item.avatar, + raw: item, + })); + return formatted; + }); + + promise + .then((newOptions) => { + if (fetchId !== fetchRef.current) return; + setOptions(newOptions); + setFetching(false); + }) + .catch(() => setFetching(false)); + }, debounceTimeout); + }, [url, params, searchKey, fetchOptions, format, valueKey, labelKey, debounceTimeout]); + + return ( + : null} + {...props} + options={options} + optionRender={(option) => ( +
+ {option.data.avatar && } + {option.label} +
+ )} + /> + ); +}; + +export default DebounceSelect; diff --git a/web/src/components/Empty/BodyWrapper.tsx b/web/src/components/Empty/BodyWrapper.tsx index 22bd226a..067b743c 100644 --- a/web/src/components/Empty/BodyWrapper.tsx +++ b/web/src/components/Empty/BodyWrapper.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:02:47 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-02 15:47:24 + * @Last Modified time: 2026-03-26 14:58:24 */ /** * BodyWrapper Component diff --git a/web/src/components/Header/index.module.css b/web/src/components/Header/index.module.css index 2d300abf..d39c91ec 100644 --- a/web/src/components/Header/index.module.css +++ b/web/src/components/Header/index.module.css @@ -3,7 +3,6 @@ align-items: center; justify-content: space-between; padding: 16px 24px 16px 20px; - height: 64px; color: #212332; z-index: 0; font-size: 14px; @@ -25,4 +24,26 @@ .header :global(.ant-breadcrumb .ant-breadcrumb-item:last-child a) { color: #212332; font-weight: 600; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu) { + padding: 12px 8px; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item), +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-submenu-title), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-submenu-title) { + padding-left: 8px; + padding-right: 4px; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover) { + background-color: #F6F6F6; + color: #FF5D34; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item-divider), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item-divider), +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-submenu-title-divider), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-submenu-title-divider) { + margin: 10px 8px; } \ No newline at end of file diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index 7a5d17b9..ac59fcc2 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -13,12 +13,12 @@ * @component */ -import { type FC, useRef } from 'react'; -import { Layout, Dropdown, Breadcrumb } from 'antd'; +import { type FC, useRef, useState } from 'react'; +import { Layout, Dropdown, Breadcrumb, Flex } from 'antd'; import type { MenuProps, BreadcrumbProps } from 'antd'; -import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import clsx from 'clsx'; import { useUser } from '@/store/user'; import { useMenu } from '@/store/menu'; @@ -76,27 +76,39 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { const userMenuItems: MenuProps['items'] = [ { key: '1', + icon: + {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(0, 2) : user.username[0]} + , label: (<> -
{user.username}
-
{user.email}
+
{user.username}
+
{user.email}
), }, { key: '2', type: 'divider', + className: 'rb:bg-[#EBEBEB]!' }, { key: '3', - icon: , - label: t('header.userInfo'), + icon:
, + label: + {t('header.userInfo')} +
+
, + className: 'rb:text-[#212332]!', onClick: () => { userInfoModalRef.current?.handleOpen() }, }, { key: '4', - icon: , - label: t('header.settings'), + icon:
, + label: + {t('header.settings')} +
+
, + className: 'rb:text-[#212332]!', onClick: () => { settingModalRef.current?.handleOpen() }, @@ -104,12 +116,14 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { { key: '5', type: 'divider', + className: 'rb:bg-[#EBEBEB]!' }, { key: '6', - icon: , + icon:
, label: t('header.logout'), danger: true, + className: 'rb:hover:rb:bg-transparent rb:hover:text-[#FF5D34]!', onClick: handleLogout, }, ]; @@ -147,18 +161,34 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }); } + const [open, setOpen] = useState(false); + const handleOpenChange = (open: boolean) => { + setOpen(open); + } return (
{/* Breadcrumb navigation */} {/* User info dropdown menu */} - -
{user.username}
-
+ {user.username && ( + + + + {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(0, 2) : user.username[0]} + + {user.username} +
+
+
+ )} diff --git a/web/src/components/Layout/AuthLayout.tsx b/web/src/components/Layout/AuthLayout.tsx index a7516367..d71bf147 100644 --- a/web/src/components/Layout/AuthLayout.tsx +++ b/web/src/components/Layout/AuthLayout.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:11:02 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 18:43:42 + * @Last Modified time: 2026-03-26 15:01:02 */ /** * AuthLayout Component @@ -54,14 +54,14 @@ const AuthLayout: FC = () => { }, []); return ( - + {/* Sidebar navigation */} - + {/* Header with breadcrumbs and user menu */} {/* Main content area - renders child routes */} - + diff --git a/web/src/components/Layout/AuthSpaceLayout.tsx b/web/src/components/Layout/AuthSpaceLayout.tsx index 1efa6fd4..129f67bc 100644 --- a/web/src/components/Layout/AuthSpaceLayout.tsx +++ b/web/src/components/Layout/AuthSpaceLayout.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:11:43 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-05 14:57:08 + * @Last Modified time: 2026-03-26 15:00:54 */ /** * AuthSpaceLayout Component @@ -56,14 +56,14 @@ const AuthSpaceLayout: FC = () => { }, []); return ( - + {/* Sidebar navigation configured for space mode */} - + {/* Header with breadcrumbs and user menu configured for space mode */} {/* Main content area for knowledge base pages - renders child routes */} - + diff --git a/web/src/components/Layout/BasicAuthLayout.tsx b/web/src/components/Layout/BasicAuthLayout.tsx index f279a48b..2f40ac37 100644 --- a/web/src/components/Layout/BasicAuthLayout.tsx +++ b/web/src/components/Layout/BasicAuthLayout.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:12:42 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-28 17:28:41 + * @Last Modified time: 2026-03-26 15:36:25 */ /** * BasicAuthLayout Component @@ -35,7 +35,7 @@ const BasicAuthLayout: FC = () => { }, [getUserInfo]); return ( -
+
{/* Render child routes without additional UI */}
diff --git a/web/src/components/Layout/PageHeader.tsx b/web/src/components/Layout/PageHeader.tsx index ddc420fd..88117da0 100644 --- a/web/src/components/Layout/PageHeader.tsx +++ b/web/src/components/Layout/PageHeader.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-10 11:08:27 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 11:19:48 + * @Last Modified time: 2026-03-26 15:38:31 */ /* * PageHeader Component @@ -43,7 +43,7 @@ const PageHeader: FC = ({ }) => { return ( // Main header container: full width, 64px height, flex layout with space between -
+
{avatarUrl ? {avatarUrl} @@ -58,7 +58,7 @@ const PageHeader: FC = ({ {operation} - {centerContent && + {centerContent && {centerContent} } {/* Right section: Extra content (buttons, filters, etc.) */} diff --git a/web/src/components/PageScrollList/index.tsx b/web/src/components/PageScrollList/index.tsx index 19e29297..fd449848 100644 --- a/web/src/components/PageScrollList/index.tsx +++ b/web/src/components/PageScrollList/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:18:19 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-19 20:47:34 + * @Last Modified time: 2026-03-27 15:52:37 */ /** * PageScrollList Component @@ -56,9 +56,10 @@ interface PageScrollListProps> { /** Additional CSS classes */ className?: string; needLoading?: boolean; + heightClass?: string; } -const heightClass = 'rb:h-[calc(100vh-124px)]!'; +const defaultHeightClass = 'rb:h-[calc(100vh-116px)]!'; /** Infinite scroll list component with pagination support */ const PageScrollList = forwardRef(>({ @@ -68,6 +69,7 @@ const PageScrollList = forwardRef(>({ column = 4, className = '', needLoading = true, + heightClass, }: PageScrollListProps, ref: React.Ref) => { /** Expose refresh method to parent component */ useImperativeHandle(ref, () => ({ @@ -140,13 +142,13 @@ const PageScrollList = forwardRef(>({
loadMoreData()} hasMore={hasMore} - loader={loading && needLoading ? : false} + loader={loading && needLoading ? : false} // endMessage={It is all, nothing more 🤐} scrollableTarget="scrollableDiv" className='rb:h-full!' @@ -154,7 +156,7 @@ const PageScrollList = forwardRef(>({ {/* Render grid list or empty state */} {data.length > 0 ? ( {data.map((item, index) => ( @@ -162,7 +164,7 @@ const PageScrollList = forwardRef(>({ ))} - ) : !loading ? : null} + ) : !loading ? : null}
diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index 4be749e7..3bd0cea3 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:25:31 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 12:32:58 + * @Last Modified time: 2026-03-27 19:11:43 */ /** * SiderMenu Component @@ -128,11 +128,35 @@ const Menu: FC<{ /** Filter menus based on user role and source */ useEffect(() => { + let menuList: MenuItem[] = [] + if (user.role === 'member' && source === 'space') { - setMenus((allMenus[source] || []).filter(menu => menu.code !== 'member')) + menuList = (allMenus[source] || []).filter(menu => menu.code !== 'member') } else if (user) { - setMenus(allMenus[source] || []) + menuList = allMenus[source] || [] } + + const noAuthList = ['user', 'pricing'].filter(vo => !user.permissions?.includes(vo) && !user.permissions?.includes('all')) + + if (noAuthList && !noAuthList?.includes('all')) { + const filterMenus = (list: MenuItem[]): MenuItem[] =>{ + const filterList = list?.filter(menu => !noAuthList?.includes(menu.code as string)) + + const showList: MenuItem[] = [] + filterList?.forEach(menu => { + const filteredSubs = filterMenus(menu.subs || []) + const hadSubs = menu.subs && menu.subs.length > 0 + if (hadSubs && filteredSubs.length === 0) return + if (menu.type === 'group' && (!menu.subs || menu.subs?.length < 1)) return + showList.push({ ...menu, subs: filteredSubs }) + }) + + return showList + } + menuList = filterMenus(menuList) + } + + setMenus(menuList) }, [source, allMenus, user]) /** Handle menu item click and navigate to path */ @@ -153,7 +177,7 @@ const Menu: FC<{ const iconKey = selectedKeys.includes(menu.path || '') ? `${menu.code}Active` : menu.code; const iconSrc = iconPathMap[iconKey as keyof typeof iconPathMap]; const subs = (menu.subs || []).filter(sub => sub.display); - + /** Leaf node - menu item without children */ if (!subs || subs.length === 0) { if (menu.path) { diff --git a/web/src/components/Table/index.tsx b/web/src/components/Table/index.tsx index d33f8171..62c9421a 100644 --- a/web/src/components/Table/index.tsx +++ b/web/src/components/Table/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:29:46 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 17:11:55 + * @Last Modified time: 2026-03-26 14:52:23 */ /** * RbTable Component @@ -199,7 +199,7 @@ const RbTable = forwardRef(, Q = Record 0 ? config : undefined; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 9b957a84..2975796a 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -980,6 +980,7 @@ export const en = { scene_id: 'Ontology Scenario', }, member: { + memberList: 'Member List', username: 'Username', account: 'Account', role: 'Role', @@ -1908,7 +1909,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re permissionInfo: 'Permission Information', is_expired: 'Status', active: 'Active', - inactive: 'Expired' + inactive: 'Expired', + noScopes: 'There is no permission information here…', }, tool: { mcp: 'MCP Services', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 862ed5d4..3edd84e3 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1375,6 +1375,7 @@ export const zh = { scene_id: '本体场景', }, member: { + memberList: '成员列表', username: '用户名', account: '账号', role: '角色', @@ -1905,7 +1906,8 @@ export const zh = { permissionInfo: '授权信息', is_expired: '状态', active: '活跃', - inactive: '过期' + inactive: '过期', + noScopes: '暂无权限信息…', }, tool: { mcp: 'MCP 服务', diff --git a/web/src/store/menu.json b/web/src/store/menu.json index f1453d1e..8d30dcc4 100644 --- a/web/src/store/menu.json +++ b/web/src/store/menu.json @@ -143,7 +143,7 @@ "code": "systemSettings", "label": "systemSettings", "i18nKey": "menu.systemSettings", - "path": "/", + "path": null, "enable": true, "display": true, "level": 1, diff --git a/web/src/styles/antdThemeConfig.ts b/web/src/styles/antdThemeConfig.ts index 0d9786b4..8b610ea0 100644 --- a/web/src/styles/antdThemeConfig.ts +++ b/web/src/styles/antdThemeConfig.ts @@ -32,7 +32,7 @@ export const lightTheme: ThemeConfig = { bodyBg: '#EEEFF4', siderBg: '#FAFCFF', headerPadding: '0 24px 0 20px', - headerHeight: 64, + headerHeight: 56, headerColor: '#212332', }, Menu: { diff --git a/web/src/styles/index.css b/web/src/styles/index.css index b928b939..684a4ba6 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -88,17 +88,15 @@ body { padding: 0; height: 100%; } - +.ant-menu-light .ant-menu-item { + font-weight: 500; +} .ant-menu-light .ant-menu-item-selected, .ant-menu-light>.ant-menu .ant-menu-item-selected, .ant-menu-light:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):hover, .ant-menu-light>.ant-menu:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):hover { box-shadow: none; } -.ant-menu-light .ant-menu-item-selected, -.ant-menu-light>.ant-menu .ant-menu-item-selected { - font-weight: 500; -} .ant-menu-inline-collapsed-tooltip .ant-tooltip-arrow { display: none; } @@ -380,6 +378,11 @@ body { .ant-select-filled:not(.ant-select-customize-input) .ant-select-selector { background-color: #FFFFFF; } +.ant-input-filled:hover, +.ant-select-filled:not(.ant-select-customize-input) .ant-select-selector { + background-color: #FFFFFF; + border-color: #171719; +} .ant-checkbox .ant-checkbox-inner { border-radius: 6px !important; } @@ -392,4 +395,17 @@ body { .ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-submenu-title-selected { color: #FFFFFF; background-color: #171719; +} +.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover, +.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover { + background-color: #F6F6F6; + color: #FF5D34; +} + +.spin.ant-spin-nested-loading .ant-spin-container::after { + background: transparent; +} +.upload-block, +.upload-block.ant-upload-wrapper .ant-upload-select { + display: block; } \ No newline at end of file diff --git a/web/src/views/ApiKeyManagement/index.tsx b/web/src/views/ApiKeyManagement/index.tsx index 7bb417c5..071b9ef5 100644 --- a/web/src/views/ApiKeyManagement/index.tsx +++ b/web/src/views/ApiKeyManagement/index.tsx @@ -6,20 +6,22 @@ */ import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, App, Space } from 'antd'; +import { Button, App, Dropdown, Flex } from 'antd'; import clsx from 'clsx'; import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons'; import copy from 'copy-to-clipboard' +import type { MenuInfo } from 'rc-menu/lib/interface'; import type { ApiKey, ApiKeyModalRef } from './types'; import ApiKeyModal from './components/ApiKeyModal'; import ApiKeyDetailModal from './components/ApiKeyDetailModal'; -import RbCard from '@/components/RbCard/Card' +import RbCard from '@/components/RbCard' import { getApiKeyListUrl, deleteApiKey } from '@/api/apiKey'; import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' import { formatDateTime } from '@/utils/format'; import Tag from '@/components/Tag' import { maskApiKeys } from '@/utils/apiKeyReplacer'; +import RbDescriptions from '@/components/RbDescriptions'; /** * API Key Management page component @@ -87,59 +89,85 @@ const ApiKeyManagement: React.FC = () => { } return ( <> -
+ -
+
ref={scrollListRef} url={getApiKeyListUrl} query={{ is_active: true, type: 'service' }} - column={2} + column={3} renderItem={(apiKeyItem) => { return ( - - {['id', 'is_expired', 'created_at'].map((key, index) => ( -
- {t(`apiKey.${key}`)} - - { key === 'created_at' - ? formatDateTime(apiKeyItem[key], 'YYYY-MM-DD HH:mm:ss') - : key === 'is_expired' - ? {apiKeyItem[key] ? t('apiKey.inactive') : t('apiKey.active')} - : String(apiKeyItem[key as keyof ApiKey]) - } - -
- ))} + + + {apiKeyItem.name} + + {apiKeyItem.scopes?.includes('memory') && {t('apiKey.memoryEngine')}} + {apiKeyItem.scopes?.includes('rag') && {t('apiKey.knowledgeBase')}} + {!apiKeyItem.scopes?.includes('memory') && !apiKeyItem.scopes?.includes('rag') &&
{t('apiKey.noScopes')}
} +
+
+ , + label: t('common.edit'), + onClick: () => handleEdit(apiKeyItem), + }, + { + key: 'view', + icon:
, + label: t('common.view'), + onClick: () => handleView(apiKeyItem), + }, + { + key: 'delete', + danger: true, + icon:
, + label: t('common.delete'), + onClick: () => handleDelete(apiKeyItem), + }, + ] + }} + placement="bottomRight" + > +
+ + + } + isNeedTooltip={false} + headerClassName="rb:min-h-[78px]!" + > + ({ + key, + label: t(`apiKey.${key}`), + children: + {key === 'created_at' + ? formatDateTime(apiKeyItem[key], 'YYYY-MM-DD HH:mm:ss') + : key === 'is_expired' + ? {apiKeyItem[key] ? t('apiKey.inactive') : t('apiKey.active')} + : String(apiKeyItem[key as keyof ApiKey]) + } + + }))} + /> -
- {maskApiKeys(apiKeyItem.api_key)} - - -
- - - {apiKeyItem.scopes?.includes('memory') && {t('apiKey.memoryEngine')}} - {apiKeyItem.scopes?.includes('rag') && {t('apiKey.knowledgeBase')}} - - -
- - - -
+ + {maskApiKeys(apiKeyItem.api_key)} + +
handleCopy(apiKeyItem.api_key)} className="rb:cursor-pointer rb:rounded-md rb:size-6 rb:bg-[url('@/assets/images/common/copy_dark.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat" style={{ backgroundColor: 'rgba(0,0,0,0.08)' }}>
+
); }} diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 0d272c1d..0cfdde05 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:21 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 12:13:33 + * @Last Modified time: 2026-03-27 18:13:51 */ import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useMemo } from 'react'; import { useTranslation } from 'react-i18next' @@ -44,6 +44,14 @@ import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import DescWrapper from '@/components/FormItem/DescWrapper' import FeaturesConfig from './components/FeaturesConfig' import { getListLogoUrl } from '@/views/ModelManagement/utils'; +import type { ChatItem } from '@/components/Chat/types' + +export const replaceVariables = (statement: string, variables: Variable[]) => { + return statement.replace(/\{\{([^}]+)\}\}/g, (match, name) => { + const v = variables.find(item => item.name === name) + return v?.value != null && v.value !== '' ? String(v.value) : match + }) +} /** * Agent configuration component @@ -148,8 +156,9 @@ const Agent = forwardRef { if (flag) { - message.success(t('common.saveSuccess')) + message.success({ content: t('common.saveSuccess'), duration: 1 }) } setIsSave(false) resolve(res) @@ -331,28 +340,13 @@ const Agent = forwardRef { chatVariableConfigModalRef.current?.handleOpen(chatVariables) } + /** * Save chat variable configuration * @param values - Variable values */ const handleSaveChatVariable = (variables: Variable[]) => { setChatVariables(variables) - const opening_statement = form.getFieldValue(['features', 'opening_statement']) - - if (opening_statement?.statement && opening_statement?.statement.trim() !== '') { - const statement = opening_statement.statement as string - const replacedContent = statement.replace(/\{\{([^}]+)\}\}/g, (match, name) => { - const v = variables.find(item => item.name === name) - return v?.value != null && v.value !== '' ? String(v.value) : match - }) - setChatList(prev => prev.map(item => { - const list = [...(item.list || [])] - if (list.length > 0 && list[0].role === 'assistant') { - list[0] = { ...list[0], content: replacedContent } - } - return { ...item, list } - })) - } } useEffect(() => { setChatVariables(values?.variables || []) @@ -360,43 +354,55 @@ const Agent = forwardRef { form.setFieldValue('features', value) - - if (value?.opening_statement?.statement && value?.opening_statement?.statement.trim() !== '') { - setChatList(prev => (prev.map(item => { - const firstMsg = item.list?.[0] - - if (firstMsg?.role === 'assistant') { - firstMsg.meta_data = { - suggested_questions: value.opening_statement?.suggested_questions || [] - } - return item - } else { - return { - ...item, - list: [{ - role: 'assistant', - content: value.opening_statement?.statement, - meta_data: { - suggested_questions: value.opening_statement?.suggested_questions || [] - } - }, ...(item.list || [])] - } - } - }))) - } } const modelLogo = useMemo(() => { return defaultModel?.name && getListLogoUrl(defaultModel.provider, defaultModel.logo as string) }, [defaultModel]) + + useEffect(() => { + const opening_statement = form.getFieldValue(['features', 'opening_statement']) + console.log('opening_statement', opening_statement, defaultModel, chatList) + + if (opening_statement?.enabled && opening_statement?.statement && opening_statement?.statement.trim() !== '') { + const assistantMsg: ChatItem = { + role: 'assistant', + content: replaceVariables(opening_statement.statement, chatVariables), + meta_data: { + suggested_questions: opening_statement?.suggested_questions + } + } + setChatList(prev => { + if (prev.length === 0 && !defaultModel) return prev + if (defaultModel && prev.length === 1) { + return [{ + label: defaultModel.name, + model_config_id: defaultModel.id, + model_parameters: defaultModel.config as unknown as ModelConfig, + list: [assistantMsg] + }] + } + + return prev.map(vo => { + if (vo.list?.length === 0) { + return { ...vo, list: [assistantMsg] } + } else if (vo.list && vo.list[0].role === 'assistant') { + return { ...vo, list: [assistantMsg, ...vo.list.slice(1)] } + } else { + return { ...vo, list: [assistantMsg, ...(vo.list || [])] } + } + }) + }) + } + }, [defaultModel, chatList.length, form.getFieldValue(['features', 'opening_statement']), chatVariables]) console.log('agent values', values) return ( <> {loading && } - - + +
- + - - - - ({ - value: type, - label: t(`application.${type}`), - labelDesc: t(`application.${type}Desc`), - }))} - allowClear={false} - block={true} - /> - - + + + + + ({ + value: type, + label: t(`application.${type}`), + labelDesc: t(`application.${type}Desc`), + }))} + allowClear={false} + block={true} + /> + + @@ -289,45 +290,46 @@ const Cluster = forwardRef{t('application.modelConfig')} - - {t('application.orchestrationMode')}} - className="rb:mb-4!" - > - ({ - value: type, - label: t(`application.${type}`), - }))} - placeholder={t('common.pleaseSelect')} - /> - - } + + {t('application.orchestrationMode')}} + className="rb:mb-4!" + > + ({ + value: type, + label: t(`application.${type}`), + }))} + placeholder={t('common.pleaseSelect')} + /> + + } +
- + = { history: 'default', } -const heightClass = 'rb:h-[calc(100vh-88px)]' +const heightClass = 'rb:max-h-[calc(100vh-140px)]' /** * Release page component * Manages application version releases, rollbacks, and version history @@ -77,51 +77,55 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres } return ( -
+
{t('application.versionList')}
{t('application.versionListDesc')}
- {releaseList.length === 0 - ? - : selectedVersion && releaseList.map((version, index) => { - const tagKey = version.id === data.current_release_id && index === 0 - ? 'current' - : version.id === data.current_release_id - ? 'rolledBack' : 'history' - return ( - - {version.version_name && version.version_name[0].toLocaleLowerCase() === 'v' ? version.version_name : version.version_name ? `v${version.version_name}` : `v${version.version}`} - {tagKey && - {tagKey} - } - } - className={clsx("rb:hover:shadow-[0px_2px_8px_0px_rgba(0,0,0,0.2)]! rb:cursor-pointer rb:bg-white", { - 'rb:border-[#171719]!': version.id === selectedVersion.id, - 'rb:border-[#DFE4ED] ': version.id !== selectedVersion.id - })} - headerType="borderless" - onClick={() => setSelectedVersion(version)} - > -
- -
-
- {t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')} -
-
- {t('application.publisher')}: {version.publisher_name} -
-
- ) - }) - } +
+ {releaseList.length === 0 + ? + : + {selectedVersion && releaseList.map((version, index) => { + const tagKey = version.id === data.current_release_id && index === 0 + ? 'current' + : version.id === data.current_release_id + ? 'rolledBack' : 'history' + return ( + + {version.version_name && version.version_name[0].toLocaleLowerCase() === 'v' ? version.version_name : version.version_name ? `v${version.version_name}` : `v${version.version}`} + {tagKey && + {tagKey} + } + } + className={clsx("rb:hover:shadow-[0px_2px_8px_0px_rgba(0,0,0,0.2)]! rb:cursor-pointer rb:bg-white", { + 'rb:border-[#171719]!': version.id === selectedVersion.id, + 'rb:border-[#DFE4ED] ': version.id !== selectedVersion.id + })} + headerType="borderless" + onClick={() => setSelectedVersion(version)} + > +
+ +
+
+ {t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')} +
+
+ {t('application.publisher')}: {version.publisher_name} +
+
+ ) + })} +
+ } +
-
+
void}> = ({data, refres {selectedVersion && - + = ({ } return ( -
+
= ({ if (loading || !id) return setLoading(true) setCompareLoading(true) + const files = (fileList || []).filter(item => !['uploading', 'error'].includes(item.status)) handleSave(false) .then(() => { const message = msg if (!message?.trim()) return - const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status)) // Validate required variables before sending let isCanSend = true const params: Record = {} @@ -427,11 +427,11 @@ const Chat: FC = ({ if (loading || !id) return setLoading(true) setCompareLoading(true) + const files = (fileList || []).filter(item => !['uploading', 'error'].includes(item.status)) handleSave(false) .then(() => { const message = msg if (!message || message.trim() === '') return - const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status)) addUserMessage(message, files) setMessage(undefined) toolbarRef.current?.setFiles([]) diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx index 5cee841a..8e6fc875 100644 --- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx +++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx @@ -2,11 +2,11 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 15:58:23 + * @Last Modified time: 2026-03-27 19:07:24 */ import { type FC, useRef, useMemo, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { Tabs, Dropdown, Button, Flex } from 'antd'; +import { Tabs, Dropdown, Flex, Popover } from 'antd'; import type { MenuProps } from 'antd'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx'; @@ -212,15 +212,42 @@ const ConfigHeader: FC = ({ } extra={application?.type === 'workflow' && source !== 'sharing' && activeTab === 'arrangement' ? - - - - - -
+ ({ ...v, display_name: v.name }))} + /> + +
+
+ +
+
+ +
+
+ +
+
+ +
+
: diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx index e8761662..15a8b4e0 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:56 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 14:03:01 + * @Last Modified time: 2026-03-27 17:32:10 */ /** * Copy Application Modal @@ -74,16 +74,16 @@ const FeaturesConfigModal = forwardRef { - let options = [{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }] - if (!capability) return options + let options = fu.document_enabled ? [{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }] : [] + if (!capability && source !== 'workflow') return options - if (capability.includes('vision')) { + if ((capability?.includes('vision') || source === 'workflow') && fu.image_enabled) { options.push({ type: 'image', enabled: fu.image_enabled, maxSize: fu.image_max_size_mb }) } - if (capability.includes('audio')) { + if ((capability?.includes('audio') || source === 'workflow') && fu.audio_enabled) { options.push({ type: 'audio', enabled: fu.audio_enabled, maxSize: fu.audio_max_size_mb }) } - if (capability.includes('video')) { + if ((capability?.includes('video') || source === 'workflow') && fu.video_enabled) { options.push({ type: 'video', enabled: fu.video_enabled, maxSize: fu.video_max_size_mb }) } return options.filter(item => item.enabled) @@ -201,6 +201,7 @@ const FeaturesConfigModal = forwardRef @@ -23,6 +24,7 @@ interface FileUploadSettingModalRef { interface FileUploadSettingModalProps { onSave: (values: FileUpload) => void; capability?: Capability[]; + source?: Application['type'] } const documentType = { type: 'document', @@ -108,6 +110,7 @@ const defaultValues: FileUpload = { const FileUploadSettingModal = forwardRef(({ onSave, capability, + source, }, ref) => { const { t } = useTranslation(); const [visible, setVisible] = useState(false); @@ -149,6 +152,14 @@ const FileUploadSettingModal = forwardRef { + if (source === 'workflow') { + return [ + documentType, + imageType, + audioType, + videoType, + ] + } let options = [documentType] if (!capability) return options if (capability.includes('vision')) options = [...options, imageType] diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx index b85a9006..2d829790 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-05 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 14:12:11 + * @Last Modified time: 2026-03-27 14:38:28 */ import { forwardRef, useImperativeHandle, useState } from 'react'; import { Button, Form, Input, Flex, App } from 'antd'; @@ -44,12 +44,13 @@ const OpenStatementSettingModal = forwardRef { form.validateFields().then(values => { + const { suggested_questions, ...rest } = values + const filterSuggestedQuestions = suggested_questions.filter(vo => vo && vo.trim() !== '' && vo !== null) if (values?.enabled && values?.statement && values?.statement?.trim() !== '') { const usedVars = [...new Set([...values.statement?.matchAll(/\{\{(\w+)\}\}/g)].map(m => m[1]))] const validNames = new Set(chatVariables.map(v => v.name)) const invalid = usedVars.filter(v => !validNames.has(v)) - console.log('invalid', invalid) if (invalid.length > 0) { modal.confirm({ title: t('application.invalidVariablesTitle'), @@ -57,14 +58,26 @@ const OpenStatementSettingModal = forwardRef { - onSave(values); + onSave({ + ...rest, + suggested_questions: filterSuggestedQuestions + }); handleClose(); }, }) } else { - onSave(values); + onSave({ + ...rest, + suggested_questions: filterSuggestedQuestions + }); handleClose(); } + } else { + onSave({ + ...rest, + suggested_questions: filterSuggestedQuestions + }); + handleClose(); } }); }; diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx index e27c2b86..dba03ab2 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx @@ -2,11 +2,11 @@ * @Author: ZhaoYing * @Date: 2026-03-13 17:20:21 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 11:00:25 + * @Last Modified time: 2026-03-27 19:07:35 */ import { type FC, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button } from 'antd'; +import { Button, Popover } from 'antd'; import FeaturesConfigModal from './FeaturesConfigModal' import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types' @@ -45,7 +45,16 @@ const FeaturesConfig: FC = ({ return ( <> {/* Button that triggers the feature configuration modal */} - + {source === 'workflow' + ? + +
+
+ : + } {/* Modal for editing feature settings; calls refresh on save */} ( > {source !== 'multi_agent' && diff --git a/web/src/views/ApplicationConfig/index.tsx b/web/src/views/ApplicationConfig/index.tsx index 1630be49..a9e1df5d 100644 --- a/web/src/views/ApplicationConfig/index.tsx +++ b/web/src/views/ApplicationConfig/index.tsx @@ -2,10 +2,11 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:37 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 15:59:47 + * @Last Modified time: 2026-03-26 15:37:18 */ import React, { useEffect, useState, useRef } from 'react'; import { useParams } from 'react-router-dom'; +import { Flex } from 'antd' import ConfigHeader from './components/ConfigHeader' import type { AgentRef, ClusterRef, WorkflowRef, Config } from './types' @@ -108,7 +109,7 @@ const ApplicationConfig: React.FC = () => { } return ( - <> + { features={features} onFeaturesChange={setFeatures} /> -
+
{activeTab === 'arrangement' && application?.type === 'agent' && } {activeTab === 'arrangement' && application?.type === 'multi_agent' && } {activeTab === 'arrangement' && application?.type === 'workflow' && } @@ -129,7 +130,7 @@ const ApplicationConfig: React.FC = () => { {activeTab === 'test' && } {activeTab === 'log' && }
- + ); }; diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts index e125e73a..6c82ea64 100644 --- a/web/src/views/ApplicationConfig/types.ts +++ b/web/src/views/ApplicationConfig/types.ts @@ -8,7 +8,7 @@ import type { KnowledgeConfig } from './components/Knowledge/types' import type { Variable } from './components/VariableList/types' import type { ToolOption } from './components/ToolList/types' import type { ChatItem } from '@/components/Chat/types' -import type { GraphRef, WorkflowConfig } from '@/views/Workflow/types'; +import type { ChatVariable, GraphRef, WorkflowConfig } from '@/views/Workflow/types'; import type { ApiKey } from '@/views/ApiKeyManagement/types' import type { SkillConfigForm } from './components/Skill/types' import type { Capability } from '@/views/ModelManagement/types' @@ -164,6 +164,7 @@ export interface WorkflowRef { graphRef: GraphRef; /** Add variable */ addVariable: () => void; + chatVariables: ChatVariable[]; config: WorkflowConfig | null; features: WorkflowConfig['features']; handleSaveFeaturesConfig?: (value: FeaturesConfigForm) => void; diff --git a/web/src/views/ApplicationManagement/MySharing.tsx b/web/src/views/ApplicationManagement/MySharing.tsx index 291ec4b3..434bf465 100644 --- a/web/src/views/ApplicationManagement/MySharing.tsx +++ b/web/src/views/ApplicationManagement/MySharing.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:34:12 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 11:37:51 + * @Last Modified time: 2026-03-26 14:39:18 */ import React, { useState, useEffect, useMemo, type MouseEvent } from 'react'; import { useTranslation } from 'react-i18next'; @@ -81,7 +81,7 @@ const MySharing: React.FC = () => { } return ( - + {grouped.map(({ workspace, items }) => ( { featureConfig: FeaturesConfigForm['file_upload'] } -const transform_file_type = { +export const transform_file_type: Record = { 'text/plain': 'document/text', 'text/markdown': 'document/markdown', 'text/x-markdown': 'document/x-markdown', @@ -313,7 +313,7 @@ const UploadFiles = forwardRef(({
); }, - className: 'rb:-mb-1.5!', + className: 'rb:-mb-1.5! upload-block', ...props, }; @@ -327,7 +327,7 @@ const UploadFiles = forwardRef(({ - {t('memoryConversation.uploadFile')} +
{t('memoryConversation.uploadFile')}
); }); diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 3e64dfe1..80394317 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:58:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 13:35:42 + * @Last Modified time: 2026-03-27 14:28:19 */ /** * Conversation Page @@ -30,8 +30,10 @@ import { type SSEMessage } from '@/utils/stream' import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage' import ChatToolbar, { type ChatToolbarRef } from '@/components/Chat/ChatToolbar' import type { Variable } from '@/views/Workflow/components/Properties/VariableList/types' +import type { Variable as AppVariable } from '@/views/ApplicationConfig/components/VariableList/types' import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types'; import { getFileStatusById } from '@/api/fileStorage'; +import { replaceVariables } from '@/views/ApplicationConfig/Agent' const Conversation: FC = () => { const { t } = useTranslation() @@ -84,11 +86,11 @@ const Conversation: FC = () => { if (shareToken && token) { getExperienceConfig(token) .then(res => { - const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory?: boolean; } + const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory: boolean; } toolbarRef.current?.setVariables(response.variables || []) setConfig(response) setFeatures(response.features) - setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow')) + setIsHasMemory((response.app_type === 'workflow' && response.memory) || response.memory) }) } else { setChatList([]) @@ -375,6 +377,17 @@ const Conversation: FC = () => { }) } + const handleChangeVariables = (variables: Variable[]) => { + setChatList(prev => { + const firstMsg = prev[0] + console.log('firstMsg', firstMsg) + if (firstMsg && firstMsg.role === 'assistant' && firstMsg.content && features?.opening_statement.enabled && features?.opening_statement.statement && variables.length > 0) { + firstMsg.content = replaceVariables(features?.opening_statement.statement, variables as unknown as AppVariable[]) + } + return [firstMsg, ...prev.slice(1)] + }) + } + console.log('chatList', chatList) return ( @@ -460,7 +473,8 @@ const Conversation: FC = () => { } }} rightExtra={ - + (features?.web_search?.enabled || isHasMemory) + ? {features?.web_search?.enabled && { } + : undefined } + onVariablesChange={handleChangeVariables} />
diff --git a/web/src/views/EmotionEngine/index.tsx b/web/src/views/EmotionEngine/index.tsx index 5784a82f..8c0188da 100644 --- a/web/src/views/EmotionEngine/index.tsx +++ b/web/src/views/EmotionEngine/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:56:54 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 18:28:18 + * @Last Modified time: 2026-03-26 15:43:29 */ /** * Emotion Engine Configuration Page @@ -118,8 +118,8 @@ const EmotionEngine: React.FC = () => { } return ( - - + + { } - className="rb:h-[calc(100vh-76px)]!" + className="rb:h-full!" bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-3! rb:pt-0!" > { - + diff --git a/web/src/views/ForgettingEngine/index.tsx b/web/src/views/ForgettingEngine/index.tsx index 2386d070..0b15867e 100644 --- a/web/src/views/ForgettingEngine/index.tsx +++ b/web/src/views/ForgettingEngine/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:00:12 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 18:29:00 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-26 15:47:37 */ /** * Forgetting Engine Configuration Page @@ -155,8 +155,8 @@ const ForgettingEngine: React.FC = () => { } return ( - - + + @@ -165,7 +165,7 @@ const ForgettingEngine: React.FC = () => { } headerType="borderless" headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold" - className="rb:h-[calc(100vh-76px)]!" + className="rb:h-full!" bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-3! rb:pt-0!" >
{
- + = ({ chartData, loading }) => { > {loading ? - : + : } ) diff --git a/web/src/views/Index/components/GuideCard.tsx b/web/src/views/Index/components/GuideCard.tsx index f1cbc06c..1d6472ae 100644 --- a/web/src/views/Index/components/GuideCard.tsx +++ b/web/src/views/Index/components/GuideCard.tsx @@ -75,7 +75,7 @@ const GuideCard: React.FC = () => {
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx index 8859a8c8..f3716cd3 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx @@ -7,7 +7,7 @@ * @LastEditTime: 2025-12-19 20:19:59 */ import { useEffect, useState, useRef, type FC } from 'react'; -import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useNavigate, useParams, useLocation, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager'; import { Button, Spin, message, Switch } from 'antd'; @@ -29,11 +29,16 @@ const DocumentDetails: FC = () => { const { updateBreadcrumbs } = useBreadcrumbManager({ breadcrumbType: 'detail' }); + const [searchParams] = useSearchParams(); const { documentId, parentId: locationParentId, breadcrumbPath - } = (location.state || {}) as { + } = ({ + documentId: searchParams.get('documentId') ?? undefined, + parentId: searchParams.get('parentId') ?? undefined, + ...(location.state || {}) + }) as { documentId?: string; parentId?: string; breadcrumbPath?: BreadcrumbPath; diff --git a/web/src/views/KnowledgeBase/index.tsx b/web/src/views/KnowledgeBase/index.tsx index 38fa48e6..b996bc16 100644 --- a/web/src/views/KnowledgeBase/index.tsx +++ b/web/src/views/KnowledgeBase/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useMemo, useCallback, type FC } from 'react'; -import { Row, Col, Button, Dropdown, Tooltip, App } from 'antd' +import { Button, Dropdown, Tooltip, App, Flex } from 'antd' import type { MenuProps } from 'antd'; -import { EllipsisOutlined, RightOutlined, DownOutlined } from '@ant-design/icons'; +import { RightOutlined, DownOutlined } from '@ant-design/icons'; import { useNavigate, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -78,6 +78,7 @@ const KnowledgeBaseManagement: FC = () => { if (permissionId !== 'share') { items.push({ key: '1', + icon:
, label: t('knowledgeBase.edit'), onClick: () => { handleEdit(item); @@ -87,6 +88,7 @@ const KnowledgeBaseManagement: FC = () => { items.push({ key: '2', + icon:
, label: t('knowledgeBase.delete'), onClick: () => { handleDelete(item); @@ -560,21 +562,23 @@ const KnowledgeBaseManagement: FC = () => { {data.length === 0 && !loading ? ( ) : ( - +
{data.map((item) => { const modelInfo = modelMenus[item.id]; const hasModelInfo = modelInfo && modelInfo.menu.length > 1; return ( - +
e.stopPropagation()}> - - + +
e.stopPropagation()} className="rb:cursor-pointer rb:size-5.5 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]">
} @@ -583,24 +587,23 @@ const KnowledgeBaseManagement: FC = () => {
{/*
{t('knowledgeBase.description')}
*/} -
{(item.description && item.description != '') ? item.description : t('knowledgeBase.noDescription')}
+
{(item.description && item.description != '') ? item.description : t('knowledgeBase.noDescription')}
-
- {item.descriptionItems?.map((description: Record) => ( + + {item.descriptionItems?.map((description: Record) => (
-
{(description.label as string)}
-
{(description.children as string)}
+
{(description.label as string)}
+
{(description.children as string)}
- ))} - -
+ ))} + {hasModelInfo && (
e.stopPropagation()}>
{ )}
- +
)})} - +
)} diff --git a/web/src/views/MemberManagement/index.tsx b/web/src/views/MemberManagement/index.tsx index f1f6c0b5..b9b392ff 100644 --- a/web/src/views/MemberManagement/index.tsx +++ b/web/src/views/MemberManagement/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:42:12 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 16:25:23 + * @Last Modified time: 2026-03-30 11:38:42 */ /** * Member Management Page @@ -105,10 +105,11 @@ const MemberManagement: React.FC = () => { ]; return ( -
- +
+ +
{t('member.memberList')}
@@ -117,6 +118,7 @@ const MemberManagement: React.FC = () => { columns={columns} rowKey="id" pagination={false} + scrollY="calc(100vh - 248px)" /> = ({ children, title, bodyClassName, style, ...props }) => { - return ( - - {children} - - ) -} -export default RbCard \ No newline at end of file diff --git a/web/src/views/MemoryConversation/index.tsx b/web/src/views/MemoryConversation/index.tsx index 362ca756..5aace0a8 100644 --- a/web/src/views/MemoryConversation/index.tsx +++ b/web/src/views/MemoryConversation/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:09:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-20 10:22:08 + * @Last Modified time: 2026-03-31 12:21:56 */ /** * Memory Conversation Page @@ -12,16 +12,18 @@ import { type FC, type ReactNode, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Col, Row, App, Skeleton, Select, Segmented, Tooltip, Flex } from 'antd' +import { Col, Row, App, Skeleton, Segmented, Tooltip, Flex } from 'antd' import dayjs from 'dayjs' import type { AnyObject } from 'antd/es/_util/type'; import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg' import AnalysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png' -import { readService, getUserMemoryList } from '@/api/memory' +import { readService, userMemoryListUrl } from '@/api/memory' import Empty from '@/components/Empty' +import DebounceSelect from '@/components/DebounceSelect' import Markdown from '@/components/Markdown' import type { Data } from '@/views/UserMemory/types' +import type { DefaultOptionType } from 'antd/es/select' import Chat from '@/components/Chat' import type { ChatItem } from '@/components/Chat/types' import RbCard from '@/components/RbCard/Card'; @@ -60,7 +62,7 @@ export interface TestParams { search_switch: string; /** Conversation history */ history: { role: string; content: string }[]; - /** Enable web search */ + /** Enable web keyword */ web_search?: boolean; /** Enable memory function */ memory?: boolean; @@ -108,21 +110,10 @@ const MemoryConversation: FC = () => { const [loading, setLoading] = useState(false) const [chatData, setChatData] = useState([]) const [logs, setLogs] = useState([]) - const [userList, setUserList] = useState([]) const [search_switch, setSearchSwitch] = useState('0') const [msg, setMsg] = useState('') const [expandedLogs, setExpandedLogs] = useState>({}) - /** Load user list on mount */ - useEffect(() => { - getUserMemoryList().then(res => { - setUserList((res as Data[] || []).map(item => ({ - ...item, - name: item.end_user?.other_name && item.end_user?.other_name !== '' ? item.end_user?.other_name : item.end_user?.id - }))) - }) - }, []) - /** Handle message send */ const handleSend = () => { if(!userId) { @@ -149,7 +140,7 @@ const MemoryConversation: FC = () => { }) } - /** Handle search mode change */ + /** Handle keyword mode change */ const handleChange = (value: string) => { setSearchSwitch(value) } @@ -158,30 +149,32 @@ const MemoryConversation: FC = () => { <> -