From 79264af3f9704585e5767dae6207a95d5d8135c2 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 6 Jul 2026 18:36:45 +0800 Subject: [PATCH] docs: add stage 2.5 frontend prototype plan --- ...-06-stage-2-5-global-frontend-prototype.md | 1661 +++++++++++++++++ 1 file changed, 1661 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md diff --git a/docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md b/docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md new file mode 100644 index 0000000..ca6ae08 --- /dev/null +++ b/docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md @@ -0,0 +1,1661 @@ +# Stage 2.5 Global Frontend Prototype Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a Chinese, shadcn-based, interactive frontend prototype that exposes the full AI ticket platform product map before implementing Stage 3 to Stage 5 backend APIs. + +**Architecture:** Keep existing real API-backed pages for tickets, solutions, knowledge, and system management. Add a frontend-only prototype module with typed mock contracts, shared App components, and mock-backed pages for dashboard, model hub, Agents, Skills, and MCP tools. Route everything through the current hash router and sidebar without adding backend models or APIs. + +**Tech Stack:** React, Vite, TypeScript, Tailwind CSS, shadcn/ui, Radix UI, lucide-react, existing project-owned `App*` components. + +--- + +## Source Spec + +- `docs/superpowers/specs/2026-07-06-stage-2-5-global-frontend-prototype-design.md` + +## File Structure + +Create: + +- `frontend/src/features/prototype/types.ts` - Typed mock contracts that document future API payloads. +- `frontend/src/features/prototype/mockData.ts` - Realistic Chinese mock data for dashboard, model hub, Agent, Skill, and MCP pages. +- `frontend/src/components/app/AppMetricCard.tsx` - Reusable metric card for workbench/dashboard/model summaries. +- `frontend/src/components/app/AppRiskBadge.tsx` - Reusable risk badge for MCP and Agent safety states. +- `frontend/src/components/app/AppTraceTimeline.tsx` - Reusable execution trace timeline for Agent/tool/model steps. +- `frontend/src/components/app/AppReferenceCard.tsx` - Reusable citable reference card for knowledge/AI panels. +- `frontend/src/features/dashboard/DashboardPage.tsx` - Mock-backed operational dashboard. +- `frontend/src/features/modelhub/ModelProviderPage.tsx` - Mock-backed model provider list and edit dialog. +- `frontend/src/features/modelhub/ModelTestingPage.tsx` - Mock-backed model comparison/testing page. +- `frontend/src/features/agents/AgentListPage.tsx` - Mock-backed Agent configuration list. +- `frontend/src/features/agents/AgentRunsPage.tsx` - Mock-backed Agent run list. +- `frontend/src/features/agents/AgentRunDetailPage.tsx` - Mock-backed Agent run detail and step trace. +- `frontend/src/features/skills/SkillListPage.tsx` - Mock-backed Skill list and editing/testing surface. +- `frontend/src/features/tools/McpToolsPage.tsx` - Mock-backed MCP server/tool/risk page. + +Modify: + +- `frontend/src/app/router.tsx` - Add Stage 2.5 routes. +- `frontend/src/components/app/AppSidebar.tsx` - Expose full product map through built-in prototype navigation when backend menus do not include it. +- `frontend/src/features/workbench/WorkbenchPage.tsx` - Add AI/knowledge/model/tool overview blocks using real tickets plus prototype metrics. +- `frontend/src/features/tickets/TicketCreatePage.tsx` - Add a future Agent-assisted creation panel. +- `frontend/src/features/tickets/TicketDetailPage.tsx` - Add consistent AI analysis/reference prototype sections. +- `docs/frontend/design-system.md` - Document frontend-first prototype rules. +- `README.md` - Mention Stage 2.5 frontend prototype status. +- `docs/superpowers/plans/2026-07-06-stage-2-5-global-frontend-prototype.md` - Mark checkboxes during execution. + +Do not modify backend files in this stage. + +--- + +### Task 1: Prototype Contracts And Shared App Components + +**Files:** +- Create: `frontend/src/features/prototype/types.ts` +- Create: `frontend/src/features/prototype/mockData.ts` +- Create: `frontend/src/components/app/AppMetricCard.tsx` +- Create: `frontend/src/components/app/AppRiskBadge.tsx` +- Create: `frontend/src/components/app/AppTraceTimeline.tsx` +- Create: `frontend/src/components/app/AppReferenceCard.tsx` + +- [ ] **Step 1: Create typed mock contracts** + +Create `frontend/src/features/prototype/types.ts`: + +```ts +export type PrototypeStatus = 'success' | 'warning' | 'danger' | 'muted' + +export type DashboardMetric = { + id: string + label: string + value: string + helper: string + status: PrototypeStatus +} + +export type ModelProvider = { + id: number + name: string + provider_type: 'openai_compatible' | 'azure_openai' | 'local' + base_url: string + enabled: boolean + model_count: number + default_chat_model: string + default_embedding_model: string + last_test_status: 'passed' | 'failed' | 'untested' + updated_at: string +} + +export type ModelTestResult = { + id: number + provider_name: string + model_name: string + prompt: string + response: string + latency_ms: number + input_tokens: number + output_tokens: number + status: 'success' | 'failed' + error_message: string +} + +export type AgentDefinition = { + id: number + name: string + agent_type: 'ticket_create' | 'support_analysis' | 'knowledge_distill' + description: string + enabled: boolean + model_name: string + knowledge_scope: string + tool_scope: string + requires_human_confirm: boolean + updated_at: string +} + +export type AgentStepType = 'retrieval' | 'skill' | 'model_call' | 'tool_call' | 'human_confirm' | 'artifact' + +export type AgentStep = { + id: number + step_type: AgentStepType + step_name: string + status: 'success' | 'failed' | 'waiting_confirm' | 'skipped' + input_summary: string + output_summary: string + latency_ms: number + created_at: string +} + +export type AgentRun = { + id: number + agent_name: string + run_type: 'ticket_create' | 'support_analysis' | 'knowledge_distill' | 'test' + ticket_no: string + status: 'running' | 'success' | 'failed' | 'waiting_confirm' + trigger_user: string + duration_ms: number + created_at: string + output_summary: string + steps: AgentStep[] +} + +export type SkillDefinition = { + id: number + name: string + scenario: 'field_extract' | 'missing_info' | 'solution_recommend' | 'reply_draft' | 'knowledge_draft' + description: string + enabled: boolean + version: string + prompt_template: string + input_schema: string + output_schema: string + updated_at: string +} + +export type McpServer = { + id: number + name: string + server_url: string + auth_type: 'none' | 'token' | 'basic' + connection_state: 'connected' | 'disconnected' | 'error' + tool_count: number + last_sync_at: string +} + +export type McpTool = { + id: number + server_id: number + name: string + description: string + risk_level: 'low' | 'medium' | 'high' + enabled: boolean + requires_human_confirm: boolean +} + +export type ToolCall = { + id: number + tool_name: string + run_id: number + input_summary: string + output_summary: string + status: 'success' | 'failed' | 'blocked' | 'waiting_confirm' + confirmed_by: string + created_at: string +} + +export type KnowledgeReference = { + id: number + title: string + source_type: 'knowledge_item' | 'solution' | 'document' | 'historical_ticket' + content: string + score: number +} +``` + +- [ ] **Step 2: Create realistic mock data** + +Create `frontend/src/features/prototype/mockData.ts`: + +```ts +import type { + AgentDefinition, + AgentRun, + DashboardMetric, + KnowledgeReference, + McpServer, + McpTool, + ModelProvider, + ModelTestResult, + SkillDefinition, + ToolCall +} from './types' + +export const dashboardMetrics: DashboardMetric[] = [ + { id: 'pending', label: '待处理工单', value: '18', helper: '其中 6 个超过 4 小时未更新', status: 'warning' }, + { id: 'knowledge', label: '知识命中率', value: '72%', helper: '近 7 天解决方案和知识引用', status: 'success' }, + { id: 'agent', label: 'Agent 运行', value: '46', helper: '3 次等待人工确认', status: 'muted' }, + { id: 'risk', label: '高风险工具', value: '2', helper: '均处于人工确认模式', status: 'danger' } +] + +export const modelProviders: ModelProvider[] = [ + { + id: 1, + name: 'OpenAI 兼容渠道', + provider_type: 'openai_compatible', + base_url: 'https://api.example.com/v1', + enabled: true, + model_count: 4, + default_chat_model: 'gpt-4.1', + default_embedding_model: 'text-embedding-3-large', + last_test_status: 'passed', + updated_at: '2026-07-06T09:20:00Z' + }, + { + id: 2, + name: '本地推理服务', + provider_type: 'local', + base_url: 'http://model-gateway.internal/v1', + enabled: true, + model_count: 2, + default_chat_model: 'qwen-support-32b', + default_embedding_model: 'bge-m3', + last_test_status: 'failed', + updated_at: '2026-07-05T16:10:00Z' + } +] + +export const modelTestResults: ModelTestResult[] = [ + { + id: 1, + provider_name: 'OpenAI 兼容渠道', + model_name: 'gpt-4.1', + prompt: '客户反馈税局采集进度长时间不更新,请给出排查建议。', + response: '建议先查询采集任务状态和 Python 日志,确认税局接口是否返回超时或限流,再向客户说明任务仍在采集中。', + latency_ms: 1860, + input_tokens: 62, + output_tokens: 88, + status: 'success', + error_message: '' + }, + { + id: 2, + provider_name: '本地推理服务', + model_name: 'qwen-support-32b', + prompt: '客户反馈税局采集进度长时间不更新,请给出排查建议。', + response: '', + latency_ms: 30000, + input_tokens: 62, + output_tokens: 0, + status: 'failed', + error_message: '模型服务响应超时' + } +] + +export const agents: AgentDefinition[] = [ + { + id: 1, + name: '建单辅助 Agent', + agent_type: 'ticket_create', + description: '从自然语言和截图中提取工单字段,并生成缺失信息追问。', + enabled: true, + model_name: 'gpt-4.1', + knowledge_scope: '工单分类、历史相似问题', + tool_scope: '只读知识检索', + requires_human_confirm: true, + updated_at: '2026-07-06T08:30:00Z' + }, + { + id: 2, + name: '支持分析 Agent', + agent_type: 'support_analysis', + description: '结合工单上下文、知识库和解决方案生成排查建议。', + enabled: true, + model_name: 'qwen-support-32b', + knowledge_scope: '全部已发布知识库', + tool_scope: '日志查询、监控查询', + requires_human_confirm: true, + updated_at: '2026-07-06T10:05:00Z' + } +] + +export const agentRuns: AgentRun[] = [ + { + id: 1001, + agent_name: '支持分析 Agent', + run_type: 'support_analysis', + ticket_no: 'TK202607060018', + status: 'waiting_confirm', + trigger_user: '技术支持-张敏', + duration_ms: 4200, + created_at: '2026-07-06T10:12:00Z', + output_summary: '已生成排查步骤和客户回复草稿,等待人工确认是否采纳。', + steps: [ + { + id: 1, + step_type: 'retrieval', + step_name: '检索相似知识', + status: 'success', + input_summary: '查询:税局采集进度长时间不更新', + output_summary: '命中 3 条知识引用和 1 个解决方案', + latency_ms: 120, + created_at: '2026-07-06T10:12:01Z' + }, + { + id: 2, + step_type: 'model_call', + step_name: '生成支持分析', + status: 'success', + input_summary: '工单详情、知识引用、解决方案', + output_summary: '输出可能原因、排查步骤、客户回复草稿', + latency_ms: 1830, + created_at: '2026-07-06T10:12:03Z' + }, + { + id: 3, + step_type: 'human_confirm', + step_name: '等待人工确认', + status: 'waiting_confirm', + input_summary: '是否采纳客户回复草稿', + output_summary: '等待处理人确认', + latency_ms: 0, + created_at: '2026-07-06T10:12:05Z' + } + ] + } +] + +export const skills: SkillDefinition[] = [ + { + id: 1, + name: '字段抽取', + scenario: 'field_extract', + description: '从用户描述中抽取标题、系统、环境、优先级和问题分类。', + enabled: true, + version: 'v1.0.0', + prompt_template: '你是技术支持建单助手,请从用户描述中抽取结构化工单字段。', + input_schema: '{ "description": "string", "attachments": "array" }', + output_schema: '{ "title": "string", "category": "string", "priority": "string" }', + updated_at: '2026-07-06T09:00:00Z' + }, + { + id: 2, + name: '客户回复草稿', + scenario: 'reply_draft', + description: '根据处理结论生成面向客户的简洁回复。', + enabled: true, + version: 'v1.1.0', + prompt_template: '请根据工单处理记录生成礼貌、准确、可发送给客户的回复。', + input_schema: '{ "ticket": "object", "resolution": "string" }', + output_schema: '{ "reply": "string", "risk_note": "string" }', + updated_at: '2026-07-05T18:30:00Z' + } +] + +export const mcpServers: McpServer[] = [ + { + id: 1, + name: '日志平台 MCP', + server_url: 'https://logs.internal/mcp', + auth_type: 'token', + connection_state: 'connected', + tool_count: 6, + last_sync_at: '2026-07-06T08:50:00Z' + }, + { + id: 2, + name: '配置中心 MCP', + server_url: 'https://config.internal/mcp', + auth_type: 'token', + connection_state: 'error', + tool_count: 4, + last_sync_at: '2026-07-05T14:20:00Z' + } +] + +export const mcpTools: McpTool[] = [ + { + id: 1, + server_id: 1, + name: 'search_python_logs', + description: '按工单编号、客户名称或任务 ID 查询 Python 服务日志。', + risk_level: 'medium', + enabled: true, + requires_human_confirm: false + }, + { + id: 2, + server_id: 2, + name: 'restart_collector_job', + description: '重启指定采集任务,仅允许人工确认后执行。', + risk_level: 'high', + enabled: false, + requires_human_confirm: true + } +] + +export const toolCalls: ToolCall[] = [ + { + id: 1, + tool_name: 'search_python_logs', + run_id: 1001, + input_summary: '查询任务 TASK-20260706-18 的最近 30 分钟日志', + output_summary: '发现税局接口连续 3 次超时', + status: 'success', + confirmed_by: '', + created_at: '2026-07-06T10:12:02Z' + }, + { + id: 2, + tool_name: 'restart_collector_job', + run_id: 1001, + input_summary: '尝试重启采集任务 TASK-20260706-18', + output_summary: '高风险工具已阻止,需人工确认', + status: 'blocked', + confirmed_by: '', + created_at: '2026-07-06T10:12:04Z' + } +] + +export const knowledgeReferences: KnowledgeReference[] = [ + { + id: 1, + title: '税局采集进度查询', + source_type: 'knowledge_item', + content: '采集进度长时间不更新时,先查询 Python 日志确认税局接口状态,再判断是否需要客户等待。', + score: 0.91 + }, + { + id: 2, + title: '税局不稳定采集处理中', + source_type: 'solution', + content: '回复客户税局服务不稳定,当前任务仍在采集中,稍后复查结果。', + score: 0.88 + } +] +``` + +- [ ] **Step 3: Create `AppMetricCard`** + +Create `frontend/src/components/app/AppMetricCard.tsx`: + +```tsx +import type * as React from 'react' + +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' + +type AppMetricCardProps = { + label: string + value: string | number + helper?: string + icon?: React.ReactNode + status?: 'success' | 'warning' | 'danger' | 'muted' +} + +const statusLabels = { + danger: '需关注', + muted: '正常', + success: '良好', + warning: '预警' +} + +export function AppMetricCard({ label, value, helper, icon, status = 'muted' }: AppMetricCardProps) { + return ( + + + {label} + {icon} + + +
+

