Compare commits

...

1 Commits
main ... v0.1.1

Author SHA1 Message Date
Ke Sun
5b13b4a949 GitHub release (#20)
* feat(web): remove mock data
* feat(knowledgeBase): Refactor document list API and improve polling logic

- Update getDocumentList API to accept kb_id as separate parameter instead of extracting from query object
- Fix parameter name from auto_question to auto_questions in parser config
- Add progress field initialization in document update params
- Improve polling logic to handle both auto-return and manual stay scenarios with proper loading state management
- Add console logging for debugging polling status and document processing
- Reduce polling interval from 5000ms to 3000ms for faster status updates
- Enhance cleanup logic with route change detection to prevent memory leaks
- Add record parameter to progress render function for better data access
- Refactor confirm dialog callbacks to properly manage loading state timing
- Ensure loading indicator displays correctly when user chooses to stay on page

* feat(web): Add Workflow

* feat(web): Workflow

* feat(web): node show id; update reflection engine example

* feat(components): Add markdown editing capability and enhance component styling

- Add editable mode to Markdown component with edit/save/cancel buttons
- Import EditOutlined, SaveOutlined, CloseOutlined icons from ant-design
- Add useState, useRef, useEffect hooks for managing edit state
- Add editable, onContentChange, and onSave props to RbMarkdownProps interface
- Create RbModal component with new index.css stylesheet for modal styling
- Add index.css stylesheet to KnowledgeBase components for consistent styling
- Update i18n translations in en.ts and zh.ts for new UI elements
- Refactor Markdown component handlers to accept and spread additional props
- Update InsertModal and RecallTestResult components for improved UX
- Fix prop spreading in component handlers to maintain compatibility with Ant Design components

* feat(web): Graph user memory update

* feat(web): update routes.json

* fix(web): workflow bug

* fix(web): workflow variable

* fix(web): workflow properties

* feat(web): workflow support lexical editor

* feat(web): workflow support lexical editor

* feat(web): update reflection engine result

* feat(web): workflow's chat support abort output

* fix:git commit

* fix:vite config

* fix:breadcrumbs

* feat(i18n): add document processing confirmation dialog translations

- Add "processingDocuments" translation key for loading state message in English and Chinese
- Add "startUploadConfirmTitle" translation for confirmation dialog title
- Add "startUploadConfirmContent" translation for confirmation dialog description
- Add "returnToList" translation for returning to list page action
- Add "stayOnPage" translation for staying on current page action
- Support user choice to either return to list or stay on page during background document processing

* fix(web): user memory detail

* feat(web): order

* fix:面包屑修改

* feat(web): 1. user memory; 2. update workspace's model config

* feat(web): update zh.ts / en.ts

* fix(web): update user profile

* feat(web): Agent add ai prompt

* feat(web): Agent add ai prompt

* feat(web): add pricing menu

* feat(knowledgeBase): add media file validation and PDF enhancement method selection

- Add i18n translations for file size and duration validation errors in English and Chinese
- Implement media file validation with 256MB size limit and 150-second duration limit
- Add support for audio and video file formats (mp3, mp4, mov, wav) in dataset creation
- Add checkMediaDuration helper function to validate media file duration using HTML5 media API
- Add PDF enhancement method selection dropdown with options (DeepDoc, MinerU, TextLN)
- Change default PDF enhancement setting from disabled to enabled
- Update file type array to include media formats
- Add error messaging for file size and duration validation failures
- Improve UI spacing for file parsing settings section

* feat(knowledgeBase): add media dataset support and improve file handling

- Add media dataset translations in English and Chinese locales
- Add "mediaDataSet" and "uploadMedia" i18n keys for UI labels
- Enable media dataset creation option in Private component by uncommenting menu item
- Import and display image icon for media dataset menu option
- Refactor file ID handling in CreateDataset to support both string and array types
- Improve fileIds initialization logic to handle mixed input types
- Update CreateImageDataset component to use file chunking workflow
- Add navigation to parameter settings step after file upload
- Pass file IDs to dataset creation flow for media processing
- Add message API and navigate hook for improved UX feedback

* fix(knowledgeBase): improve navigation and folder tree refresh logic

- Add path comparison check in breadcrumb navigation to avoid unnecessary route changes when already on target page
- Implement delayed folder tree refresh with setTimeout to ensure state reset completes before refreshing
- Add manual table refresh trigger to ensure data updates after navigation
- Reset expanded keys in FolderTree component during load to ensure consistent state from root directory
- Add expanded keys reset in breadcrumb navigation to prevent stale expansion state
- Improve navigation state handling by using replace flag only when on target path to reduce history stack pollution

* fix:pdfEnhancementEnabled

* feat(web): add tool management

* fix(web): get the parent domain name adaptation IP

* fix(web): Conversation add initialValue

* feat(web): workflow’s Editor Variable support Tag

* fix(web): pricing UI

* feat(web): JSON Tool update

* fix(web): update get llm,chat model list function

* fix(web): time tool / cluster chat

* fix(web): time tool add time zone

* feat(web): neo4j type user memory detail

* fix(web): update parseSchema api param

* feat: workflow add knowledge-retrieval node

* feat(knowledgeBase): enhance file upload and dataset creation with abort support and improved UX

- Add AbortSignal support to uploadFile API for cancellable uploads
- Implement custom onRemove callback in UploadFiles component with confirmation dialog
- Add i18n translations for file removal confirmation and error messages
- Update supported file types documentation to include IMAGE and MEDIA formats
- Improve file removal UI with cursor pointer styling
- Refactor getModelList API to remove unused type parameter
- Add Form import and UploadFile type for better type safety in CreateDataset
- Enhance error handling and user feedback for file operations

* feat(web): MCP add bearer token auth type

* fix(web): UI update

---------

Co-authored-by: zhaoying <yzhao96@best-inc.com>
Co-authored-by: yujiangping <yujiangping@taofen8.com>
Co-authored-by: 赵莹 <zhaoying@redbearai.com>
Co-authored-by: vrhs@163.com <accounts_660b6454a0eb398d3f8d2c76@mail.teambition.com>
2025-12-30 18:37:40 +08:00
225 changed files with 18068 additions and 2461 deletions

2
web/.gitignore vendored
View File

@@ -22,5 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
vite.config.js
package-lock.json package-lock.json

View File

@@ -10,10 +10,18 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@antv/layout": "^1.2.14-beta.8",
"@antv/x6": "^3.0.1",
"@antv/x6-react-shape": "^3.0.1",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@lexical/code": "^0.39.0",
"@lexical/link": "^0.39.0",
"@lexical/list": "^0.39.0",
"@lexical/react": "^0.39.0",
"@lexical/rich-text": "^0.39.0",
"antd": "^5.27.4", "antd": "^5.27.4",
"axios": "^1.12.2", "axios": "^1.12.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -23,6 +31,8 @@
"echarts": "^5.6.0", "echarts": "^5.6.0",
"echarts-for-react": "^3.0.2", "echarts-for-react": "^3.0.2",
"i18next": "^25.6.0", "i18next": "^25.6.0",
"js-yaml": "^4.1.1",
"lexical": "^0.39.0",
"mermaid": "^11.12.1", "mermaid": "^11.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -31,7 +41,6 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^6.22.0", "react-router-dom": "^6.22.0",
"react-syntax-highlighter": "^16.1.0", "react-syntax-highlighter": "^16.1.0",
"reactflow": "^11.11.4",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-breaks": "^4.0.0", "remark-breaks": "^4.0.0",
@@ -46,6 +55,7 @@
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.6.0", "@types/node": "^24.6.0",
"@types/react": "^18.2.0", "@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0", "@types/react-dom": "^18.2.0",

View File

@@ -6,22 +6,31 @@
// biome-ignore lint: disable // biome-ignore lint: disable
export {} export {}
declare global { declare global {
const Activity: typeof import('react').Activity
const Fragment: typeof import('react').Fragment
const Link: typeof import('react-router-dom').Link const Link: typeof import('react-router-dom').Link
const NavLink: typeof import('react-router-dom').NavLink const NavLink: typeof import('react-router-dom').NavLink
const Navigate: typeof import('react-router-dom').Navigate const Navigate: typeof import('react-router-dom').Navigate
const Outlet: typeof import('react-router-dom').Outlet const Outlet: typeof import('react-router-dom').Outlet
const Route: typeof import('react-router-dom').Route const Route: typeof import('react-router-dom').Route
const Routes: typeof import('react-router-dom').Routes const Routes: typeof import('react-router-dom').Routes
const Suspense: typeof import('react').Suspense
const cache: typeof import('react').cache
const cacheSignal: typeof import('react').cacheSignal
const createContext: typeof import('react').createContext
const createRef: typeof import('react').createRef const createRef: typeof import('react').createRef
const forwardRef: typeof import('react').forwardRef const forwardRef: typeof import('react').forwardRef
const lazy: typeof import('react').lazy const lazy: typeof import('react').lazy
const memo: typeof import('react').memo const memo: typeof import('react').memo
const startTransition: typeof import('react').startTransition const startTransition: typeof import('react').startTransition
const use: typeof import('react').use
const useActionState: typeof import('react').useActionState
const useCallback: typeof import('react').useCallback const useCallback: typeof import('react').useCallback
const useContext: typeof import('react').useContext const useContext: typeof import('react').useContext
const useDebugValue: typeof import('react').useDebugValue const useDebugValue: typeof import('react').useDebugValue
const useDeferredValue: typeof import('react').useDeferredValue const useDeferredValue: typeof import('react').useDeferredValue
const useEffect: typeof import('react').useEffect const useEffect: typeof import('react').useEffect
const useEffectEvent: typeof import('react').useEffectEvent
const useHref: typeof import('react-router-dom').useHref const useHref: typeof import('react-router-dom').useHref
const useId: typeof import('react').useId const useId: typeof import('react').useId
const useImperativeHandle: typeof import('react').useImperativeHandle const useImperativeHandle: typeof import('react').useImperativeHandle
@@ -33,6 +42,7 @@ declare global {
const useMemo: typeof import('react').useMemo const useMemo: typeof import('react').useMemo
const useNavigate: typeof import('react-router-dom').useNavigate const useNavigate: typeof import('react-router-dom').useNavigate
const useNavigationType: typeof import('react-router-dom').useNavigationType const useNavigationType: typeof import('react-router-dom').useNavigationType
const useOptimistic: typeof import('react').useOptimistic
const useOutlet: typeof import('react-router-dom').useOutlet const useOutlet: typeof import('react-router-dom').useOutlet
const useOutletContext: typeof import('react-router-dom').useOutletContext const useOutletContext: typeof import('react-router-dom').useOutletContext
const useParams: typeof import('react-router-dom').useParams const useParams: typeof import('react-router-dom').useParams

View File

@@ -27,12 +27,21 @@ import 'dayjs/locale/en'
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import 'dayjs/plugin/timezone' import 'dayjs/plugin/timezone'
import 'dayjs/plugin/utc' import 'dayjs/plugin/utc'
import { cookieUtils } from './utils/request';
function App() { function App() {
const { t } = useTranslation(); const { t } = useTranslation();
const { locale, language, timeZone } = useI18n() const { locale, language, timeZone } = useI18n()
useEffect(() => {
const authToken = cookieUtils.get('authToken')
if (!authToken && !window.location.hash.includes('#/login')) {
window.location.href = `/#/login`;
}
}, [])
useEffect(() => { useEffect(() => {
document.title = t('memoryBear') document.title = t('memoryBear')

33
web/src/api/apiKey.ts Normal file
View File

@@ -0,0 +1,33 @@
import { request } from '@/utils/request'
import type { ApiKey } from '@/views/ApiKeyManagement/types'
// API Key列表
export const getApiKeyListUrl = '/apikeys'
export const getApiKeyList = (data: Record<string, unknown>) => {
return request.get(getApiKeyListUrl, data)
}
// API Key详情
export const getApiKey = (id: string) => {
return request.get(`/apikeys/${id}`)
}
// 创建API Key
export const createApiKey = (values: ApiKey) => {
return request.post('/apikeys', values)
}
// 更新API Key
export const updateApiKey = (id: string, values: ApiKey) => {
return request.put(`/apikeys/${id}`, values)
}
// 删除 API Key
export const deleteApiKey = (id: string) => {
return request.delete(`/apikeys/${id}`)
}
// 使用统计
export const getApiKeyStats = (app_key_id: string) => {
return request.get(`/apikeys/${app_key_id}/stats`)
}

View File

@@ -1,7 +1,9 @@
import { request } from '@/utils/request' import { request } from '@/utils/request'
import type { Application } from '@/views/ApplicationManagement/types' import type { ApplicationModalData } from '@/views/ApplicationManagement/types'
import type { Config } from '@/views/ApplicationConfig/types' import type { Config } from '@/views/ApplicationConfig/types'
import { handleSSE } from '@/utils/stream' import { handleSSE, type SSEMessage } from '@/utils/stream'
import type { QueryParams } from '@/views/Conversation/types'
import type { WorkflowConfig } from '@/views/Workflow/types'
// 应用列表 // 应用列表
export const getApplicationListUrl = '/apps' export const getApplicationListUrl = '/apps'
@@ -12,20 +14,24 @@ export const getApplicationList = (data: Record<string, unknown>) => {
export const getApplicationConfig = (id: string) => { export const getApplicationConfig = (id: string) => {
return request.get(`/apps/${id}/config`) return request.get(`/apps/${id}/config`)
} }
// 获取集群应配置 // 获取集群应配置
export const getMultiAgentConfig = (id: string) => { export const getMultiAgentConfig = (id: string) => {
return request.get(`/apps/${id}/multi-agent`) return request.get(`/apps/${id}/multi-agent`)
} }
// 获取 workflow应用配置
export const getWorkflowConfig = (id: string) => {
return request.get(`/apps/${id}/workflow`)
}
// 应用详情 // 应用详情
export const getApplication = (id: string) => { export const getApplication = (id: string) => {
return request.get(`/apps/${id}`) return request.get(`/apps/${id}`)
} }
// 更新应用 // 更新应用
export const updateApplication = (id: string, values: Application) => { export const updateApplication = (id: string, values: ApplicationModalData) => {
return request.put(`/apps/${id}`, values) return request.put(`/apps/${id}`, values)
} }
// 创建应用 // 创建应用
export const addApplication = (values: Application) => { export const addApplication = (values: ApplicationModalData) => {
return request.post('/apps', values) return request.post('/apps', values)
} }
// 保存Agent配置 // 保存Agent配置
@@ -36,11 +42,15 @@ export const saveAgentConfig = (app_id: string, values: Config) => {
export const saveMultiAgentConfig = (app_id: string, values: Config) => { export const saveMultiAgentConfig = (app_id: string, values: Config) => {
return request.put(`/apps/${app_id}/multi-agent`, values) return request.put(`/apps/${app_id}/multi-agent`, values)
} }
// 保存workflow配置
export const saveWorkflowConfig = (app_id: string, values: WorkflowConfig) => {
return request.put(`/apps/${app_id}/workflow`, values)
}
// 模型比对试运行 // 模型比对试运行
export const runCompare = (app_id: string, values: Record<string, unknown>, onMessage?: (data: string) => void) => { export const runCompare = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run/compare`, values, onMessage) return handleSSE(`/apps/${app_id}/draft/run/compare`, values, onMessage)
} }
export const draftRun = (app_id: string, values: Record<string, unknown>, onMessage?: (data: string) => void) => { export const draftRun = (app_id: string, values: Record<string, unknown>, onMessage?: (data: SSEMessage[]) => void) => {
return handleSSE(`/apps/${app_id}/draft/run`, values, onMessage) return handleSSE(`/apps/${app_id}/draft/run`, values, onMessage)
} }
// 删除应用 // 删除应用
@@ -76,18 +86,7 @@ export const getConversationHistory = (share_token: string, data: { page: number
}) })
} }
// 发送体验对话 // 发送体验对话
export const sendConversation = (share_token: string, values: { export const sendConversation = (values: QueryParams, onMessage: (data: SSEMessage[]) => void, shareToken: string) => {
message: string;
web_search: boolean;
memory: boolean;
stream: boolean;
conversation_id: string | null;
}, onMessage, shareToken: string) => {
// return request.post(`/public/share/chat`, values, {
// headers: {
// 'Authorization': `Bearer ${localStorage.getItem(`shareToken_${share_token}`)}`
// }
// })
return handleSSE(`/public/share/chat`, values, onMessage, { return handleSSE(`/public/share/chat`, values, onMessage, {
headers: { headers: {
'Authorization': `Bearer ${shareToken}` 'Authorization': `Bearer ${shareToken}`

View File

@@ -64,8 +64,8 @@ export const getModelTypeList = async () => {
return response as any[]; return response as any[];
}; };
// 获取模型列表 // 获取模型列表
export const getModelList = async (type: string | string[], pageInfo: PageRequest) => { export const getModelList = async (pageInfo: PageRequest) => {
const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo }); const response = await request.get(`${apiPrefix}/models`, pageInfo);
return response as any; return response as any;
}; };
//获取模型提供者 //获取模型提供者
@@ -135,16 +135,18 @@ interface UploadFileOptions {
kb_id?: string; kb_id?: string;
parent_id?: string; parent_id?: string;
onUploadProgress?: (event: AxiosProgressEvent) => void; onUploadProgress?: (event: AxiosProgressEvent) => void;
signal?: AbortSignal;
} }
// 上传文件 // 上传文件
export const uploadFile = async (data: FormData, options?: UploadFileOptions) => { export const uploadFile = async (data: FormData, options?: UploadFileOptions) => {
const { kb_id, parent_id, onUploadProgress } = options || {}; const { kb_id, parent_id, onUploadProgress, signal } = options || {};
const params: Record<string, string> = {}; const params: Record<string, string> = {};
if (kb_id) params.kb_id = kb_id; if (kb_id) params.kb_id = kb_id;
if (parent_id) params.parent_id = parent_id; if (parent_id) params.parent_id = parent_id;
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, { const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
params, params,
onUploadProgress, onUploadProgress,
signal,
}); });
return response as UploadFileResponse; return response as UploadFileResponse;
}; };
@@ -199,8 +201,8 @@ export const deleteFile = async (id: string) => {
}; };
// 获取文档列表 // 获取文档列表
export const getDocumentList = async (query: PathQuery) => { export const getDocumentList = async (kb_id:string, query: PathQuery) => {
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/${query.parent_id}/documents`, query); const response = await request.get(`${apiPrefix}/documents/${kb_id}/documents`, query);
return response as KnowledgeBaseDocumentData[]; return response as KnowledgeBaseDocumentData[];
}; };
// 文档详情 // 文档详情
@@ -213,6 +215,11 @@ export const createDocument = async (data: KnowledgeBaseDocumentData) => {
const response = await request.post(`${apiPrefix}/documents/document`, data); const response = await request.post(`${apiPrefix}/documents/document`, data);
return response as KnowledgeBaseDocumentData; return response as KnowledgeBaseDocumentData;
}; };
// 自定义文档上传并创建
export const createDocumentAndUpload = async ( data: any, params: PathQuery) => {
const response = await request.post(`${apiPrefix}/files/customtext`, data, { params } );
return response as any;
};
// 更新文档 // 更新文档
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => { export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
const response = await request.put(`${apiPrefix}/documents/${id}`, data); const response = await request.put(`${apiPrefix}/documents/${id}`, data);
@@ -223,9 +230,9 @@ export const deleteDocument = async (id: string) => {
const response = await request.delete(`${apiPrefix}/documents/${id}`); const response = await request.delete(`${apiPrefix}/documents/${id}`);
return response; return response;
}; };
// 文档解析 // 文档解析 / 分块
export const parseDocument = async (id: string) => { export const parseDocument = async (id: string, data: any) => {
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`); const response = await request.post(`${apiPrefix}/documents/${id}/chunks`, data);
return response as any; return response as any;
}; };
// 文档分块预览 // 文档分块预览

View File

@@ -8,7 +8,15 @@ import type {
import type { import type {
ConfigForm as ExtractionConfigForm ConfigForm as ExtractionConfigForm
} from '@/views/MemoryExtractionEngine/types' } from '@/views/MemoryExtractionEngine/types'
import type {
ConfigForm as EmotionConfig
} from '@/views/EmotionEngine/types'
import type {
ConfigForm as SelfReflectionEngineConfig
} from '@/views/SelfReflectionEngine/types'
import type { TestParams } from '@/views/MemoryConversation' import type { TestParams } from '@/views/MemoryConversation'
import type { EndUser } from '@/views/UserMemoryDetail/types'
import { handleSSE, type SSEMessage } from '@/utils/stream'
// 记忆对话 // 记忆对话
export const readService = (query: TestParams) => { export const readService = (query: TestParams) => {
@@ -59,6 +67,7 @@ export const getTotalEndUsers = () => {
export const getUserProfile = (end_user_id: string) => { export const getUserProfile = (end_user_id: string) => {
return request.get(`/memory/analytics/user_profile`, { end_user_id }) return request.get(`/memory/analytics/user_profile`, { end_user_id })
} }
// 用户记忆-记忆洞察 // 用户记忆-记忆洞察
export const getMemoryInsightReport = (end_user_id: string) => { export const getMemoryInsightReport = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/memory_insight/report`, { end_user_id }) return request.get(`/memory-storage/analytics/memory_insight/report`, { end_user_id })
@@ -67,9 +76,20 @@ export const getMemoryInsightReport = (end_user_id: string) => {
export const getUserSummary = (end_user_id: string) => { export const getUserSummary = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/user_summary`, { end_user_id }) return request.get(`/memory-storage/analytics/user_summary`, { end_user_id })
} }
// 记忆分类
export const getNodeStatistics = (end_user_id: string) => {
return request.get(`/memory-storage/analytics/node_statistics`, { end_user_id })
}
// 基本信息
export const getEndUserProfile = (end_user_id: string) => {
return request.get(`/memory-storage/read_end_user/profile`, { end_user_id })
}
export const updatedEndUserProfile = (values: EndUser) => {
return request.post(`/memory-storage/updated_end_user/profile`, values)
}
// 用户记忆-关系网络 // 用户记忆-关系网络
export const getMemorySearchEdges = (end_user_id: string) => { export const getMemorySearchEdges = (end_user_id: string) => {
return request.get(`/memory-storage/search/entity_graph`, { end_user_id }) return request.get(`/memory-storage/analytics/graph_data`, { end_user_id })
} }
// 用户记忆-用户兴趣分布 // 用户记忆-用户兴趣分布
export const getHotMemoryTagsByUser = (end_user_id: string) => { export const getHotMemoryTagsByUser = (end_user_id: string) => {
@@ -95,6 +115,26 @@ export const getChunkInsight = (end_user_id: string) => {
export const getRagContent = (end_user_id: string) => { export const getRagContent = (end_user_id: string) => {
return request.get(`/dashboard/rag_content`, { end_user_id, limit: 20 }) return request.get(`/dashboard/rag_content`, { end_user_id, limit: 20 })
} }
// 情感分布分析
export const getWordCloud = (group_id: string) => {
return request.post(`/memory/emotion/wordcloud`, { group_id, limit: 20 })
}
// 高频情绪关键词
export const getEmotionTags = (group_id: string) => {
return request.post(`/memory/emotion/tags`, { group_id, limit: 20 })
}
// 情绪健康指数
export const getEmotionHealth = (group_id: string) => {
return request.post(`/memory/emotion/health`, { group_id, limit: 20 })
}
// 个性化建议
export const getEmotionSuggestions = (group_id: string) => {
return request.post(`/memory/emotion/suggestions`, { group_id, limit: 20 })
}
export const analyticsRefresh = (end_user_id: string) => {
return request.post('/memory-storage/analytics/generate_cache', { end_user_id })
}
/*************** end 用户记忆 相关接口 ******************************/ /*************** end 用户记忆 相关接口 ******************************/
/****************** 记忆管理 相关接口 *******************************/ /****************** 记忆管理 相关接口 *******************************/
@@ -132,9 +172,30 @@ export const updateMemoryExtractionConfig = (values: ExtractionConfigForm) => {
return request.post('/memory-storage/update_config_extracted', values) return request.post('/memory-storage/update_config_extracted', values)
} }
// 记忆萃取引擎-试运行 // 记忆萃取引擎-试运行
export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string }) => { export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string; }, onMessage?: (data: SSEMessage[]) => void) => {
return request.post('/memory-storage/pilot_run', values) return handleSSE('/memory-storage/pilot_run', values, onMessage)
} }
// 情绪引擎-获取配置
export const getMemoryEmotionConfig = (config_id: number | string) => {
return request.get('/memory/emotion/read_config', { config_id: config_id })
}
// 情绪引擎-更新配置
export const updateMemoryEmotionConfig = (values: EmotionConfig) => {
return request.post('/memory/emotion/updated_config', values)
}
// 反思引擎-获取配置
export const getMemoryReflectionConfig = (config_id: number | string) => {
return request.get('/memory/reflection/configs', { config_id: config_id })
}
// 反思引擎-更新配置
export const updateMemoryReflectionConfig = (values: SelfReflectionEngineConfig) => {
return request.post('/memory/reflection/save', values)
}
// 反思引擎-试运行
export const pilotRunMemoryReflectionConfig = (values: { config_id: number | string; language_type: string; }) => {
return request.get('/memory/reflection/run', values)
}
/*************** end 记忆管理 相关接口 ******************************/ /*************** end 记忆管理 相关接口 ******************************/

17
web/src/api/order.ts Normal file
View File

@@ -0,0 +1,17 @@
import { request } from '@/utils/request'
import type { VoucherForm } from '@/views/OrderPayment/types'
export const getOrderListUrl = '/v1/orders/customer'
// 提交支付凭证API
export const submitPaymentVoucherAPI = (voucherData: VoucherForm) => {
return request.post('/v1/orders/', voucherData)
}
// 订单详情
export const getOrderDetail = (order_no: string) => {
return request.get(`/v1/orders/customer/${order_no}`)
}
export const orderStatusUrl = '/v1/order-status/'
export const getOrderStatus = () => {
return request.get(orderStatusUrl)
}

12
web/src/api/prompt.ts Normal file
View File

@@ -0,0 +1,12 @@
import { request } from '@/utils/request'
import type { AiPromptForm } from '@/views/ApplicationConfig/types'
export const createPromptSessions = () => {
return request.post(`/prompt/sessions`)
}
export const getPrompt = (session_id: string) => {
return request.get(`/prompt/sessions/${session_id}`)
}
export const updatePromptMessages = (session_id: string, data: AiPromptForm) => {
return request.post(`/prompt/sessions/${session_id}/messages`, data)
}

31
web/src/api/tools.ts Normal file
View File

@@ -0,0 +1,31 @@
import { request } from '@/utils/request'
import type { Query, CustomToolItem, ExecuteData, MCPToolItem, InnerToolItem } from '@/views/ToolManagement/types'
// 工具列表
export const getTools = (data: Query) => {
return request.get('/tools', data)
}
// 创建MCP工具
export const addTool = (values: MCPToolItem | CustomToolItem) => {
return request.post('/tools', values)
}
// 更新工具
export const updateTool = (tool_id: string, data: MCPToolItem | InnerToolItem | CustomToolItem) => {
return request.put(`/tools/${tool_id}`, data)
}
// 删除工具
export const deleteTool = (tool_id: string) => {
return request.delete(`/tools/${tool_id}`)
}
// MCP 测试连接
export const testConnection = (tool_id: string) => {
return request.post(`/tools/${tool_id}/test`)
}
// 工具测试
export const execute = (data: ExecuteData) => {
return request.post(`/tools/execution/execute`, data)
}
export const parseSchema = (data: Record<string, any>) => {
return request.post(`/tools/parse_schema`, data)
}

View File

@@ -1,5 +1,6 @@
import { request } from '@/utils/request' import { request } from '@/utils/request'
import type { SpaceModalData } from '@/views/SpaceManagement/types' import type { SpaceModalData } from '@/views/SpaceManagement/types'
import type { ConfigModalData } from '@/views/UserMemory/types'
// 空间列表 // 空间列表
export const getWorkspaces = () => { export const getWorkspaces = () => {
@@ -22,6 +23,6 @@ export const getWorkspaceModels = () => {
return request.get(`/workspaces/workspace_models`) return request.get(`/workspaces/workspace_models`)
} }
// 更新空间模型配置 // 更新空间模型配置
export const updateWorkspaceModels = () => { export const updateWorkspaceModels = (data: ConfigModalData) => {
return request.post(`/workspaces/workspace_models`) return request.put(`/workspaces/workspace_models`, data)
} }

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>全屏</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="红熊空间-记忆管理-个人记忆" transform="translate(-967, -349)" stroke="#5B6167">
<g id="图谱" transform="translate(432, 334)">
<g id="编组-18" transform="translate(535, 13)">
<g id="全屏" transform="translate(0, 2)">
<g id="编组-6" transform="translate(2, 2)">
<polyline id="路径" points="0 3 0 0 3 0"></polyline>
<polyline id="路径" transform="translate(10.5, 1.5) scale(-1, 1) translate(-10.5, -1.5)" points="9 3 9 0 12 0"></polyline>
<polyline id="路径" transform="translate(1.5, 10.5) scale(1, -1) translate(-1.5, -10.5)" points="0 12 0 9 3 9"></polyline>
<polyline id="路径" transform="translate(10.5, 10.5) scale(-1, -1) translate(-10.5, -10.5)" points="9 12 9 9 12 9"></polyline>
<line x1="5.30274338e-05" y1="0" x2="4.00960414" y2="4.02323899" id="路径-3"></line>
<line x1="12" y1="0" x2="8.01466597" y2="4.01783837" id="路径-4"></line>
<line x1="0" y1="11.9995998" x2="4.00871245" y2="8.01746225" id="路径-5"></line>
<line x1="11.9977093" y1="12" x2="8.01643138" y2="8.01746225" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>全屏</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="红熊空间-记忆管理-个人记忆" transform="translate(-967, -349)" stroke="#212332">
<g id="图谱" transform="translate(432, 334)">
<g id="编组-18" transform="translate(535, 13)">
<g id="全屏" transform="translate(0, 2)">
<g id="编组-6" transform="translate(2, 2)">
<polyline id="路径" points="0 3 0 0 3 0"></polyline>
<polyline id="路径" transform="translate(10.5, 1.5) scale(-1, 1) translate(-10.5, -1.5)" points="9 3 9 0 12 0"></polyline>
<polyline id="路径" transform="translate(1.5, 10.5) scale(1, -1) translate(-1.5, -10.5)" points="0 12 0 9 3 9"></polyline>
<polyline id="路径" transform="translate(10.5, 10.5) scale(-1, -1) translate(-10.5, -10.5)" points="9 12 9 9 12 9"></polyline>
<line x1="5.30274338e-05" y1="0" x2="4.00960414" y2="4.02323899" id="路径-3"></line>
<line x1="12" y1="0" x2="8.01466597" y2="4.01783837" id="路径-4"></line>
<line x1="0" y1="11.9995998" x2="4.00871245" y2="8.01746225" id="路径-5"></line>
<line x1="11.9977093" y1="12" x2="8.01643138" y2="8.01746225" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 16</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-记忆看板" transform="translate(-1372, -1136)" stroke="#212332">
<g id="快速操作" transform="translate(848, 1048)">
<g id="1" transform="translate(296, 68)">
<g id="编组-16" transform="translate(228, 20)">
<line x1="13.7142857" y1="2.28571429" x2="2.28571429" y2="13.7142857" id="路径-15"></line>
<polyline id="路径" points="5.55102041 2.28571429 13.7142857 2.28571429 13.7142857 10.4489796"></polyline>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>菜单-收费管理</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="首页-弹窗" transform="translate(-904, -514)" stroke="#5F6266">
<g id="编组-7" transform="translate(904, 314)">
<g id="菜单-收费管理" transform="translate(0, 200)">
<g id="编组-8" transform="translate(2, 2)">
<polyline id="路径" points="7.5 5.80029489 6 7.30029489 4.5 5.80029489"></polyline>
<path d="M9.5,2 L10.5,2 C11.3284271,2 12,2.67157288 12,3.5 L12,10.5 C12,11.3284271 11.3284271,12 10.5,12 L1.5,12 C0.671572875,12 0,11.3284271 0,10.5 L0,3.5 C0,2.67157288 0.671572875,2 1.5,2 L2.5,2 L2.5,2" id="路径"></path>
<path d="M2.5,2.5 L2.5,1 C2.5,0.44771525 2.94771525,1.11022302e-16 3.5,0 L8.5,0 C9.05228475,0 9.5,0.44771525 9.5,1 L9.5,2.5 L9.5,2.5" id="路径"></path>
<line x1="0" y1="4.01402447" x2="12" y2="4.01402447" id="路径-4"></line>
<line x1="4.01045351" y1="1.8" x2="8.01045351" y2="1.8" id="路径-5"></line>
<line x1="6" y1="7.22565966" x2="6" y2="10.0211794" id="路径-6"></line>
<line x1="4.5" y1="7.70544887" x2="7.5" y2="7.70544887" id="路径-7"></line>
<line x1="4.5" y1="9.20544887" x2="7.5" y2="9.20544887" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>菜单-收费管理</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="首页-弹窗" transform="translate(-975, -514)" stroke="#212332">
<g id="编组-7" transform="translate(975, 314)">
<g id="菜单-收费管理" transform="translate(0, 200)">
<g id="编组-8" transform="translate(2, 2)">
<polyline id="路径" points="7.5 5.80029489 6 7.30029489 4.5 5.80029489"></polyline>
<path d="M9.5,2 L10.5,2 C11.3284271,2 12,2.67157288 12,3.5 L12,10.5 C12,11.3284271 11.3284271,12 10.5,12 L1.5,12 C0.671572875,12 0,11.3284271 0,10.5 L0,3.5 C0,2.67157288 0.671572875,2 1.5,2 L2.5,2 L2.5,2" id="路径"></path>
<path d="M2.5,2.5 L2.5,1 C2.5,0.44771525 2.94771525,1.11022302e-16 3.5,0 L8.5,0 C9.05228475,0 9.5,0.44771525 9.5,1 L9.5,2.5 L9.5,2.5" id="路径"></path>
<line x1="0" y1="4.01402447" x2="12" y2="4.01402447" id="路径-4"></line>
<line x1="4.01045351" y1="1.8" x2="8.01045351" y2="1.8" id="路径-5"></line>
<line x1="6" y1="7.22565966" x2="6" y2="10.0211794" id="路径-6"></line>
<line x1="4.5" y1="7.70544887" x2="7.5" y2="7.70544887" id="路径-7"></line>
<line x1="4.5" y1="9.20544887" x2="7.5" y2="9.20544887" id="路径-7"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 29</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-应用管理" transform="translate(-24, -249)" stroke="#212332">
<g id="记忆库" transform="translate(12, 241)">
<g id="编组-29" transform="translate(12, 8)">
<g id="编组-30" transform="translate(1.5, 2)">
<path d="M5.15208739,12 L3.96504286,11.9873871 C3.14287934,11.9786512 2.48098017,11.3096817 2.48098017,10.4874718 L2.48098017,8.17573134 L2.48098017,8.17573134 L0.701703053,7.96314675 C0.482349244,7.93693878 0.325773604,7.73787163 0.351981572,7.51851782 C0.360586526,7.44649662 0.388612869,7.37817062 0.433061291,7.3208519 L1.79815052,5.56049306 L1.79815052,5.56049306 L1.79815052,5.43150782 C1.79815052,2.43176888 4.30576994,0 7.39907526,0 C10.4923806,0 13,2.43176888 13,5.43150782" id="路径" stroke-linecap="round"></path>
<path d="M8,6 L11,6 C11.5522847,6 12,6.44771525 12,7 L12,11 C12,11.5522847 11.5522847,12 11,12 L8,12 C7.44771525,12 7,11.5522847 7,11 L7,7 C7,6.44771525 7.44771525,6 8,6 Z" id="矩形"></path>
<line x1="7" y1="8" x2="12" y2="8" id="路径-26"></line>
<line x1="7" y1="10" x2="12" y2="10" id="路径-26备份"></line>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>注意</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-280, -839)" fill="#FF5D34" fill-rule="nonzero">
<g id="编组-10" transform="translate(264, 823)">
<g id="注意" transform="translate(16, 16)">
<path d="M8.7755948,1.95407174 C9.31341765,1.01530942 10.6865824,1.01530942 11.2358482,1.95407174 L19.8066846,16.6349572 C20.3559505,17.5737195 19.6693682,18.7386896 18.5708364,18.75 L1.42916361,18.75 C0.330631841,18.75 -0.355950514,17.5737195 0.19331537,16.6349572 Z M10,15 C9.48223305,15 9.0625,15.419733 9.0625,15.9375 C9.0625,16.455267 9.48223305,16.875 10,16.875 C10.517767,16.875 10.9375,16.455267 10.9375,15.9375 C10.9375,15.419733 10.517767,15 10,15 Z M10,6.25 C9.48223305,6.25 9.0625,6.66973305 9.0625,7.1875 L9.0625,12.8125 C9.0625,13.330267 9.48223305,13.75 10,13.75 C10.517767,13.75 10.9375,13.330267 10.9375,12.8125 L10.9375,7.1875 C10.9375,6.66973305 10.517767,6.25 10,6.25 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>对号备份</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="定价-支付" transform="translate(-941, -357)" fill="#5B6167" fill-rule="nonzero">
<g id="编组-8" transform="translate(360, 219)">
<g id="编组-2" transform="translate(581, 134)">
<g id="对号备份" transform="translate(0, 4)">
<path d="M6,0 C10,0 12,2 12,6 C12,10 10,12 6,12 C2,12 0,10 0,6 C0,2 2,0 6,0 Z M8.96637129,3.91456632 C8.74659858,3.69499924 8.39044332,3.69516594 8.17087624,3.91493864 L5.06113614,7.0275 L3.85141273,5.81979618 C3.63162085,5.60024831 3.2754656,5.60044612 3.05591773,5.82023801 C2.83636985,6.04002989 2.83656766,6.39618514 3.05635955,6.61573301 L4.6637238,8.22131277 C4.88350211,8.44084708 5.23963056,8.44066484 5.45918407,8.22090572 L8.9667436,4.71006136 C9.18631068,4.49028866 9.18614399,4.1341334 8.96637129,3.91456632 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>企业_画板 39@2x</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="定价-支付" transform="translate(-404, -623)" fill="#5B6167" fill-rule="nonzero">
<g id="编组-9" transform="translate(360, 572)">
<g id="编组-13" transform="translate(24, 28)">
<g id="企业_画板-39" transform="translate(20, 23)">
<path d="M22.6288053,2.37305419 C23.0204473,2.23747924 23.3145868,2.25900923 23.6919502,2.44276018 C24.0617664,2.62288728 25.6534241,3.72773786 25.9496034,4.01380953 C26.0658721,4.12614915 26.3712305,4.58595288 26.3712305,4.72515169 L26.3712305,28.0018359 C26.4530266,28.0862505 27.1973505,27.7436893 27.3764451,28.1371977 C27.4557934,28.3119956 27.4672163,29.6287353 27.2467137,29.6287353 L21.8000336,29.6287353 C21.7818794,29.6287353 21.6233867,29.5236434 21.6056404,29.4931604 L21.5446504,5.02657147 C21.4958991,4.86989287 21.331899,4.74412365 21.166879,4.77098284 C21.0187893,4.79507085 20.3731919,5.12974486 20.1677838,5.2218335 C18.029459,6.17981122 15.6408506,7.2130373 13.5747348,8.29294721 C13.376262,8.3967601 13.2344958,8.39505476 13.2049187,8.65192239 C13.1657544,8.99022026 13.1547395,10.3811425 13.2102221,10.6870388 C13.2300082,10.7957546 13.3515804,10.9596809 13.449083,10.9831294 C13.6673418,11.0353556 15.7193829,10.1487892 16.1403981,10.0260044 C16.2782888,9.98571558 16.4594232,9.91110672 16.5977218,9.92389681 C16.9036922,9.95203501 18.7858177,11.1775388 19.1684847,11.4395224 C19.2908728,11.5232975 19.6443705,11.7298575 19.680475,11.8532818 L19.7406492,27.6469109 C19.9835897,28.1139624 20.7572867,27.6294311 20.9818689,28.04383 C21.1122123,28.2842837 21.1334262,29.458414 20.8600927,29.5944153 L15.2826613,29.5950548 L15.1829149,29.4283573 L15.1563975,12.6505308 C15.1329398,12.3595562 15.1168253,12.2231286 14.7978003,12.2116175 L7.01432266,14.8538369 L6.69162593,15.0586915 L6.6579692,29.3893475 C6.65144183,29.4910287 6.60044677,29.537073 6.52640195,29.5907914 C6.29610827,29.7579152 4.81786354,29.7711317 4.64346045,29.5325965 C4.54534596,29.3987269 4.56003253,28.3989682 4.61306739,28.1992296 C4.68996794,27.9097473 5.17544089,28.12995 5.23622699,27.6326287 L5.23459515,14.2139061 L5.32414247,14.0365502 L11.5253454,11.7079012 C11.6140768,11.6079253 11.6389624,11.4994227 11.653445,11.3674716 C11.7713456,10.2926777 11.5033156,8.89003123 11.6560968,7.84657308 C11.6811863,7.67582538 11.7862362,7.47629998 11.9263706,7.38357183 Z M13.1298539,26.7341249 C13.41767,26.7230401 13.7566852,26.6450206 13.8427648,27.033413 C13.8603581,27.1127115 13.87035,27.429576 13.8728157,27.8021561 L13.8727754,28.1865728 C13.8693843,28.7059304 13.8526579,29.2165747 13.8227748,29.2874531 C13.7956454,29.3518299 13.7146652,29.411517 13.6606105,29.4569218 C12.4008286,29.6148794 11.1332954,29.5374994 9.86617025,29.557324 C9.31134403,29.5660639 8.69348791,29.6911936 8.11398008,29.632146 C7.95915908,29.6163716 7.80291023,29.5944153 7.73049725,29.423028 C7.65910416,29.2537725 7.6701191,27.3030707 7.7443679,27.1365863 C7.77170125,27.0754071 7.80291023,27.0187044 7.8692038,26.9961085 C9.62119,26.9070042 11.3768478,26.8021255 13.1298539,26.734338 Z M13.6149189,22.525759 C13.8127797,22.624669 13.840929,22.7357297 13.8605111,22.949111 C13.902531,23.408062 13.902939,24.395457 13.8564315,24.85185 C13.8466405,24.9490547 13.8360335,25.1031753 13.748322,25.1609438 L7.69643255,26.0366386 C7.79169131,25.3491713 7.56833296,24.2673429 7.69643255,23.6310359 C7.70561166,23.5860575 7.77272115,23.5152856 7.79413908,23.4619936 Z M13.5565806,18.2619694 C13.7026304,18.3067348 13.8248146,18.3877387 13.8552076,18.5499596 C13.9153818,18.8720567 13.8951878,20.217361 13.8603071,20.5855024 C13.851128,20.6816413 13.8684663,20.7790591 13.7964613,20.8577182 L13.5476054,21.0041647 L7.69663653,22.3096065 L7.69663653,20.0395787 C7.69663653,20.0163434 7.79638286,19.8671257 7.8290197,19.8392007 Z M13.5115009,13.9915717 C13.6646901,13.9862425 13.8266504,14.1254413 13.8554116,14.2808409 C13.9096703,14.5739471 13.9006952,16.0603687 13.8598992,16.3835316 C13.8480683,16.4779651 13.8456206,16.5600348 13.7634165,16.6216404 L7.99179592,18.5844929 C7.73090521,18.5465489 7.71581067,18.222107 7.70030817,18.0093652 C7.671139,17.60733 7.60851707,16.2677813 7.89164163,16.041823 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>订单</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-1266, -130)" fill-rule="nonzero">
<g id="编组-5" transform="translate(264, 88)">
<g id="编组-9" transform="translate(986, 32)">
<g id="订单" transform="translate(16, 10)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M12.8333281,4 C12.8333281,3.72385763 13.0571858,3.5 13.3333281,3.5 C13.6094705,3.5 13.8333281,3.72385763 13.8333281,4 L13.8333281,12.8333281 C13.8333281,13.8458281 13.0125,14.6666719 12,14.6666719 L4,14.6666719 C2.9875,14.6666719 2.16667188,13.8458281 2.16667188,12.8333281 L2.16667188,3.16667188 C2.16667188,2.15417187 2.9875,1.33332812 4,1.33332812 L13.3333281,1.33332812 C13.6094705,1.33332812 13.8333281,1.55718575 13.8333281,1.83332812 C13.8333281,2.1094705 13.6094705,2.33332812 13.3333281,2.33332812 L4,2.33332812 C3.77898493,2.33332812 3.5670223,2.42112709 3.41074251,2.57740981 C3.25446272,2.73369252 3.16667188,2.94565681 3.16667188,3.16667188 L3.16667188,12.8333281 C3.16667188,13.0543432 3.25446272,13.2663075 3.41074251,13.4225902 C3.5670223,13.5788729 3.77898493,13.6666719 4,13.6666719 L12,13.6666719 C12.2210151,13.6666719 12.4329777,13.5788729 12.5892575,13.4225902 C12.7455373,13.2663075 12.8333323,13.0543432 12.8333281,12.8333281 L12.8333281,4 Z M5.33332812,6.83332812 C5.05718575,6.83332812 4.83332812,6.6094705 4.83332812,6.33332812 C4.83332812,6.05718575 5.05718575,5.83332812 5.33332812,5.83332812 L10.6666719,5.83332812 C10.9428142,5.83332812 11.1666719,6.05718575 11.1666719,6.33332812 C11.1666719,6.6094705 10.9428142,6.83332812 10.6666719,6.83332812 L5.33332812,6.83332812 Z M5.33332812,9.5 C5.05718575,9.5 4.83332812,9.27614237 4.83332812,9 C4.83332812,8.72385763 5.05718575,8.5 5.33332812,8.5 L8.66667188,8.5 C8.94281425,8.5 9.16667188,8.72385763 9.16667188,9 C9.16667188,9.27614237 8.94281425,9.5 8.66667188,9.5 L5.33332812,9.5 Z" id="形状" stroke="#212332" stroke-width="0.4" fill="#212332"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>订单</title>
<g id="V1.1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="收费管理" transform="translate(-1266, -130)" fill-rule="nonzero">
<g id="编组-5" transform="translate(264, 88)">
<g id="编组-9" transform="translate(986, 32)">
<g id="订单" transform="translate(16, 10)">
<rect id="矩形" fill="#155EEF" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M12.8333281,4 C12.8333281,3.72385763 13.0571858,3.5 13.3333281,3.5 C13.6094705,3.5 13.8333281,3.72385763 13.8333281,4 L13.8333281,12.8333281 C13.8333281,13.8458281 13.0125,14.6666719 12,14.6666719 L4,14.6666719 C2.9875,14.6666719 2.16667188,13.8458281 2.16667188,12.8333281 L2.16667188,3.16667188 C2.16667188,2.15417187 2.9875,1.33332812 4,1.33332812 L13.3333281,1.33332812 C13.6094705,1.33332812 13.8333281,1.55718575 13.8333281,1.83332812 C13.8333281,2.1094705 13.6094705,2.33332812 13.3333281,2.33332812 L4,2.33332812 C3.77898493,2.33332812 3.5670223,2.42112709 3.41074251,2.57740981 C3.25446272,2.73369252 3.16667188,2.94565681 3.16667188,3.16667188 L3.16667188,12.8333281 C3.16667188,13.0543432 3.25446272,13.2663075 3.41074251,13.4225902 C3.5670223,13.5788729 3.77898493,13.6666719 4,13.6666719 L12,13.6666719 C12.2210151,13.6666719 12.4329777,13.5788729 12.5892575,13.4225902 C12.7455373,13.2663075 12.8333323,13.0543432 12.8333281,12.8333281 L12.8333281,4 Z M5.33332812,6.83332812 C5.05718575,6.83332812 4.83332812,6.6094705 4.83332812,6.33332812 C4.83332812,6.05718575 5.05718575,5.83332812 5.33332812,5.83332812 L10.6666719,5.83332812 C10.9428142,5.83332812 11.1666719,6.05718575 11.1666719,6.33332812 C11.1666719,6.6094705 10.9428142,6.83332812 10.6666719,6.83332812 L5.33332812,6.83332812 Z M5.33332812,9.5 C5.05718575,9.5 4.83332812,9.27614237 4.83332812,9 C4.83332812,8.72385763 5.05718575,8.5 5.33332812,8.5 L8.66667188,8.5 C8.94281425,8.5 9.16667188,8.72385763 9.16667188,9 C9.16667188,9.27614237 8.94281425,9.5 8.66667188,9.5 L5.33332812,9.5 Z" id="形状" stroke="#155EEF" stroke-width="0.4" fill="#155EEF"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -2,7 +2,7 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>刷新</title> <title>刷新</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="应用管理--API" transform="translate(-1071, -490)" fill="#212332" fill-rule="nonzero"> <g id="应用管理--API" transform="translate(-1071, -490)" fill="#5B6167" fill-rule="nonzero">
<g id="2" transform="translate(220, 336)"> <g id="2" transform="translate(220, 336)">
<g id="主操作" transform="translate(839, 144)"> <g id="主操作" transform="translate(839, 144)">
<g id="刷新" transform="translate(12, 10)"> <g id="刷新" transform="translate(12, 10)">

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>刷新</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="应用管理--API" transform="translate(-1071, -490)" fill="#155EEF" fill-rule="nonzero">
<g id="2" transform="translate(220, 336)">
<g id="主操作" transform="translate(839, 144)">
<g id="刷新" transform="translate(12, 10)">
<path d="M14.5,6.60760714 L14.5,3.35760714 L13.4384813,4.41757143 C12.2346397,2.59629629 10.195406,1.50029795 8.00999678,1.5 C4.41487535,1.5 1.5,4.41014286 1.5,8.00046429 C1.5,11.5907857 4.41487535,14.5009287 8.00999678,14.5009287 C10.6602392,14.5014558 13.046297,12.8977788 14.0434028,10.4458571 C14.1184045,10.261489 14.0892053,10.0511714 13.9668045,9.89412914 C13.8444036,9.73708685 13.6473966,9.65717825 13.4499941,9.68450414 C13.2525917,9.71183003 13.0847838,9.84223896 13.0097822,10.0266071 C12.1832078,12.0581943 10.2060581,13.38691 8.00999678,13.3866429 C5.03095604,13.3866429 2.61591974,10.9751429 2.61591974,8.00046429 C2.61591974,5.02578571 5.03095604,2.61428571 8.00999678,2.61428571 C9.93449337,2.61428571 11.6706785,3.63107143 12.6308344,5.22357143 L11.2452341,6.60760714 L14.5,6.60760714 Z" id="路径"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -34,12 +34,12 @@ const ButtonCheckbox: FC<ButtonCheckboxProps> = ({
} }
return ( return (
<div className={clsx("rb:flex rb:items-center rb:border rb:rounded-[8px] rb:px-[8px] rb:text-[12px] rb:h-[24px] rb:cursor-pointer rb:hover:bg-[#F0F3F8]", { <div className={clsx("rb:flex rb:items-center rb:border rb:rounded-lg rb:px-2 rb:text-[12px] rb:h-6 rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
"rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF] rb:text-[#155EEF]": checked, "rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF] rb:text-[#155EEF]": checked,
"rb:border-[#DFE4ED] rb:text-[#212332]": !checked, "rb:border-[#DFE4ED] rb:text-[#212332]": !checked,
})} onClick={handleChange}> })} onClick={handleChange}>
{icon && !checked && <img src={icon} className="rb:w-[16px] rb:h-[16px] rb:mr-[4px]" />} {icon && !checked && <img src={icon} className="rb:w-4 rb:h-4 rb:mr-1" />}
{checkedIcon && checked && <img src={checkedIcon} className="rb:w-[16px] rb:h-[16px] rb:mr-[4px]" />} {checkedIcon && checked && <img src={checkedIcon} className="rb:w-4 rb:h-4 rb:mr-1" />}
{children} {children}
</div> </div>
); );

View File

@@ -0,0 +1,84 @@
/*
* @Author: ZhaoYing
* @Date: 2025-12-10 16:46:17
* @Last Modified by: ZhaoYing
* @Last Modified time: 2025-12-11 13:40:18
*/
import { type FC, useRef, useEffect } from 'react'
import clsx from 'clsx'
import Markdown from '@/components/Markdown'
import type { ChatContentProps } from './types'
/**
* 聊天内容显示组件
* 负责渲染聊天消息列表,支持不同角色的消息样式和自动滚动
*/
const ChatContent: FC<ChatContentProps> = ({
classNames,
contentClassNames,
data = [],
streamLoading = false,
empty,
labelPosition = 'bottom',
labelFormat,
errorDesc
}) => {
// 滚动容器引用,用于控制自动滚动到底部
const scrollContainerRef = useRef<(HTMLDivElement | null)>(null)
// 当数据变化时,自动滚动到底部显示最新消息
useEffect(() => {
setTimeout(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
}
}, 0);
}, [data])
return (
<div ref={scrollContainerRef} className={clsx("rb:relative rb:overflow-y-auto", classNames)}>
{data.length === 0
? empty // 显示空状态
: data.map((item, index) => (
<div key={index} className={clsx("rb:relative", {
'rb:mt-6': index !== 0, // 非第一条消息添加上边距
'rb:right-0 rb:text-right': item.role === 'user', // 用户消息右对齐
'rb:left-0 rb:text-left': item.role === 'assistant', // 助手消息左对齐
})}>
{/* 流式加载时且内容为空则不显示 */}
{streamLoading && item.content === ''
? null
: <>
{/* 顶部标签(如时间戳、用户名等) */}
{labelPosition === 'top' &&
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4 rb:font-regular">
{labelFormat(item)}
</div>
}
{/* 消息气泡框 */}
<div className={clsx('rb:border rb:text-left rb:rounded-lg rb:mt-1.5 rb:leading-4.5 rb:p-[10px_12px_2px_12px] rb:inline-block rb:max-w-100', contentClassNames, {
// 错误消息样式内容为null且非助手消息
'rb:border-[rgba(255,93,52,0.30)] rb:bg-[rgba(255,93,52,0.08)] rb:text-[#FF5D34]': errorDesc && item.role === 'assistant' && item.content === null,
// 助手消息样式
'rb:bg-[rgba(21,94,239,0.08)] rb:border-[rgba(21,94,239,0.30)]': item.role === 'user',
// 用户消息样式
'rb:bg-[#FFFFFF] rb:border-[#EBEBEB]': item.role === 'assistant' && (item.content || item.content === ''),
})}>
{/* 使用Markdown组件渲染消息内容 */}
<Markdown content={item.content ?? errorDesc ?? ''} />
</div>
{/* 底部标签(如时间戳、用户名等) */}
{labelPosition === 'bottom' &&
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4 rb:font-regular">
{labelFormat(item)}
</div>
}
</>
}
</div>
))
}
</div>
)
}
export default ChatContent

View File

@@ -0,0 +1,80 @@
/*
* @Author: ZhaoYing
* @Date: 2025-12-10 16:46:14
* @Last Modified by: ZhaoYing
* @Last Modified time: 2025-12-20 15:38:40
*/
import { useEffect } from 'react'
import { Flex, Input, Form } from 'antd'
import SendIcon from '@/assets/images/conversation/send.svg'
import SendDisabledIcon from '@/assets/images/conversation/sendDisabled.svg'
import LoadingIcon from '@/assets/images/conversation/loading.svg'
import type { ChatInputProps } from './types'
/**
* 聊天输入框组件
* 提供消息输入、发送功能,支持键盘快捷键和加载状态显示
*/
const ChatInput = ({ message, onChange, onSend, loading, children }: ChatInputProps) => {
const [form] = Form.useForm()
// 监听表单值变化,用于控制发送按钮状态
const values = Form.useWatch([], form);
// 当外部message为空时清空表单
useEffect(() => {
if (!message) {
form.setFieldsValue({
message: undefined,
})
}
}, [form, message])
// 当加载状态时,清空输入框
useEffect(() => {
if (loading) {
form.setFieldsValue({
message: undefined,
})
}
}, [loading])
return (
<div className="rb:absolute rb:bottom-3 rb:left-0 rb:right-0">
<Flex vertical justify="space-between" className="rb:border rb:border-[#DFE4ED] rb:rounded-xl rb:min-h-30">
{/* 消息输入表单 */}
<Form form={form} layout="vertical">
<Form.Item name="message" noStyle>
<Input.TextArea
className="rb:m-[10px_12px_10px_12px]! rb:p-0! rb:w-[calc(100%-24px)]! rb:flex-[1_1_auto]"
variant="borderless"
autoSize={{ minRows: 2, maxRows: 2 }}
onChange={(e) => onChange(e.target.value)}
onKeyDown={(e) => {
// Enter键发送Shift+Enter换行
if (e.key === 'Enter' && !e.shiftKey && (e.target as HTMLTextAreaElement).value?.trim() !== '' && !loading) {
e.preventDefault();
onSend();
}
}}
/>
</Form.Item>
</Form>
{/* 底部操作区域 */}
<Flex align="center" justify="space-between" className="rb:m-[0_10px_10px_10px]!">
{/* 子组件内容(如按钮等) */}
{children}
{/* 发送按钮 - 根据状态显示不同图标 */}
{loading
? <img src={LoadingIcon} className="rb:w-5.5 rb:h-5.5 rb:cursor-pointer" />
: !values || !values?.message || values?.message?.trim() === ''
? <img src={SendDisabledIcon} className="rb:w-5.5 rb:h-5.5 rb:cursor-pointer" />
: <img src={SendIcon} className="rb:w-5.5 rb:h-5.5 rb:cursor-pointer" onClick={onSend} />
}
</Flex>
</Flex>
</div>
)
}
export default ChatInput

View File

@@ -0,0 +1,47 @@
/*
* @Author: ZhaoYing
* @Date: 2025-12-10 16:46:09
* @Last Modified by: ZhaoYing
* @Last Modified time: 2025-12-11 13:43:51
*/
import { type FC } from 'react'
import ChatInput from './ChatInput'
import type { ChatProps } from './types'
import ChatContent from './ChatContent'
/**
* 聊天组件 - 主要组件,由内容区域和输入框组成
* 提供完整的聊天界面功能,包括消息显示和输入交互
*/
const Chat: FC<ChatProps> = ({
empty,
data,
onChange,
onSend,
streamLoading = false,
loading,
contentClassName = '',
children,
labelFormat,
errorDesc
}) => {
return (
<div className="rb:h-full rb:relative rb:pt-2">
{/* 聊天内容显示区域 */}
<ChatContent
classNames={contentClassName}
data={data}
streamLoading={streamLoading}
empty={empty}
labelFormat={labelFormat}
errorDesc={errorDesc}
/>
{/* 聊天输入框区域 */}
<ChatInput onChange={onChange} onSend={onSend} loading={loading}>
{children}
</ChatInput>
</div>
)
}
export default Chat

View File

@@ -0,0 +1,84 @@
/*
* @Author: ZhaoYing
* @Date: 2025-12-10 16:45:54
* @Last Modified by: ZhaoYing
* @Last Modified time: 2025-12-11 13:43:52
*/
import { type ReactNode } from 'react'
/**
* 聊天消息项接口
*/
export interface ChatItem {
/** 消息唯一标识 */
id?: string;
/** 会话ID */
conversation_id?: string | null;
/** 消息角色:用户或助手 */
role?: 'user' | 'assistant';
/** 消息内容 */
content?: string | null;
/** 创建时间 */
created_at?: number | string
}
/**
* 聊天组件主要属性接口
*/
export interface ChatProps {
/** 空状态显示内容 */
empty?: ReactNode;
/** 聊天数据列表 */
data: ChatItem[];
/** 输入内容变化回调 */
onChange: (message: string) => void;
/** 发送消息回调 */
onSend: () => void;
/** 流式加载状态 */
streamLoading?: boolean;
/** 加载状态 */
loading: boolean;
/** 内容区域自定义样式类名 */
contentClassName?: string;
/** 子组件内容 */
children?: ReactNode;
/** 标签格式化函数 */
labelFormat: (item: ChatItem) => any;
errorDesc?: string;
}
/**
* 聊天输入框组件属性接口
*/
export interface ChatInputProps {
/** 当前输入消息 */
message?: string;
/** 输入内容变化回调 */
onChange: (message: string) => void;
/** 发送消息回调 */
onSend: () => void;
/** 加载状态 */
loading: boolean;
/** 子组件内容 */
children?: ReactNode;
}
/**
* 聊天内容区域组件属性接口
*/
export interface ChatContentProps {
/** 自定义样式类名 */
classNames?: string | Record<string, boolean>;
contentClassNames?: string | Record<string, boolean>;
/** 聊天数据列表 */
data: ChatItem[];
/** 流式加载状态 */
streamLoading: boolean;
/** 空状态显示内容 */
empty?: ReactNode;
/** 标签位置:顶部或底部 */
labelPosition?: 'top' | 'bottom';
/** 标签格式化函数 */
labelFormat: (item: ChatItem) => any;
errorDesc?: string;
}

View File

@@ -9,7 +9,7 @@ interface ApiResponse<T> {
items?: T[]; items?: T[];
} }
interface CustomSelectProps { interface CustomSelectProps extends Omit<SelectProps, 'filterOption'> {
url: string; url: string;
params?: Record<string, unknown>; params?: Record<string, unknown>;
valueKey?: string; valueKey?: string;

View File

@@ -27,7 +27,7 @@ const Empty: FC<EmptyProps> = ({
<div className={`rb:flex rb:items-center rb:justify-center rb:flex-col ${className}`}> <div className={`rb:flex rb:items-center rb:justify-center rb:flex-col ${className}`}>
<img src={url || emptyIcon} alt="404" style={{ width: `${width}px`, height: `${height}px` }} /> <img src={url || emptyIcon} alt="404" style={{ width: `${width}px`, height: `${height}px` }} />
{title && <div className="rb:mt-2 rb:leading-5">{title}</div>} {title && <div className="rb:mt-2 rb:leading-5">{title}</div>}
{curSubTitle && <div className={`rb:mt-[${url ? 8 : 5}px] rb:leading-4 rb:text-[12px] rb:text-[#A8A9AA]`}>{subTitle}</div>} {curSubTitle && <div className={`rb:mt-[${url ? 8 : 5}px] rb:leading-4 rb:text-[12px] rb:text-[#A8A9AA]`}>{curSubTitle}</div>}
</div> </div>
); );
} }

View File

@@ -3,6 +3,7 @@ import { Layout, Dropdown, Space, Breadcrumb } from 'antd';
import type { MenuProps, BreadcrumbProps } from 'antd'; import type { MenuProps, BreadcrumbProps } from 'antd';
import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons'; import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { useUser } from '@/store/user'; import { useUser } from '@/store/user';
import { useMenu } from '@/store/menu'; import { useMenu } from '@/store/menu';
import styles from './index.module.css' import styles from './index.module.css'
@@ -12,12 +13,35 @@ const { Header } = Layout;
const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation();
const settingModalRef = useRef<SettingModalRef>(null) const settingModalRef = useRef<SettingModalRef>(null)
const userInfoModalRef = useRef<UserInfoModalRef>(null) const userInfoModalRef = useRef<UserInfoModalRef>(null)
const { user, logout } = useUser(); const { user, logout } = useUser();
const { allBreadcrumbs } = useMenu(); const { allBreadcrumbs } = useMenu();
const breadcrumbs = allBreadcrumbs[source] || [];
// 根据当前路由动态选择面包屑源
const getBreadcrumbSource = () => {
const pathname = location.pathname;
// 知识库列表页面使用默认的 space 面包屑
if (pathname === '/knowledge-base') {
return 'space';
}
// 知识库详情相关页面使用独立的面包屑
if (pathname.includes('/knowledge-base/') && pathname !== '/knowledge-base') {
return 'space-detail';
}
// 其他页面使用传入的 source
return source;
};
const breadcrumbSource = getBreadcrumbSource();
const breadcrumbs = allBreadcrumbs[breadcrumbSource] || [];
// 处理退出登录 // 处理退出登录
const handleLogout = () => { const handleLogout = () => {

View File

@@ -13,7 +13,7 @@ const { Content } = Layout;
// 认证布局组件使用useRouteGuard hook进行路由鉴权 // 认证布局组件使用useRouteGuard hook进行路由鉴权
const AuthLayout: FC = () => { const AuthLayout: FC = () => {
const { getUserInfo } = useUser(); const { getUserInfo, getStorageType } = useUser();
// 使用路由守卫hook处理认证和权限检查 // 使用路由守卫hook处理认证和权限检查
useRouteGuard('manage'); useRouteGuard('manage');
// 自动更新面包屑导航 // 自动更新面包屑导航
@@ -24,6 +24,7 @@ const AuthLayout: FC = () => {
window.location.href = `/#/login`; window.location.href = `/#/login`;
} else { } else {
getUserInfo() getUserInfo()
getStorageType()
} }
}, []); }, []);

View File

@@ -4,12 +4,13 @@ import { useUser } from '@/store/user';
// 基础布局组件,用于展示内容并保留用户信息获取功能 // 基础布局组件,用于展示内容并保留用户信息获取功能
const BasicLayout: FC = () => { const BasicLayout: FC = () => {
const { getUserInfo } = useUser(); const { getUserInfo, getStorageType } = useUser();
// 获取用户信息 // 获取用户信息
useEffect(() => { useEffect(() => {
getUserInfo(); getUserInfo();
}, [getUserInfo]); getStorageType()
}, [getUserInfo, getStorageType]);
return ( return (
<div className="rb:relative rb:h-full rb:w-full"> <div className="rb:relative rb:h-full rb:w-full">

View File

@@ -1,4 +1,5 @@
import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider } from 'antd' import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider, Button } from 'antd'
import { EditOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import RemarkGfm from 'remark-gfm' import RemarkGfm from 'remark-gfm'
import RemarkMath from 'remark-math' import RemarkMath from 'remark-math'
@@ -6,6 +7,7 @@ import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex' import RehypeKatex from 'rehype-katex'
import RehypeRaw from 'rehype-raw' import RehypeRaw from 'rehype-raw'
import type { FC } from 'react' import type { FC } from 'react'
import { useState, useRef, useEffect } from 'react'
import Code from './Code' import Code from './Code'
import VideoBlock from './VideoBlock' import VideoBlock from './VideoBlock'
@@ -16,42 +18,45 @@ import RbButton from './RbButton'
interface RbMarkdownProps { interface RbMarkdownProps {
content: string; content: string;
showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false隐藏 showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false隐藏
editable?: boolean; // 是否可编辑,默认为 false
onContentChange?: (content: string) => void; // 内容变化回调
onSave?: (content: string) => void; // 保存回调
} }
const components = { const components = {
h1: ({ children }: { children: string }) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2">{children}</h1>, h1: ({ children, ...props }: any) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2" {...props}>{children}</h1>,
h2: ({ children }: { children: string }) => <h2 className="rb:text-xl rb:font-bold rb:mb-2">{children}</h2>, h2: ({ children, ...props }: any) => <h2 className="rb:text-xl rb:font-bold rb:mb-2" {...props}>{children}</h2>,
h3: ({ children }: { children: string }) => <h3 className="rb:text-lg rb:font-bold rb:mb-2">{children}</h3>, h3: ({ children, ...props }: any) => <h3 className="rb:text-lg rb:font-bold rb:mb-2" {...props}>{children}</h3>,
h4: ({ children }: { children: string }) => <h4 className="rb:text-md rb:font-bold rb:mb-2">{children}</h4>, h4: ({ children, ...props }: any) => <h4 className="rb:text-md rb:font-bold rb:mb-2" {...props}>{children}</h4>,
h5: ({ children }: { children: string }) => <h5 className="rb:text-sm rb:font-bold rb:mb-2">{children}</h5>, h5: ({ children, ...props }: any) => <h5 className="rb:text-sm rb:font-bold rb:mb-2" {...props}>{children}</h5>,
h6: ({ children }: { children: string }) => <h6 className="rb:text-xs rb:font-bold rb:mb-2">{children}</h6>, h6: ({ children, ...props }: any) => <h6 className="rb:text-xs rb:font-bold rb:mb-2" {...props}>{children}</h6>,
ul: ({ children }: { children: string }) => <ul className="rb:list-disc rb:ml-6 rb:mb-2">{children}</ul>, ul: ({ children, ...props }: any) => <ul className="rb:list-disc rb:ml-6 rb:mb-2" {...props}>{children}</ul>,
ol: ({ children }: { children: string }) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2">{children}</ol>, ol: ({ children, ...props }: any) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2" {...props}>{children}</ol>,
li: ({ children }: { children: string }) => <li className="rb:mb-1">{children}</li>, li: ({ children, ...props }: any) => <li className="rb:mb-1" {...props}>{children}</li>,
blockquote: ({ children }: { children: string }) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2">{children}</blockquote>, blockquote: ({ children, ...props }: any) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2" {...props}>{children}</blockquote>,
p: ({ children }: { children: string }) => <p className="rb:mb-2">{children}</p>, p: ({ children, ...props }: any) => <p className="rb:mb-2" {...props}>{children}</p>,
strong: ({ children }: { children: string }) => <strong className="rb:font-bold">{children}</strong>, strong: ({ children, ...props }: any) => <strong className="rb:font-bold" {...props}>{children}</strong>,
em: ({ children }: { children: string }) => <em className="rb:italic">{children}</em>, em: ({ children, ...props }: any) => <em className="rb:italic" {...props}>{children}</em>,
del: ({ children }: { children: string }) => <del className="rb:line-through">{children}</del>, del: ({ children, ...props }: any) => <del className="rb:line-through" {...props}>{children}</del>,
span: ({ children, ...props }: any) => { span: ({ children, style, ...restProps }: any) => {
// 如果是 HTML 注释的 span应用特殊样式 // 如果是 HTML 注释的 span应用特殊样式
if (props.style?.color === '#999') { if (style?.color === '#999') {
return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span> return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span>
} }
return <span {...props}>{children}</span> return <span style={style} {...restProps}>{children}</span>
}, },
code: Code, code: ({ children, className, ...props }: any) => <Code children={String(children)} className={className || ''} {...props} />,
img: Image, img: ({ src, alt, ...props }: any) => <Image src={src} alt={alt} {...props} />,
video: VideoBlock, video: ({ src, ...props }: any) => <VideoBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
audio: AudioBlock, audio: ({ src, ...props }: any) => <AudioBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
a: Link, a: ({ href, children, ...props }: any) => <Link href={href || '#'} {...props}>{children}</Link>,
button: RbButton, button: ({ children, ...props }: any) => <RbButton node={{ children }}>{[children]}</RbButton>,
table: ({ children }: { children: string }) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2">{children}</table>, table: ({ children, ...props }: any) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2" {...props}>{children}</table>,
tr: ({ children }: { children: string }) => <tr className="rb:border rb:border-[#D9D9D9]">{children}</tr>, tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#D9D9D9]" {...props}>{children}</tr>,
th: ({ children }: { children: string }) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold">{children}</th>, th: ({ children, ...props }: any) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold" {...props}>{children}</th>,
td: ({ children }: { children: string }) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left">{children}</td>, td: ({ children, ...props }: any) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
input: ({ children, ...props }: { children: string }) => { input: ({ children, ...props }: any) => {
switch (props.type) { switch (props.type) {
case 'color': case 'color':
return <ColorPicker {...props} /> return <ColorPicker {...props} />
@@ -74,7 +79,7 @@ const components = {
return <Slider {...props} /> return <Slider {...props} />
case 'submit': case 'submit':
case 'button': case 'button':
return <RbButton {...props}>{props.value}</RbButton> return <RbButton node={{ children: props.value || children }}>{[props.value || children]}</RbButton>
case 'checkbox': case 'checkbox':
return <Checkbox {...props}>{children}</Checkbox> return <Checkbox {...props}>{children}</Checkbox>
case 'password': case 'password':
@@ -85,37 +90,158 @@ const components = {
return <Input value={children} {...props} /> return <Input value={children} {...props} />
} }
}, },
select: ({ children, ...props }: { children: string }) => <Select style={{width: '100%'}} {...props}>{children}</Select>, select: ({ children, ...props }: any) => <Select style={{width: '100%'}} {...props}>{children}</Select>,
textarea: ({ children, ...props }: { children: string }) => <Input.TextArea {...props}>{children}</Input.TextArea>, textarea: ({ children, ...props }: any) => <Input.TextArea {...props}>{children}</Input.TextArea>,
form: ({ children }: { children: string }) => <Form>{children}</Form>, form: ({ children, ...props }: any) => <Form {...props}>{children}</Form>,
} }
const RbMarkdown: FC<RbMarkdownProps> = ({ const RbMarkdown: FC<RbMarkdownProps> = ({
content, content,
showHtmlComments = false, showHtmlComments = false,
editable = false,
onContentChange,
onSave,
}) => { }) => {
const [isEditing, setIsEditing] = useState(editable) // 如果可编辑,默认进入编辑模式
const [editContent, setEditContent] = useState(content)
const textareaRef = useRef<any>(null)
// 当外部 content 变化时,同步更新编辑内容
useEffect(() => {
setEditContent(content)
}, [content])
// 当editable变化时自动切换编辑状态
useEffect(() => {
if (editable) {
setIsEditing(true)
// 延迟聚焦,确保 textarea 已渲染
setTimeout(() => {
textareaRef.current?.focus()
}, 100)
}
}, [editable])
// 进入编辑模式
const handleEdit = () => {
setIsEditing(true)
setEditContent(content)
// 延迟聚焦,确保 textarea 已渲染
setTimeout(() => {
textareaRef.current?.focus()
}, 100)
}
// 保存编辑
const handleSave = () => {
onContentChange?.(editContent)
onSave?.(editContent)
if (!editable) {
setIsEditing(false) // 只有在非强制编辑模式下才退出编辑
}
}
// 取消编辑
const handleCancel = () => {
setEditContent(content) // 恢复原内容
if (!editable) {
setIsEditing(false) // 只有在非强制编辑模式下才退出编辑
}
}
// 处理 textarea 内容变化
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newContent = e.target.value
setEditContent(newContent)
// 实时回调内容变化
onContentChange?.(newContent)
}
// 根据参数决定是否将 HTML 注释转换为可见文本 // 根据参数决定是否将 HTML 注释转换为可见文本
// 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤 // 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤
const processedContent = showHtmlComments const processedContent = showHtmlComments
? content.replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => { ? (isEditing ? editContent : content).replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => {
// 转换为带样式的文本,使用 <span class="html-comment"> 标记 // 转换为带样式的文本,使用 <span class="html-comment"> 标记
const escaped = commentContent.trim().replace(/</g, '&lt;').replace(/>/g, '&gt;') const escaped = commentContent.trim().replace(/</g, '&lt;').replace(/>/g, '&gt;')
return `<span class="html-comment">&lt;!-- ${escaped} --&gt;</span>` return `<span class="html-comment">&lt;!-- ${escaped} --&gt;</span>`
}) })
: content : (isEditing ? editContent : content)
// 如果是编辑模式,显示 textarea
if (isEditing) {
return ( return (
<div> <div className="rb:relative">
<style>{` <style>{`
.html-comment { .html-comment {
color: #999; color: #999;
font-size: 0.9em; font-size: 0.9em;
} }
`}</style> `}</style>
{/* 编辑工具栏 - 只在非强制编辑模式下显示 */}
{!editable && (
<div className="rb:flex rb:justify-end rb:gap-2 rb:mb-2">
<Button
type="primary"
size="small"
icon={<SaveOutlined />}
onClick={handleSave}
>
</Button>
<Button
size="small"
icon={<CloseOutlined />}
onClick={handleCancel}
>
</Button>
</div>
)}
{/* 编辑区域 */}
<Input.TextArea
ref={textareaRef}
value={editContent}
onChange={handleTextareaChange}
rows={editable ? 5 : 10}
className="rb:font-mono rb:text-sm"
placeholder="请输入 Markdown 内容..."
style={{ resize: 'vertical' }}
/>
</div>
)
}
// 预览模式
return (
<div className="rb:relative rb:group">
<style>{`
.html-comment {
color: #999;
font-size: 0.9em;
}
`}</style>
{/* 编辑按钮 - 只在非强制编辑模式且鼠标悬停时显示 */}
{!editable && (
<div className="rb:absolute rb:top-0 rb:right-0 rb:opacity-0 group-hover:rb:opacity-100 rb:transition-opacity rb:z-10">
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={handleEdit}
className="rb:bg-white rb:shadow-sm"
>
</Button>
</div>
)}
<ReactMarkdown <ReactMarkdown
// allowElement={[]} // allowElement={[]}
// allowedElements={[]} // allowedElements={[]}
components={components} components={components as any}
disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']} disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
rehypePlugins={[ rehypePlugins={[
RehypeKatex, RehypeKatex,

View File

@@ -29,7 +29,7 @@ interface PageScrollListProps {
const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
renderItem, renderItem,
query = {}, query,
url, url,
column = 4, column = 4,
className = '', className = '',
@@ -51,11 +51,11 @@ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
request.get(url, { request.get(url, {
page: page, page: page,
pagesize: PAGE_SIZE, pagesize: PAGE_SIZE,
...query, ...(query||{}),
}) })
.then((res) => { .then((res) => {
const response = res as ApiResponse; const response = res as ApiResponse;
const results = Array.isArray(response.items) ? response.items : Array.isArray(response.hosts) ? response.hosts : Array.isArray(response) ? response : []; const results = Array.isArray(response.items) ? response.items : Array.isArray(response) ? response : [];
if (flag) { if (flag) {
setData(results); setData(results);
} else { } else {

View File

@@ -15,6 +15,7 @@ interface RadioCardProps extends Omit<RadioGroupProps, 'onChange'> {
onValueChange?: (value: string | null | undefined, option?: RadioCardOption) => void; onValueChange?: (value: string | null | undefined, option?: RadioCardOption) => void;
onChange?: (value: string | null | undefined, option?: RadioCardOption) => void; onChange?: (value: string | null | undefined, option?: RadioCardOption) => void;
itemRender?: (option: RadioCardOption) => ReactNode; itemRender?: (option: RadioCardOption) => ReactNode;
allowClear?: boolean;
} }
const RadioGroupCard: FC<RadioCardProps> = ({ const RadioGroupCard: FC<RadioCardProps> = ({
@@ -22,7 +23,8 @@ const RadioGroupCard: FC<RadioCardProps> = ({
value, value,
onValueChange, onValueChange,
onChange, onChange,
itemRender itemRender,
allowClear = true
}) => { }) => {
// 监听value变化 // 监听value变化
useEffect(() => { useEffect(() => {
@@ -34,23 +36,27 @@ const RadioGroupCard: FC<RadioCardProps> = ({
const handleChange = (option: RadioCardOption) => { const handleChange = (option: RadioCardOption) => {
if (option.disabled) return if (option.disabled) return
if (onChange) { if (onChange) {
onChange(value === option.value ? null : String(option.value), value === option.value ? undefined : option); if (allowClear && value === option.value) {
onChange(null, undefined);
} else {
onChange(String(option.value), option);
}
} }
} }
return ( return (
<div className={`rb:grid rb:grid-cols-${options.length} rb:gap-[12px]`}> <div className={`rb:grid rb:grid-cols-${options.length} rb:gap-3`}>
{options.map(option => ( {options.map(option => (
<div key={String(option.value)} className={clsx("rb:border rb:rounded-[8px] rb:w-full rb:p-[20px_12px] rb:text-center rb:cursor-pointer", { <div key={String(option.value)} className={clsx("rb:border rb:rounded-lg rb:w-full rb:p-[20px_12px] rb:text-center rb:cursor-pointer", {
'rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF]': option.value === value, 'rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF]': option.value === value,
'rb:border-[#EBEBEB] rb:bg-[#ffffff]': option.value !== value, 'rb:border-[#EBEBEB] rb:bg-[#ffffff]': option.value !== value,
'rb:opacity-[0.75]': option.disabled 'rb:opacity-[0.75]': option.disabled
})} onClick={() => handleChange(option)}> })} onClick={() => handleChange(option)}>
{itemRender ? itemRender(option) : ( {itemRender ? itemRender(option) : (
<> <>
{option.icon && <img src={option.icon} className="rb:w-[40px] rb:h-[40px] rb:mb-[12px] rb:m-[0_auto]" />} {option.icon && <img src={option.icon} className="rb:w-10 rb:h-10 rb:mb-3 rb:m-[0_auto]" />}
<div className="rb:text-[14px] rb:font-medium">{option.label}</div> <div className="rb:text-[14px] rb:font-medium">{option.label}</div>
<div className="rb:mt-[6px] rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{option.labelDesc}</div> <div className="rb:mt-1.5 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{option.labelDesc}</div>
</> </>
)} )}
</div> </div>

View File

@@ -3,7 +3,7 @@ import { type FC, type ReactNode } from 'react'
interface RbAlertProps { interface RbAlertProps {
color?: 'blue' | 'green' | 'orange' | 'purple', color?: 'blue' | 'green' | 'orange' | 'purple',
children: ReactNode | string; children: ReactNode | string;
icon: ReactNode; icon?: ReactNode;
className?: string; className?: string;
} }
@@ -16,8 +16,8 @@ const colors = {
const RbAlert: FC<RbAlertProps> = ({ color = 'blue', icon, className, children }) => { const RbAlert: FC<RbAlertProps> = ({ color = 'blue', icon, className, children }) => {
return ( return (
<div className={`${colors[color]} ${className} rb:p-[6px_9px] rb:flex rb:items-center rb:text-[12px] rb:font-regular rb:leading-[16px] rb:border-[1px] rb:rounded-[6px]`}> <div className={`${colors[color]} ${className} rb:p-[6px_9px] rb:flex rb:items-center rb:text-[12px] rb:font-regular rb:leading-4 rb:border rb:rounded-md`}>
{icon && <span className="rb:text-[16px] rb:mr-[9px]">{icon}</span>} {icon && <span className="rb:text-[16px] rb:mr-2.25">{icon}</span>}
{children} {children}
</div> </div>
) )

View File

@@ -52,7 +52,7 @@ const RbCard: FC<RbCardProps> = ({
title={typeof title === 'function' ? title() : title ? title={typeof title === 'function' ? title() : title ?
<div className="rb:flex rb:items-center"> <div className="rb:flex rb:items-center">
{avatarUrl {avatarUrl
? <img src={avatarUrl} className="rb:mr-[13px] rb:w-[48px] rb:h-[48px] rb:rounded-[8px]" /> ? <img src={avatarUrl} className="rb:mr-3.25 rb:w-12 rb:h-12 rb:rounded-lg" />
: avatar ? avatar : null : avatar ? avatar : null
} }
<div className={ <div className={

View File

@@ -0,0 +1,3 @@
.rb-modal .ant-modal-header {
margin-bottom: 24px;
}

View File

@@ -1,11 +1,19 @@
/*
* @Description:
* @Version: 0.0.1
* @Author: yujiangping
* @Date: 2025-12-16 10:19:18
* @LastEditors: yujiangping
* @LastEditTime: 2025-12-22 12:31:31
*/
import { type FC } from 'react' import { type FC } from 'react'
import { Modal, type ModalProps } from 'antd' import { Modal, type ModalProps } from 'antd'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const RbModal: FC<ModalProps> = ({ const RbModal: FC<ModalProps> = ({
onOk, onOk,
onCancel, onCancel,
children, children,
className,
...props ...props
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -16,9 +24,11 @@ const RbModal: FC<ModalProps> = ({
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
onOk={onOk} onOk={onOk}
destroyOnHidden={true} destroyOnHidden={true}
className={`rb-modal ${className || ''}`}
maskClosable={false}
{...props} {...props}
> >
<div className='rb:max-h-[550px] rb:overflow-y-auto rb:overflow-x-hidden'> <div className='rb:max-h-137.5 rb:overflow-y-auto rb:overflow-x-hidden'>
{children} {children}
</div> </div>
</Modal> </Modal>

View File

@@ -21,11 +21,11 @@ import modelActiveIcon from '@/assets/images/menu/model_active.svg';
import memoryIcon from '@/assets/images/menu/memory.svg'; import memoryIcon from '@/assets/images/menu/memory.svg';
import memoryActiveIcon from '@/assets/images/menu/memory_active.svg'; import memoryActiveIcon from '@/assets/images/menu/memory_active.svg';
import spaceIcon from '@/assets/images/menu/space.svg'; import spaceIcon from '@/assets/images/menu/space.svg';
import spaceActiveIcon from '@/assets/images/menu/space_acitve.svg'; import spaceActiveIcon from '@/assets/images/menu/space_active.svg';
import userIcon from '@/assets/images/menu/user.svg'; import userIcon from '@/assets/images/menu/user.svg';
import userActiveIcon from '@/assets/images/menu/user_active.svg'; import userActiveIcon from '@/assets/images/menu/user_active.svg';
import userMemoryIcon from '@/assets/images/menu/userMemory.svg'; import userMemoryIcon from '@/assets/images/menu/userMemory.svg';
import userMemoryActiveIcon from '@/assets/images/menu/userMemory_acitve.svg'; import userMemoryActiveIcon from '@/assets/images/menu/userMemory_active.svg';
import applicationIcon from '@/assets/images/menu/application.svg'; import applicationIcon from '@/assets/images/menu/application.svg';
import applicationActiveIcon from '@/assets/images/menu/application_active.svg'; import applicationActiveIcon from '@/assets/images/menu/application_active.svg';
import knowledgeIcon from '@/assets/images/menu/knowledge.svg'; import knowledgeIcon from '@/assets/images/menu/knowledge.svg';
@@ -34,6 +34,12 @@ import memoryConversationIcon from '@/assets/images/menu/memoryConversation.svg'
import memoryConversationActiveIcon from '@/assets/images/menu/memoryConversation_active.svg'; import memoryConversationActiveIcon from '@/assets/images/menu/memoryConversation_active.svg';
import memberIcon from '@/assets/images/menu/member.svg'; import memberIcon from '@/assets/images/menu/member.svg';
import memberActiveIcon from '@/assets/images/menu/member_active.svg'; import memberActiveIcon from '@/assets/images/menu/member_active.svg';
import toolIcon from '@/assets/images/menu/tool.png';
import toolActiveIcon from '@/assets/images/menu/tool_active.png';
import apiKeyIcon from '@/assets/images/menu/apiKey.png';
import apiKeyActiveIcon from '@/assets/images/menu/apiKey_active.png';
import pricingIcon from '@/assets/images/menu/pricing.svg'
import pricingActiveIcon from '@/assets/images/menu/pricing_active.svg'
// 图标路径映射表 // 图标路径映射表
const iconPathMap: Record<string, string> = { const iconPathMap: Record<string, string> = {
@@ -57,6 +63,12 @@ const iconPathMap: Record<string, string> = {
'memoryConversationActive': memoryConversationActiveIcon, 'memoryConversationActive': memoryConversationActiveIcon,
'member': memberIcon, 'member': memberIcon,
'memberActive': memberActiveIcon, 'memberActive': memberActiveIcon,
'tool': toolIcon,
'toolActive': toolActiveIcon,
'apiKey': apiKeyIcon,
'apiKeyActive': apiKeyActiveIcon,
'pricing': pricingIcon,
'pricingActive': pricingActiveIcon
}; };
const { Sider } = Layout; const { Sider } = Layout;

View File

@@ -19,6 +19,7 @@ interface TableComponentProps extends Omit<TableProps, 'pagination'> {
isScroll?: boolean; isScroll?: boolean;
scrollX?: number | string | true; // 支持自定义横向滚动宽度 scrollX?: number | string | true; // 支持自定义横向滚动宽度
scrollY?: number | string; // 支持自定义纵向滚动高度 scrollY?: number | string; // 支持自定义纵向滚动高度
currentPageKey?: string;
} }
export interface TableRef { export interface TableRef {
loadData: () => void; loadData: () => void;
@@ -48,6 +49,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
isScroll = false, isScroll = false,
scrollX, scrollX,
scrollY, scrollY,
currentPageKey = 'page',
...props ...props
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -86,7 +88,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
...currentPagination, ...currentPagination,
...pageData, ...pageData,
}) })
params = {...params, ...pageData} params = { ...params, ...pageData, [currentPageKey]: pageData.page}
} }
setLoading(true) setLoading(true)
// 构建查询参数并调用API // 构建查询参数并调用API
@@ -95,7 +97,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
// 支持两种响应格式:直接返回 total 或在 page 对象中返回 // 支持两种响应格式:直接返回 total 或在 page 对象中返回
const totalCount = res.page?.total ?? res.total ?? 0; const totalCount = res.page?.total ?? res.total ?? 0;
setTotal(totalCount) setTotal(totalCount)
setData(Array.isArray(res.items) ? res.items : Array.isArray(res.hosts) ? res.hosts : res || []) setData(Array.isArray(res.items) ? res.items : Array.isArray(res.hosts) ? res.hosts : Array.isArray(res.list) ? res.list : res || [])
setLoading(false) setLoading(false)
}) })
.catch(err => { .catch(err => {

View File

@@ -1,6 +1,6 @@
import { type FC, type ReactNode } from 'react' import { type FC, type ReactNode } from 'react'
interface TagProps { export interface TagProps {
color?: 'processing' | 'error' | 'success' | 'warning' | 'default', color?: 'processing' | 'error' | 'success' | 'warning' | 'default',
children: ReactNode; children: ReactNode;
className?: string; className?: string;
@@ -16,7 +16,7 @@ const colors = {
const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => { const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => {
return ( return (
<span className={`rb:inline-block rb:px-[4px] rb:py-[2px] rb:rounded-[4px] rb:text-[12px] rb:font-regular! rb:leading-[16px] rb:border-[1px] ${colors[color]} ${className || ''}`}> <span className={`rb:inline-block rb:px-1 rb:py-0.5 rb:rounded-sm rb:text-[12px] rb:font-regular! rb:leading-4 rb:border ${colors[color]} ${className || ''}`}>
{children} {children}
</span> </span>
) )

View File

@@ -38,6 +38,8 @@ interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
maxCount?: number; maxCount?: number;
/** 是否支持拖拽上传默认为false */ /** 是否支持拖拽上传默认为false */
isCanDrag?: boolean; isCanDrag?: boolean;
/** 自定义移除文件回调 */
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
} }
const ALL_FILE_TYPE: { const ALL_FILE_TYPE: {
[key: string]: string; [key: string]: string;
@@ -77,6 +79,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
isAutoUpload = true, isAutoUpload = true,
maxCount = 1, maxCount = 1,
isCanDrag = false, isCanDrag = false,
onRemove: customOnRemove,
...props ...props
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -86,11 +89,20 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
// 处理文件移除 // 处理文件移除
const handleRemove = (file: UploadFile) => { const handleRemove = (file: UploadFile) => {
// 如果有自定义的 onRemove 回调,先执行它
if (customOnRemove) {
const result = customOnRemove(file);
// 如果返回 false阻止移除
if (result === false) {
return false;
}
}
confirm({ confirm({
title: '确定要删除此文件吗?', title: `${t('common.confirmRemoveFile')}`,
okText: '确定', okText: `${t('common.confirm')}`,
okType: 'danger', okType: 'danger',
cancelText: '取消', cancelText: `${t('common.cancel')}`,
onOk: () => { onOk: () => {
const newFileList = fileList.filter((item) => item.uid !== file.uid); const newFileList = fileList.filter((item) => item.uid !== file.uid);
setFileList(newFileList); setFileList(newFileList);
@@ -236,7 +248,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
<div key={file.uid} className="rb:relative rb:w-full rb:pt-[8px] rb:pl-[10px] rb:pr-[10px] rb-pb-[10px] rb:border-1 rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white"> <div key={file.uid} className="rb:relative rb:w-full rb:pt-[8px] rb:pl-[10px] rb:pr-[10px] rb-pb-[10px] rb:border-1 rb:border-[#EBEBEB] rb:rounded rb:p-2 rb:mt-2 rb:bg-white">
<div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]"> <div className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]">
{file.name} {file.name}
<span className="rb:text-[#5B6167]" onClick={() => actions?.remove()}>Cancel</span> <span className="rb:text-[#5B6167] rb:cursor-pointer" onClick={() => actions?.remove()}>Cancel</span>
</div> </div>
<Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} /> <Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />
</div> </div>

View File

@@ -0,0 +1,319 @@
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMenu } from '@/store/menu';
import type { MenuItem } from '@/store/menu';
export interface BreadcrumbItem {
id: string;
name: string;
type?: 'knowledgeBase' | 'folder' | 'document';
}
export interface BreadcrumbPath {
knowledgeBaseFolderPath: BreadcrumbItem[]; // 知识库文件夹路径
knowledgeBase?: BreadcrumbItem; // 知识库信息
documentFolderPath: BreadcrumbItem[]; // 文档文件夹路径
document?: BreadcrumbItem; // 文档信息
}
export interface BreadcrumbOptions {
onKnowledgeBaseMenuClick?: () => void;
onKnowledgeBaseFolderClick?: (folderId: string, folderPath: BreadcrumbItem[]) => void;
// 新增:区分面包屑类型
breadcrumbType?: 'list' | 'detail';
}
export const useBreadcrumbManager = (options?: BreadcrumbOptions) => {
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
const navigate = useNavigate();
const updateBreadcrumbs = useCallback((breadcrumbPath: BreadcrumbPath) => {
const breadcrumbType = options?.breadcrumbType || 'list';
// 对于详情页面,直接使用固定的知识库管理面包屑,不依赖可能被污染的 allBreadcrumbs
let baseBreadcrumbs: MenuItem[] = [];
if (breadcrumbType === 'detail') {
// 详情页面:始终使用固定的知识库管理面包屑
baseBreadcrumbs = [
{
id: 6,
parent: 0,
code: 'knowledge',
label: '知识库',
i18nKey: 'menu.knowledgeManagement',
path: '/knowledge-base',
enable: true,
display: true,
level: 1,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
}
];
} else {
// 列表页面:从 space 获取基础面包屑,但确保包含知识库管理
const spaceBreadcrumbs = allBreadcrumbs['space'] || [];
const knowledgeBaseMenuIndex = spaceBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
if (knowledgeBaseMenuIndex >= 0) {
baseBreadcrumbs = spaceBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1);
} else {
// 如果没有找到知识库菜单,使用默认的知识库管理面包屑
baseBreadcrumbs = [
{
id: 6,
parent: 0,
code: 'knowledge',
label: '知识库',
i18nKey: 'menu.knowledgeManagement',
path: '/knowledge-base',
enable: true,
display: true,
level: 1,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
}
];
}
}
const filteredBaseBreadcrumbs = baseBreadcrumbs;
// 给"知识库管理"添加点击事件
const breadcrumbsWithClick = filteredBaseBreadcrumbs.map((item) => {
if (item.path === '/knowledge-base') {
return {
...item,
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
if (options?.onKnowledgeBaseMenuClick) {
// 如果提供了回调函数,执行回调
options.onKnowledgeBaseMenuClick();
} else if (breadcrumbType === 'detail') {
// 知识库详情页面:没有回调函数时,返回到知识库列表页面
navigate('/knowledge-base', {
state: {
resetToRoot: true,
}
});
}
return false;
},
};
}
return item;
});
let customBreadcrumbs: MenuItem[] = [...breadcrumbsWithClick];
if (breadcrumbType === 'list') {
// 知识库列表页面:只显示知识库文件夹路径
customBreadcrumbs = [
...breadcrumbsWithClick,
...breadcrumbPath.knowledgeBaseFolderPath.map((folder, index) => ({
id: 0,
parent: 0,
code: null,
label: folder.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
// 如果有回调函数,直接调用回调函数来更新状态
if (options?.onKnowledgeBaseFolderClick) {
options.onKnowledgeBaseFolderClick(folder.id, breadcrumbPath.knowledgeBaseFolderPath.slice(0, index + 1));
} else {
// 否则使用导航(兜底逻辑)
navigate('/knowledge-base', {
state: {
navigateToFolder: folder.id,
folderPath: breadcrumbPath.knowledgeBaseFolderPath.slice(0, index + 1)
}
});
}
return false;
},
})),
];
} else {
// 知识库详情页面:显示知识库名称 + 文档文件夹路径 + 文档名称
customBreadcrumbs = [
...breadcrumbsWithClick,
// 添加知识库名称
...(breadcrumbPath.knowledgeBase ? [{
id: 0,
parent: 0,
code: null,
label: breadcrumbPath.knowledgeBase.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
// 返回到知识库详情页的根目录
const navigationState = {
fromKnowledgeBaseList: true,
knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
resetToRoot: true, // 添加重置到根目录的标志
refresh: true, // 添加刷新标志
timestamp: Date.now(), // 添加时间戳确保状态变化
};
// 使用当前页面路径进行导航,避免不必要的路由变化
const currentPath = window.location.pathname;
const targetPath = `/knowledge-base/${breadcrumbPath.knowledgeBase!.id}/private`;
if (currentPath === targetPath) {
// 如果已经在目标页面,直接更新状态而不导航
navigate(targetPath, {
state: navigationState,
replace: true // 使用 replace 避免历史记录堆积
});
} else {
// 如果不在目标页面,正常导航
navigate(targetPath, {
state: navigationState
});
}
return false;
},
}] : []),
// 添加文档文件夹路径
...breadcrumbPath.documentFolderPath.map((folder, index) => ({
id: 0,
parent: 0,
code: null,
label: folder.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
// 返回到知识库详情页的对应文件夹
const navigationState = {
fromKnowledgeBaseList: true,
knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
navigateToDocumentFolder: folder.id,
documentFolderPath: breadcrumbPath.documentFolderPath.slice(0, index + 1),
refresh: true, // 添加刷新标志
timestamp: Date.now(), // 添加时间戳确保状态变化
};
navigate(`/knowledge-base/${breadcrumbPath.knowledgeBase!.id}/private`, {
state: navigationState,
replace: true // 使用 replace 避免历史记录堆积
});
return false;
},
})),
// 添加文档名称(如果存在)
...(breadcrumbPath.document ? [{
id: 0,
parent: 0,
code: null,
label: breadcrumbPath.document.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
// 文档名称不可点击
}] : []),
];
}
// 根据面包屑类型使用不同的键,实现独立的面包屑路径
const breadcrumbKey = breadcrumbType === 'list' ? 'space' : 'space-detail';
setCustomBreadcrumbs(customBreadcrumbs, breadcrumbKey);
}, [setCustomBreadcrumbs, navigate, options?.breadcrumbType, options?.onKnowledgeBaseMenuClick, options?.onKnowledgeBaseFolderClick]);
return {
updateBreadcrumbs,
};
};

View File

@@ -11,8 +11,10 @@ export const checkAuthStatus = (): boolean => {
// 递归检查路由是否存在于菜单数据中 // 递归检查路由是否存在于菜单数据中
export const checkRoutePermission = (menus: MenuItem[], currentPath: string): boolean => { export const checkRoutePermission = (menus: MenuItem[], currentPath: string): boolean => {
// 首页默认有权限 // 首页和知识库相关页面默认有权限
if (currentPath === '/' || currentPath.includes('knowledge-detail')) return true; if (currentPath === '/' || currentPath.includes('knowledge-detail') || currentPath.includes('knowledge-base')) {
return true;
}
for (const menu of menus) { for (const menu of menus) {
// 检查当前菜单的path是否匹配 // 检查当前菜单的path是否匹配
@@ -26,6 +28,7 @@ export const checkRoutePermission = (menus: MenuItem[], currentPath: string): bo
} }
} }
} }
return false; return false;
}; };
@@ -52,7 +55,7 @@ export const useRouteGuard = (source: 'space' | 'manage') => {
const hasPermission = checkRoutePermission(menus, location.pathname); const hasPermission = checkRoutePermission(menus, location.pathname);
if (!hasPermission) { if (!hasPermission) {
// 无权限访问该路由,重定向到无权限页面 // 无权限访问该路由,重定向到无权限页面
// navigate('/not-found', { replace: true }); // navigate('/no-permission', { replace: true });
} }
} }
}, [navigate, location.pathname, location.search, location.hash, menus]); }, [navigate, location.pathname, location.search, location.hash, menus]);

File diff suppressed because it is too large Load Diff

View File

@@ -28,16 +28,24 @@ export const zh = {
spaceManagement: '空间管理', spaceManagement: '空间管理',
memoryExtractionEngine: '记忆提取引擎', memoryExtractionEngine: '记忆提取引擎',
forgettingEngine: '遗忘引擎', forgettingEngine: '遗忘引擎',
apiKeyManagement: 'API KEY管理',
knowledgePrivate: '详情', knowledgePrivate: '详情',
knowledgeShare: '详情', knowledgeShare: '详情',
knowledgeCreateDataset: '新建数据集', knowledgeCreateDataset: '新建数据集',
knowledgeDocumentDetails: '详情', knowledgeDocumentDetails: '详情',
userMemoryDetail: '用户记忆详情', userMemoryDetail: '用户记忆详情',
toolManagement: '工具管理',
emotionEngine: '情感引擎',
statementDetail: '情绪记忆',
selfReflectionEngine: '反思引擎',
pricing: '收费管理',
orderPayment: '订单支付',
orderHistory: '订单记录',
}, },
knowledgeBase: { knowledgeBase: {
home: '首页', home: '首页',
selectSpace: '请选择空间', selectSpace: '请选择空间',
preview:'预览', preview: '预览',
pleaseUploadFileFirst: '请先上传文件', pleaseUploadFileFirst: '请先上传文件',
shareSuccess: '分享成功', shareSuccess: '分享成功',
shareFailed: '分享失败', shareFailed: '分享失败',
@@ -86,7 +94,7 @@ export const zh = {
operation: '操作', operation: '操作',
selectDataSource: '选择来源', selectDataSource: '选择来源',
localFile: '本地文件', localFile: '本地文件',
uploadFileTypes: '上传 PDF、TXT、DOCX 等格式的文件', uploadFileTypes: '上传 PDF、TXT、DOCX, IMAGE, MEDIA 等格式的文件',
webLink: '网页链接', webLink: '网页链接',
webLinkPlaceholder: '请输入', webLinkPlaceholder: '请输入',
webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接', webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接',
@@ -94,6 +102,7 @@ export const zh = {
readStaticWebPage: '读取静态网页内容作为数据集', readStaticWebPage: '读取静态网页内容作为数据集',
customText: '自定义文本', customText: '自定义文本',
customContent: '自定义内容', customContent: '自定义内容',
createContentError: '创建自定义文件失败',
manuallyInputText: '手动输入一段文本作为数据集', manuallyInputText: '手动输入一段文本作为数据集',
openKnowledgeBase: '打开知识库', openKnowledgeBase: '打开知识库',
searchPlaceholder: '搜索', searchPlaceholder: '搜索',
@@ -110,13 +119,20 @@ export const zh = {
noDataSets: '暂无数据集,点击下方按钮或拖拽文件创建。', noDataSets: '暂无数据集,点击下方按钮或拖拽文件创建。',
createEmptyDataSet: '+ 空白数据集', createEmptyDataSet: '+ 空白数据集',
createImageDataSet: '+ 图片数据集', createImageDataSet: '+ 图片数据集',
createContent: '创建内容',
title: '标题',
content: '内容',
pleaseEnterTitle: '请输入标题',
pleaseEnterContent: '请输入内容',
dragFilesHere: '拖拽文件到此处上传', dragFilesHere: '拖拽文件到此处上传',
downloadOriginal: '下载原始内容', downloadOriginal: '下载原始内容',
createImport: '新建/导入', createImport: '新建/导入',
textDataSet: '文本数据集', textDataSet: '文本数据集',
imageDataSet: '图片数据集', imageDataSet: '图片数据集',
mediaDataSet: '媒体数据集',
blankDataset: '空白数据集', blankDataset: '空白数据集',
emptyDataSet: '空白数据集', emptyDataSet: '空白数据集',
customTextDataset: '自定义文本数据集',
text: '文本', text: '文本',
search: '搜索', search: '搜索',
image: '图片', image: '图片',
@@ -190,6 +206,7 @@ export const zh = {
datasetName: '数据集名称', datasetName: '数据集名称',
pleaseEnterDatasetName: '请输入数据集名称', pleaseEnterDatasetName: '请输入数据集名称',
uploadImages: '上传图片', uploadImages: '上传图片',
uploadMedia: '上传媒体文件',
pleaseUploadImages: '请上传图片', pleaseUploadImages: '请上传图片',
embedding_id: '嵌入模型', embedding_id: '嵌入模型',
llm_id: '大语言模型', llm_id: '大语言模型',
@@ -210,7 +227,6 @@ export const zh = {
insertContent: '插入内容', insertContent: '插入内容',
editContent: '编辑内容', editContent: '编辑内容',
insertContentPlaceholder: '请输入内容', insertContentPlaceholder: '请输入内容',
pleaseEnterContent: '请输入内容',
documentIdRequired: '文档ID是必需的', documentIdRequired: '文档ID是必需的',
editContentDesc: '编辑内容', editContentDesc: '编辑内容',
insertContentDesc: '插入内容', insertContentDesc: '插入内容',
@@ -223,6 +239,17 @@ export const zh = {
semantic: '语义', semantic: '语义',
hybrid: '混合', hybrid: '混合',
updateEmbeddingContent: '确定要更新嵌入模型吗?更新后,分块向量数据需要重新构建?', updateEmbeddingContent: '确定要更新嵌入模型吗?更新后,分块向量数据需要重新构建?',
question: '问题',
answer: '答案',
normalMode: '常规模式',
qaMode: '问答模式',
fileParsingSettings: '文件解析设置',
pdfEnhancementAnalysis: 'PDF增强解析',
fileSizeExceeds: '文件大小超过限制',
sizeLimitError: '文件大小超过限制最大支持256MB当前文件大小',
fileDurationExceeds:'文件时长超过限制',
fileDurationLimitError: '媒体文件时长超过限制最大支持150秒当前时长',
unableReadFile:'无法读取媒体文件信息,请检查文件格式',
createForm: { createForm: {
name: '名称', name: '名称',
embedding_id: '嵌入模型', embedding_id: '嵌入模型',
@@ -308,7 +335,7 @@ export const zh = {
promptConfiguration: '提示词配置', promptConfiguration: '提示词配置',
configurationDesc: '定义Agent的角色、能力和行为准则', configurationDesc: '定义Agent的角色、能力和行为准则',
aiPrompt: 'AI提示词', aiPrompt: 'AI提示词',
promptPlaceholder: '你是一个专业的AI助手你的职责是..', promptPlaceholder: '你是一个专业的AI助手你的职责是帮助用户解决问题。',
knowledgeBaseAssociation: '知识库关联', knowledgeBaseAssociation: '知识库关联',
associatedKnowledgeBase: '关联知识库', associatedKnowledgeBase: '关联知识库',
addKnowledgeBase: '添加知识库', addKnowledgeBase: '添加知识库',
@@ -390,12 +417,6 @@ export const zh = {
clusterName: '集群名称', clusterName: '集群名称',
clusterDescription: '集群描述', clusterDescription: '集群描述',
clusterDescriptionPlaceholder: '这是一个专门处理核心业务的Agent集群能够协作完成复杂的业务处理任务。', clusterDescriptionPlaceholder: '这是一个专门处理核心业务的Agent集群能够协作完成复杂的业务处理任务。',
toolCalling: '工具调用',
toolCallingDesc: '主控代理将子代理作为工具调用',
toolCallingFeature: '集中控制,适合结构化工作流',
handoffs: '交接',
handoffsDesc: '代理之间动态转移控制权',
handoffsFeature: '去中心化控制,适合复杂对话场景',
recommend: '推荐', recommend: '推荐',
advanced: '高级', advanced: '高级',
multiAgentArchitecture: '多代理架构模式', multiAgentArchitecture: '多代理架构模式',
@@ -422,7 +443,7 @@ export const zh = {
stateSharingStrategy: '状态共享策略', stateSharingStrategy: '状态共享策略',
intermediateResultProcessing: '中间结果处理', intermediateResultProcessing: '中间结果处理',
metadataTransfer: '元数据传输', metadataTransfer: '元数据传输',
knowledgeConfig: '知识库配置',
temperature: '温度', temperature: '温度',
temperature_desc: '温度参数,控制输出的随机性', temperature_desc: '温度参数,控制输出的随机性',
max_tokens: '最大令牌数', max_tokens: '最大令牌数',
@@ -450,9 +471,9 @@ export const zh = {
releasePreview: '发布预览', releasePreview: '发布预览',
globalConfig: '全局配置', globalConfig: '全局配置',
globalConfigDesc: '全局配置将应用于所有关联的知识库作为默认配置。单个知识库的配置将覆盖全局配置。', globalConfigDesc: '全局配置将应用于所有关联的知识库作为默认配置。单个知识库的配置将覆盖全局配置。',
rerankModel: '重排序模型', rerankModel: 'Reranker 模型',
rerankModelDesc: '激活后,搜索结果将被重新排序以提高相关性', rerankModelDesc: '激活后,搜索结果将被重新排序以提高相关性',
rearrangementModel: '重排序模型', rearrangementModel: 'Reranker 模型',
rearrangementModelDesc: '选择用于重新排序的模型', rearrangementModelDesc: '选择用于重新排序的模型',
reranker_top_k: 'Top K', reranker_top_k: 'Top K',
reranker_top_k_desc: '返回的文档片段数量范围从1到20', reranker_top_k_desc: '返回的文档片段数量范围从1到20',
@@ -481,6 +502,51 @@ export const zh = {
chooseKnowledge: '选择知识库', chooseKnowledge: '选择知识库',
active: '活跃', active: '活跃',
inactive: '不活跃', inactive: '不活跃',
endpointConfigurationSubTitle: '配置 API 访问地址和支持的 HTTP 方法',
apiKeys: 'API Keys 管理',
apiKeySubTitle: '管理 API 密钥,查看每个密钥的使用情况和流量统计',
addApiKey: '添加新 API Key',
apiKeyName: 'Key 名称',
apiKeyNamePlaceholder: '例如:生产环境、测试环境、开发环境',
apiKeyDescPlaceholder: '描述这个 Key 的用途',
apiKeyTotal: '总 Keys',
apiKeyRequestTotal: '总请求数',
qps: '平均 QPS',
qpsLimit: 'QPS 限制',
qpsLimitTip: '(每秒请求数)',
apiLimitConfig: '限流配置',
qpsLimitDesc: '限制此 Key 每秒最多可以发起的请求数',
dailyUsageLimit: '日调用量限制',
dailyUsageLimitDesc: '限制此 Key 每天最多可以发起的请求总数',
dailyUsageLimitUnit: '次/天',
apiKeyDeleteContent: '删除后将无法恢复使用此Key的应用将无法访问 API',
currentValue: '当前值',
qpsLimitUnit: '次/秒',
addVariable: '插入变量',
defineVariableName: '自定义变量名',
defineVariableNamePlaceholder: '输入变量名例如user_name',
defineVariableNameExtra: '格式:变量名会自动添加{{}}包裹',
you: '你',
ai: 'AI 助手',
promptChatPlaceholder: '描述你需要的提示词,例如:我需要一个客服助手',
promptChatEmpty: '目前没有对话内容',
promptEmpty: '在左侧描述您的用例,编排预览将在此处显示。',
master: '主管模式',
master_agent: '主管模式',
masterDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。',
handoffs: '协作模式',
handoffsDesc: '多个 Agent 平等协作,根据任务需求自主协调配合,适合需要灵活互动的复杂场景。',
masterConfig: '主管配置',
orchestrationMode: '任务分配策略',
conditional: '智能分配',
sequential: '顺序分配',
parallel: '并行分配',
aggregationStrategy: '结果汇总方式',
merge: '完整汇总',
vote: '关键信息提取',
priority: '结构化整合',
}, },
// 角色管理相关翻译 // 角色管理相关翻译
role: { role: {
@@ -730,8 +796,8 @@ export const zh = {
copy: '复制', copy: '复制',
copySuccess: '复制成功', copySuccess: '复制成功',
viewDetails: '查看详情', viewDetails: '查看详情',
enabled: '启用', enabled: '启用',
disabled: '停用', disabled: '停用',
updateWarning: '更新警告', updateWarning: '更新警告',
deleteWarning: '删除警告', deleteWarning: '删除警告',
deleteWarningContent: '确定要删除此{{content}}吗?', deleteWarningContent: '确定要删除此{{content}}吗?',
@@ -740,7 +806,9 @@ export const zh = {
add: '添加', add: '添加',
addOption: '添加选项', addOption: '添加选项',
viewDetail: '查看详情', viewDetail: '查看详情',
confirmRemoveFile: '确定要移除此文件吗?',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
deleteFailed: '删除失败',
foldUp: '收起', foldUp: '收起',
expanded: '展开', expanded: '展开',
clickUploadIcon: '点击上传图标', clickUploadIcon: '点击上传图标',
@@ -772,8 +840,8 @@ export const zh = {
publicApiCannotRefreshToken: '公共接口不能刷新token', publicApiCannotRefreshToken: '公共接口不能刷新token',
refreshTokenNotExist: '刷新token不存在', refreshTokenNotExist: '刷新token不存在',
reset: '重置', reset: '重置',
statusEnabled: '已启用', refresh: '刷新',
statusDisabled: '已禁用', return: '返回',
}, },
product: { product: {
applicationManagement: '应用管理', applicationManagement: '应用管理',
@@ -871,6 +939,17 @@ export const zh = {
saveConfig: '保存配置', saveConfig: '保存配置',
apiKeyName: 'API密钥名称', apiKeyName: 'API密钥名称',
llm: 'LLM',
chat: 'Chat',
embedding: 'Embedding',
rerank: 'Rerank',
openai: "Openai",
dashscope: "Dashscope",
ollama: "Ollama",
xinference: "Xinference",
gpustack: "Gpustack",
bedrock: "Bedrock"
}, },
timezones: { timezones: {
'Asia/Shanghai': '中国标准时间 (UTC+8)', 'Asia/Shanghai': '中国标准时间 (UTC+8)',
@@ -967,6 +1046,8 @@ export const zh = {
active: '活跃', active: '活跃',
inactive: '不活跃', inactive: '不活跃',
configurationName: '配置名称', configurationName: '配置名称',
emotionEngine: '情感引擎',
reflectionEngine: '反思引擎'
}, },
member: { member: {
username: '用户名', username: '用户名',
@@ -979,8 +1060,6 @@ export const zh = {
inviteToMember: '邀请成员', inviteToMember: '邀请成员',
member: '成员', member: '成员',
memberDesc: '只能使用应用,不能创建应用', memberDesc: '只能使用应用,不能创建应用',
admin: '管理员',
adminDesc: '可以创建应用和管理团队设置',
sendInvitation: '发送邀请', sendInvitation: '发送邀请',
manager: '管理员', manager: '管理员',
managerDesc: '可以创建应用和管理团队设置', managerDesc: '可以创建应用和管理团队设置',
@@ -1029,8 +1108,8 @@ export const zh = {
minimumRetentionDesc: '控制记忆随时间的遗忘速度,值越高时间越短', minimumRetentionDesc: '控制记忆随时间的遗忘速度,值越高时间越短',
forgettingRate: '记忆遗忘率 (λ_mem)', forgettingRate: '记忆遗忘率 (λ_mem)',
forgettingRateDesc: '控制记忆遗忘的速度,值越高遗忘越快', forgettingRateDesc: '控制记忆遗忘的速度,值越高遗忘越快',
offset: '最小保留度 (offset)', offset: '偏移量 (offset)',
offsetDesc: '控制记忆保留的最小保留阈值 遗忘这地方改个文字描述', offsetDesc: '最小保留度的偏移量',
CurrentValue: '当前值', CurrentValue: '当前值',
range: '范围', range: '范围',
forgettingEngineConfigParams: '遗忘引擎配置参数', forgettingEngineConfigParams: '遗忘引擎配置参数',
@@ -1062,7 +1141,7 @@ export const zh = {
drag: '拖放移动节点', drag: '拖放移动节点',
zoom: '滚动缩放视图', zoom: '滚动缩放视图',
memoryDetailEmpty: '请选择一个记忆节点', memoryDetailEmpty: '请选择一个记忆节点',
memoryDetailEmptyDesc: '点击上方视图中的任何节点查看详细信息', memoryDetailEmptyDesc: '点击左侧图表中的节点查看实体记忆详情',
totalNumOfMemories: '记忆总数', totalNumOfMemories: '记忆总数',
footprintCity: '足迹城市', footprintCity: '足迹城市',
@@ -1075,6 +1154,41 @@ export const zh = {
occupation: '职业', occupation: '职业',
memories: '记忆', memories: '记忆',
expanded: '展开', expanded: '展开',
description: '描述',
conversationMemory: '对话存储内容',
editConfig: '编辑模型',
chooseModel: '选择模型',
nodeStatistics: '记忆分类',
total: '总计',
Chunk: '长期记忆',
MemorySummary: '情景记忆',
Statement: '情绪记忆',
ExtractedEntity: '短期记忆',
PERCEPTUAL_MEMORY: '感知记忆',
WORKING_MEMORY: '工作记忆',
SHORT_TERM_MEMORY: '短期记忆',
LONG_TERM_MEMORY: '长期记忆',
EXPLICIT_MEMORY: '显性记忆',
IMPLICIT_MEMORY: '隐性记忆',
EMOTIONAL_MEMORY: '情绪记忆',
EPISODIC_MEMORY: '情景记忆',
endUserProfile: '核心档案',
editEndUserProfile: '编辑',
other_name: '姓名',
position: '职位',
department: '部门',
contact: '联系方式',
phone: '电话',
hire_date: '入职时间',
memoryContent: '记忆内容',
created_at: '创建时间',
updated_at: '最后更新时间',
fullScreen: '全屏'
}, },
space: { space: {
createSpace: '创建空间', createSpace: '创建空间',
@@ -1086,7 +1200,7 @@ export const zh = {
storageType: '存储类型', storageType: '存储类型',
rag: 'RAG存储', rag: 'RAG存储',
ragDesc: '基于向量检索,适合文档问答和语义搜索', ragDesc: '基于向量检索,适合文档问答和语义搜索',
neo4j: '图存储', neo4j: '图存储',
neo4jDesc: '基于知识图谱,适合关系推理和路径查询', neo4jDesc: '基于知识图谱,适合关系推理和路径查询',
llmModel: 'LLM 模型', llmModel: 'LLM 模型',
embeddingModel: 'Embedding 模型', embeddingModel: 'Embedding 模型',
@@ -1127,7 +1241,6 @@ export const zh = {
exampleMemoryExtractionResults: '示例记忆提取结果', exampleMemoryExtractionResults: '示例记忆提取结果',
exampleMemoryExtractionResultsSubTitle: '(来自技术会议)', exampleMemoryExtractionResultsSubTitle: '(来自技术会议)',
warning: '当您修改左侧的配置项时,提取结论将在此处实时更新',
extractTheNumberOfEntities: '提取实体数量', extractTheNumberOfEntities: '提取实体数量',
extractTheNumberOfEntitiesDesc: '去重后合并:{{num}}(精确:{{exact}},模糊:{{fuzzy}}LLM{{llm}}', extractTheNumberOfEntitiesDesc: '去重后合并:{{num}}(精确:{{exact}},模糊:{{fuzzy}}LLM{{llm}}',
@@ -1166,7 +1279,7 @@ export const zh = {
intelligentSemanticPruningSceneDesc: '选择智能语义修剪场景education、online_service、outbound。', intelligentSemanticPruningSceneDesc: '选择智能语义修剪场景education、online_service、outbound。',
intelligentSemanticPruningThreshold: '智能语义修剪阈值', intelligentSemanticPruningThreshold: '智能语义修剪阈值',
intelligentSemanticPruningThresholdDesc: '设置智能语义修剪阈值0-0.9)。', intelligentSemanticPruningThresholdDesc: '设置智能语义修剪阈值0-0.9)。',
selfReflexionEngine: '自我反思引擎', reflectionEngine: '自我反思引擎',
selfReflexionEngineSubTitle: '通过反思和精炼,将情节记忆转化为更深层的语义记忆。', selfReflexionEngineSubTitle: '通过反思和精炼,将情节记忆转化为更深层的语义记忆。',
enableSelfReflexion: '启用自我反思', enableSelfReflexion: '启用自我反思',
iterationPeriod: '迭代周期', iterationPeriod: '迭代周期',
@@ -1217,7 +1330,27 @@ export const zh = {
记忆熊:秦国统一的原因包括:商鞅变法彻底,建立法律、户籍和军功爵制度,提升国家组织能力;旧贵族势力弱,中央集权程度高;关中地理优越,资源丰富且易守难攻;从孝公到秦始皇政策连续性强。 记忆熊:秦国统一的原因包括:商鞅变法彻底,建立法律、户籍和军功爵制度,提升国家组织能力;旧贵族势力弱,中央集权程度高;关中地理优越,资源丰富且易守难攻;从孝公到秦始皇政策连续性强。
学生:那我换到唐朝史:安史之乱后,中央已开始整顿,为何藩镇割据反而加剧? 学生:那我换到唐朝史:安史之乱后,中央已开始整顿,为何藩镇割据反而加剧?
记忆熊:安史之乱后藩镇割据加剧的原因包括:节度使掌握募兵权、财政调度权与军事指挥权,形成地方军阀;中央财政因均田制瓦解和租庸调失效而衰退,难以支撑军队,导致地方军事力量依附节度使;募兵制使士兵效忠个人而非国家;宦官掌控禁军,文官集团失势,中央制衡能力削弱。` 记忆熊:安史之乱后藩镇割据加剧的原因包括:节度使掌握募兵权、财政调度权与军事指挥权,形成地方军阀;中央财政因均田制瓦解和租庸调失效而衰退,难以支撑军队,导致地方军事力量依附节度使;募兵制使士兵效忠个人而非国家;宦官掌控禁军,文官集团失势,中央制衡能力削弱。`,
warning: '当您修改左侧的配置项后,点击【调试】,提取结论将在此处实时更新',
processing: '配置已更新,正在重新萃取示例记忆...',
success: '记忆萃取完成!',
overallProgress: '整体进度',
text_preprocessing: '文本预处理',
fragment: '片段',
knowledge_extraction: '知识抽取',
creating_nodes_edges: '创建实体关系',
deduplication: '去重消歧',
status: {
pending: '等待中',
processing: '处理中',
completed: '已完成',
failed: '失败'
},
time: '耗时: ',
text_preprocessing_desc: '文本切分为{{count}}个语义片段',
knowledge_extraction_desc: '知识抽取完成,共识别{{entities}}个实体,{{statements}}个句子, {{temporal_ranges_count}}个时间提取, {{triplets}}个三元组',
creating_nodes_edges_desc: '实体关系创建完成,共{{num}}条关系',
deduplication_desc: '去重消歧完成,最终{{count}}个唯一实体'
}, },
memoryConversation: { memoryConversation: {
searchPlaceholder: '输入用户ID...', searchPlaceholder: '输入用户ID...',
@@ -1242,8 +1375,8 @@ export const zh = {
startANewConversation: '开始新对话', startANewConversation: '开始新对话',
normalReply: '正常回复', normalReply: '正常回复',
quickReply: '快速回复', quickReply: '快速回复',
memoryConversationAnalysisEmpty: '当前没有可用的对话分析内容', web_search: '联网搜索',
memoryConversationAnalysisEmptySubTitle: '输入用户ID后单击“发送”查看对话记忆' memory: '记忆',
}, },
login: { login: {
title: '红熊记忆科学', title: '红熊记忆科学',
@@ -1314,28 +1447,599 @@ export const zh = {
websocketDemoCard: 'WebSocket 演示', websocketDemoCard: 'WebSocket 演示',
sseDemoCard: 'SSE演示' sseDemoCard: 'SSE演示'
}, },
workflow: {
title: '工作流编辑器',
description: '拖拽节点创建连接,构建您的工作流程。点击节点可进行配置。',
addNode: '添加节点',
deleteNode: '删除选中',
saveWorkflow: '保存工作流',
startNode: '触发节点',
conditionNode: '条件判断',
actionNode: '执行动作',
endNode: '结束节点',
newNode: '新节点',
node: '节点',
nodesCreated: '已创建节点',
loadingNodes: '正在加载节点 {{progress}}%',
loadingFailed: '加载节点失败',
create5kNodes: '创建5000节点',
create10kNodes: '创建10000节点'
},
notFound: { notFound: {
title: '页面未找到', title: '页面未找到',
description: '请求的页面不存在。', description: '请求的页面不存在。',
backToHome: '返回首页' backToHome: '返回首页'
},
apiKey: {
name: '项目名称',
createApiKey: '创建API Key',
updateApiKey: '编辑API Key',
id: 'ID',
created_at: '创建时间',
description: '描述',
memoryEngine: '记忆引擎',
knowledgeBase: '知识库',
advancedSettings: '高级设置',
expires_at: '过期时间',
apiKey: 'API Key',
status: '状态',
createdAt: '创建时间',
expiresAt: '过期时间',
requestsPerMinute: '次/分钟',
viewDetail: '查看详情',
disable: '禁用',
enable: '启用',
baseInfo: '基础信息',
permissionInfo: '授权信息',
is_expired: '状态',
active: '活跃',
inactive: '过期'
},
tool: {
mcp: 'MCP 服务',
inner: '内置工具',
custom: '自定义工具',
mcpSearchPlaceholder: '搜索MCP服务...',
innerSearchPlaceholder: '搜索工具...',
customSearchPlaceholder: '搜索自定义工具...',
addService: '添加MCP服务',
editService: '编辑MCP服务',
addServiceSuccess: '服务添加成功',
server_url: '服务地址',
last_health_check: '最后连接',
responseTime: '响应时间',
status: {
available: '可用',
unconfigured: '未配置',
configured_disabled: '已配置未启用',
error: '链接异常'
},
available_desc: 'API 已配置并启用',
unconfigured_desc: '需要配置 API Key',
configured_disabled_desc: 'API 已配置但未启用',
error_desc: 'API 已配置但链接异常',
testConnectionSuccess: '测试连接成功',
serviceEndpoint: '服务端点 URL',
serviceEndpointPlaceholder: '服务端点的 URL',
serviceEndpointExtra: 'MCP服务的完整访问地址',
nameAndIcon: '名称和图标',
namePlaceholder: '命名你的 MCP 服务',
serverIdentifier: '服务器标识符',
serverIdentifierPlaceholder: '服务器唯一标识,例如 my-mcp-server',
serverIdentifierLength: '最多 24 个字符',
serverIdentifierPattern: '支持小写字母、数字、下划线和连字符',
description: '描述信息',
auth: '认证',
requestHeader: '请求头',
config: '配置',
auth_type: '认证方式',
none: '无需认证',
api_key: 'API Key',
basic_auth: 'Basic Auth',
bearer_token: 'Bearer Token',
username: '用户名',
password: '密码',
key_name: 'Key Name',
requestHeaderDesc: '发送到 MCP 服务器的额外 HTTP 请求头',
addRequestHeader: '添加请求头',
editRequestHeader: '编辑请求头',
requestHeaderName: '请求头名称',
requestHeaderValue: '请求头值',
timeout: '超时时间(秒)',
sseReadTimeout: 'SSE 读取超时时间(秒)',
saveAndTest: '保存并测试',
timeFormat: '时间格式化',
timeZoneConversion: '时区转换',
timestampConversion: '时间戳转换',
timeCalculation: '时间计算',
time_desc: '日期时间处理',
DateTimeTool_features: '提供时间格式转换、时区转换、时间戳计算等功能',
currentTime: '当前时间',
timestamp: '时间戳',
localTime: '本地时间',
utcTime: 'UTC时间',
secondsTimestamp: '时间戳(秒)',
millisecondsTimestamp: '时间戳(毫秒)',
enterTimestamp: '输入时间戳',
conversion: '转换',
conversionResult: '转换结果',
chooseFormatType: '选择格式',
JsonTool_desc: '数据格式转换',
JsonTool_features: 'JSON格式化、压缩、验证和转换功能',
jsonFormat: 'JSON格式化',
jsonGzip: 'JSON压缩',
jsonCheck: 'JSON验证',
jsonConversion: '格式转换',
jsonEg: '示例JSON',
enterJson: '输入JSON',
jsonPlaceholder: '输入JSON数据例如{"name": "测试", "value": 123}',
clear: '清空',
parse: '粘贴',
format: '格式化',
minify: '压缩',
validate: '验证',
convert: '转义',
outputResult: '输出结果',
validJosn: 'JSON格式正确验证通过',
BaiduSearchTool_desc: '搜索引擎服务',
BaiduSearchTool_features: '集成百度搜索API提供网页搜索、新闻搜索等功能',
webSearch: '网页搜索',
newsSearch: '新闻搜索',
imageSearch: '图片搜索',
realTimeResults: '实时结果',
configStatus: '配置状态',
hasApiKey: 'API 已配置并启用',
needApiKey: '需要配置API Key',
MinerUTool_desc: 'PDF解析工具',
MinerUTool_features: '高精度PDF文档解析工具支持文字、表格、图片提取',
pdfParser: 'PDF解析',
tableExtraction: '表格提取',
imageRecognition: '图片识别',
textExtraction: '文本提取',
TextInTool_desc: 'OCR文字识别',
TextInTool_features: '智能OCR文字识别服务支持多语言、手写体识别',
universalOCR: '通用OCR',
handwritingRecognition: '手写识别',
multilingualSupport: '多语言支持',
highPrecisionRecognition: '高精度识别',
configDesc: '配置说明',
BaiduSearchTool_config_desc: '使用百度搜索API需要先在百度开放平台申请API Key和Secret Key。',
MinerUTool_config_desc: 'MinerU是高精度PDF文档解析工具需要API Key才能使用。',
TextInTool_config_desc: 'TextIn提供智能OCR文字识别服务支持多语言识别。',
link: '申请地址',
BaiduSearchTool_api_key_desc: '从百度开放平台获取的API Key',
MinerUTool_api_key_desc: '从MinerU平台获取的API Key',
secret_key: 'Secret Key',
BaiduSearchTool_secret_key_desc: '从百度开放平台获取的Secret Key',
TextInTool_secret_key_desc: '从TextIn平台获取的Secret Key',
type: '搜索类型',
pagesize: '每页结果数',
pagesize_desc: '每次搜索返回的结果数量({{count1}}-{{count2}}',
BaiduSearchTool_enable: '启用百度搜索',
BaiduSearchTool_safe_enable: '启用安全搜索',
BaiduSearchTool_safe_enable_desc: '过滤不适宜内容',
api_address: 'API地址',
MinerUTool_api_address_desc: '默认使用官方API地址如有私有部署可修改',
TextInTool_api_address_desc: '默认使用官方API地址',
parsing_mode: '解析模式',
auto_recognition: '自动识别',
pure_text_mode: '纯文本模式',
table_priority: '表格优先',
image_priority: '图片优先',
MinerUTool_timeout_desc: 'PDF解析超时时间10-300秒',
MinerUTool_enable: '启用MinerU',
MinerUTool_extract_images_enable: '提取图片',
MinerUTool_extract_images_enable_desc: '是否提取PDF中的图片内容',
app_id: 'APP ID',
TextInTool_app_id_desc: '从TextIn平台获取的App ID',
language_identification: '识别语言',
automatic_detection: '自动检测',
simplified_chinese: '简体中文',
traditional_chinese: '繁体中文',
english: '英文',
japanese: '日文',
korean_language: '韩文',
pattern_recognition: '识别模式',
universal_identification: '通用识别',
high_precision_identification: '高精度识别',
handwriting_recognition: '手写体识别',
formula_recognition: '公式识别',
TextInTool_enable: '启用TextIn',
return_text_position_enable: '返回文本位置信息',
return_text_position_enable_desc: '是否返回识别文字的坐标位置',
addCustom: '添加自定义工具',
editCustom: '编辑自定义工具',
schema: 'Schema',
schemaPlaceholder: '在此处输入您的 OpenAPI schema',
authentication: '鉴权方式',
tags: '标签',
created_at: '创建时间',
headerName: 'Header 名称',
null: '无',
tagDesc: '多个标签用逗号分隔',
availableTools: '可用工具',
name: '名称',
desc: '描述',
method: '方法',
path: '路径',
viewDetail: '查看详情',
textLink: '测试连接',
noResult: '处理结果将显示在这里'
},
workflow: {
coreNode: '核心节点',
start: '开始Start',
end: '结束End',
answer: '回复Answer',
aiAndCognitiveProcessing: 'AI与认知处理',
llm: '大语言模型 (LLM)',
model_selection: '多模型选择',
model_voting: '多模型投票',
'knowledge-retrieval': '知识检索 (RAG)',
classification: '智能分类',
'parameter-extractor': '参数提取',
flowControl: '流程控制',
'if-else': '条件分支',
iteration: '迭代 (Iteration)',
loop: '循环 (Loop)',
parallel: '并行执行',
'var-aggregator': '变量聚合器',
externalInteraction: '外部交互',
http_request: 'HTTP请求',
tools: '工具 (Tools)',
code_execution: '代码执行',
template_rendering: '模板渲染',
cognitiveUpgrading: '认知升级(创新)',
task_planning: '任务规划',
reasoning_control: '推理控制',
self_reflection: '自我反思',
memory_enhancement: '记忆增强',
agentCollaborationNode: 'Agent 协作节点',
agent_scheduling: 'Agent 调度',
agent_collaboration: 'Agent 协同',
agent_arbitration: 'Agent 仲裁',
safetyAndCompliance: '安全与合规',
sensitive_detection: '敏感识别',
output_audit: '输出审计',
evolutionAndGovernance: '演化与治理',
self_optimization: '自我优化',
process_evolution: '流程演化',
clickToConfigure: '点击配置节点参数',
nodeProperties: '节点属性',
empty: "Emmm…盒子是空的这里什么都没有",
nodeName: '节点名称',
config: {
llm: {
model_id: '模型',
temperature: '温度',
max_tokens: '最大令牌数',
},
start: {
variables: '输入字段',
string: '文本',
number: '数字',
boolean: '复选框',
array: '下拉选项',
object: '对象',
addVariable: '添加变量',
editVariable: '编辑变量',
variableType: '变量类型',
variableName: '变量名称',
invalidVariableName: '变量名只能以英文字母开头,包含英文字母、数字和下划线',
description: '显示名称',
default: '默认值',
required: '必填',
max_length: '最大长度',
defaultChecked: '选中',
notDefaultChecked: '不选中',
options: '选项',
},
end: {
output: '回复'
},
'knowledge-retrieval': {
query: '查询变量',
knowledge_retrieval: '知识库',
recallConfig: '召回测试',
},
'parameter-extractor': {
model_id: '模型',
text: '输入变量',
params: '提取参数',
prompt: '指令',
addParam: '添加提取参数',
editParam: '编辑提取参数',
name: '名称',
invalidParamName: '提取参数名只能以英文字母开头,包含英文字母、数字和下划线',
type: '类型',
desc: '描述',
required: '必填',
'string': 'String',
'number': 'Number',
'boolean': 'Boolean',
'array[string]': 'Array[String]',
'array[number]': 'Array[Number]',
'array[boolean]': 'Array[Boolean]',
'array[object]': 'Array[Object]',
},
'var-aggregator': {
group: '聚合分组',
invalidVariableName: '变量名只能以英文字母开头,包含英文字母、数字和下划线',
addGroup: '添加分组',
variable: '变量赋值'
},
'if-else': {
"empty": '为空',
"not_empty": '不为空',
"contains": '包含',
"not_contains": '不包含',
"startwith": '开始是',
"endwith": '结束是',
"eq": '==',
"ne": '!=',
"lt": '<',
"le": '<=',
"gt": '>',
"ge": '>='
} }
}, },
clear: '清空',
run: '运行',
save: '保存',
export: '导出',
variableConfig: '变量配置',
variableRequired: '必填',
addMessage: '添加消息',
answerDesc: '回复'
},
emotionEngine: {
emotionEngineConfig: '情感引擎配置',
emotion_enabled: '启用情感引擎',
emotion_enabled_desc: '自动分析对话中的情感倾向',
emotion_model_id: '情感分析模型',
emotion_model_id_desc: '不同模型在准确度和速度上有所差异',
emotion_extract_keywords: '情绪关键词提取',
emotion_extract_keywords_subTitle: '自动提取对话中的情绪相关关键词',
emotion_extract_keywords_desc: '提取如"开心"、"失望"、"期待"等情绪关键词,帮助更好地理解用户情绪',
emotion_min_intensity: '置信度阈值',
emotion_min_intensity_desc: '置信度越高,识别越准确,但可能遗漏部分信息',
emotion_enable_subject: '情绪主体分类 ',
emotion_enable_subject_subTitle: '识别情绪归属(自己/他人/物体)',
emotion_enable_subject_desc: '区分情绪主体: self (我感到开心)、other (他很生气)、object (这个产品很棒)',
currentValue: '当前值',
emotion_min_intensity_description: '置信度阈值说明',
question: '什么是置信度阈值?',
answer: '置信度阈值是情感引擎判断情绪时的"确定程度"标准。当 AI 分析出的情感置信度低于设定阈值时,该情感将不会被记录。',
differentTitle: '不同阈值的影响',
advantage: '优点',
shortcoming: '缺点',
scene: '适用场景',
low_title: '低阈值 (0.0 - 0.4)',
low_tag: '灵敏',
low_advantage: '能捕捉到更多细微的情感变化,不会遗漏潜在的情绪信号',
low_shortcoming: '可能产生误判,将中性或不明确的表达识别为特定情感',
low_scene: '需要全面了解用户情绪波动,对准确度要求不高的场景',
middle_title: '中阈值 (0.5 - 0.7)',
middle_tag: '推荐',
middle_advantage: '平衡准确度和覆盖率,既能识别明显情感,也不会过度敏感',
middle_shortcoming: '可能遗漏一些不太明显的情感表达',
middle_scene: '大多数日常对话场景,适合一般性情感分析需求',
high_title: '高阈值 (0.8 - 1.0)',
high_tag: '精准',
high_advantage: '只记录非常明确的情感表达,准确度极高,误判率低',
high_shortcoming: '会遗漏大量不够明显的情感信息,数据覆盖率低',
high_scene: '对准确度要求极高的场景,如情感危机预警、重要决策参考',
configSuggest: '配置建议',
first: '初次使用',
first_desc: '建议从中等阈值0.6-0.7)开始,观察一段时间后根据实际效果调整',
customer_service: '客服场景',
customer_service_desc: '建议使用较低阈值0.4-0.6),及时捕捉用户的不满情绪',
data_analysis: '数据分析',
data_analysis_desc: '建议使用中等阈值0.6-0.7),保证数据质量的同时有足够样本量',
risk_warning: '风险预警',
risk_warning_desc: '建议使用较高阈值0.7-0.8),确保预警的准确性',
actual_case: '实际案例',
user_input: '用户输入',
user_input_message: '"这个功能还行吧,不过有点小问题"',
neutral_emotion: '中性情感',
neutral_emotion_tag: '所有阈值都会记录',
minor_dissatisfaction: '轻微不满',
minor_dissatisfaction_tag: '仅低/中阈值会记录',
expect_improvement: '期待改进',
expect_improvement_tag: '仅低阈值会记录',
confidence: '置信度'
},
statementDetail: {
wordCloud: '情感分布分析',
pieces: '条',
emotionTags: '高频情绪关键词',
joy: '喜悦',
anger: '愤怒',
sadness: '悲伤',
fear: '恐惧',
neutral: '中性',
surprise: '惊讶',
health: '情绪健康指数',
positivity_rate: '积极率',
stability: '稳定性',
resilience: '恢复力',
suggestions: '个性化建议',
},
reflectionEngine: {
reflectionEngineConfig: '反思引擎配置',
reflection_enabled: '启用反思引擎',
reflection_enabled_desc: '将情节记忆转化为语义记忆,形成长期认知',
reflection_model_id: '反思模型',
reflection_model_id_desc: '不同模型在准确度和速度上有所差异',
reflection_period_in_hours: '迭代周期',
reflection_period_in_hours_desc: '决定系统多久进行一次记忆反思和提炼',
reflexion_range: '反思范围',
partial: '部分反思 (仅新增记忆)',
all: '全部反思 (所有历史记忆)',
reflexion_range_desc: '',
baseline: '反思基线',
baseline_desc: '',
TIME: '基于时间(时序关系)',
FACT: '基于事实(知识点)',
HYBRID: '事实+时间(综合维度)',
quality_assessment: '启用质量评估',
quality_assessment_desc: '自动评估记忆的准确性、完整性和时效性',
memory_verify: '启用记忆审核',
memory_verify_desc: '检测敏感信息并过滤违规内容',
oneHour: '每1个小时',
threeHours: '每3个小时',
sixHours: '每6个小时',
twelveHours: '每12个小时',
daily: '每天',
run: '运行调试',
example: '原始数据',
exampleText: '我是 2023 年春天去北京工作的后来基本一直都在北京上班也没怎么换过城市。不过后来公司调整2024 年上半年我被调到上海待了差不多半年,那段时间每天都是在上海办公室打卡。当时入职资料用的还是我之前的身份信息,身份证号是 11010119950308123X银行卡是 6222023847595898这些一直没变。对了其实我 从 2023 年开始就一直在北京生活,从来没有长期离开过北京,上海那段更多算是远程配合',
runTitle: '反思试运行',
status: '状态',
message: '消息',
conflictDetection: '冲突检测',
reason: '冲突原因',
solution: '解决方案',
qualityAssessment: '质量评估',
qualityAssessmentObj: {
score: '质量评分',
summary: '评估摘要',
},
privacyAudit: '隐私审核',
privacyAuditObj: {
true: '是',
false: '否',
has_privacy: '包含隐私信息',
privacy_types: '隐私类型',
summary: '审核摘要',
}
},
pricing: {
title: '灵活定价,满足各类团队需求',
desc: '透明的定价策略,助您轻松找到符合预算的方案',
solution: '方案定位',
targetAudience: '目标人群',
orderPayment: '订单支付',
orderPaymentDesc: '请确认订单信息并完成支付',
orderInformation: '订单信息',
paymentMethod: '支付方式',
paymentVoucher: '支付凭证',
corporateTransfer: '企业转账',
corporateTransferDesc: '通过企业对公账户转账支付',
receivingEntity: '收款单位',
bankName: '开户银行',
bankAccountNumber: '银行账号',
pay_txn_id: '支付流水号',
payer: '付款单位/个人',
transferDate: '转账日期',
payerAccount: '付款账号',
remark: '备注信息',
remarkPlaceholder: '如有其他说明,请在此填写(选填)',
confirm: '确认支付',
submitting: '提交中...',
payInfo: '提交后我们将在1-3个工作日内核实您的付款信息',
paySuccess: '核实通过后,系统将自动开通您的套餐服务',
pay_txn_idPlaceholder: '请输入支付流水号',
pay_txn_idDesc: '请填写转账时的交易流水号,以便我们快速确认您的付款',
payerPlaceholder: '请输入付款单位或个人姓名',
redirectCountdown: '秒后跳转后台...',
confirmRedirect: '支付凭证已提交成功!',
confirmRedirectContent: '我们将在1-3个工作日内核实您的付款信息。是否立即跳转到记忆熊开始体验',
stayCurrentPage: '留在当前页',
goBack: '跳转后台',
payeeInformation: '收款信息',
creationTime: '创建时间',
comboName: '套餐名称',
spAndTa: '方案定位与目标人群',
versionInformation: '版本信息',
orderCycle: '订购周期',
orderAmount: '订单金额',
personal: {
type: '个人版',
label: '当前安装包',
typeDesc: '个人玩家版本',
solution: '个人的第二大脑最多可存储2000条记忆。',
targetAudience: '个人用户、学生及初次使用者',
priceDesc: '/永久免费',
supportServices: '社区论坛 + 邮件支持'
},
team: {
type: '团队版',
label: '小型团队',
typeDesc: '小型团队版本',
solution: '让每一条业务记录瞬间成为团队的第二大脑。',
targetAudience: '初创团队、小微型企业、小型项目',
priceDesc: '/月',
supportServices: '标准客服支持'
},
biz: {
type: '企业增长版',
label: '最受欢迎',
typeDesc: '企业增长版本',
solution: '让每一条业务记录瞬间成为团队的第二大脑。',
targetAudience: '初创团队、小微型企业、小型项目',
priceDesc: '/月/工作区',
supportServices: '优先客服支持'
},
commerce: {
type: '商业OEM版',
label: '商业OEM',
typeDesc: '商业OEM版本',
solution: '将强大的记忆能力无缝嵌入您的SaaS产品中。',
targetAudience: '需要集成解决方案的大型企业、SaaS厂商及系统集成商。',
priceDesc: '本地化部署',
supportServices: '标准客服支持',
flexibleDeployment: '支持在数据中心进行本地化部署',
reliableGuarantee: '99.9% SLA保障'
},
mostPopular: '最受欢迎',
startedBtn: '立即开始',
choosePlanBtn: '选择方案',
contactBtn: '联系我们',
memoryCapacity: '记忆容量:',
entries: '条',
intelligentSearchFrequency: '智能搜索次数:',
timesMonth: '次/月',
supportServices: '支持服务:',
flexibleDeployment: '灵活部署:',
reliableGuarantee: '可靠保障:',
alertTitle: '知识产权授权提醒',
alertContent: '请注意使用某些AI模型如GPT-4、Claude等可能涉及第三方API调用费用这些费用不包含在Memory Bear平台订阅费中。您需要单独向模型提供商支付相关费用。Memory Bear仅收取平台管理和服务费不承担第三方API的使用费用。',
currentAccountType: '当前账户类型',
validUntil: '有效期至',
orderHistory: '订单记录',
order_no: '订单号',
product_type: '套餐名称',
payable_amount: '订单金额',
status: '订单状态',
pay_time: '支付时间',
viewDetail: '查看详情',
PENDING: '待审核',
APPROVED: '审核通过',
REJECTED: '审核不通过',
allStatus: '全部状态',
allTime: '全部时间',
today: '今天',
week: '最近一周',
month: '最近一月',
threeMonth: '最近三个月',
year: '最近一年',
searchPlaceholder: '搜索订单号',
allType: '全部套餐',
orderDetail: '订单详情',
orderInfo: '订单信息',
orderPayInfo: '支付信息',
create_time: '创建时间',
},
},
} }

View File

@@ -38,6 +38,7 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
Home: lazy(() => import('@/views/Home')), Home: lazy(() => import('@/views/Home')),
UserMemory: lazy(() => import('@/views/UserMemory')), UserMemory: lazy(() => import('@/views/UserMemory')),
UserMemoryDetail: lazy(() => import('@/views/UserMemoryDetail')), UserMemoryDetail: lazy(() => import('@/views/UserMemoryDetail')),
Neo4jUserMemoryDetail: lazy(() => import('@/views/UserMemoryDetail/Neo4j')),
MemberManagement: lazy(() => import('@/views/MemberManagement')), MemberManagement: lazy(() => import('@/views/MemberManagement')),
MemoryManagement: lazy(() => import('@/views/MemoryManagement')), MemoryManagement: lazy(() => import('@/views/MemoryManagement')),
ForgettingEngine: lazy(() => import('@/views/ForgettingEngine')), ForgettingEngine: lazy(() => import('@/views/ForgettingEngine')),
@@ -54,10 +55,18 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
UserManagement: lazy(() => import('@/views/UserManagement')), UserManagement: lazy(() => import('@/views/UserManagement')),
ModelManagement: lazy(() => import('@/views/ModelManagement')), ModelManagement: lazy(() => import('@/views/ModelManagement')),
SpaceManagement: lazy(() => import('@/views/SpaceManagement')), SpaceManagement: lazy(() => import('@/views/SpaceManagement')),
ApiKeyManagement: lazy(() => import('@/views/ApiKeyManagement')),
EmotionEngine: lazy(() => import('@/views/EmotionEngine')),
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')),
OrderPayment: lazy(() => import('@/views/OrderPayment')),
OrderHistory: lazy(() => import('@/views/OrderHistory')),
Pricing: lazy(() => import('@/views/Pricing')),
ToolManagement: lazy(() => import('@/views/ToolManagement')),
Login: lazy(() => import('@/views/Login')), Login: lazy(() => import('@/views/Login')),
InviteRegister: lazy(() => import('@/views/InviteRegister')), InviteRegister: lazy(() => import('@/views/InviteRegister')),
NoPermission: lazy(() => import('@/views/NoPermission')), NoPermission: lazy(() => import('@/views/NoPermission')),
NotFound: lazy(() => import('@/views/NotFound')), NotFound: lazy(() => import('@/views/NotFound'))
}; };
// 检查并报告缺失的组件 // 检查并报告缺失的组件

View File

@@ -5,6 +5,10 @@
{ "path": "/user-management", "element": "UserManagement" }, { "path": "/user-management", "element": "UserManagement" },
{ "path": "/model", "element": "ModelManagement" }, { "path": "/model", "element": "ModelManagement" },
{ "path": "/space", "element": "SpaceManagement" }, { "path": "/space", "element": "SpaceManagement" },
{ "path": "/tool", "element": "ToolManagement" },
{ "path": "/pricing", "element": "Pricing" },
{ "path": "/order-pay", "element": "OrderPayment" },
{ "path": "/orders", "element": "OrderHistory" },
{ "path": "/no-permission", "element": "NoPermission" } { "path": "/no-permission", "element": "NoPermission" }
] ]
}, },
@@ -25,6 +29,10 @@
{ "path": "/knowledge-base/:knowledgeBaseId/share", "element": "Share" }, { "path": "/knowledge-base/:knowledgeBaseId/share", "element": "Share" },
{ "path": "/knowledge-base/:knowledgeBaseId/create-dataset", "element": "CreateDataset" }, { "path": "/knowledge-base/:knowledgeBaseId/create-dataset", "element": "CreateDataset" },
{ "path": "/knowledge-base/:knowledgeBaseId/DocumentDetails", "element": "DocumentDetails" }, { "path": "/knowledge-base/:knowledgeBaseId/DocumentDetails", "element": "DocumentDetails" },
{ "path": "/api-key", "element": "ApiKeyManagement" },
{ "path": "/emotion-engine/:id", "element": "EmotionEngine" },
{ "path": "/reflection-engine/:id", "element": "SelfReflectionEngine" },
{ "path": "/statement/:id", "element": "StatementDetail" },
{ "path": "/no-permission", "element": "NoPermission" }, { "path": "/no-permission", "element": "NoPermission" },
{ "path": "/*", "element": "NotFound" } { "path": "/*", "element": "NotFound" }
] ]
@@ -33,7 +41,8 @@
"element": "BasicLayout", "element": "BasicLayout",
"children": [ "children": [
{ "path": "/application/config/:id", "element": "ApplicationConfig" }, { "path": "/application/config/:id", "element": "ApplicationConfig" },
{ "path": "/conversation/:token", "element": "Conversation" } { "path": "/conversation/:token", "element": "Conversation" },
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }
] ]
}, },
{ {

View File

@@ -26,6 +26,59 @@
"sort": 0, "sort": 0,
"subs": [] "subs": []
}, },
{
"id": 7,
"parent": 0,
"code": "tool",
"label": "工具管理",
"i18nKey": "menu.toolManagement",
"path": "/tool",
"enable": true,
"display": true,
"level": 1,
"sort": 0,
"subs": []
},
{
"id": 6,
"parent": 0,
"code": "pricing",
"label": "收费管理",
"i18nKey": "menu.pricing",
"path": "/pricing",
"enable": true,
"display": true,
"level": 1,
"sort": 0,
"subs": [
{
"id": 61,
"parent": 6,
"code": "order",
"label": "订单支付",
"i18nKey": "menu.orderPayment",
"path": "/order-pay",
"enable": true,
"display": false,
"level": 1,
"sort": 0,
"subs": []
},
{
"id": 62,
"parent": 6,
"code": "orderHistory",
"label": "订单记录",
"i18nKey": "menu.orderHistory",
"path": "/orders",
"enable": true,
"display": false,
"level": 1,
"sort": 0,
"subs": []
}
]
},
{ {
"id": 3, "id": 3,
"parent": 0, "parent": 0,
@@ -183,6 +236,32 @@
"level": 1, "level": 1,
"sort": 0, "sort": 0,
"subs": null "subs": null
},
{
"id": 72,
"parent": 7,
"code": "emotionEngine",
"label": "情感引擎",
"i18nKey": "menu.emotionEngine",
"path": "/emotion-engine/:id",
"enable": true,
"display": false,
"level": 1,
"sort": 0,
"subs": null
},
{
"id": 72,
"parent": 7,
"code": "selfReflectionEngine",
"label": "反思引擎",
"i18nKey": "menu.selfReflectionEngine",
"path": "/reflection-engine/:id",
"enable": true,
"display": false,
"level": 1,
"sort": 0,
"subs": null
} }
] ]
}, },
@@ -205,15 +284,41 @@
"code": "userMemoryDetail", "code": "userMemoryDetail",
"label": "记忆详情", "label": "记忆详情",
"i18nKey": "menu.userMemoryDetail", "i18nKey": "menu.userMemoryDetail",
"path": "/user-memory/:id", "path": "/user-memory/neo4j/:id",
"enable": true, "enable": true,
"display": false, "display": false,
"level": 2, "level": 2,
"sort": 0, "sort": 0,
"subs": [
{
"id": 811,
"parent": 81,
"code": "statementDetail",
"label": "记忆详情",
"i18nKey": "menu.statementDetail",
"path": "/statement/:id",
"enable": true,
"display": false,
"level": 3,
"sort": 0,
"subs": null "subs": null
} }
] ]
}, },
{
"id": 81,
"parent": 8,
"code": "userMemoryDetail",
"label": "记忆详情",
"i18nKey": "menu.userMemoryDetail",
"path": "/user-memory/:id",
"enable": true,
"display": false,
"level": 2,
"sort": 0
}
]
},
{ {
"id": 19, "id": 19,
"parent": 0, "parent": 0,
@@ -243,6 +348,21 @@
"icon": null, "icon": null,
"iconActive": null, "iconActive": null,
"subs": null "subs": null
},
{
"id": 11,
"parent": 0,
"code": "apiKey",
"label": "API KEY管理",
"i18nKey": "menu.apiKeyManagement",
"path": "/api-key",
"enable": true,
"display": true,
"level": 1,
"sort": 0,
"icon": null,
"iconActive": null,
"subs": null
} }
] ]
} }

View File

@@ -32,7 +32,7 @@ interface MenuState {
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>; allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
loadMenus: (source: 'space' | 'manage') => void; loadMenus: (source: 'space' | 'manage') => void;
updateBreadcrumbs: (keyPath: string[], source: 'space' | 'manage') => void; updateBreadcrumbs: (keyPath: string[], source: 'space' | 'manage') => void;
setCustomBreadcrumbs: (breadcrumbs: MenuItem[], source: 'space' | 'manage') => void; setCustomBreadcrumbs: (breadcrumbs: MenuItem[], source: string) => void;
} }
const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]' const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]'
@@ -57,6 +57,31 @@ export const useMenu = create<MenuState>((set, get) => ({
const { allMenus } = get() const { allMenus } = get()
const menus = allMenus[source] || [] const menus = allMenus[source] || []
let result: MenuItem[] = [] let result: MenuItem[] = []
console.log('updateBreadcrumbs paths:', paths);
if (paths.length === 3) {
// 三级菜单:[subSubPath, subId, menuId]
const menuId = paths[2];
const subId = paths[1];
const subSubPath = paths[0];
const matchedMenu = menus.find(menu => `${menu.id}` === menuId);
if (matchedMenu && matchedMenu.subs) {
const matchedSub = matchedMenu.subs.find(sub => `${sub.id}` === subId);
if (matchedSub && matchedSub.subs) {
const matchedSubSub = matchedSub.subs.find(subSub => subSub.path === subSubPath);
if (matchedSubSub) {
result = [
{ ...matchedMenu, subs: null },
{ ...matchedSub, subs: null },
{ ...matchedSubSub, subs: null }
];
}
}
}
} else {
// 原有逻辑处理一级和二级菜单
const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]); const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]);
if (matchedMenu) { if (matchedMenu) {
@@ -71,6 +96,8 @@ export const useMenu = create<MenuState>((set, get) => ({
} else { } else {
result = [] as MenuItem[] result = [] as MenuItem[]
} }
}
const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result } const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result }
set({ allBreadcrumbs }) set({ allBreadcrumbs })
localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs)) localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs))

View File

@@ -175,3 +175,9 @@ body {
.ant-breadcrumb a:hover { .ant-breadcrumb a:hover {
background-color: transparent; background-color: transparent;
} }
/* X6 节点样式 */
.x6-node foreignObject > body {
min-height: 100%;
max-height: 100%;
}

View File

@@ -0,0 +1,46 @@
/**
* API密钥替换工具
*/
const API_KEY_PATTERNS = {
service: /sk-service-[A-Za-z0-9_-]+/g,
agent: /sk-agent-[A-Za-z0-9_-]+/g,
multiAgent: /sk-multi_agent-[A-Za-z0-9_-]+/g,
workflow: /sk-workflow-[A-Za-z0-9_-]+/g
}
const API_KEY_PREFIX = {
service: 'sk-service-',
agent: 'sk-agent-',
multiAgent: 'sk-multi_agent-',
workflow: 'sk-workflow-'
}
/**
* 替换文本中的API密钥为*号
* @param text 原始文本
* @returns 替换后的文本
*/
export const maskApiKeys = (text: string): string => {
if (!text) return text
let result = text
Object.keys(API_KEY_PREFIX).map(type => {
const key = type as keyof typeof API_KEY_PREFIX
result = result.replace(API_KEY_PATTERNS[key as keyof typeof API_KEY_PREFIX], (match) => {
const prefixLength = API_KEY_PREFIX[key].length
const prefix = match.substring(0, prefixLength)
return prefix + '*'.repeat(match.length - prefixLength)
})
})
return result
}
/**
* 检测文本中是否包含API密钥
* @param text 待检测文本
* @returns 是否包含API密钥
*/
export const hasApiKeys = (text: string): boolean => {
return Object.values(API_KEY_PATTERNS).some(pattern => pattern.test(text))
}

View File

@@ -302,6 +302,10 @@ export const request = {
// 获取父级域名 // 获取父级域名
const getParentDomain = () => { const getParentDomain = () => {
const hostname = window.location.hostname const hostname = window.location.hostname
// 检查是否为IP地址
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
return hostname
}
const parts = hostname.split('.') const parts = hostname.split('.')
return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : hostname return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : hostname
} }

Some files were not shown because too many files have changed in this diff Show More