feat(web): Home page ui upgrade

This commit is contained in:
zhaoying
2026-03-07 12:20:55 +08:00
parent 0b3b241436
commit e2b6c713e7
22 changed files with 1016 additions and 1000 deletions

View File

@@ -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)
}

View File

@@ -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]);
};
};

View File

@@ -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',

View File

@@ -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: '你',

View File

@@ -88,6 +88,7 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
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')),

View File

@@ -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" }
]

View File

@@ -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
}
]
}
]
}

View File

@@ -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<MenuState>((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<MenuState>((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))

View File

@@ -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'
}
}
};

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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<ChartData[]>([])
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<string, string> = {}
seriesList.forEach(key => {
list[key] = t(`dashboard.${key}`)
})
return list
}
return (
<Card
title={t(`dashboard.apiCallTrends`)}
headerOperate={
<Select
value={query}
options={options}
onChange={(value) => setQuery(value)}
className="rb:w-35!"
/>
}
className={`rb:pb-6`}
>
<LineChart
chartData={chartData}
seriesList={formatSeriesList()}
height={239}
/>
</Card>
)
}
export default ApiLineCard

View File

@@ -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<CardProps> = ({ children, title, headerOperate, className }) => {
const Card: FC<CardProps> = ({ children, title, headerOperate, className, bodyClassName }) => {
return (
<RbCard
headerType="borderless"
title={title}
extra={headerOperate}
className={`rb:h-full! ${className}`}
variant="borderless"
className={`rb:h-full! rb:bg-[#FFFFFF]! ${className}`}
bodyClassName={bodyClassName}
headerClassName="rb:min-h-[58px]!"
>
{children}
</RbCard>

View File

@@ -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<Record<string, string | number>>;
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<LineCardProps> = ({ chartData, limit, onChange, type, className, seriesList }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(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<LineCardProps> = ({ 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<string, string> = {}
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 (
<Card
@@ -104,79 +55,18 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
<Select
value={limit}
options={options}
onChange={(value) => onChange(String(value), type)}
style={{ width: '150px' }}
onChange={(value) => onChange(String(value), type)}
className="rb:w-35!"
/>
}
className={`rb:pb-6 ${className}`}
>
{chartData && chartData.length > 0 ? (
<ReactEcharts
ref={chartRef}
option={{
color: Colors,
tooltip: {
trigger: 'axis',
extraCssText: 'box-shadow: 0px 2px 6px 0px rgba(33,35,50,0.16); border-radius: 8px;',
axisPointer: {
type: 'line',
crossStyle: {
color: '#5F6266',
},
lineStyle: {
color: '#5F6266',
},
label: {
show: false
}
},
},
legend: {
data: formatSeriesList(),
textStyle: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 16,
},
itemGap: 32,
padding: 0,
itemWidth: 26,
itemHeight: 10,
left: 'center'
},
grid: {
left: 4,
right: '3%',
bottom: 0,
containLabel: true
},
xAxis: {
type: 'category',
data: chartData.map(item => formatDateTime(item.created_at, 'DD/MM')),
boundaryGap: false,
},
yAxis: {
type: 'value',
axisLabel: {
color: '#A8A9AA',
fontFamily: 'PingFangSC, PingFang SC',
align: 'right',
lineHeight: 17,
},
axisLine: {
lineStyle: {
color: '#EBEBEB',
}
},
},
series: getSeries()
}}
style={{ height: '265px', width: '100%', minWidth: '100%', boxSizing: 'border-box' }}
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
/>
) : <Empty size={120} className="rb:mt-12 rb:mb-20.25" />}
<AreaLineChart
xAxisKey="date"
chartData={chartData}
seriesList={formatSeriesList()}
height={239}
/>
</Card>
)
}

View File

@@ -1,137 +1,39 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:16:45
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:16:45
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-10 11:57:35
*/
/**
* Pie Chart Card Component
* Displays knowledge base type distribution with ECharts donut chart
*/
import { type FC, useRef, useEffect } from 'react'
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import Card from './Card'
import Loading from '@/components/Empty/Loading'
import Empty from '@/components/Empty'
import PieChart, { type ChartData } from '@/components/Charts/PieChart'
/**
* Component props
*/
interface PieCardProps {
chartData: Array<Record<string, string | number>>;
chartData: ChartData[];
loading: boolean;
}
/** Chart color palette */
const Colors = ['#155EEF', '#31E8FF', '#AD88FF', '#FFB048', '#4DA8FF', '#03BDFF']
const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const resizeScheduledRef = useRef(false)
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [chartData])
return (
<Card
title={t('dashboard.knowledgeBaseTypeDistribution')}
>
{loading
? <Loading size={249} />
: !chartData || chartData.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
: <ReactEcharts
option={{
color: Colors,
tooltip: {
trigger: 'item',
textStyle: {
color: '#5B6167',
fontSize: 12,
width: 27,
height: 16,
},
formatter: '{d}%',
padding: [8, 5],
backgroundColor: '#FFFFFF',
borderColor: '#DFE4ED',
extraCssText: 'width: 36px; height: 36px; box-shadow: 0px 2px 4px 0px rgba(33,35,50,0.12);border-radius: 36px;'
},
legend: {
right: 20 ,
top: 'middle',
padding: 0,
itemWidth: 12,
itemHeight: 12,
borderRadius: 2,
orient: 'vertical',
textStyle: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 16,
}
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['60%', '100%'],
avoidLabelOverlap: false,
percentPrecision: 0,
padAngle: 4,
width: 200,
height: 200,
left: '10%',
top: 'middle',
itemStyle: {
borderRadius: 0
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 24,
fontWeight: 'bold',
color: '#212332',
formatter: '{d}%\n{b}',
}
},
labelLine: {
show: false
},
data: chartData
}
]
}}
style={{ height: '265px', width: '100%', minWidth: '400px' }}
notMerge={true}
lazyUpdate={true}
/>
? <Loading size={249} />
: <PieChart chartData={chartData} />
}
</Card>
)

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:16:38
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:16:38
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:57:35
*/
/**
* Quick Operation Component
@@ -11,15 +11,16 @@
*/
import { type FC } from 'react'
import clsx from 'clsx';
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom';
import { Flex } from 'antd';
import Card from './Card';
import applicationIcon from '@/assets/images/menu/application_active.svg';
import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg';
import memoryConversationIcon from '@/assets/images/menu/memoryConversation_active.svg';
import applicationIcon from '@/assets/images/home/application.svg';
import knowledgeIcon from '@/assets/images/home/knowledge.svg';
import memoryConversationIcon from '@/assets/images/home/memoryConversation.svg';
import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg'
import arrowTopRight from '@/assets/images/home/arrow_top_right.svg';
/** Quick operation items configuration */
const quickOperations = [
@@ -29,6 +30,13 @@ const quickOperations = [
{ key: 'helpCenter', url: '' },
]
const bgStyleList = [
'rb:bg-[rgba(21,94,239,0.1)]',
'rb:bg-[rgba(156,111,255,0.1)]',
'rb:bg-[rgba(255,176,72,0.1)]',
'rb:bg-[rgba(77,168,255,0.1)]'
]
/** Icon mapping for quick operations */
const quickOperationIcons: {[key: string]: string | undefined} = {
createNewApplication: applicationIcon,
@@ -62,17 +70,19 @@ const QuickOperation:FC = () => {
return (
<Card
title={t('dashboard.quickOperation')}
bodyClassName="rb:pt-0! rb:pb-[14px]! rb:px-4!"
>
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
{quickOperations.map(item => (
<div key={item.key} className="rb:rounded-lg rb:p-[20px_16px] rb:border rb:border-[#DFE4ED] rb:cursor-pointer rb:hover:border-[#155EEF]" onClick={() => handleJump(item.url)}>
<div className="rb:flex rb:justify-between">
<img className="rb:w-8 rb:h-8" src={quickOperationIcons[item.key]} />
<img className="rb:w-4 rb:h-4" src={arrowTopRight} />
<div className="rb:grid rb:grid-cols-1 rb:gap-3">
{quickOperations.map((item, index) => (
<Flex key={item.key} align="center" gap={20} className={clsx("rb:relative rb:rounded-xl rb:py-2! rb:px-3! rb:cursor-pointer", bgStyleList[index])} onClick={() => handleJump(item.url)}>
<div className="rb:size-8 rb:rounded-lg rb:p-1 rb:bg-[#FFFFFF]">
<img className="rb:size-6" src={quickOperationIcons[item.key]} />
</div>
<div className="rb:mt-6 rb:text-[#212332] rb:text-[16px] rb:leading-5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:mt-2 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
</div>
<div>
<div className="rb:text-[14px] rb:leading-5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:mt-0.5 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
</div>
</Flex>
))}
</div>
</Card>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:15:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:15:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:48:31
*/
/**
* Recent Activity Component
@@ -13,7 +13,7 @@
import { type FC, useEffect, useState } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Skeleton } from 'antd';
import { Skeleton, Flex } from 'antd';
import chunkCountIcon from '@/assets/images/home/chunk_count.svg';
import statementsCountIcon from '@/assets/images/home/statements_count.svg';
@@ -79,30 +79,31 @@ const RecentActivity:FC = () => {
return (
<Card
title={t('dashboard.recentMemoryActivities')}
bodyClassName="rb:pt-0! rb:pb-[14px]! rb:px-4!"
>
{loading
? <Skeleton />
: !recentActivities || Object.keys(recentActivities).length === 0
? <Empty url={activityEmpty} subTitle={t('dashboard.activityEmpty')} size={120} className="rb:mt-11.25 rb:mb-20.25" />
: activityList.map((item, index) => (
<div key={item.key} className={clsx("rb:flex rb:justify-between rb:items-center rb:not-italic", {
'rb:mt-6': index !== 0
})}>
<div className="rb:flex rb:items-center rb:text-[#060419] rb:text-[16px] rb:font-medium">
<img className="rb:w-10 rb:h-10 rb:mr-4" src={item.icon} />
<div>
{t(`dashboard.${item.key}`)}
<div className="rb:text-[#5B6167] rb:text-[14px] rb:font-normal">
{item.key === 'triplet_count'
? t(`dashboard.${item.key}_desc`, { entities_count: recentActivities.triplet_entities_count, relations_count: recentActivities.triplet_relations_count })
: t(`dashboard.${item.key}_desc`, { count: recentActivities[item.key as keyof RecentActivities] })
}
: <Flex vertical gap={24} justify="space-around">
{activityList.map((item) => (
<Flex key={item.key} align="center" justify="space-between" className={clsx("rb:not-italic")}>
<Flex align="center" gap={20}>
<img className="rb:size-10" src={item.icon} />
<div>
<div className="rb:text-[16px] rb:leading-5.5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:text-[#7B8085] rb:text-[14px] rb:font-regular rb:mt-1 rb:leading-4.5">
{item.key === 'triplet_count'
? t(`dashboard.${item.key}_desc`, { entities_count: recentActivities.triplet_entities_count, relations_count: recentActivities.triplet_relations_count })
: t(`dashboard.${item.key}_desc`, { count: recentActivities[item.key as keyof RecentActivities] })
}
</div>
</div>
</div>
</div>
<div className="rb:text-[#5F6266] rb:text-right rb:whitespace-nowrap">{data?.latest_relative || ''}</div>
</div>
))
</Flex>
<div className="rb:text-[#7B8085] rb:text-right rb:whitespace-nowrap">{data?.latest_relative || ''}</div>
</Flex>
))}
</Flex>
}
</Card>
)

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:15:04
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:15:04
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-26 11:15:15
*/
/**
* Tag List Component
@@ -12,13 +12,24 @@
import { type FC, useEffect, useState } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Skeleton } from 'antd';
import { Skeleton, Flex } from 'antd';
import tagEmpty from '@/assets/images/home/tagEmpty.svg'
import Empty from '@/components/Empty';
import Card from './Card';
import { getHotMemoryTags } from '@/api/memory';
const btnStyleList = [
'rb:bg-[rgba(21,94,239,0.06)]',
'rb:bg-[rgba(33,35,50,0.06)]',
'rb:bg-[rgba(156,111,255,0.06)]'
]
const numStyleList = [
'rb:bg-[#155EEF]',
'rb:bg-[#212332]',
'rb:bg-[#9C6FFF]'
]
const TagList:FC = () => {
const { t } = useTranslation()
const [loading, setLoading] = useState<boolean>(false)
@@ -38,23 +49,24 @@ const TagList:FC = () => {
return (
<Card
title={t('dashboard.popularMemoryTags')}
bodyClassName='rb:overflow-hidden! rb:pt-0! rb:pb-4! rb:pl-4! rb:pr-3.25!'
className="rb:min-h-[calc(100vh-744px)]"
>
{loading
? <Skeleton />
: !tagList || tagList.length === 0
? <Empty url={tagEmpty} title={t('dashboard.activityEmpty')} size={120} className="rb:mt-9 rb:mb-20.25" />
: <div className="rb:gap-3 rb:flex rb:flex-wrap">
: <Flex wrap className="rb:gap-x-3! rb:gap-y-2.5!">
{tagList.map((item, index) => (
<div
key={item.name}
className={clsx("rb:pt-1.5 rb:pb-1.5 rb:pr-5.75 rb:pl-5 rb:border rb:leading-5 rb:bg-white rb:rounded-[17px]", {
'rb:border-[rgba(21,94,239,0.4)] rb:text-[#155EEF]': index % 3 === 0,
'rb:border-[rgba(255,138,76,0.4)] rb:text-[#FF5D34]': index % 3 === 1,
'rb:border-[rgba(54,159,33,0.4)] rb:text-[#369F21]': index % 3 === 2,
})}
>{item.name} {item.frequency}</div>
className={clsx("rb:rounded-[17px] rb:py-1.5 rb:pl-3 rb:pr-2", btnStyleList[index % 3])}
>
{item.name}
<span className={clsx('rb:px-2 rb:py-0.5 rb:rounded-[10px] rb:text-[#FFFFFF] rb:text-[12px] rb:font-bold rb:font-[MiSans-Demibold] rb:ml-2', numStyleList[index % 3])}>{item.frequency}</span>
</div>
))}
</div>
</Flex>
}
</Card>
)

View File

@@ -0,0 +1,112 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:28:07
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:57:55
*/
/**
* Top Card List Component
* Displays dashboard summary cards for key metrics
* Shows total memory capacity, applications, knowledge bases, and API calls
*/
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import clsx from 'clsx';
import { Flex } from 'antd';
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
import userMemory from '@/assets/images/home/userMemory.svg';
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
import type { DashboardData } from '../index'
/** Card configuration with styling */
const list = [
{
key: 'totalMemoryCapacity',
icon: totalMemoryCapacity,
// value: '45,678',
// trendValue: '12.5%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'rb:bg-[url("@/assets/images/home/totalMemoryCapacity.png")] rb:bg-cover rb:bg-no-repeat',
},
{
key: 'application',
icon: userMemory,
// value: '32,145',
// trendValue: '12.5%',
// trend: 'down',
// trendDesc: 'comparedToYesterday',
// background: 'linear-gradient( 180deg, #F1FBF5 0%, #F9FDFF 100%)',
},
{
key: 'knowledgeBaseCount',
icon: knowledgeBaseCount,
// value: '13,533',
// trendValue: '15.7%',
// trend: 'up',
// trendDesc: 'thisWeek',
// background: 'linear-gradient( 180deg, #E6F5FE 0%, #FBFDFF 100%)',
},
{
key: 'apiCallCount',
icon: apiCallCount,
// value: '856.2k',
// trendValue: '23.1%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
// background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
},
]
/**
* Component props
* @param data - Dashboard statistics data
*/
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
const { t } = useTranslation()
return (
<div className="rb:grid rb:grid-cols-2 rb:gap-3">
{list.map((item) => {
return (
<div
key={item.key}
className={`rb:rounded-2xl rb:bg-[#FFFFFF] rb:py-4 rb:px-3 ${item.background || ''}`}
>
<div className={clsx("rb:text-[12px] rb:leading-4", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#5B6167]': item.key !== 'totalMemoryCapacity',
})}>{t(`dashboard.${item.key}`)}</div>
<div className={clsx("rb:text-[20px] rb:font-bold rb:leading-7 rb:mt-1 rb:font-[MiSans-Bold]", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
// 'rb:text-[#171719]': item.key !== 'totalMemoryCapacity',
})}>
{data?.[item.key as keyof DashboardData] || 0}
</div>
<Flex align="center" className={clsx('rb:font-medium rb:mt-7.5!', {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#369F21]': item.key !== 'totalMemoryCapacity',
})}>
0%
<div className={clsx("rb:size-3.5 rb:cursor-pointer rb:bg-cover", {
"rb:bg-[url('@/assets/images/home/arrow_up.svg')]": item.key === 'totalMemoryCapacity',
"rb:bg-[url('@/assets/images/home/arrow_up_success.svg')]": item.key !== 'totalMemoryCapacity',
})}></div>
</Flex>
<div className={clsx("rb:text-[12px] rb:leading-4 rb:mt-0.5", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#5B6167]': item.key !== 'totalMemoryCapacity',
})}>
{t('dashboard.comparedToYesterday')}
</div>
</div>
)
})}
</div>
)
}
export default TopCardList

View File

@@ -1,97 +0,0 @@
.card {
border-radius: 12px;
border: 1px solid #DFE4ED;
padding: 0;
}
.header {
padding: 20px;
line-height: 44px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px;
color: #5B6167;
font-style: normal;
display: flex;
align-items: center;
border-bottom: 1px solid #DFE4ED;
}
.avatar {
width: 44px;
height: 44px;
background: #FFFFFF;
box-shadow: 0px 2px 6px 0px rgba(33, 35, 50, 0.1);
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.avatar img {
width: 24px;
height: 24px;
}
.content {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: Gilroy, Gilroy;
font-weight: 800;
font-size: 28px;
color: #212332;
text-align: left;
font-style: normal;
}
.content-right {
text-align: right;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #5F6266;
line-height: 16px;
font-style: normal;
row-gap: 4px;
}
.trend {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
line-height: 16px;
font-style: normal;
padding-left: 15px;
position: relative;
margin-bottom: 4px;
display: inline-block;
}
.trend::before {
width: 14px;
height: 14px;
content: '';
position: absolute;
left: 0;
top: 1px;
background-repeat: no-repeat;
background-size: contain;
}
.trend.up {
color: #369F21;
}
.trend.up::before {
background-image: url('@/assets/images/home/arrow_up_success.svg');
}
.trend.down {
color: #FF5D34;
}
.trend.down::before {
background-image: url('@/assets/images/home/arrow_down.png');
}
.trend-desc {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #155EEF;
line-height: 16px;
text-align: left;
font-style: normal;
}

View File

@@ -1,94 +0,0 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:28:07
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:28:07
*/
/**
* Top Card List Component
* Displays dashboard summary cards for key metrics
* Shows total memory capacity, applications, knowledge bases, and API calls
*/
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
import userMemory from '@/assets/images/home/userMemory.svg';
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
import styles from './index.module.css'
import type { DashboardData } from '../../index'
/** Card configuration with styling */
const list = [
{
key: 'totalMemoryCapacity',
icon: totalMemoryCapacity,
// value: '45,678',
// trendValue: '12.5%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient(180deg, #E6EFFE 0%, #F9FDFF 100%)',
},
{
key: 'application',
icon: userMemory,
// value: '32,145',
// trendValue: '12.5%',
// trend: 'down',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient( 180deg, #F1FBF5 0%, #F9FDFF 100%)',
},
{
key: 'knowledgeBaseCount',
icon: knowledgeBaseCount,
// value: '13,533',
// trendValue: '15.7%',
// trend: 'up',
// trendDesc: 'thisWeek',
background: 'linear-gradient( 180deg, #E6F5FE 0%, #FBFDFF 100%)',
},
{
key: 'apiCallCount',
icon: apiCallCount,
// value: '856.2k',
// trendValue: '23.1%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
},
]
/**
* Component props
* @param data - Dashboard statistics data
*/
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
const { t } = useTranslation()
return (
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
{list.map((item) => {
return (
<div
key={item.key}
className={styles.card}
style={{
background: item.background,
}}
>
<div className={styles.header}>
<div className={styles.avatar}><img src={item.icon} /></div>
<div className={styles.headerTitle}>{t(`dashboard.${item.key}`)}</div>
</div>
<div className={styles.content}>
{data?.[item.key as keyof DashboardData] || 0}
</div>
</div>
)
})}
</div>
)
}
export default TopCardList

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:12:43
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:26:04
* @Last Modified time: 2026-02-10 11:57:58
*/
/**
* Home Dashboard Page
@@ -19,6 +19,7 @@ import { getDashboardData, getMemoryIncrement, getKbTypes } from '@/api/memory';
import RecentActivity from './components/RecentActivity'
import TagList from './components/TagList'
import QuickOperation from './components/QuickOperation'
import ApiLineCard from './components/ApiLineCard'
/**
* Dashboard statistics data
@@ -120,42 +121,39 @@ const Home = () => {
}
return (
<div className="rb:pb-6">
<TopCardList data={dashboardData} />
<Row className="rb:mt-4" gutter={16}>
<Col span={12}>
<LineCard
chartData={memoryIncrement}
limit={limit}
onChange={handleRangeChange}
type="memoryGrowthTrend"
seriesList={['total_num']}
/>
</Col>
<Col span={12}>
<PieCard
loading={loading.knowledgeTypeDistribution}
chartData={knowledgeTypeDistribution}
/>
</Col>
</Row>
<Row className="rb:mt-4" gutter={16}>
<Col span={12}>
<RecentActivity />
</Col>
<Col span={12}>
<TagList />
</Col>
</Row>
<Row className="rb:mt-4" gutter={16}>
<Col span={24}>
<QuickOperation />
</Col>
</Row>
</div>
<Row gutter={[12, 12]}>
<Col span={8}>
<TopCardList data={dashboardData} />
</Col>
<Col span={8}>
<LineCard
chartData={memoryIncrement}
limit={limit}
onChange={handleRangeChange}
type="memoryGrowthTrend"
seriesList={['total_num']}
/>
</Col>
<Col span={8}>
<ApiLineCard
/>
</Col>
<Col span={8}>
<PieCard
loading={loading.knowledgeTypeDistribution}
chartData={knowledgeTypeDistribution}
/>
</Col>
<Col span={8}>
<RecentActivity />
</Col>
<Col span={8}>
<QuickOperation />
</Col>
<Col span={24}>
<TagList />
</Col>
</Row>
);
}