test: add stage a prototype contracts
This commit is contained in:
1203
frontend/package-lock.json
generated
1203
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"typecheck": "tsc -b",
|
||||
"lint": "eslint src --ext ts,tsx"
|
||||
"lint": "eslint src --ext ts,tsx",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/geist": "^5.2.9",
|
||||
@@ -27,14 +29,19 @@
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^26.0.1",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.16.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"typescript": "^5.7.0"
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
26
frontend/src/features/prototype/mockData.test.ts
Normal file
26
frontend/src/features/prototype/mockData.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
agentRuns,
|
||||
agents,
|
||||
dashboardMetrics,
|
||||
knowledgeReferences,
|
||||
mcpTools,
|
||||
modelProviders,
|
||||
skills
|
||||
} from './mockData'
|
||||
|
||||
describe('Stage A 演示数据', () => {
|
||||
it('提供所有产品模块所需数据', () => {
|
||||
expect(dashboardMetrics).toHaveLength(4)
|
||||
expect(modelProviders.length).toBeGreaterThanOrEqual(2)
|
||||
expect(agents).toHaveLength(3)
|
||||
expect(skills.length).toBeGreaterThanOrEqual(3)
|
||||
expect(agentRuns.every((run) => run.steps.length > 0)).toBe(true)
|
||||
})
|
||||
|
||||
it('保持可信边界和引用完整性', () => {
|
||||
expect(mcpTools.filter((tool) => tool.access_mode === 'write').every((tool) => tool.requires_human_confirm)).toBe(true)
|
||||
expect(knowledgeReferences.every((reference) => reference.score >= 0 && reference.score <= 1)).toBe(true)
|
||||
})
|
||||
})
|
||||
81
frontend/src/features/prototype/mockData.ts
Normal file
81
frontend/src/features/prototype/mockData.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type {
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
DashboardMetric,
|
||||
KnowledgeReference,
|
||||
McpServer,
|
||||
McpTool,
|
||||
ModelProvider,
|
||||
ModelTestResult,
|
||||
SimilarTicket,
|
||||
SkillDefinition,
|
||||
ToolCall
|
||||
} from './types'
|
||||
|
||||
export const dashboardMetrics: DashboardMetric[] = [
|
||||
{ id: 'pending', label: '待处理工单', value: '18', change: '较昨日增加 3 单', trend: 'up', tone: 'warning' },
|
||||
{ id: 'resolved', label: '今日已完成', value: '42', change: '完成率 78%', trend: 'up', tone: 'success' },
|
||||
{ id: 'knowledge', label: '可复用方案', value: '126', change: '本周新增 8 条', trend: 'up', tone: 'default' },
|
||||
{ id: 'agent', label: '智能体成功率', value: '96.4%', change: '最近 24 小时', trend: 'flat', tone: 'ai' }
|
||||
]
|
||||
|
||||
export const modelProviders: ModelProvider[] = [
|
||||
{ id: 1, name: '企业 OpenAI 兼容渠道', protocol: 'OpenAI-compatible', base_url: 'https://llm.example.com/v1', credential_masked: 'sk-****82F1', enabled: true, health: 'healthy', model_count: 4, last_checked_at: '2026-07-10T08:32:00+08:00' },
|
||||
{ id: 2, name: '内部推理服务', protocol: 'OpenAI-compatible', base_url: 'http://model-gateway.internal/v1', credential_masked: 'token-****19AC', enabled: true, health: 'degraded', model_count: 2, last_checked_at: '2026-07-10T08:28:00+08:00' }
|
||||
]
|
||||
|
||||
export const modelTestResults: ModelTestResult[] = [
|
||||
{ id: 1, provider_name: '企业 OpenAI 兼容渠道', model_name: 'support-large', prompt: '分析采集进度长期不更新的可能原因', output: '建议先核对任务状态、最近一次采集日志和税局接口响应。', latency_ms: 1280, input_tokens: 86, output_tokens: 42, status: 'success', created_at: '2026-07-10T09:10:00+08:00' },
|
||||
{ id: 2, provider_name: '内部推理服务', model_name: 'support-small', prompt: '生成客户回复草稿', output: '已确认任务仍在采集中,我们会继续关注上游接口状态。', latency_ms: 760, input_tokens: 64, output_tokens: 31, status: 'success', created_at: '2026-07-10T09:06:00+08:00' }
|
||||
]
|
||||
|
||||
export const agents: AgentDefinition[] = [
|
||||
{ id: 1, code: 'ticket_intake', name: '建单智能体', purpose: '补全工单字段、识别分类并追问缺失信息', enabled: true, model_name: 'support-small', knowledge_scopes: ['客户支持知识库'], skill_count: 2, tool_count: 0, updated_at: '2026-07-10T08:20:00+08:00' },
|
||||
{ id: 2, code: 'support_analysis', name: '支持分析智能体', purpose: '检索知识、推荐方案并生成排查步骤', enabled: true, model_name: 'support-large', knowledge_scopes: ['客户支持知识库', '解决方案库'], skill_count: 3, tool_count: 2, updated_at: '2026-07-10T08:24:00+08:00' },
|
||||
{ id: 3, code: 'knowledge_draft', name: '知识沉淀智能体', purpose: '从已解决工单生成待审核知识草稿', enabled: false, model_name: 'support-large', knowledge_scopes: ['解决方案库'], skill_count: 1, tool_count: 0, updated_at: '2026-07-10T08:26:00+08:00' }
|
||||
]
|
||||
|
||||
export const agentRuns: AgentRun[] = [
|
||||
{ id: 1001, agent_name: '支持分析智能体', ticket_no: 'T202607100006', trigger_user: '张工', status: 'waiting_confirm', output_summary: '已生成 3 条排查建议和 1 份客户回复草稿。', duration_ms: 2180, created_at: '2026-07-10T09:18:00+08:00', steps: [
|
||||
{ id: 1, step_name: '检索相关知识', step_type: 'retrieval', status: 'success', input_summary: '采集进度长期不更新', output_summary: '命中 2 条知识和 1 个解决方案', latency_ms: 180 },
|
||||
{ id: 2, step_name: '查询采集任务', step_type: 'tool', status: 'success', input_summary: '任务编号 TASK-8821', output_summary: '任务运行中,上游接口最近一次返回超时', latency_ms: 420 },
|
||||
{ id: 3, step_name: '生成支持分析', step_type: 'model', status: 'success', input_summary: '工单、知识引用与工具结果', output_summary: '生成原因分析、排查步骤和回复草稿', latency_ms: 1580 },
|
||||
{ id: 4, step_name: '等待人工采纳', step_type: 'approval', status: 'waiting', input_summary: 'AI 支持分析结果', output_summary: '等待技术人员确认', latency_ms: 0 }
|
||||
] },
|
||||
{ id: 1002, agent_name: '建单智能体', ticket_no: '草稿', trigger_user: '李娜', status: 'success', output_summary: '已识别为快速咨询并补全问题对象。', duration_ms: 940, created_at: '2026-07-10T09:12:00+08:00', steps: [
|
||||
{ id: 5, step_name: '抽取工单字段', step_type: 'skill', status: 'success', input_summary: '客户自然语言描述', output_summary: '识别分类、对象和环境', latency_ms: 120 },
|
||||
{ id: 6, step_name: '生成字段草稿', step_type: 'model', status: 'success', input_summary: '结构化字段', output_summary: '生成可确认的建单草稿', latency_ms: 820 }
|
||||
] }
|
||||
]
|
||||
|
||||
export const skills: SkillDefinition[] = [
|
||||
{ id: 1, name: '工单字段抽取', scenario: 'field_extract', version: 'v1.3', enabled: true, prompt_template: '从用户描述中抽取工单分类、问题对象、问题表现、环境和关联系统。', input_schema: '{ "description": "string" }', output_schema: '{ "category_code": "string", "problem_object": "string" }', updated_at: '2026-07-10T08:00:00+08:00' },
|
||||
{ id: 2, name: '缺失信息追问', scenario: 'missing_info', version: 'v1.1', enabled: true, prompt_template: '根据已填写字段判断处理工单仍缺少哪些必要信息。', input_schema: '{ "ticket_draft": "object" }', output_schema: '{ "questions": "string[]" }', updated_at: '2026-07-10T08:02:00+08:00' },
|
||||
{ id: 3, name: '解决方案推荐', scenario: 'solution_recommend', version: 'v1.5', enabled: true, prompt_template: '根据工单内容和检索结果推荐可复用解决方案,并说明匹配原因。', input_schema: '{ "ticket": "object", "references": "object[]" }', output_schema: '{ "recommendations": "object[]" }', updated_at: '2026-07-10T08:04:00+08:00' }
|
||||
]
|
||||
|
||||
export const mcpServers: McpServer[] = [
|
||||
{ id: 1, name: '采集任务查询服务', endpoint: 'http://collector-mcp.internal', auth_type: '令牌', connection_state: 'connected', tool_count: 3, last_sync_at: '2026-07-10T08:40:00+08:00' },
|
||||
{ id: 2, name: '日志检索服务', endpoint: 'http://logs-mcp.internal', auth_type: '令牌', connection_state: 'connected', tool_count: 2, last_sync_at: '2026-07-10T08:36:00+08:00' }
|
||||
]
|
||||
|
||||
export const mcpTools: McpTool[] = [
|
||||
{ id: 1, server_name: '采集任务查询服务', name: '查询采集进度', description: '按任务编号查询当前阶段和最近更新时间', access_mode: 'read', risk_level: 'low', requires_human_confirm: false, enabled: true },
|
||||
{ id: 2, server_name: '日志检索服务', name: '检索任务日志', description: '按任务编号读取脱敏日志摘要', access_mode: 'read', risk_level: 'medium', requires_human_confirm: false, enabled: true },
|
||||
{ id: 3, server_name: '采集任务查询服务', name: '重启采集任务', description: '重新触发指定采集任务', access_mode: 'write', risk_level: 'high', requires_human_confirm: true, enabled: false }
|
||||
]
|
||||
|
||||
export const toolCalls: ToolCall[] = [
|
||||
{ id: 1, tool_name: '查询采集进度', agent_name: '支持分析智能体', input_summary: 'TASK-8821', output_summary: '运行中,最近更新时间 09:16', status: 'success', duration_ms: 420, created_at: '2026-07-10T09:18:20+08:00' },
|
||||
{ id: 2, tool_name: '重启采集任务', agent_name: '支持分析智能体', input_summary: 'TASK-8821', output_summary: '非只读工具不允许自动执行', status: 'blocked', duration_ms: 8, created_at: '2026-07-10T09:18:40+08:00' }
|
||||
]
|
||||
|
||||
export const knowledgeReferences: KnowledgeReference[] = [
|
||||
{ id: 1, title: '采集任务长时间无进度的排查步骤', source_type: 'knowledge_item', content: '先检查任务状态、上游接口响应和最近一次有效日志,再判断是否需要人工重试。', score: 0.93, source_label: '客户支持知识库' },
|
||||
{ id: 2, title: '税局接口不稳定时的客户回复模板', source_type: 'solution', content: '确认任务仍在执行后,向客户说明上游接口状态并持续关注采集进度。', score: 0.87, source_label: '解决方案库' }
|
||||
]
|
||||
|
||||
export const similarTickets: SimilarTicket[] = [
|
||||
{ id: 5, ticket_no: 'T202607030005', title: '税局采集任务停留在初始化阶段', status: 'closed', similarity: 0.91, resolution: '确认税局接口波动,任务恢复后回复客户并关闭。' },
|
||||
{ id: 2, ticket_no: 'T202607010002', title: '查询申报数据采集进度', status: 'resolved', similarity: 0.84, resolution: '查询任务日志后确认仍在采集中。' }
|
||||
]
|
||||
130
frontend/src/features/prototype/types.ts
Normal file
130
frontend/src/features/prototype/types.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
export type DashboardMetric = {
|
||||
id: string
|
||||
label: string
|
||||
value: string
|
||||
change: string
|
||||
trend: 'up' | 'down' | 'flat'
|
||||
tone: 'default' | 'success' | 'warning' | 'danger' | 'ai'
|
||||
}
|
||||
|
||||
export type ModelProvider = {
|
||||
id: number
|
||||
name: string
|
||||
protocol: 'OpenAI-compatible'
|
||||
base_url: string
|
||||
credential_masked: string
|
||||
enabled: boolean
|
||||
health: 'healthy' | 'degraded' | 'unchecked'
|
||||
model_count: number
|
||||
last_checked_at: string
|
||||
}
|
||||
|
||||
export type ModelTestResult = {
|
||||
id: number
|
||||
provider_name: string
|
||||
model_name: string
|
||||
prompt: string
|
||||
output: string
|
||||
latency_ms: number
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
status: 'success' | 'failed'
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type AgentDefinition = {
|
||||
id: number
|
||||
code: 'ticket_intake' | 'support_analysis' | 'knowledge_draft'
|
||||
name: string
|
||||
purpose: string
|
||||
enabled: boolean
|
||||
model_name: string
|
||||
knowledge_scopes: string[]
|
||||
skill_count: number
|
||||
tool_count: number
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type AgentStep = {
|
||||
id: number
|
||||
step_name: string
|
||||
step_type: 'model' | 'retrieval' | 'skill' | 'tool' | 'approval'
|
||||
status: 'success' | 'failed' | 'waiting'
|
||||
input_summary: string
|
||||
output_summary: string
|
||||
latency_ms: number
|
||||
}
|
||||
|
||||
export type AgentRun = {
|
||||
id: number
|
||||
agent_name: string
|
||||
ticket_no: string
|
||||
trigger_user: string
|
||||
status: 'success' | 'failed' | 'running' | 'waiting_confirm'
|
||||
output_summary: string
|
||||
duration_ms: number
|
||||
created_at: string
|
||||
steps: AgentStep[]
|
||||
}
|
||||
|
||||
export type SkillDefinition = {
|
||||
id: number
|
||||
name: string
|
||||
scenario: 'field_extract' | 'missing_info' | 'solution_recommend' | 'reply_draft' | 'knowledge_draft'
|
||||
version: string
|
||||
enabled: boolean
|
||||
prompt_template: string
|
||||
input_schema: string
|
||||
output_schema: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type McpServer = {
|
||||
id: number
|
||||
name: string
|
||||
endpoint: string
|
||||
auth_type: '无认证' | '令牌'
|
||||
connection_state: 'connected' | 'disconnected' | 'error'
|
||||
tool_count: number
|
||||
last_sync_at: string
|
||||
}
|
||||
|
||||
export type McpTool = {
|
||||
id: number
|
||||
server_name: string
|
||||
name: string
|
||||
description: string
|
||||
access_mode: 'read' | 'write'
|
||||
risk_level: 'low' | 'medium' | 'high'
|
||||
requires_human_confirm: boolean
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export type ToolCall = {
|
||||
id: number
|
||||
tool_name: string
|
||||
agent_name: string
|
||||
input_summary: string
|
||||
output_summary: string
|
||||
status: 'success' | 'failed' | 'blocked'
|
||||
duration_ms: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type KnowledgeReference = {
|
||||
id: number
|
||||
title: string
|
||||
source_type: 'knowledge_item' | 'solution' | 'ticket' | 'document'
|
||||
content: string
|
||||
score: number
|
||||
source_label: string
|
||||
}
|
||||
|
||||
export type SimilarTicket = {
|
||||
id: number
|
||||
ticket_no: string
|
||||
title: string
|
||||
status: 'resolved' | 'closed'
|
||||
similarity: number
|
||||
resolution: string
|
||||
}
|
||||
1
frontend/src/test/setup.ts
Normal file
1
frontend/src/test/setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
15
frontend/vitest.config.ts
Normal file
15
frontend/vitest.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import path from 'node:path'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
css: true
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user