{value}

+ {statusLabels[status]} +
+ {helper ?

{helper}

: null} +
+
+ ) +} +``` + +- [ ] **Step 4: Create `AppRiskBadge`** + +Create `frontend/src/components/app/AppRiskBadge.tsx`: + +```tsx +import { Badge } from '@/components/ui/badge' + +type AppRiskBadgeProps = { + risk: 'low' | 'medium' | 'high' +} + +const riskLabels = { + high: '高风险', + low: '低风险', + medium: '中风险' +} + +export function AppRiskBadge({ risk }: AppRiskBadgeProps) { + return {riskLabels[risk]} +} +``` + +- [ ] **Step 5: Create `AppTraceTimeline`** + +Create `frontend/src/components/app/AppTraceTimeline.tsx`: + +```tsx +import { CheckCircle2, CircleDashed, Clock, XCircle } from 'lucide-react' + +import { Badge } from '@/components/ui/badge' +import { Card, CardContent } from '@/components/ui/card' +import { cn } from '@/lib/utils' + +export type AppTraceTimelineItem = { + id: number | string + title: string + type: string + status: 'success' | 'failed' | 'waiting_confirm' | 'skipped' | 'running' + input: string + output: string + latencyMs?: number +} + +const statusLabels = { + failed: '失败', + running: '运行中', + skipped: '已跳过', + success: '成功', + waiting_confirm: '待确认' +} + +function getStatusIcon(status: AppTraceTimelineItem['status']) { + if (status === 'success') return CheckCircle2 + if (status === 'failed') return XCircle + if (status === 'waiting_confirm') return Clock + return CircleDashed +} + +export function AppTraceTimeline({ items }: { items: AppTraceTimelineItem[] }) { + return ( +
+ {items.map((item, index) => { + const Icon = getStatusIcon(item.status) + return ( + + +
+
+
+ +
+
+

{index + 1}. {item.title}

+

{item.type}

+
+
+
+ {statusLabels[item.status]} + {typeof item.latencyMs === 'number' ? {item.latencyMs} ms : null} +
+
+
+
+

输入

+

{item.input}

+
+
+

输出

+

{item.output}

+
+
+
+
+ ) + })} +
+ ) +} +``` + +- [ ] **Step 6: Create `AppReferenceCard`** + +Create `frontend/src/components/app/AppReferenceCard.tsx`: + +```tsx +import { Badge } from '@/components/ui/badge' +import { Card, CardContent } from '@/components/ui/card' + +type AppReferenceCardProps = { + title: string + sourceType: string + content: string + score?: number +} + +const sourceLabels: Record = { + document: '文档', + historical_ticket: '历史工单', + knowledge_item: '知识条目', + solution: '解决方案' +} + +export function AppReferenceCard({ title, sourceType, content, score }: AppReferenceCardProps) { + return ( + + +
+

{title}

+ {sourceLabels[sourceType] ?? sourceType} + {typeof score === 'number' ? 匹配度 {score.toFixed(2)} : null} +
+

{content}

+
+
+ ) +} +``` + +- [ ] **Step 7: Verify Task 1** + +Run: + +```powershell +npm run typecheck +``` + +Expected: TypeScript completes without errors. + +- [ ] **Step 8: Commit Task 1** + +Run: + +```powershell +git add frontend/src/features/prototype frontend/src/components/app/AppMetricCard.tsx frontend/src/components/app/AppRiskBadge.tsx frontend/src/components/app/AppTraceTimeline.tsx frontend/src/components/app/AppReferenceCard.tsx +git commit -m "feat: add frontend prototype contracts" +``` + +--- + +### Task 2: Global Navigation, Dashboard, And Workbench Overview + +**Files:** +- Create: `frontend/src/features/dashboard/DashboardPage.tsx` +- Modify: `frontend/src/app/router.tsx` +- Modify: `frontend/src/components/app/AppSidebar.tsx` +- Modify: `frontend/src/features/workbench/WorkbenchPage.tsx` + +- [ ] **Step 1: Create dashboard page** + +Create `frontend/src/features/dashboard/DashboardPage.tsx`: + +```tsx +import { Activity, Bot, Database, ShieldAlert } from 'lucide-react' + +import { AppMetricCard } from '@/components/app/AppMetricCard' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { AppSection } from '@/components/app/AppSection' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { dashboardMetrics, agentRuns, mcpTools, modelProviders } from '@/features/prototype/mockData' + +const icons = [Activity, Database, Bot, ShieldAlert] + +export function DashboardPage() { + return ( + + + +
+ {dashboardMetrics.map((metric, index) => { + const Icon = icons[index] ?? Activity + return } /> + })} +
+ +
+ +
+ {['税局采集进度', '登录失败', '数据同步延迟', '发票下载异常'].map((name, index) => ( +
+
+ {name} + {32 - index * 6} 单 +
+
+
+
+
+ ))} +
+ + +
+ + + 模型健康 + + + {modelProviders.map((provider) => ( +
+ {provider.name} + + {provider.last_test_status === 'passed' ? '通过' : '失败'} + +
+ ))} +
+
+ + + + 待确认事项 + + +

Agent 待确认:{agentRuns.filter((run) => run.status === 'waiting_confirm').length} 项

+

高风险工具:{mcpTools.filter((tool) => tool.risk_level === 'high').length} 个

+
+
+
+
+ + ) +} +``` + +- [ ] **Step 2: Add dashboard route** + +Modify `frontend/src/app/router.tsx`: + +```tsx +import { DashboardPage } from '@/features/dashboard/DashboardPage' +``` + +Add before the workbench route: + +```tsx + if (route === '/dashboard') { + return + } +``` + +- [ ] **Step 3: Expand built-in sidebar prototype navigation** + +Modify `frontend/src/components/app/AppSidebar.tsx`: + +Add lucide imports if absent: + +```tsx + Activity, + BrainCircuit, + Boxes, + Cpu, + FlaskConical, + PlugZap, + Wrench +``` + +Add icon mappings: + +```tsx + activity: Activity, + agents: BrainCircuit, + boxes: Boxes, + cpu: Cpu, + flask: FlaskConical, + plug: PlugZap, + wrench: Wrench +``` + +Replace the current single built-in knowledge group with a `prototypeNavGroups` array: + +```tsx +const prototypeNavGroups: AuthMenu[] = [ + { + id: -100, + code: 'prototype_workspace', + title: '工作台', + path: '#/', + icon: 'home', + permission_code: 'workspace.view', + is_visible: true, + is_enabled: true, + children: [ + { id: -101, code: 'workspace_today', title: '今日工作台', path: '#/', icon: 'home', permission_code: 'workspace.view', is_visible: true, is_enabled: true, children: [] }, + { id: -102, code: 'workspace_dashboard', title: '运营总览', path: '#/dashboard', icon: 'activity', permission_code: 'dashboard.view', is_visible: true, is_enabled: true, children: [] } + ] + }, + { + id: -200, + code: 'prototype_knowledge', + title: '知识与方案', + path: '#/knowledge', + icon: 'book_open', + permission_code: 'knowledge.view', + is_visible: true, + is_enabled: true, + children: [ + { id: -201, code: 'knowledge_bases', title: '知识库', path: '#/knowledge', icon: 'book_open', permission_code: 'knowledge.view', is_visible: true, is_enabled: true, children: [] }, + { id: -202, code: 'knowledge_search', title: '知识检索', path: '#/knowledge/search', icon: 'search', permission_code: 'knowledge.view', is_visible: true, is_enabled: true, children: [] }, + { id: -203, code: 'solutions', title: '解决方案', path: '#/solutions', icon: 'library', permission_code: 'solutions.view', is_visible: true, is_enabled: true, children: [] } + ] + }, + { + id: -300, + code: 'prototype_agents', + title: 'Agent', + path: '#/agents', + icon: 'agents', + permission_code: 'agents.view', + is_visible: true, + is_enabled: true, + children: [ + { id: -301, code: 'agents', title: 'Agent 配置', path: '#/agents', icon: 'agents', permission_code: 'agents.view', is_visible: true, is_enabled: true, children: [] }, + { id: -302, code: 'agent_runs', title: '运行记录', path: '#/agents/runs', icon: 'activity', permission_code: 'agents.view', is_visible: true, is_enabled: true, children: [] }, + { id: -303, code: 'skills', title: 'Skill 配置', path: '#/skills', icon: 'wrench', permission_code: 'skills.view', is_visible: true, is_enabled: true, children: [] } + ] + }, + { + id: -400, + code: 'prototype_model_tools', + title: '模型与工具', + path: '#/modelhub/providers', + icon: 'cpu', + permission_code: 'modelhub.view', + is_visible: true, + is_enabled: true, + children: [ + { id: -401, code: 'model_providers', title: '模型供应商', path: '#/modelhub/providers', icon: 'cpu', permission_code: 'modelhub.view', is_visible: true, is_enabled: true, children: [] }, + { id: -402, code: 'model_testing', title: '模型测试', path: '#/modelhub/testing', icon: 'flask', permission_code: 'modelhub.view', is_visible: true, is_enabled: true, children: [] }, + { id: -403, code: 'mcp_tools', title: 'MCP 工具', path: '#/tools/mcp', icon: 'plug', permission_code: 'tools.view', is_visible: true, is_enabled: true, children: [] } + ] + } +] +``` + +Then derive `groups` like this: + +```tsx + const menuGroups = (user?.menus ?? []).filter((menu) => menu.is_visible && menu.is_enabled) + const existingPaths = new Set(menuGroups.flatMap((group) => visibleChildren(group).map((item) => item.path))) + const supplementalGroups = prototypeNavGroups + .map((group) => ({ + ...group, + children: group.children.filter((item) => !existingPaths.has(item.path)) + })) + .filter((group) => group.children.length > 0) + const groups = [...menuGroups, ...supplementalGroups] +``` + +- [ ] **Step 4: Enhance workbench overview** + +Modify `frontend/src/features/workbench/WorkbenchPage.tsx`: + +Add imports: + +```tsx +import { Bot, Database, ShieldAlert } from 'lucide-react' +import { AppMetricCard } from '@/components/app/AppMetricCard' +import { dashboardMetrics, agentRuns, mcpTools } from '@/features/prototype/mockData' +``` + +Replace the current metric card grid with: + +```tsx +
+ {metrics.map((metric) => ( + + ))} +
+ +
+ } /> + run.status === 'waiting_confirm').length} helper="AI 建议默认等待人工确认" status="warning" icon={} /> + tool.risk_level === 'high').length} helper="高风险工具不会自动执行" status="danger" icon={} /> +
+``` + +- [ ] **Step 5: Verify Task 2** + +Run: + +```powershell +npm run typecheck +npm run build +``` + +Expected: both commands pass. + +- [ ] **Step 6: Commit Task 2** + +Run: + +```powershell +git add frontend/src/features/dashboard frontend/src/app/router.tsx frontend/src/components/app/AppSidebar.tsx frontend/src/features/workbench/WorkbenchPage.tsx +git commit -m "feat: add prototype dashboard navigation" +``` + +--- + +### Task 3: Model Hub Prototype Pages + +**Files:** +- Create: `frontend/src/features/modelhub/ModelProviderPage.tsx` +- Create: `frontend/src/features/modelhub/ModelTestingPage.tsx` +- Modify: `frontend/src/app/router.tsx` + +- [ ] **Step 1: Create model provider page** + +Create `frontend/src/features/modelhub/ModelProviderPage.tsx` with: + +```tsx +import * as React from 'react' +import { Plus, ServerCog } from 'lucide-react' + +import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable' +import { AppField } from '@/components/app/AppField' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { formatDateTime } from '@/lib/format' +import { modelProviders } from '@/features/prototype/mockData' +import type { ModelProvider } from '@/features/prototype/types' + +const columns: AppDataTableColumn[] = [ + { key: 'name', header: '供应商', cell: (provider) => {provider.name} }, + { key: 'type', header: '类型', cell: (provider) => {provider.provider_type === 'local' ? '本地模型' : 'OpenAI 兼容'} }, + { key: 'models', header: '模型数量', cell: (provider) => provider.model_count }, + { key: 'default', header: '默认模型', cell: (provider) => {provider.default_chat_model} }, + { key: 'status', header: '测试状态', cell: (provider) => {provider.last_test_status === 'passed' ? '通过' : provider.last_test_status === 'failed' ? '失败' : '未测试'} }, + { key: 'updated_at', header: '更新时间', cell: (provider) => {formatDateTime(provider.updated_at)} } +] + +export function ModelProviderPage() { + const [open, setOpen] = React.useState(false) + + return ( + + + + + + + + 新增模型供应商 + +
+ + + + + + +
+
+ + } + /> + provider.id} emptyTitle="暂无模型供应商" /> +
+ ) +} +``` + +- [ ] **Step 2: Create model testing page** + +Create `frontend/src/features/modelhub/ModelTestingPage.tsx` with prompt input, model selector, parameter selector, and result cards: + +```tsx +import * as React from 'react' +import { FlaskConical, Play } from 'lucide-react' + +import { AppField } from '@/components/app/AppField' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { AppSection } from '@/components/app/AppSection' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' +import { modelProviders, modelTestResults } from '@/features/prototype/mockData' + +export function ModelTestingPage() { + const [running, setRunning] = React.useState(false) + + function runMockTest() { + setRunning(true) + window.setTimeout(() => setRunning(false), 600) + } + + return ( + + + +
+ +