Compare commits
1 Commits
feat/wxy-d
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b13b4a949 |
2
web/.gitignore
vendored
@@ -22,5 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
vite.config.js
|
||||
package-lock.json
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"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/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@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",
|
||||
"axios": "^1.12.2",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -23,6 +31,8 @@
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"i18next": "^25.6.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"lexical": "^0.39.0",
|
||||
"mermaid": "^11.12.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -31,7 +41,6 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"reactflow": "^11.11.4",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
@@ -46,6 +55,7 @@
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^24.6.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
|
||||
10
web/public/auto-imports.d.ts
vendored
@@ -6,22 +6,31 @@
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const Activity: typeof import('react').Activity
|
||||
const Fragment: typeof import('react').Fragment
|
||||
const Link: typeof import('react-router-dom').Link
|
||||
const NavLink: typeof import('react-router-dom').NavLink
|
||||
const Navigate: typeof import('react-router-dom').Navigate
|
||||
const Outlet: typeof import('react-router-dom').Outlet
|
||||
const Route: typeof import('react-router-dom').Route
|
||||
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 forwardRef: typeof import('react').forwardRef
|
||||
const lazy: typeof import('react').lazy
|
||||
const memo: typeof import('react').memo
|
||||
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 useContext: typeof import('react').useContext
|
||||
const useDebugValue: typeof import('react').useDebugValue
|
||||
const useDeferredValue: typeof import('react').useDeferredValue
|
||||
const useEffect: typeof import('react').useEffect
|
||||
const useEffectEvent: typeof import('react').useEffectEvent
|
||||
const useHref: typeof import('react-router-dom').useHref
|
||||
const useId: typeof import('react').useId
|
||||
const useImperativeHandle: typeof import('react').useImperativeHandle
|
||||
@@ -33,6 +42,7 @@ declare global {
|
||||
const useMemo: typeof import('react').useMemo
|
||||
const useNavigate: typeof import('react-router-dom').useNavigate
|
||||
const useNavigationType: typeof import('react-router-dom').useNavigationType
|
||||
const useOptimistic: typeof import('react').useOptimistic
|
||||
const useOutlet: typeof import('react-router-dom').useOutlet
|
||||
const useOutletContext: typeof import('react-router-dom').useOutletContext
|
||||
const useParams: typeof import('react-router-dom').useParams
|
||||
|
||||
@@ -27,12 +27,21 @@ import 'dayjs/locale/en'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import 'dayjs/plugin/timezone'
|
||||
import 'dayjs/plugin/utc'
|
||||
import { cookieUtils } from './utils/request';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { locale, language, timeZone } = useI18n()
|
||||
useEffect(() => {
|
||||
const authToken = cookieUtils.get('authToken')
|
||||
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||
window.location.href = `/#/login`;
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t('memoryBear')
|
||||
|
||||
33
web/src/api/apiKey.ts
Normal 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`)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
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 { 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'
|
||||
@@ -12,20 +14,24 @@ export const getApplicationList = (data: Record<string, unknown>) => {
|
||||
export const getApplicationConfig = (id: string) => {
|
||||
return request.get(`/apps/${id}/config`)
|
||||
}
|
||||
// 获取集群应配置
|
||||
// 获取集群应用配置
|
||||
export const getMultiAgentConfig = (id: string) => {
|
||||
return request.get(`/apps/${id}/multi-agent`)
|
||||
}
|
||||
// 获取 workflow应用配置
|
||||
export const getWorkflowConfig = (id: string) => {
|
||||
return request.get(`/apps/${id}/workflow`)
|
||||
}
|
||||
// 应用详情
|
||||
export const getApplication = (id: string) => {
|
||||
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)
|
||||
}
|
||||
// 创建应用
|
||||
export const addApplication = (values: Application) => {
|
||||
export const addApplication = (values: ApplicationModalData) => {
|
||||
return request.post('/apps', values)
|
||||
}
|
||||
// 保存Agent配置
|
||||
@@ -36,11 +42,15 @@ export const saveAgentConfig = (app_id: string, values: Config) => {
|
||||
export const saveMultiAgentConfig = (app_id: string, values: Config) => {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 删除应用
|
||||
@@ -76,18 +86,7 @@ export const getConversationHistory = (share_token: string, data: { page: number
|
||||
})
|
||||
}
|
||||
// 发送体验对话
|
||||
export const sendConversation = (share_token: string, values: {
|
||||
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}`)}`
|
||||
// }
|
||||
// })
|
||||
export const sendConversation = (values: QueryParams, onMessage: (data: SSEMessage[]) => void, shareToken: string) => {
|
||||
return handleSSE(`/public/share/chat`, values, onMessage, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${shareToken}`
|
||||
|
||||
@@ -64,8 +64,8 @@ export const getModelTypeList = async () => {
|
||||
return response as any[];
|
||||
};
|
||||
// 获取模型列表
|
||||
export const getModelList = async (type: string | string[], pageInfo: PageRequest) => {
|
||||
const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo });
|
||||
export const getModelList = async (pageInfo: PageRequest) => {
|
||||
const response = await request.get(`${apiPrefix}/models`, pageInfo);
|
||||
return response as any;
|
||||
};
|
||||
//获取模型提供者
|
||||
@@ -135,16 +135,18 @@ interface UploadFileOptions {
|
||||
kb_id?: string;
|
||||
parent_id?: string;
|
||||
onUploadProgress?: (event: AxiosProgressEvent) => void;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
// 上传文件
|
||||
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> = {};
|
||||
if (kb_id) params.kb_id = kb_id;
|
||||
if (parent_id) params.parent_id = parent_id;
|
||||
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
|
||||
params,
|
||||
onUploadProgress,
|
||||
signal,
|
||||
});
|
||||
return response as UploadFileResponse;
|
||||
};
|
||||
@@ -199,8 +201,8 @@ export const deleteFile = async (id: string) => {
|
||||
};
|
||||
|
||||
// 获取文档列表
|
||||
export const getDocumentList = async (query: PathQuery) => {
|
||||
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/${query.parent_id}/documents`, query);
|
||||
export const getDocumentList = async (kb_id:string, query: PathQuery) => {
|
||||
const response = await request.get(`${apiPrefix}/documents/${kb_id}/documents`, query);
|
||||
return response as KnowledgeBaseDocumentData[];
|
||||
};
|
||||
// 文档详情
|
||||
@@ -213,6 +215,11 @@ export const createDocument = async (data: KnowledgeBaseDocumentData) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/document`, data);
|
||||
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) => {
|
||||
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}`);
|
||||
return response;
|
||||
};
|
||||
// 文档解析
|
||||
export const parseDocument = async (id: string) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`);
|
||||
// 文档解析 / 分块
|
||||
export const parseDocument = async (id: string, data: any) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`, data);
|
||||
return response as any;
|
||||
};
|
||||
// 文档分块预览
|
||||
|
||||
@@ -8,7 +8,15 @@ import type {
|
||||
import type {
|
||||
ConfigForm as ExtractionConfigForm
|
||||
} 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 { EndUser } from '@/views/UserMemoryDetail/types'
|
||||
import { handleSSE, type SSEMessage } from '@/utils/stream'
|
||||
|
||||
// 记忆对话
|
||||
export const readService = (query: TestParams) => {
|
||||
@@ -59,6 +67,7 @@ export const getTotalEndUsers = () => {
|
||||
export const getUserProfile = (end_user_id: string) => {
|
||||
return request.get(`/memory/analytics/user_profile`, { end_user_id })
|
||||
}
|
||||
|
||||
// 用户记忆-记忆洞察
|
||||
export const getMemoryInsightReport = (end_user_id: string) => {
|
||||
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) => {
|
||||
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) => {
|
||||
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) => {
|
||||
@@ -95,6 +115,26 @@ export const getChunkInsight = (end_user_id: string) => {
|
||||
export const getRagContent = (end_user_id: string) => {
|
||||
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 用户记忆 相关接口 ******************************/
|
||||
|
||||
/****************** 记忆管理 相关接口 *******************************/
|
||||
@@ -132,9 +172,30 @@ export const updateMemoryExtractionConfig = (values: ExtractionConfigForm) => {
|
||||
return request.post('/memory-storage/update_config_extracted', values)
|
||||
}
|
||||
// 记忆萃取引擎-试运行
|
||||
export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string }) => {
|
||||
return request.post('/memory-storage/pilot_run', values)
|
||||
export const pilotRunMemoryExtractionConfig = (values: { config_id: number | string; dialogue_text: string; }, onMessage?: (data: SSEMessage[]) => void) => {
|
||||
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 记忆管理 相关接口 ******************************/
|
||||
|
||||
|
||||
|
||||
17
web/src/api/order.ts
Normal 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
@@ -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
@@ -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)
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { request } from '@/utils/request'
|
||||
import type { SpaceModalData } from '@/views/SpaceManagement/types'
|
||||
import type { ConfigModalData } from '@/views/UserMemory/types'
|
||||
|
||||
// 空间列表
|
||||
export const getWorkspaces = () => {
|
||||
@@ -22,6 +23,6 @@ export const getWorkspaceModels = () => {
|
||||
return request.get(`/workspaces/workspace_models`)
|
||||
}
|
||||
// 更新空间模型配置
|
||||
export const updateWorkspaceModels = () => {
|
||||
return request.post(`/workspaces/workspace_models`)
|
||||
export const updateWorkspaceModels = (data: ConfigModalData) => {
|
||||
return request.put(`/workspaces/workspace_models`, data)
|
||||
}
|
||||
|
||||
24
web/src/assets/images/fullScreen.svg
Normal 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 |
24
web/src/assets/images/fullScreen_hover.svg
Normal 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 |
16
web/src/assets/images/home/arrow_top_right_hover.svg
Normal 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 |
BIN
web/src/assets/images/menu/apiKey.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
web/src/assets/images/menu/apiKey_active.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
22
web/src/assets/images/menu/pricing.svg
Normal 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 |
22
web/src/assets/images/menu/pricing_active.svg
Normal 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 |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
web/src/assets/images/menu/tool.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
web/src/assets/images/menu/tool_active.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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 |
13
web/src/assets/images/order/alert.svg
Normal 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 |
BIN
web/src/assets/images/order/bg.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
web/src/assets/images/order/biz.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
15
web/src/assets/images/order/check.svg
Normal 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 |
BIN
web/src/assets/images/order/commerce.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
15
web/src/assets/images/order/corporate.svg
Normal 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 |
16
web/src/assets/images/order/order.svg
Normal 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 |
16
web/src/assets/images/order/order_hover.svg
Normal 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 |
BIN
web/src/assets/images/order/personal.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
web/src/assets/images/order/team.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
@@ -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">
|
||||
<title>刷新</title>
|
||||
<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="主操作" transform="translate(839, 144)">
|
||||
<g id="刷新" transform="translate(12, 10)">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
15
web/src/assets/images/refresh_hover.svg
Normal 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 |
BIN
web/src/assets/images/userMemory/detail_empty.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
web/src/assets/images/workflow/agent_arbitration.png
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
web/src/assets/images/workflow/agent_collaboration.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
web/src/assets/images/workflow/agent_scheduling.png
Normal file
|
After Width: | Height: | Size: 785 B |
BIN
web/src/assets/images/workflow/aggregator.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
web/src/assets/images/workflow/answer.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
web/src/assets/images/workflow/arrow.png
Normal file
|
After Width: | Height: | Size: 775 B |
BIN
web/src/assets/images/workflow/classification.png
Normal file
|
After Width: | Height: | Size: 849 B |
BIN
web/src/assets/images/workflow/code_execution.png
Normal file
|
After Width: | Height: | Size: 684 B |
BIN
web/src/assets/images/workflow/condition.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
web/src/assets/images/workflow/empty.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
web/src/assets/images/workflow/end.png
Normal file
|
After Width: | Height: | Size: 792 B |
BIN
web/src/assets/images/workflow/http_request.png
Normal file
|
After Width: | Height: | Size: 745 B |
BIN
web/src/assets/images/workflow/iteration.png
Normal file
|
After Width: | Height: | Size: 612 B |
BIN
web/src/assets/images/workflow/llm.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
web/src/assets/images/workflow/loop.png
Normal file
|
After Width: | Height: | Size: 815 B |
BIN
web/src/assets/images/workflow/memory_enhancement.png
Normal file
|
After Width: | Height: | Size: 810 B |
BIN
web/src/assets/images/workflow/model_selection.png
Normal file
|
After Width: | Height: | Size: 908 B |
BIN
web/src/assets/images/workflow/model_voting.png
Normal file
|
After Width: | Height: | Size: 769 B |
BIN
web/src/assets/images/workflow/output_audit.png
Normal file
|
After Width: | Height: | Size: 624 B |
BIN
web/src/assets/images/workflow/parallel.png
Normal file
|
After Width: | Height: | Size: 979 B |
BIN
web/src/assets/images/workflow/parameter_extraction.png
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
web/src/assets/images/workflow/process_evolution.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
web/src/assets/images/workflow/rag.png
Normal file
|
After Width: | Height: | Size: 741 B |
BIN
web/src/assets/images/workflow/reasoning_control.png
Normal file
|
After Width: | Height: | Size: 815 B |
BIN
web/src/assets/images/workflow/robot-2-line@2x.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
web/src/assets/images/workflow/self_optimization.png
Normal file
|
After Width: | Height: | Size: 922 B |
BIN
web/src/assets/images/workflow/self_reflection.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/src/assets/images/workflow/sensitive_detection.png
Normal file
|
After Width: | Height: | Size: 803 B |
BIN
web/src/assets/images/workflow/start.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
web/src/assets/images/workflow/task_planning.png
Normal file
|
After Width: | Height: | Size: 648 B |
BIN
web/src/assets/images/workflow/template_rendering.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
web/src/assets/images/workflow/tools.png
Normal file
|
After Width: | Height: | Size: 869 B |
@@ -34,12 +34,12 @@ const ButtonCheckbox: FC<ButtonCheckboxProps> = ({
|
||||
}
|
||||
|
||||
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:border-[#DFE4ED] rb:text-[#212332]": !checked,
|
||||
})} onClick={handleChange}>
|
||||
{icon && !checked && <img src={icon} className="rb:w-[16px] rb:h-[16px] rb:mr-[4px]" />}
|
||||
{checkedIcon && checked && <img src={checkedIcon} 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-4 rb:h-4 rb:mr-1" />}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
84
web/src/components/Chat/ChatContent.tsx
Normal 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
|
||||
80
web/src/components/Chat/ChatInput.tsx
Normal 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
|
||||
47
web/src/components/Chat/index.tsx
Normal 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
|
||||
84
web/src/components/Chat/types.ts
Normal 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;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ interface ApiResponse<T> {
|
||||
items?: T[];
|
||||
}
|
||||
|
||||
interface CustomSelectProps {
|
||||
interface CustomSelectProps extends Omit<SelectProps, 'filterOption'> {
|
||||
url: string;
|
||||
params?: Record<string, unknown>;
|
||||
valueKey?: string;
|
||||
|
||||
@@ -27,7 +27,7 @@ const Empty: FC<EmptyProps> = ({
|
||||
<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` }} />
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Layout, Dropdown, Space, Breadcrumb } from 'antd';
|
||||
import type { MenuProps, BreadcrumbProps } from 'antd';
|
||||
import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useUser } from '@/store/user';
|
||||
import { useMenu } from '@/store/menu';
|
||||
import styles from './index.module.css'
|
||||
@@ -12,12 +13,35 @@ const { Header } = Layout;
|
||||
|
||||
const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const settingModalRef = useRef<SettingModalRef>(null)
|
||||
const userInfoModalRef = useRef<UserInfoModalRef>(null)
|
||||
|
||||
const { user, logout } = useUser();
|
||||
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 = () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const { Content } = Layout;
|
||||
|
||||
// 认证布局组件,使用useRouteGuard hook进行路由鉴权
|
||||
const AuthLayout: FC = () => {
|
||||
const { getUserInfo } = useUser();
|
||||
const { getUserInfo, getStorageType } = useUser();
|
||||
// 使用路由守卫hook处理认证和权限检查
|
||||
useRouteGuard('manage');
|
||||
// 自动更新面包屑导航
|
||||
@@ -24,6 +24,7 @@ const AuthLayout: FC = () => {
|
||||
window.location.href = `/#/login`;
|
||||
} else {
|
||||
getUserInfo()
|
||||
getStorageType()
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -4,12 +4,13 @@ import { useUser } from '@/store/user';
|
||||
|
||||
// 基础布局组件,用于展示内容并保留用户信息获取功能
|
||||
const BasicLayout: FC = () => {
|
||||
const { getUserInfo } = useUser();
|
||||
const { getUserInfo, getStorageType } = useUser();
|
||||
|
||||
// 获取用户信息
|
||||
useEffect(() => {
|
||||
getUserInfo();
|
||||
}, [getUserInfo]);
|
||||
getStorageType()
|
||||
}, [getUserInfo, getStorageType]);
|
||||
|
||||
return (
|
||||
<div className="rb:relative rb:h-full rb:w-full">
|
||||
|
||||
@@ -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 RemarkGfm from 'remark-gfm'
|
||||
import RemarkMath from 'remark-math'
|
||||
@@ -6,6 +7,7 @@ import RemarkBreaks from 'remark-breaks'
|
||||
import RehypeKatex from 'rehype-katex'
|
||||
import RehypeRaw from 'rehype-raw'
|
||||
import type { FC } from 'react'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
|
||||
import Code from './Code'
|
||||
import VideoBlock from './VideoBlock'
|
||||
@@ -16,42 +18,45 @@ import RbButton from './RbButton'
|
||||
interface RbMarkdownProps {
|
||||
content: string;
|
||||
showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false(隐藏)
|
||||
editable?: boolean; // 是否可编辑,默认为 false
|
||||
onContentChange?: (content: string) => void; // 内容变化回调
|
||||
onSave?: (content: string) => void; // 保存回调
|
||||
}
|
||||
|
||||
const components = {
|
||||
h1: ({ children }: { children: string }) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2">{children}</h1>,
|
||||
h2: ({ children }: { children: string }) => <h2 className="rb:text-xl rb:font-bold rb:mb-2">{children}</h2>,
|
||||
h3: ({ children }: { children: string }) => <h3 className="rb:text-lg rb:font-bold rb:mb-2">{children}</h3>,
|
||||
h4: ({ children }: { children: string }) => <h4 className="rb:text-md rb:font-bold rb:mb-2">{children}</h4>,
|
||||
h5: ({ children }: { children: string }) => <h5 className="rb:text-sm rb:font-bold rb:mb-2">{children}</h5>,
|
||||
h6: ({ children }: { children: string }) => <h6 className="rb:text-xs rb:font-bold rb:mb-2">{children}</h6>,
|
||||
ul: ({ children }: { children: string }) => <ul className="rb:list-disc rb:ml-6 rb:mb-2">{children}</ul>,
|
||||
ol: ({ children }: { children: string }) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2">{children}</ol>,
|
||||
li: ({ children }: { children: string }) => <li className="rb:mb-1">{children}</li>,
|
||||
blockquote: ({ children }: { children: string }) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2">{children}</blockquote>,
|
||||
p: ({ children }: { children: string }) => <p className="rb:mb-2">{children}</p>,
|
||||
strong: ({ children }: { children: string }) => <strong className="rb:font-bold">{children}</strong>,
|
||||
em: ({ children }: { children: string }) => <em className="rb:italic">{children}</em>,
|
||||
del: ({ children }: { children: string }) => <del className="rb:line-through">{children}</del>,
|
||||
span: ({ children, ...props }: any) => {
|
||||
h1: ({ children, ...props }: any) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2" {...props}>{children}</h1>,
|
||||
h2: ({ children, ...props }: any) => <h2 className="rb:text-xl rb:font-bold rb:mb-2" {...props}>{children}</h2>,
|
||||
h3: ({ children, ...props }: any) => <h3 className="rb:text-lg rb:font-bold rb:mb-2" {...props}>{children}</h3>,
|
||||
h4: ({ children, ...props }: any) => <h4 className="rb:text-md rb:font-bold rb:mb-2" {...props}>{children}</h4>,
|
||||
h5: ({ children, ...props }: any) => <h5 className="rb:text-sm rb:font-bold rb:mb-2" {...props}>{children}</h5>,
|
||||
h6: ({ children, ...props }: any) => <h6 className="rb:text-xs rb:font-bold rb:mb-2" {...props}>{children}</h6>,
|
||||
ul: ({ children, ...props }: any) => <ul className="rb:list-disc rb:ml-6 rb:mb-2" {...props}>{children}</ul>,
|
||||
ol: ({ children, ...props }: any) => <ol className="rb:list-decimal rb:ml-6 rb:mb-2" {...props}>{children}</ol>,
|
||||
li: ({ children, ...props }: any) => <li className="rb:mb-1" {...props}>{children}</li>,
|
||||
blockquote: ({ children, ...props }: any) => <blockquote className="rb:border-l-4 rb:border-[#D9D9D9] rb:pl-4 rb:mb-2" {...props}>{children}</blockquote>,
|
||||
p: ({ children, ...props }: any) => <p className="rb:mb-2" {...props}>{children}</p>,
|
||||
strong: ({ children, ...props }: any) => <strong className="rb:font-bold" {...props}>{children}</strong>,
|
||||
em: ({ children, ...props }: any) => <em className="rb:italic" {...props}>{children}</em>,
|
||||
del: ({ children, ...props }: any) => <del className="rb:line-through" {...props}>{children}</del>,
|
||||
span: ({ children, style, ...restProps }: any) => {
|
||||
// 如果是 HTML 注释的 span,应用特殊样式
|
||||
if (props.style?.color === '#999') {
|
||||
if (style?.color === '#999') {
|
||||
return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span>
|
||||
}
|
||||
return <span {...props}>{children}</span>
|
||||
return <span style={style} {...restProps}>{children}</span>
|
||||
},
|
||||
|
||||
code: Code,
|
||||
img: Image,
|
||||
video: VideoBlock,
|
||||
audio: AudioBlock,
|
||||
a: Link,
|
||||
button: RbButton,
|
||||
table: ({ children }: { children: string }) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2">{children}</table>,
|
||||
tr: ({ children }: { children: string }) => <tr className="rb:border rb:border-[#D9D9D9]">{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>,
|
||||
td: ({ children }: { children: string }) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left">{children}</td>,
|
||||
input: ({ children, ...props }: { children: string }) => {
|
||||
code: ({ children, className, ...props }: any) => <Code children={String(children)} className={className || ''} {...props} />,
|
||||
img: ({ src, alt, ...props }: any) => <Image src={src} alt={alt} {...props} />,
|
||||
video: ({ src, ...props }: any) => <VideoBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
|
||||
audio: ({ src, ...props }: any) => <AudioBlock node={{ children: [{ properties: { src: src || '' } }] }} {...props} />,
|
||||
a: ({ href, children, ...props }: any) => <Link href={href || '#'} {...props}>{children}</Link>,
|
||||
button: ({ children, ...props }: any) => <RbButton node={{ children }}>{[children]}</RbButton>,
|
||||
table: ({ children, ...props }: any) => <table className="rb:border rb:border-[#D9D9D9] rb:mb-2" {...props}>{children}</table>,
|
||||
tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#D9D9D9]" {...props}>{children}</tr>,
|
||||
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, ...props }: any) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
|
||||
input: ({ children, ...props }: any) => {
|
||||
switch (props.type) {
|
||||
case 'color':
|
||||
return <ColorPicker {...props} />
|
||||
@@ -74,7 +79,7 @@ const components = {
|
||||
return <Slider {...props} />
|
||||
case 'submit':
|
||||
case 'button':
|
||||
return <RbButton {...props}>{props.value}</RbButton>
|
||||
return <RbButton node={{ children: props.value || children }}>{[props.value || children]}</RbButton>
|
||||
case 'checkbox':
|
||||
return <Checkbox {...props}>{children}</Checkbox>
|
||||
case 'password':
|
||||
@@ -85,37 +90,158 @@ const components = {
|
||||
return <Input value={children} {...props} />
|
||||
}
|
||||
},
|
||||
select: ({ children, ...props }: { children: string }) => <Select style={{width: '100%'}} {...props}>{children}</Select>,
|
||||
textarea: ({ children, ...props }: { children: string }) => <Input.TextArea {...props}>{children}</Input.TextArea>,
|
||||
form: ({ children }: { children: string }) => <Form>{children}</Form>,
|
||||
select: ({ children, ...props }: any) => <Select style={{width: '100%'}} {...props}>{children}</Select>,
|
||||
textarea: ({ children, ...props }: any) => <Input.TextArea {...props}>{children}</Input.TextArea>,
|
||||
form: ({ children, ...props }: any) => <Form {...props}>{children}</Form>,
|
||||
}
|
||||
|
||||
const RbMarkdown: FC<RbMarkdownProps> = ({
|
||||
content,
|
||||
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 注释转换为可见文本
|
||||
// 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤
|
||||
const processedContent = showHtmlComments
|
||||
? content.replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => {
|
||||
? (isEditing ? editContent : content).replace(/<!--([\s\S]*?)-->/g, (_match, commentContent) => {
|
||||
// 转换为带样式的文本,使用 <span class="html-comment"> 标记
|
||||
const escaped = commentContent.trim().replace(/</g, '<').replace(/>/g, '>')
|
||||
return `<span class="html-comment"><!-- ${escaped} --></span>`
|
||||
})
|
||||
: content
|
||||
: (isEditing ? editContent : content)
|
||||
|
||||
// 如果是编辑模式,显示 textarea
|
||||
if (isEditing) {
|
||||
return (
|
||||
<div className="rb:relative">
|
||||
<style>{`
|
||||
.html-comment {
|
||||
color: #999;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
`}</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>
|
||||
<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
|
||||
// allowElement={[]}
|
||||
// allowedElements={[]}
|
||||
components={components}
|
||||
components={components as any}
|
||||
disallowedElements={['script', 'iframe', 'head', 'html', 'meta', 'link', 'style', 'body']}
|
||||
rehypePlugins={[
|
||||
RehypeKatex,
|
||||
|
||||
@@ -29,7 +29,7 @@ interface PageScrollListProps {
|
||||
|
||||
const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
|
||||
renderItem,
|
||||
query = {},
|
||||
query,
|
||||
url,
|
||||
column = 4,
|
||||
className = '',
|
||||
@@ -51,11 +51,11 @@ const PageScrollList = forwardRef<PageScrollListRef, PageScrollListProps>(({
|
||||
request.get(url, {
|
||||
page: page,
|
||||
pagesize: PAGE_SIZE,
|
||||
...query,
|
||||
...(query||{}),
|
||||
})
|
||||
.then((res) => {
|
||||
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) {
|
||||
setData(results);
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@ interface RadioCardProps extends Omit<RadioGroupProps, 'onChange'> {
|
||||
onValueChange?: (value: string | null | undefined, option?: RadioCardOption) => void;
|
||||
onChange?: (value: string | null | undefined, option?: RadioCardOption) => void;
|
||||
itemRender?: (option: RadioCardOption) => ReactNode;
|
||||
allowClear?: boolean;
|
||||
}
|
||||
|
||||
const RadioGroupCard: FC<RadioCardProps> = ({
|
||||
@@ -22,7 +23,8 @@ const RadioGroupCard: FC<RadioCardProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
onChange,
|
||||
itemRender
|
||||
itemRender,
|
||||
allowClear = true
|
||||
}) => {
|
||||
// 监听value变化
|
||||
useEffect(() => {
|
||||
@@ -34,23 +36,27 @@ const RadioGroupCard: FC<RadioCardProps> = ({
|
||||
const handleChange = (option: RadioCardOption) => {
|
||||
if (option.disabled) return
|
||||
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 (
|
||||
<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 => (
|
||||
<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:border-[#EBEBEB] rb:bg-[#ffffff]': option.value !== value,
|
||||
'rb:opacity-[0.75]': option.disabled
|
||||
})} onClick={() => handleChange(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: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>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { type FC, type ReactNode } from 'react'
|
||||
interface RbAlertProps {
|
||||
color?: 'blue' | 'green' | 'orange' | 'purple',
|
||||
children: ReactNode | string;
|
||||
icon: ReactNode;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ const colors = {
|
||||
|
||||
const RbAlert: FC<RbAlertProps> = ({ color = 'blue', icon, className, children }) => {
|
||||
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]`}>
|
||||
{icon && <span className="rb:text-[16px] rb:mr-[9px]">{icon}</span>}
|
||||
<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-2.25">{icon}</span>}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ const RbCard: FC<RbCardProps> = ({
|
||||
title={typeof title === 'function' ? title() : title ?
|
||||
<div className="rb:flex rb:items-center">
|
||||
{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
|
||||
}
|
||||
<div className={
|
||||
|
||||
3
web/src/components/RbModal/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.rb-modal .ant-modal-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@@ -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 { Modal, type ModalProps } from 'antd'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const RbModal: FC<ModalProps> = ({
|
||||
onOk,
|
||||
onCancel,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -16,9 +24,11 @@ const RbModal: FC<ModalProps> = ({
|
||||
cancelText={t('common.cancel')}
|
||||
onOk={onOk}
|
||||
destroyOnHidden={true}
|
||||
className={`rb-modal ${className || ''}`}
|
||||
maskClosable={false}
|
||||
{...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}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -21,11 +21,11 @@ import modelActiveIcon from '@/assets/images/menu/model_active.svg';
|
||||
import memoryIcon from '@/assets/images/menu/memory.svg';
|
||||
import memoryActiveIcon from '@/assets/images/menu/memory_active.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 userActiveIcon from '@/assets/images/menu/user_active.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 applicationActiveIcon from '@/assets/images/menu/application_active.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 memberIcon from '@/assets/images/menu/member.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> = {
|
||||
@@ -57,6 +63,12 @@ const iconPathMap: Record<string, string> = {
|
||||
'memoryConversationActive': memoryConversationActiveIcon,
|
||||
'member': memberIcon,
|
||||
'memberActive': memberActiveIcon,
|
||||
'tool': toolIcon,
|
||||
'toolActive': toolActiveIcon,
|
||||
'apiKey': apiKeyIcon,
|
||||
'apiKeyActive': apiKeyActiveIcon,
|
||||
'pricing': pricingIcon,
|
||||
'pricingActive': pricingActiveIcon
|
||||
};
|
||||
|
||||
const { Sider } = Layout;
|
||||
|
||||
@@ -19,6 +19,7 @@ interface TableComponentProps extends Omit<TableProps, 'pagination'> {
|
||||
isScroll?: boolean;
|
||||
scrollX?: number | string | true; // 支持自定义横向滚动宽度
|
||||
scrollY?: number | string; // 支持自定义纵向滚动高度
|
||||
currentPageKey?: string;
|
||||
}
|
||||
export interface TableRef {
|
||||
loadData: () => void;
|
||||
@@ -48,6 +49,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
|
||||
isScroll = false,
|
||||
scrollX,
|
||||
scrollY,
|
||||
currentPageKey = 'page',
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -86,7 +88,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
|
||||
...currentPagination,
|
||||
...pageData,
|
||||
})
|
||||
params = {...params, ...pageData}
|
||||
params = { ...params, ...pageData, [currentPageKey]: pageData.page}
|
||||
}
|
||||
setLoading(true)
|
||||
// 构建查询参数并调用API
|
||||
@@ -95,7 +97,7 @@ const TableComponent = forwardRef<TableRef, TableComponentProps>(({
|
||||
// 支持两种响应格式:直接返回 total 或在 page 对象中返回
|
||||
const totalCount = res.page?.total ?? res.total ?? 0;
|
||||
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)
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type FC, type ReactNode } from 'react'
|
||||
|
||||
interface TagProps {
|
||||
export interface TagProps {
|
||||
color?: 'processing' | 'error' | 'success' | 'warning' | 'default',
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
@@ -16,7 +16,7 @@ const colors = {
|
||||
|
||||
const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => {
|
||||
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}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -38,6 +38,8 @@ interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
|
||||
maxCount?: number;
|
||||
/** 是否支持拖拽上传,默认为false */
|
||||
isCanDrag?: boolean;
|
||||
/** 自定义移除文件回调 */
|
||||
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
|
||||
}
|
||||
const ALL_FILE_TYPE: {
|
||||
[key: string]: string;
|
||||
@@ -77,6 +79,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
isAutoUpload = true,
|
||||
maxCount = 1,
|
||||
isCanDrag = false,
|
||||
onRemove: customOnRemove,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -86,11 +89,20 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
|
||||
// 处理文件移除
|
||||
const handleRemove = (file: UploadFile) => {
|
||||
// 如果有自定义的 onRemove 回调,先执行它
|
||||
if (customOnRemove) {
|
||||
const result = customOnRemove(file);
|
||||
// 如果返回 false,阻止移除
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
confirm({
|
||||
title: '确定要删除此文件吗?',
|
||||
okText: '确定',
|
||||
title: `${t('common.confirmRemoveFile')}`,
|
||||
okText: `${t('common.confirm')}`,
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
cancelText: `${t('common.cancel')}`,
|
||||
onOk: () => {
|
||||
const newFileList = fileList.filter((item) => item.uid !== file.uid);
|
||||
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 className="rb:text-[12px] rb:flex rb:items-center rb:justify-between rb:mb-[2px]">
|
||||
{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>
|
||||
<Progress percent={file.percent || 0} strokeColor={file.status === 'error' ? '#FF5D34' : '#155EEF'} size="small" showInfo={false} />
|
||||
</div>
|
||||
|
||||
319
web/src/hooks/useBreadcrumbManager.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
@@ -11,8 +11,10 @@ export const checkAuthStatus = (): 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) {
|
||||
// 检查当前菜单的path是否匹配
|
||||
@@ -26,6 +28,7 @@ export const checkRoutePermission = (menus: MenuItem[], currentPath: string): bo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -52,7 +55,7 @@ export const useRouteGuard = (source: 'space' | 'manage') => {
|
||||
const hasPermission = checkRoutePermission(menus, location.pathname);
|
||||
if (!hasPermission) {
|
||||
// 无权限访问该路由,重定向到无权限页面
|
||||
// navigate('/not-found', { replace: true });
|
||||
// navigate('/no-permission', { replace: true });
|
||||
}
|
||||
}
|
||||
}, [navigate, location.pathname, location.search, location.hash, menus]);
|
||||
|
||||
@@ -10,19 +10,19 @@ import routesConfig from './routes.json';
|
||||
// 递归收集所有路由中的element
|
||||
function collectElements(routes: RouteConfig[]): Set<string> {
|
||||
const elements = new Set<string>();
|
||||
|
||||
|
||||
function traverse(routeList: RouteConfig[]) {
|
||||
routeList.forEach(route => {
|
||||
// 添加当前路由的element
|
||||
elements.add(route.element);
|
||||
|
||||
|
||||
// 递归处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
traverse(route.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
traverse(routes);
|
||||
return elements;
|
||||
}
|
||||
@@ -38,6 +38,7 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
||||
Home: lazy(() => import('@/views/Home')),
|
||||
UserMemory: lazy(() => import('@/views/UserMemory')),
|
||||
UserMemoryDetail: lazy(() => import('@/views/UserMemoryDetail')),
|
||||
Neo4jUserMemoryDetail: lazy(() => import('@/views/UserMemoryDetail/Neo4j')),
|
||||
MemberManagement: lazy(() => import('@/views/MemberManagement')),
|
||||
MemoryManagement: lazy(() => import('@/views/MemoryManagement')),
|
||||
ForgettingEngine: lazy(() => import('@/views/ForgettingEngine')),
|
||||
@@ -54,10 +55,18 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
||||
UserManagement: lazy(() => import('@/views/UserManagement')),
|
||||
ModelManagement: lazy(() => import('@/views/ModelManagement')),
|
||||
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')),
|
||||
InviteRegister: lazy(() => import('@/views/InviteRegister')),
|
||||
NoPermission: lazy(() => import('@/views/NoPermission')),
|
||||
NotFound: lazy(() => import('@/views/NotFound')),
|
||||
NotFound: lazy(() => import('@/views/NotFound'))
|
||||
};
|
||||
|
||||
// 检查并报告缺失的组件
|
||||
@@ -87,12 +96,12 @@ const generateRoutes = (routes: RouteConfig[]): ReactNode => {
|
||||
// 获取组件
|
||||
const componentKey = route.element as keyof typeof componentMap;
|
||||
const Component = componentMap[componentKey];
|
||||
|
||||
|
||||
if (!Component) {
|
||||
console.error(`Component ${route.element} not found in componentMap`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// 如果有子路由
|
||||
if (route.children) {
|
||||
return (
|
||||
@@ -101,12 +110,12 @@ const generateRoutes = (routes: RouteConfig[]): ReactNode => {
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 如果有path属性,则为普通路由
|
||||
if (route.path) {
|
||||
return <Route key={index} path={route.path} element={<Component />} />;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
{ "path": "/user-management", "element": "UserManagement" },
|
||||
{ "path": "/model", "element": "ModelManagement" },
|
||||
{ "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" }
|
||||
]
|
||||
},
|
||||
@@ -25,6 +29,10 @@
|
||||
{ "path": "/knowledge-base/:knowledgeBaseId/share", "element": "Share" },
|
||||
{ "path": "/knowledge-base/:knowledgeBaseId/create-dataset", "element": "CreateDataset" },
|
||||
{ "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": "/*", "element": "NotFound" }
|
||||
]
|
||||
@@ -33,7 +41,8 @@
|
||||
"element": "BasicLayout",
|
||||
"children": [
|
||||
{ "path": "/application/config/:id", "element": "ApplicationConfig" },
|
||||
{ "path": "/conversation/:token", "element": "Conversation" }
|
||||
{ "path": "/conversation/:token", "element": "Conversation" },
|
||||
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,6 +26,59 @@
|
||||
"sort": 0,
|
||||
"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,
|
||||
"parent": 0,
|
||||
@@ -183,6 +236,32 @@
|
||||
"level": 1,
|
||||
"sort": 0,
|
||||
"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,12 +284,38 @@
|
||||
"code": "userMemoryDetail",
|
||||
"label": "记忆详情",
|
||||
"i18nKey": "menu.userMemoryDetail",
|
||||
"path": "/user-memory/:id",
|
||||
"path": "/user-memory/neo4j/:id",
|
||||
"enable": true,
|
||||
"display": false,
|
||||
"level": 2,
|
||||
"sort": 0,
|
||||
"subs": null
|
||||
"subs": [
|
||||
{
|
||||
"id": 811,
|
||||
"parent": 81,
|
||||
"code": "statementDetail",
|
||||
"label": "记忆详情",
|
||||
"i18nKey": "menu.statementDetail",
|
||||
"path": "/statement/:id",
|
||||
"enable": true,
|
||||
"display": false,
|
||||
"level": 3,
|
||||
"sort": 0,
|
||||
"subs": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"parent": 8,
|
||||
"code": "userMemoryDetail",
|
||||
"label": "记忆详情",
|
||||
"i18nKey": "menu.userMemoryDetail",
|
||||
"path": "/user-memory/:id",
|
||||
"enable": true,
|
||||
"display": false,
|
||||
"level": 2,
|
||||
"sort": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -243,6 +348,21 @@
|
||||
"icon": null,
|
||||
"iconActive": 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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -32,7 +32,7 @@ interface MenuState {
|
||||
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
||||
loadMenus: (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') || '[]'
|
||||
@@ -57,20 +57,47 @@ export const useMenu = create<MenuState>((set, get) => ({
|
||||
const { allMenus } = get()
|
||||
const menus = allMenus[source] || []
|
||||
let result: MenuItem[] = []
|
||||
const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]);
|
||||
|
||||
if (matchedMenu) {
|
||||
let matchedSubMenu: MenuItem | undefined = undefined;
|
||||
if (paths.length > 1 && matchedMenu?.subs?.length) {
|
||||
matchedSubMenu = matchedMenu.subs.find(menu => menu.path === paths[0]);
|
||||
|
||||
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 }
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
result = [
|
||||
{ ...matchedMenu, subs: null },
|
||||
matchedSubMenu
|
||||
].filter(item => item !== undefined) as MenuItem[]
|
||||
} else {
|
||||
result = [] as MenuItem[]
|
||||
// 原有逻辑处理一级和二级菜单
|
||||
const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]);
|
||||
|
||||
if (matchedMenu) {
|
||||
let matchedSubMenu: MenuItem | undefined = undefined;
|
||||
if (paths.length > 1 && matchedMenu?.subs?.length) {
|
||||
matchedSubMenu = matchedMenu.subs.find(menu => menu.path === paths[0]);
|
||||
}
|
||||
result = [
|
||||
{ ...matchedMenu, subs: null },
|
||||
matchedSubMenu
|
||||
].filter(item => item !== undefined) as MenuItem[]
|
||||
} else {
|
||||
result = [] as MenuItem[]
|
||||
}
|
||||
}
|
||||
|
||||
const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result }
|
||||
set({ allBreadcrumbs })
|
||||
localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs))
|
||||
|
||||
@@ -174,4 +174,10 @@ body {
|
||||
}
|
||||
.ant-breadcrumb a:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* X6 节点样式 */
|
||||
.x6-node foreignObject > body {
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
46
web/src/utils/apiKeyReplacer.ts
Normal 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))
|
||||
}
|
||||
@@ -302,6 +302,10 @@ export const request = {
|
||||
// 获取父级域名
|
||||
const getParentDomain = () => {
|
||||
const hostname = window.location.hostname
|
||||
// 检查是否为IP地址
|
||||
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
||||
return hostname
|
||||
}
|
||||
const parts = hostname.split('.')
|
||||
return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : hostname
|
||||
}
|
||||
|
||||