diff --git a/web/src/api/application.ts b/web/src/api/application.ts index c769dd91..2e598363 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -135,4 +135,8 @@ export const getExperienceConfig = (share_token: string) => { 'Authorization': `Bearer ${localStorage.getItem(`shareToken_${share_token}`)}` } }) +} +// Get workspace API call statistics +export const getWorkspaceApiStatistics = (data: { start_date: number; end_date: number; }) => { + return request.get(`/apps/workspace/api-statistics`, data) } \ No newline at end of file diff --git a/web/src/hooks/useNavigationBreadcrumbs.ts b/web/src/hooks/useNavigationBreadcrumbs.ts index 2b9cd06a..44d62874 100644 --- a/web/src/hooks/useNavigationBreadcrumbs.ts +++ b/web/src/hooks/useNavigationBreadcrumbs.ts @@ -33,92 +33,72 @@ export const useNavigationBreadcrumbs = (source: 'space' | 'manage' = 'manage') const currentPath = location.pathname; const menus = allMenus[source] || []; - /** Find matching menu item and build key path */ - const findMenuKeyPath = (menuList: any[], parentKeys: string[] = []): string[] | null => { - let bestMatch: { path: string; parentId?: string; score: number } | null = null; - - for (const menu of menuList) { - /** Check submenus */ - if (menu.subs && menu.subs.length > 0) { - const menuPath = menu.path ? (menu.path[0] !== '/' ? '/' + menu.path : menu.path) : ''; - for (const sub of menu.subs) { - if (sub.path) { - const subPath = sub.path[0] !== '/' ? '/' + sub.path : sub.path; - - /** Exact match has priority */ - if (subPath === currentPath) { - return [sub.path, `${menu.id}`]; - } - console.log('menuPath', menuPath) - /** Dynamic route matching */ - if (subPath.includes(':')) { - /** Check if under parent menu */ - if (menuPath && currentPath.startsWith(menuPath + '/')) { - const relativePath = currentPath.replace(menuPath, ''); - const pathSegments = subPath.split('/'); - const relativeSegments = relativePath.split('/'); - if (pathSegments.length === relativeSegments.length) { - const pathPattern = subPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); - const regex = new RegExp(`^${pathPattern}$`); - if (regex.test(relativePath)) { - return [sub.path, `${menu.id}`]; - } - } - } - /** Direct match submenu path */ - const pathSegments = subPath.split('/'); - const currentSegments = currentPath.split('/'); - if (pathSegments.length === currentSegments.length) { - const pathPattern = subPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); - const regex = new RegExp(`^${pathPattern}$`); - if (regex.test(currentPath)) { - return [sub.path, `${menu.id}`]; - } - } - } - } - } - } - - /** Check main menu */ - if (menu.path) { - const menuPath = menu.path[0] !== '/' ? '/' + menu.path : menu.path; - /** Exact match has priority */ - if (menuPath === currentPath) { - return [menu.path, ...parentKeys].reverse(); - } - /** Dynamic route matching */ - if (menuPath.includes(':')) { - const pathSegments = menuPath.split('/'); - const currentSegments = currentPath.split('/'); - if (pathSegments.length === currentSegments.length) { - const pathPattern = menuPath.replace(/:[\w-]+/g, '[^/]+').replace(/\[[\w-]+\]/g, '[^/]+'); - const regex = new RegExp(`^${pathPattern}$`); - if (regex.test(currentPath)) { - const score = menuPath.split('/').length; - if (!bestMatch || score > bestMatch.score) { - bestMatch = { path: menu.path, score }; - } - } - } - } else if (currentPath.startsWith(menuPath + '/')) { - const score = menuPath.split('/').length; - if (!bestMatch || score > bestMatch.score) { - bestMatch = { path: menu.path, score }; - } - } - } + const pathMatches = (pattern: string, path: string): boolean => { + const normalized = pattern[0] !== '/' ? '/' + pattern : pattern; + if (normalized === path) return true; + if (normalized.includes(':')) { + const regex = new RegExp('^' + normalized.replace(/:[\\w-]+/g, '[^/]+') + '$'); + return regex.test(path); } - - if (bestMatch) { - return bestMatch.parentId ? [bestMatch.path, bestMatch.parentId] : [bestMatch.path]; + return false; + }; + + /** + * Recursively search menu tree, returns keyPath or null. + * keyPath format: + * - 1-level: [path] + * - 2-level: [subPath, parentId] + * - 3-level: [subSubPath, subId, parentId] + */ + /** + * parentId: the group's id when recursing into group subs + * keyPath format: + * - 1-level: [path] + * - 2-level: [subPath, parentId] + * - 3-level: [subSubPath, subId, parentId] + */ + const findKeyPath = (menuList: any[], groupId?: string): string[] | null => { + for (const menu of menuList) { + /** Group menus: recurse into subs, passing group id */ + if (menu.type === 'group' && menu.subs?.length) { + const result = findKeyPath(menu.subs, `${menu.id}`); + if (result) return result; + continue; + } + + if (menu.subs?.length) { + for (const sub of menu.subs) { + /** Check third-level subs */ + if (sub.subs?.length) { + for (const subSub of sub.subs) { + if (subSub.path && pathMatches(subSub.path, currentPath)) { + return [subSub.path, `${sub.id}`, `${menu.id}`]; + } + } + } + /** Second-level match: sub is a leaf under menu */ + if (sub.path && pathMatches(sub.path, currentPath)) { + /** If menu is inside a group, return 3-level: [subPath, menuId, groupId] */ + return groupId + ? [sub.path, `${menu.id}`, groupId] + : [sub.path, `${menu.id}`]; + } + } + } + + /** First-level / group-child match */ + if (menu.path && pathMatches(menu.path, currentPath)) { + return groupId ? [menu.path, groupId] : [menu.path]; + } } return null; }; - const keyPath = findMenuKeyPath(menus); + const keyPath = findKeyPath(menus); + + console.log('keyPath', keyPath) if (keyPath) { updateBreadcrumbs(keyPath, source); } }, [location.pathname, allMenus, source, updateBreadcrumbs]); -}; \ No newline at end of file +}; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index ad9680d3..4df98709 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -115,6 +115,10 @@ export const en = { ontology: 'Ontology Engineering', prompt: 'Prompt Engineering', skills: 'Skill Library', + workbench: 'Workbench', + memoryRelated: 'Memory-Related', + advancedSettings: 'Advanced Settings', + promptHistory: 'My history', }, dashboard: { total_models: 'Available Models', @@ -185,19 +189,14 @@ export const en = { extractMemoryContent: 'Extract Memory Content', createNewMemorySummary: 'Create New Memory Entry', - createNewApplication: 'Create New Application', - createNewApplicationDesc: 'Build an app in just 3 minutes with zero-code drag-and-drop.', - + createNewApplicationDesc: 'Build an app in 3 minutes, no code.', createNewKnowledge: 'Create New Knowledge', - createNewKnowledgeDesc: 'Transform your data into a fully searchable, dedicated knowledge base in seconds.', - + createNewKnowledgeDesc: 'Create a searchable knowledge base instantly.', memoryConversation: 'Memory Conversation', - memoryConversationDesc: 'The more you use it, the better AI understands you.', + memoryConversationDesc: 'The more you use it, the better AI knows you.', helpCenter: 'Help Center', - helpCenterDesc: 'One-stop support to answer your questions and get you started fast.', - memorySummary: 'View Memory Summary', - memorySummaryDesc: 'View Memory Summary Report', + helpCenterDesc: 'One place to get help and start fast.', activityEmpty: 'There is currently no memory activity', tagEmpty: 'There are no tag records at the moment~', @@ -214,7 +213,12 @@ export const en = { dialogue: 'Dialogue', chunk: 'Chunk', statement: 'Statement', - entity: 'Entity' + entity: 'Entity', + + apiCallTrends: 'Api Call Trends', + total_calls: 'Total API Calls', + app_calls: 'Application API Calls', + service_calls: 'Service API Calls', }, table: { totalRecords: 'Total {{total}} Articles' @@ -1041,7 +1045,7 @@ export const en = { forgetting_interval_hours: 'Forgetting Interval Hours' }, application: { - searchPlaceholder: 'Search for applications or clusters', + searchPlaceholder: 'Search for applications', createApplication: 'Create Application', type: 'Type', source: 'Source', @@ -1052,6 +1056,7 @@ export const en = { applicationName: 'Application Name', applicationIcon: 'Application Icon', applicationType: 'Application Type', + allType: 'All types', agent: 'Agent', agentDesc: 'Create a single intelligent agent', @@ -1117,6 +1122,7 @@ export const en = { dialogueHistoricalMemory: 'Conversation History Memory', dialogueHistoricalMemoryDesc: 'Enable this to select memory content from memory management.', toolConfiguration: 'Tool Configuration', + toolManagement: 'Tool Management', webSearch: 'Web Search', webSearchDesc: 'Allow the Agent to access the Internet for real-time search', codeExecutor: 'Code Executor', @@ -1126,8 +1132,8 @@ export const en = { variableConfiguration: 'Variable Configuration', selectMemoryContent: 'Select Memory Content', selectMemoryContentDesc: 'From Memory Management Select the memory content to be used in the conversation', - VariableManagement: 'Variable Management', - VariableManagementDesc: 'Configure the available variables for the Agent', + variableManagement: 'Variable Management', + variableManagementDesc: 'Configure the available variables for the Agent', addVariables: 'Add Variables', variablesEmpty: 'There are currently no variables available', debuggingEmpty: 'No models available for debugging.', @@ -1172,7 +1178,7 @@ export const en = { VersionInformation: 'Version Information', publishedOn: 'Published on', publisher: 'Published by', - DetailsOfVersion: 'Version Details: {{version}}', + detailsOfVersion: 'Version Details: {{version}}', exportDSLFile: 'Export DSL file', willRollToThisVersion: 'Will roll to this version', share: 'Share', @@ -1370,7 +1376,8 @@ export const en = { gotoList: 'Return to Application List', gotoDetail: 'View Details', dify: 'Dify', - pleaseUploadFile: 'Please upload workflow file', + pleaseUploadFile: 'Please upload workflow file', + promptOptimizationEmpty: 'The prompt words for conversation optimization will be displayed here', }, userMemory: { userMemory: 'User Memory', @@ -1970,10 +1977,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re answer: 'Answer', aiAndCognitiveProcessing: 'AI & Cognitive Processing', llm: 'Large Language Model (LLM)', - model_selection: 'Multi-Model Selection', - model_voting: 'Multi-Model Voting', 'knowledge-retrieval': 'Knowledge Retrieval (RAG)', - classification: 'Intelligent Classification', 'parameter-extractor': 'Parameter Extraction', flowControl: 'Flow Control', 'if-else': 'Conditional Branch', @@ -1983,7 +1987,6 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re 'cycle-start': '', break: 'Break Loop', assigner: 'Variable Assignment', - parallel: 'Parallel Execution', 'var-aggregator': 'Variable Aggregator', externalInteraction: 'External Interaction', "http-request": 'HTTP Request', @@ -1993,20 +1996,6 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re cognitiveUpgrading: 'Cognitive Upgrading (Innovation)', 'memory-read': 'Memory Retrieval', 'memory-write': 'Memory Storage', - task_planning: 'Task Planning', - reasoning_control: 'Reasoning Control', - self_reflection: 'Self Reflection', - memory_enhancement: 'Memory Enhancement', - agentCollaborationNode: 'Agent Collaboration Nodes', - agent_scheduling: 'Agent Scheduling', - agent_collaboration: 'Agent Collaboration', - agent_arbitration: 'Agent Arbitration', - safetyAndCompliance: 'Safety & Compliance', - sensitive_detection: 'Sensitive Detection', - output_audit: 'Output Audit', - evolutionAndGovernance: 'Evolution & Governance', - self_optimization: 'Self Optimization', - process_evolution: 'Process Evolution', unknown: 'Unknown Node', clickToConfigure: 'Click to configure node parameters', @@ -2029,6 +2018,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re inner: 'Built-in', messagesPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert', vision: 'Vision', + parameterSettings: 'Parameter Settings', }, start: { variables: 'Input Fields', @@ -2115,7 +2105,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re "eq": 'Is', "ne": 'Is Not', }, - else_desc: 'Used to define the logic that should be executed when the if condition is not met.' + else_desc: 'Used to define the logic that should be executed when the if condition is not met.', + unset: 'Condition Not Set', + set: 'Set', }, 'http-request': { auth: 'Authentication', @@ -2151,7 +2143,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re categories: 'Categories', user_supplement_prompt: 'Instruction', class_name: 'Classification', - addClassName: 'Add Classification' + addClassName: 'Add Classification', + unset: 'Classification Not Set', + set: 'Set', }, loop: { cycle_vars: 'Loop Variables', @@ -2640,8 +2634,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re file: 'Import File', }, prompt: { + promptDesc: 'Input your original prompt, and AI will help you refine it into a more professional version', + chatTitle: 'Multi-turn dialogue', editor: 'Prompt Generator', history: 'My History', + historyDesc: 'View and manage your prompt optimization history', historySearchPlaceholder: 'Search by name', model: 'Model', you: 'You', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index c4d2df71..1808c18a 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -115,6 +115,10 @@ export const zh = { ontology: '本体工程', prompt: '提示词工程', skills: '技能库', + workbench: '工作台', + memoryRelated: '记忆相关', + advancedSettings: '高级设置', + promptHistory: '我的历史', }, knowledgeBase: { home: '首页', @@ -427,7 +431,7 @@ export const zh = { } }, application: { - searchPlaceholder: '搜索应用或集群', + searchPlaceholder: '搜索应用', createApplication: '创建应用', type: '类型', source: '来源', @@ -438,6 +442,7 @@ export const zh = { applicationName: '应用名称', applicationIcon: '应用图标', applicationType: '应用类型', + allType: '全部类型', agent: 'Agent', agentDesc: '创建单个智能代理', @@ -502,6 +507,7 @@ export const zh = { dialogueHistoricalMemory: '对话历史记忆', dialogueHistoricalMemoryDesc: '激活后,可以选择记忆管理中的记忆内容', toolConfiguration: '工具配置', + toolManagement: '工具管理', webSearch: '网络搜索', webSearchDesc: '允许Agent访问互联网进行实时搜索', codeExecutor: '代码执行器', @@ -511,8 +517,8 @@ export const zh = { variableConfiguration: '变量配置', selectMemoryContent: '选择记忆内容', selectMemoryContentDesc: '从记忆管理中选择要在对话中使用的记忆内容', - VariableManagement: '变量管理', - VariableManagementDesc: '配置Agent可用的变量', + variableManagement: '变量管理', + variableManagementDesc: '配置Agent可用的变量', addVariables: '添加变量', variablesEmpty: '目前没有可用的变量', debuggingEmpty: '目前没有可用的调试模型', @@ -557,7 +563,7 @@ export const zh = { VersionInformation: '版本信息', publishedOn: '发布于', publisher: '发布者', - DetailsOfVersion: '{{version}}版本详情', + detailsOfVersion: '{{version}} 版本详情', exportDSLFile: '导出DSL文件', willRollToThisVersion: '将回滚到此版本', share: '分享', @@ -755,6 +761,7 @@ export const zh = { gotoDetail: '查看详情', dify: 'Dify', pleaseUploadFile: '请上传工作流文件', + promptOptimizationEmpty: '对话优化提示词将显示在这里', }, table: { totalRecords: '共 {{total}} 条记录' @@ -828,19 +835,14 @@ export const zh = { extractMemoryContent: '提取记忆内容', createNewMemorySummary: '创建新记忆条目', - createNewApplication: '创建新应用', - createNewApplicationDesc: '零代码拖拽3分钟创应用', - + createNewApplicationDesc: '零代码,3 分钟搭建应用', createNewKnowledge: '创建知识库', - createNewKnowledgeDesc: '秒变可搜索的专属知识库', - + createNewKnowledgeDesc: '秒级生成可搜索知识库', memoryConversation: '记忆对话', - memoryConversationDesc: '让AI越用越懂你', + memoryConversationDesc: '越用越懂你的 AI', helpCenter: '帮助中心', - helpCenterDesc: '一站式解决疑问快速上手', - memorySummary: '查看记忆摘要', - memorySummaryDesc: '查看记忆摘要报告', + helpCenterDesc: '一站式上手与支持', activityEmpty: '目前没有记忆活动', tagEmpty: '目前没有标签记录~', @@ -857,7 +859,12 @@ export const zh = { dialogue: '对话', chunk: '分块', statement: '语句', - entity: '实体' + entity: '实体', + + apiCallTrends: 'Api调用趋势', + total_calls: 'Api 总调用量', + app_calls: '应用Api调用量', + service_calls: '服务Api调用量', }, header: { userInfo: '用户信息', @@ -1966,10 +1973,7 @@ export const zh = { answer: '回复(Answer)', aiAndCognitiveProcessing: 'AI与认知处理', llm: '大语言模型 (LLM)', - model_selection: '多模型选择', - model_voting: '多模型投票', 'knowledge-retrieval': '知识检索 (RAG)', - classification: '智能分类', 'parameter-extractor': '参数提取', flowControl: '流程控制', 'if-else': '条件分支', @@ -1979,7 +1983,6 @@ export const zh = { 'cycle-start': '', break: '退出循环', assigner: '变量赋值', - parallel: '并行执行', 'var-aggregator': '变量聚合器', externalInteraction: '外部交互', "http-request": 'HTTP请求', @@ -1989,20 +1992,6 @@ export const zh = { cognitiveUpgrading: '认知升级(创新)', 'memory-read': '记忆提取', 'memory-write': '记忆储存', - task_planning: '任务规划', - reasoning_control: '推理控制', - self_reflection: '自我反思', - memory_enhancement: '记忆增强', - agentCollaborationNode: 'Agent 协作节点', - agent_scheduling: 'Agent 调度', - agent_collaboration: 'Agent 协同', - agent_arbitration: 'Agent 仲裁', - safetyAndCompliance: '安全与合规', - sensitive_detection: '敏感识别', - output_audit: '输出审计', - evolutionAndGovernance: '演化与治理', - self_optimization: '自我优化', - process_evolution: '流程演化', unknown: '未知节点', clickToConfigure: '点击配置节点参数', @@ -2025,6 +2014,7 @@ export const zh = { inner: '内置', messagesPlaceholder: '在此处编写提示,输入“{”插入变量,输入“insert”插入', vision: '视觉', + parameterSettings: '参数设置', }, start: { variables: '输入字段', @@ -2111,7 +2101,9 @@ export const zh = { "eq": '是', "ne": '不是', }, - else_desc: '用于定义当 if 条件不满足时应执行的逻辑。' + else_desc: '用于定义当 if 条件不满足时应执行的逻辑。', + unset: '条件未设置', + set: '已设置', }, 'http-request': { auth: '鉴权', @@ -2147,7 +2139,9 @@ export const zh = { categories: '分类', user_supplement_prompt: '指令', class_name: '分类', - addClassName: '添加分类' + addClassName: '添加分类', + unset: '分类未设置', + set: '已设置', }, loop: { cycle_vars: '循环变量', @@ -2640,8 +2634,10 @@ export const zh = { file: '导入文件', }, prompt: { + promptDesc: 'Input your original prompt, and AI will help you refine it into a more professional version', editor: '提示词生成器', history: '我的历史', + historyDesc: 'View and manage your prompt optimization history', historySearchPlaceholder: '按名称搜索', model: '模型', you: '你', diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 42e0106a..92f7a5cf 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -88,6 +88,7 @@ const componentMap: Record>> = Ontology: lazy(() => import('@/views/Ontology')), OntologyDetail: lazy(() => import('@/views/Ontology/pages/Detail')), Prompt: lazy(() => import('@/views/Prompt')), + PromptHistory: lazy(() => import('@/views/Prompt/pages/History')), Skills: lazy(() => import('@/views/Skills')), SkillConfig: lazy(() => import('@/views/Skills/pages/SkillConfig')), Jump: lazy(() => import('@/views/JumpPage')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index ea137bd4..fd520781 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -37,6 +37,7 @@ { "path": "/space-config", "element": "SpaceConfig" }, { "path": "/ontology", "element": "Ontology" }, { "path": "/prompt", "element": "Prompt" }, + { "path": "/prompt/history", "element": "PromptHistory" }, { "path": "/no-permission", "element": "NoPermission" }, { "path": "/*", "element": "NotFound" } ] diff --git a/web/src/store/menu.json b/web/src/store/menu.json index 4f53ab50..091be7ea 100644 --- a/web/src/store/menu.json +++ b/web/src/store/menu.json @@ -124,56 +124,40 @@ ], "space": [ { - "id": 4, + "id": 1, "parent": 0, - "code": "dashboard", - "label": "记忆看板", - "i18nKey": "menu.home", + "code": "workbench", + "label": "workbench", + "i18nKey": "menu.workbench", "path": "/", "enable": true, "display": true, "level": 1, "sort": 0, - "subs": null - }, - { - "id": 5, - "parent": 0, - "code": "application", - "label": "应用管理", - "i18nKey": "menu.applicationManagement", - "path": "/application", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - }, - { - "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, + "type": "group", "subs": [ { - "id": 61, - "parent": 6, - "code": "knowledgePrivate", - "label": "Private", - "i18nKey": "menu.knowledgePrivate", - "path": "/knowledge-base/:knowledgeBaseId/private", + "id": 11, + "parent": 1, + "code": "dashboard", + "label": "记忆看板", + "i18nKey": "menu.home", + "path": "/", "enable": true, - "display": false, + "display": true, + "level": 1, + "sort": 0, + "subs": null + }, + { + "id": 12, + "parent": 1, + "code": "application", + "label": "应用管理", + "i18nKey": "menu.applicationManagement", + "path": "/application", + "enable": true, + "display": true, "level": 1, "sort": 0, "icon": null, @@ -181,261 +165,338 @@ "subs": null }, { - "id": 62, - "parent": 6, - "code": "knowledgeShare", - "label": "Share", - "i18nKey": "menu.knowledgeShare", - "path": "/knowledge-base/:knowledgeBaseId/share", + "id": 13, + "parent": 1, + "code": "knowledge", + "label": "知识库", + "i18nKey": "menu.knowledgeManagement", + "path": "/knowledge-base", "enable": true, - "display": false, + "display": true, "level": 1, "sort": 0, "icon": null, "iconActive": null, - "subs": null - }, - { - "id": 63, - "parent": 6, - "code": "knowledgeCreateDataset", - "label": "CreateDataset", - "i18nKey": "menu.knowledgeCreateDataset", - "path": "/knowledge-base/:knowledgeBaseId/create-dataset", - "enable": true, - "display": false, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - }, - { - "id": 64, - "parent": 6, - "code": "knowledgeDocumentDetails", - "label": "DocumentDetails", - "i18nKey": "menu.knowledgeDocumentDetails", - "path": "/knowledge-base/:knowledgeBaseId/DocumentDetails", - "enable": true, - "display": false, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - } - ] - }, - { - "id": 7, - "parent": 0, - "code": "memory", - "label": "记忆管理", - "i18nKey": "menu.memoryManagement", - "path": "/memory", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "subs": [ - { - "id": 71, - "parent": 7, - "code": "forgettingEngine", - "label": "遗忘引擎", - "i18nKey": "menu.forgettingEngine", - "path": "/forgetting-engine/:id", - "enable": true, - "display": false, - "level": 1, - "sort": 0, - "subs": null - }, - { - "id": 72, - "parent": 7, - "code": "memoryExtractionEngine", - "label": "记忆萃取引擎", - "i18nKey": "menu.memoryExtractionEngine", - "path": "/memory-extraction-engine/:id", - "enable": true, - "display": false, - "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 - } - ] - }, - { - "id": 8, - "parent": 0, - "code": "userMemory", - "label": "", - "i18nKey": "menu.userMemory", - "path": "/user-memory", - "enable": true, - "display": true, - "level": 1, - "sort": 1, - "menuDesc": "管理用户记忆", - "subs": [ - { - "id": 81, - "parent": 8, - "code": "userMemoryDetail", - "label": "记忆详情", - "i18nKey": "menu.userMemoryDetail", - "path": "/user-memory/neo4j/:id", - "enable": true, - "display": false, - "level": 2, - "sort": 0, "subs": [ { - "id": 811, - "parent": 81, - "code": "statementDetail", - "label": "记忆详情", - "i18nKey": "menu.statementDetail", - "path": "/statement/:id", + "id": 131, + "parent": 13, + "code": "knowledgePrivate", + "label": "Private", + "i18nKey": "menu.knowledgePrivate", + "path": "/knowledge-base/:knowledgeBaseId/private", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, + { + "id": 132, + "parent": 13, + "code": "knowledgeShare", + "label": "Share", + "i18nKey": "menu.knowledgeShare", + "path": "/knowledge-base/:knowledgeBaseId/share", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, + { + "id": 133, + "parent": 13, + "code": "knowledgeCreateDataset", + "label": "CreateDataset", + "i18nKey": "menu.knowledgeCreateDataset", + "path": "/knowledge-base/:knowledgeBaseId/create-dataset", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, + { + "id": 134, + "parent": 13, + "code": "knowledgeDocumentDetails", + "label": "DocumentDetails", + "i18nKey": "menu.knowledgeDocumentDetails", + "path": "/knowledge-base/:knowledgeBaseId/DocumentDetails", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + } + ] + }, + { + "id": 14, + "parent": 1, + "code": "prompt", + "label": "提示词", + "i18nKey": "menu.prompt", + "path": "/prompt", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": [ + { + "id": 141, + "parent": 14, + "code": "promptHistory", + "label": "promptHistory", + "i18nKey": "menu.promptHistory", + "path": "/prompt/history", "enable": true, "display": false, "level": 3, "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + } + ] + } + ] + }, + { + "id": 2, + "parent": 0, + "code": "memoryRelated", + "label": "memoryRelated", + "i18nKey": "menu.memoryRelated", + "path": "/", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "type": "group", + "subs": [ + { + "id": 21, + "parent": 2, + "code": "memory", + "label": "记忆管理", + "i18nKey": "menu.memoryManagement", + "path": "/memory", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "subs": [ + { + "id": 211, + "parent": 21, + "code": "forgettingEngine", + "label": "遗忘引擎", + "i18nKey": "menu.forgettingEngine", + "path": "/forgetting-engine/:id", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "subs": null + }, + { + "id": 212, + "parent": 21, + "code": "memoryExtractionEngine", + "label": "记忆萃取引擎", + "i18nKey": "menu.memoryExtractionEngine", + "path": "/memory-extraction-engine/:id", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "subs": null + }, + { + "id": 213, + "parent": 21, + "code": "emotionEngine", + "label": "情感引擎", + "i18nKey": "menu.emotionEngine", + "path": "/emotion-engine/:id", + "enable": true, + "display": false, + "level": 1, + "sort": 0, + "subs": null + }, + { + "id": 214, + "parent": 21, + "code": "selfReflectionEngine", + "label": "反思引擎", + "i18nKey": "menu.selfReflectionEngine", + "path": "/reflection-engine/:id", + "enable": true, + "display": false, + "level": 1, + "sort": 0, "subs": null } ] }, { - "id": 81, - "parent": 8, - "code": "userMemoryDetail", - "label": "记忆详情", - "i18nKey": "menu.userMemoryDetail", - "path": "/user-memory/:id", + "id": 22, + "parent": 2, + "code": "userMemory", + "label": "", + "i18nKey": "menu.userMemory", + "path": "/user-memory", "enable": true, - "display": false, - "level": 2, - "sort": 0 + "display": true, + "level": 1, + "sort": 1, + "menuDesc": "管理用户记忆", + "subs": [ + { + "id": 221, + "parent": 22, + "code": "userMemoryDetail", + "label": "记忆详情", + "i18nKey": "menu.userMemoryDetail", + "path": "/user-memory/neo4j/:id", + "enable": true, + "display": false, + "level": 2, + "sort": 0, + "subs": [ + { + "id": 2211, + "parent": 221, + "code": "statementDetail", + "label": "记忆详情", + "i18nKey": "menu.statementDetail", + "path": "/statement/:id", + "enable": true, + "display": false, + "level": 3, + "sort": 0, + "subs": null + } + ] + }, + { + "id": 222, + "parent": 22, + "code": "userMemoryDetail", + "label": "记忆详情", + "i18nKey": "menu.userMemoryDetail", + "path": "/user-memory/:id", + "enable": true, + "display": false, + "level": 2, + "sort": 0 + } + ] + }, + { + "id": 23, + "parent": 2, + "code": "ontology", + "label": "本体工程", + "i18nKey": "menu.ontology", + "path": "/ontology", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, + { + "id": 24, + "parent": 2, + "code": "memoryConversation", + "label": "记忆验证", + "i18nKey": "menu.memoryConversation", + "path": "/memory-conversation", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null } ] }, { - "id": 21, + "id": 3, "parent": 0, - "code": "ontology", - "label": "本体工程", - "i18nKey": "menu.ontology", - "path": "/ontology", + "code": "advancedSettings", + "label": "advancedSettings", + "i18nKey": "menu.advancedSettings", + "path": "/", "enable": true, "display": true, "level": 1, "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - }, - { - "id": 10, - "parent": 0, - "code": "memoryConversation", - "label": "记忆验证", - "i18nKey": "menu.memoryConversation", - "path": "/memory-conversation", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "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 - }, - { - "id": 20, - "parent": 0, - "code": "prompt", - "label": "提示词", - "i18nKey": "menu.prompt", - "path": "/prompt", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - }, - { - "id": 19, - "parent": 0, - "code": "member", - "label": "成员管理", - "i18nKey": "menu.memberManagement", - "path": "/member", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null - }, - { - "id": 12, - "parent": 0, - "code": "spaceConfig", - "label": "空间配置", - "i18nKey": "menu.spaceConfig", - "path": "/space-config", - "enable": true, - "display": true, - "level": 1, - "sort": 0, - "icon": null, - "iconActive": null, - "subs": null + "type": "group", + "subs": [ + { + "id": 31, + "parent": 3, + "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 + }, + { + "id": 32, + "parent": 3, + "code": "member", + "label": "成员管理", + "i18nKey": "menu.memberManagement", + "path": "/member", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + }, + { + "id": 33, + "parent": 3, + "code": "spaceConfig", + "label": "空间配置", + "i18nKey": "menu.spaceConfig", + "path": "/space-config", + "enable": true, + "display": true, + "level": 1, + "sort": 0, + "icon": null, + "iconActive": null, + "subs": null + } + ] } ] } \ No newline at end of file diff --git a/web/src/store/menu.ts b/web/src/store/menu.ts index 2aa5df3a..a4ee3610 100644 --- a/web/src/store/menu.ts +++ b/web/src/store/menu.ts @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-02 16:33:34 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-02 16:33:34 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-04 10:31:14 */ /** * Menu Store @@ -42,6 +42,7 @@ export interface MenuItem { master?: string | null; disposable?: boolean; appSystem?: string | null; + type?: 'group' | string; subs?: MenuItem[] | null; onClick?: (e?: React.MouseEvent) => void | boolean; } @@ -89,20 +90,27 @@ export const useMenu = create((set, get) => ({ const { allMenus } = get() const menus = allMenus[source] || [] let result: MenuItem[] = [] - - console.log('updateBreadcrumbs paths:', paths); - + + /** Flatten group menus so top-level items are always real menu entries */ + const flatMenus = menus.flatMap(m => m.type === 'group' ? (m.subs || []) : [m]); + + const findById = (list: MenuItem[], id: string) => list.find(m => `${m.id}` === id); + /** Find menu by id in both original menus and flatMenus (handles group ids) */ + const findMenuById = (id: string) => findById(menus, id) || findById(flatMenus, id); + const pathMatches = (pattern: string, path: string) => { + const n = pattern[0] !== '/' ? '/' + pattern : pattern; + if (n === path) return true; + if (n.includes(':')) return new RegExp('^' + n.replace(/:[\w-]+/g, '[^/]+') + '$').test(path); + return false; + }; + if (paths.length === 3) { - /** Three-level menu: [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); + /** Three-level: [subSubPath, subId, menuId] */ + const matchedMenu = findMenuById(paths[2]); + if (matchedMenu?.subs) { + const matchedSub = findById(matchedMenu.subs, paths[1]); + if (matchedSub?.subs) { + const matchedSubSub = matchedSub.subs.find(s => s.path === paths[0] || pathMatches(s.path || '', paths[0])); if (matchedSubSub) { result = [ { ...matchedMenu, subs: null }, @@ -113,23 +121,16 @@ export const useMenu = create((set, get) => ({ } } } else { - /** Original logic for one-level and two-level menus */ - const matchedMenu: MenuItem | undefined = menus.find(menu => menu.path === paths[paths.length - 1] || `${menu.id}` === paths[1]); - + const matchedMenu = flatMenus.find(m => m.path === paths[0] || `${m.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]); + let matchedSubMenu: MenuItem | undefined; + if (paths.length > 1 && matchedMenu.subs?.length) { + matchedSubMenu = matchedMenu.subs.find(m => m.path === paths[0]); } - result = [ - { ...matchedMenu, subs: null }, - matchedSubMenu - ].filter(item => item !== undefined) as MenuItem[] - } else { - result = [] as MenuItem[] + result = [{ ...matchedMenu, subs: null }, matchedSubMenu].filter(Boolean) as MenuItem[]; } } - + const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result } set({ allBreadcrumbs }) localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs)) diff --git a/web/src/styles/antdThemeConfig.ts b/web/src/styles/antdThemeConfig.ts index 1d281730..e7cedf2b 100644 --- a/web/src/styles/antdThemeConfig.ts +++ b/web/src/styles/antdThemeConfig.ts @@ -3,87 +3,99 @@ import type { ThemeConfig } from 'antd'; // 浅色主题配置 export const lightTheme: ThemeConfig = { token: { - colorPrimary: '#155EEF', + colorPrimary: '#171719', colorBgBase: '#ffffff', - colorTextBase: '#212332', + colorTextBase: '#171719', colorBorder: '#DFE4ED', colorBgLayout: '#ffffff', colorBgContainer: '#ffffff', - colorText: '#212332', + colorText: '#171719', colorTextSecondary: '#6b7280', - borderRadius: 6, + borderRadius: 8, colorSplit: '#DFE4ED', colorBorderBg: '#DFE4ED', - colorBgContainerDisabled: '#F6F8FC', + colorBgContainerDisabled: '#F6F6F6', colorTextDisabled: '#5B6167', // Card 用到 borderRadiusLG: 12, + borderRadiusSM: 8, colorBorderSecondary: '#DFE4ED', // colorBgContainer: '#FBFDFF', colorError: '#FF5D34', sizeSM: 12, fontSizeSM: 12, + boxShadow: 'none', }, components: { Layout: { headerBg: 'transparent', - bodyBg: '#FBFDFF', - siderBg: 'transparent', - headerPadding: '16px 46px 16px 21px', + bodyBg: '#EEEFF4', + siderBg: '#FAFCFF', + headerPadding: '0 24px 0 20px', headerHeight: 64, headerColor: '#212332', }, Menu: { - itemColor: '#5B6167', - itemSelectedColor: '#212332', - subMenuItemSelectedColor: '#212332', + fontSize: 13, + itemColor: '#171719', + itemSelectedColor: '#FFFFFF', + subMenuItemSelectedColor: '#FFFFFF', - itemHoverColor: '#212332', - itemHoverBg: '#FFFFFF', + itemHoverColor: '#171719', + itemHoverBg: 'rgba(223,228,237,0.5)', itemBg: 'transparent', - itemSelectedBg: '#FFFFFF', + itemSelectedBg: '#171719', subMenuItemBg: 'transparent', - itemPaddingInline: 12, - - itemHeight: 32, + itemPaddingInline: 10, + + itemHeight: 38, itemMarginBlock: 8, horizontalLineHeight: 32, itemMarginInline: 12, - itemBorderRadius: 6, + itemBorderRadius: 8, iconSize: 16, - iconMarginInlineEnd: 8, + iconMarginInlineEnd: 10, collapsedIconSize: 12, collapsedWidth: '64px', popupBg: '#FBFDFF', + groupTitleFontSize: 12, + groupTitleColor: '#5B6167', + groupTitleLineHeight: '17px', }, Button: { - defaultColor: '#5B6167', + defaultColor: '#171719', defaultBorderColor: '#EBEBEB', defaultShadow: 'none', primaryShadow: 'none', - dangerShadow: 'none' + dangerShadow: 'none', + defaultGhostBorderColor: '#EBEBEB', + defaultHoverColor: 'rgba(23, 23, 25, 0.7)', + defaultHoverBorderColor: 'rgba(23, 23, 25, 0.7)', }, Form: { - labelColor: '#212332', + labelColor: '#171719', + itemMarginBottom: 16, }, Slider: { // dotSize: 10, - controlSize: 8, - railSize: 8, + controlSize: 6, + railSize: 6, handleSize: 10, handleSizeHover: 10, - handleColor: '#155EEF', - trackBg: '#155EEF', - railBg: '#E1E2E7', + handleColor: '#171719', + handleActiveOutlineColor: '#171719', + trackBg: '#171719', + railBg: '#EBEBEB', }, Table: { - borderColor: '#DFE4ED', - headerBg: '#FBFDFF', + headerColor: '#5B6167', + borderColor: '#EBEBEB', + headerBg: '#FFFFFF', rowHoverBg: '#F0F3F8', rowSelectedBg: '#E9F1FF', rowSelectedHoverBg: '#F0F3F8', @@ -95,19 +107,36 @@ export const lightTheme: ThemeConfig = { }, Breadcrumb: { itemColor: '#5B6167', - lastItemColor: '#212332', + lastItemColor: '#171719', linkColor: '#5B6167', - linkHoverColor: '#212332', + linkHoverColor: '#171719', + fontSize: 18, }, Input: { inputFontSizeSM: 12, - controlHeightSM: 26 + controlHeightSM: 26, + activeShadow: 'none', + }, + InputNumber: { + activeShadow: 'none', }, Select: { - lineHeightSM: 26 + lineHeightSM: 26, }, Upload: { pictureCardSize: 96, + }, + Switch: { + trackHeight: 24, + trackHeightSM: 18, + handleSize: 20, + handleSizeSM: 14, + innerMinMarginSM: 8, + innerMaxMarginSM: 27, + }, + Cascader: { + optionSelectedBg: '#F6F6F6', + optionSelectedColor: '#212332' } } }; \ No newline at end of file diff --git a/web/src/styles/common.css b/web/src/styles/common.css new file mode 100644 index 00000000..8645a161 --- /dev/null +++ b/web/src/styles/common.css @@ -0,0 +1,5 @@ +.rb-border { border: 1px solid #DFE4ED; } +.rb-border-t { border-top: 1px solid #DFE4ED; } +.rb-border-r { border-right: 1px solid #DFE4ED; } +.rb-border-b { border-bottom: 1px solid #DFE4ED; } +.rb-border-l { border-left: 1px solid #DFE4ED; } \ No newline at end of file diff --git a/web/src/styles/index.css b/web/src/styles/index.css index d937396a..479740d9 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -1,5 +1,65 @@ @import "tailwindcss" prefix(rb); @plugin "@tailwindcss/typography"; +@import "./common.css"; + +@font-face { + font-family: 'MiSans-Bold'; + src: url('@/assets/font/MiSans/MiSans-Bold.woff2') format('woff2'); + font-weight: 700; + font-style: normal; +}@font-face { + font-family: 'MiSans-Demibold'; + src: url('@/assets/font/MiSans/MiSans-Demibold.woff2') format('woff2'); + font-weight: 700; + font-style: normal; +}@font-face { + font-family: 'MiSans-ExtraLight'; + src: url('@/assets/font/MiSans/MiSans-ExtraLight.woff2') format('woff2'); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Heavy'; + src: url('@/assets/font/MiSans/MiSans-Heavy.woff2') format('woff2'); + font-weight: 900; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Light'; + src: url('@/assets/font/MiSans/MiSans-Light.woff2') format('woff2'); + font-weight: 300; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Medium'; + src: url('@/assets/font/MiSans/MiSans-Medium.woff2') format('woff2'); + font-weight: 500; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Normal'; + src: url('@/assets/font/MiSans/MiSans-Normal.woff2') format('woff2'); + font-weight: 500; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Regular'; + src: url('@/assets/font/MiSans/MiSans-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Semibold'; + src: url('@/assets/font/MiSans/MiSans-Semibold.woff2') format('woff2'); + font-weight: 600; + font-style: normal; +} +@font-face { + font-family: 'MiSans-Thin'; + src: url('@/assets/font/MiSans/MiSans-Thin.woff2') format('woff2'); + font-weight: 100; + font-style: normal; +} html, body, #root { height: 100%; @@ -31,7 +91,7 @@ body { .ant-menu-light>.ant-menu .ant-menu-item-selected, .ant-menu-light:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):hover, .ant-menu-light>.ant-menu:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):hover { - box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.15); + box-shadow: none; } .ant-menu-light .ant-menu-item-selected, .ant-menu-light>.ant-menu .ant-menu-item-selected { @@ -47,8 +107,14 @@ body { background: #FFFFFF; color: #212332; } +.ant-menu-inline-collapsed>.ant-menu-item, +.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item, +.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title, +.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title { + padding-inline: calc(50% - 6px - 14px); +} .ant-slider .ant-slider-handle::after { - background-color: #155EEF; + background-color: #171719; width: 12px; height: 12px; } @@ -56,6 +122,34 @@ body { width: 12px; height: 12px; } +.ant-slider-horizontal .ant-slider-handle { + inset-block-start: 3px; +} +.ant-slider.small { + margin: 0; + padding: 8px; +} +.ant-slider-horizontal.small .ant-slider-handle { + inset-block-start: 5px; +} +.ant-slider.small .ant-slider-handle::after { + background-color: #171719; + width: 10px; + height: 10px; +} +.ant-slider.small .ant-slider-handle::before { + width: 10px; + height: 10px; +} +.ant-slider.small .ant-slider-handle:hover::after, +.ant-slider.small .ant-slider-handle:active::after, +.ant-slider.small .ant-slider-handle:focus::after { + outline: 2px solid #171719; +} +.ant-slider.small .ant-slider-rail, +.ant-slider.small .ant-slider-track { + border-radius: 3px; +} .ant-table-container { border: 1px solid #DFE4ED; border-radius: 8px; @@ -83,6 +177,16 @@ body { .ant-table-wrapper .ant-table-thead>tr>td { font-weight: 500; } +.ant-table-wrapper .ant-table.ant-table-small .ant-table-title, +.ant-table-wrapper .ant-table.ant-table-small .ant-table-footer, +.ant-table-wrapper .ant-table.ant-table-small .ant-table-cell, +.ant-table-wrapper .ant-table.ant-table-small .ant-table-thead>tr>th, +.ant-table-wrapper .ant-table.ant-table-small .ant-table-tbody>tr>th, +.ant-table-wrapper .ant-table.ant-table-small .ant-table-tbody>tr>td, +.ant-table-wrapper .ant-table.ant-table-small tfoot>tr>th, +.ant-table-wrapper .ant-table.ant-table-small tfoot>tr>td { + padding: 12px 16px; +} .ant-table-wrapper .ant-table-pagination.ant-pagination { margin: 24px 0 32px 0; } @@ -165,13 +269,7 @@ body { .infinite-scroll-component { overflow-x: hidden !important; } -.ant-slider-horizontal .ant-slider-handle { - inset-block-start: 6px; -} -::-webkit-scrollbar-track { - background: transparent !important; -} .ant-breadcrumb a:hover { background-color: transparent; } @@ -183,6 +281,34 @@ body { } .ͼ2 .cm-gutters { - background-color: #FFFFFF; + background-color: transparent; border: none; +} +::-webkit-scrollbar { + width: 6px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #DFE4ED; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #DFE4ED; +} + +.ant-select-focused.ant-select-outlined:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer) .ant-select-selector { + box-shadow: none; +} +.ant-select-dropdown .ant-select-item { + color: #212332; +} +.ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { + color: #212332; + background: #F6F6F6; } \ No newline at end of file diff --git a/web/src/views/Home/components/ApiLineCard.tsx b/web/src/views/Home/components/ApiLineCard.tsx new file mode 100644 index 00000000..89cf6eb1 --- /dev/null +++ b/web/src/views/Home/components/ApiLineCard.tsx @@ -0,0 +1,77 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 17:17:05 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-10 11:35:52 + */ +/** + * Line Chart Card Component + * Displays time-series data with ECharts line chart + * Supports multiple series and date range selection + */ + +import { type FC, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Select } from 'antd' +import dayjs from 'dayjs' + +import Card from './Card' +import { getWorkspaceApiStatistics } from '@/api/application' +import LineChart, { type ChartData } from '@/components/Charts/LineChart' + +const seriesList = ['total_calls', 'app_calls', 'service_calls'] +const ApiLineCard: FC = () => { + const { t } = useTranslation() + const options = [ + { label: t('dashboard.lastDays', { days: 7 }), value: 7 }, + { label: t('dashboard.lastDays', { days: 30 }), value: 30 }, + { label: t('dashboard.lastDays', { days: 90 }), value: 90 }, + { label: t('dashboard.lastHalfYear'), value: 180 }, + { label: t('dashboard.lastYear'), value: 365 }, + ] + const [chartData, setChartData] = useState([]) + const [query, setQuery] = useState(7) + + useEffect(() => { + getWorkspaceApiStatistics({ + start_date: dayjs().subtract(query - 1, 'd').startOf('d').valueOf(), + end_date: dayjs().endOf('d').valueOf(), + }) + .then(res => { + setChartData(res as ChartData[]) + }) + }, [query]) + + /** Format series list for legend */ + const formatSeriesList = () => { + const list: Record = {} + seriesList.forEach(key => { + list[key] = t(`dashboard.${key}`) + }) + + return list + } + + return ( + setQuery(value)} + className="rb:w-35!" + /> + } + className={`rb:pb-6`} + > + + + ) +} + +export default ApiLineCard diff --git a/web/src/views/Home/components/Card.tsx b/web/src/views/Home/components/Card.tsx index d094dbb6..538fd616 100644 --- a/web/src/views/Home/components/Card.tsx +++ b/web/src/views/Home/components/Card.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:27:28 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:27:28 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-26 11:16:09 */ /** * Card Component @@ -21,15 +21,19 @@ interface CardProps { title: string; headerOperate?: ReactNode; className?: string; + bodyClassName?: string; } -const Card: FC = ({ children, title, headerOperate, className }) => { +const Card: FC = ({ children, title, headerOperate, className, bodyClassName }) => { return ( {children} diff --git a/web/src/views/Home/components/LineCard.tsx b/web/src/views/Home/components/LineCard.tsx index 4737b1f2..ac63b5a9 100644 --- a/web/src/views/Home/components/LineCard.tsx +++ b/web/src/views/Home/components/LineCard.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:17:05 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:18:32 + * @Last Modified time: 2026-02-10 11:59:10 */ /** * Line Chart Card Component @@ -10,21 +10,18 @@ * Supports multiple series and date range selection */ -import { type FC, useRef } from 'react' +import { type FC } from 'react' import { useTranslation } from 'react-i18next' import { Select } from 'antd' -import ReactEcharts from 'echarts-for-react'; -import * as echarts from 'echarts'; -import { formatDateTime } from '@/utils/format'; -import Empty from '@/components/Empty' import Card from './Card' +import AreaLineChart, { type ChartData } from '@/components/Charts/AreaLineChart'; /** * Component props */ interface LineCardProps { - chartData: Array>; + chartData: ChartData[]; limit: number; onChange: (value: string, type: string) => void; type: string; @@ -32,30 +29,8 @@ interface LineCardProps { seriesList: string[]; } -/** ECharts series configuration */ -const SeriesConfig = { - type: 'line', - stack: 'Total', - smooth: true, - lineStyle: { - width: 3 - }, - showSymbol: false, - label: { - show: true, - position: 'top' - }, - emphasis: { - focus: 'series' - }, - data: [220, 302, 181, 234, 210, 290, 150] -} -/** Chart color palette */ -const Colors = ['#FFB048', '#4DA8FF', '#155EEF'] - const LineCard: FC = ({ chartData, limit, onChange, type, className, seriesList }) => { const { t } = useTranslation() - const chartRef = useRef(null); const options = [ { label: t('dashboard.lastDays', { days: 7 }), value: 7 }, { label: t('dashboard.lastDays', { days: 30 }), value: 30 }, @@ -63,39 +38,15 @@ const LineCard: FC = ({ chartData, limit, onChange, type, classNa { label: t('dashboard.lastHalfYear'), value: 180 }, { label: t('dashboard.lastYear'), value: 365 }, ] - - /** Generate series data with gradient colors */ - const getSeries = () => { - const list = seriesList.map((key, index) => { - return { - ...SeriesConfig, - name: t(`dashboard.${key}`), - data: chartData.map(vo => vo[key]), - areaStyle: { - opacity: 0.8, - color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { - offset: 0, - color: Colors[index] - }, - { - offset: 1, - color: '#FFFFFF' - } - ]) - }, - } + /** Format series list for legend */ + const formatSeriesList = () => { + const list: Record = {} + seriesList.forEach(key => { + list[key] = t(`dashboard.${key}`) }) return list } - /** Format series list for legend */ - const formatSeriesList = () => { - return seriesList.map(key => ({ - ...SeriesConfig, - name: t(`dashboard.${key}`), - })) - } return ( = ({ chartData, limit, onChange, type, classNa