feat(frontend): 重设计Common Agent Studio原型

This commit is contained in:
2026-05-31 23:57:37 +08:00
parent ab9b099e9b
commit 1d89bc89fe
17 changed files with 2142 additions and 213 deletions

View File

@@ -0,0 +1,228 @@
-- Common Agent Studio prototype schema draft.
-- These tables extend the current RAG, model-provider and agent_definition data model
-- without replacing existing core entities.
CREATE TABLE IF NOT EXISTS studio_project (
id BIGSERIAL PRIMARY KEY,
project_code VARCHAR(100) NOT NULL,
project_name VARCHAR(200) NOT NULL,
environment VARCHAR(50) NOT NULL DEFAULT 'DEV',
publish_status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
current_version VARCHAR(50),
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_studio_project_code UNIQUE (project_code)
);
CREATE TABLE IF NOT EXISTS workflow_definition (
id BIGSERIAL PRIMARY KEY,
project_id BIGINT,
workflow_code VARCHAR(100) NOT NULL,
workflow_name VARCHAR(200) NOT NULL,
description VARCHAR(1000),
bound_agent_id BIGINT,
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_workflow_definition_code UNIQUE (workflow_code),
CONSTRAINT fk_workflow_definition_project_id FOREIGN KEY (project_id) REFERENCES studio_project (id),
CONSTRAINT fk_workflow_definition_agent_id FOREIGN KEY (bound_agent_id) REFERENCES agent_definition (id)
);
CREATE TABLE IF NOT EXISTS workflow_version (
id BIGSERIAL PRIMARY KEY,
workflow_id BIGINT NOT NULL,
version_no INTEGER NOT NULL,
snapshot_name VARCHAR(100) NOT NULL,
graph_json JSONB NOT NULL DEFAULT '{}'::jsonb,
publish_status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
published_time TIMESTAMP,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_workflow_version_no UNIQUE (workflow_id, version_no),
CONSTRAINT fk_workflow_version_workflow_id FOREIGN KEY (workflow_id) REFERENCES workflow_definition (id)
);
CREATE TABLE IF NOT EXISTS workflow_run (
id BIGSERIAL PRIMARY KEY,
request_id VARCHAR(64) NOT NULL,
workflow_id BIGINT,
workflow_version_id BIGINT,
agent_id BIGINT,
run_source VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL,
input_json JSONB NOT NULL DEFAULT '{}'::jsonb,
output_json JSONB NOT NULL DEFAULT '{}'::jsonb,
duration_ms INTEGER,
estimated_cost NUMERIC(14, 8),
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_workflow_run_request_id UNIQUE (request_id),
CONSTRAINT fk_workflow_run_workflow_id FOREIGN KEY (workflow_id) REFERENCES workflow_definition (id),
CONSTRAINT fk_workflow_run_version_id FOREIGN KEY (workflow_version_id) REFERENCES workflow_version (id),
CONSTRAINT fk_workflow_run_agent_id FOREIGN KEY (agent_id) REFERENCES agent_definition (id)
);
CREATE TABLE IF NOT EXISTS workflow_run_step (
id BIGSERIAL PRIMARY KEY,
run_id BIGINT NOT NULL,
node_id VARCHAR(100) NOT NULL,
node_type VARCHAR(50) NOT NULL,
node_name VARCHAR(200),
status VARCHAR(50) NOT NULL,
input_json JSONB NOT NULL DEFAULT '{}'::jsonb,
output_json JSONB NOT NULL DEFAULT '{}'::jsonb,
duration_ms INTEGER,
error_message VARCHAR(1000),
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT fk_workflow_run_step_run_id FOREIGN KEY (run_id) REFERENCES workflow_run (id)
);
CREATE TABLE IF NOT EXISTS mcp_server (
id BIGSERIAL PRIMARY KEY,
server_code VARCHAR(100) NOT NULL,
server_name VARCHAR(200) NOT NULL,
import_type VARCHAR(50) NOT NULL,
endpoint_url VARCHAR(500),
package_name VARCHAR(200),
manifest_json JSONB NOT NULL DEFAULT '{}'::jsonb,
auth_type VARCHAR(50),
secret_ref VARCHAR(200),
health_status VARCHAR(50) NOT NULL DEFAULT 'UNKNOWN',
enabled BOOLEAN NOT NULL DEFAULT TRUE,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_mcp_server_code UNIQUE (server_code)
);
CREATE TABLE IF NOT EXISTS mcp_capability (
id BIGSERIAL PRIMARY KEY,
server_id BIGINT NOT NULL,
capability_code VARCHAR(150) NOT NULL,
capability_name VARCHAR(200) NOT NULL,
capability_type VARCHAR(50) NOT NULL,
schema_json JSONB NOT NULL DEFAULT '{}'::jsonb,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_mcp_capability_code UNIQUE (server_id, capability_code),
CONSTRAINT fk_mcp_capability_server_id FOREIGN KEY (server_id) REFERENCES mcp_server (id)
);
CREATE TABLE IF NOT EXISTS skill_definition (
id BIGSERIAL PRIMARY KEY,
skill_code VARCHAR(100) NOT NULL,
skill_name VARCHAR(200) NOT NULL,
skill_type VARCHAR(50) NOT NULL,
description VARCHAR(1000),
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_skill_definition_code UNIQUE (skill_code)
);
CREATE TABLE IF NOT EXISTS skill_version (
id BIGSERIAL PRIMARY KEY,
skill_id BIGINT NOT NULL,
version_no INTEGER NOT NULL,
prompt_text TEXT,
code_text TEXT,
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
variable_schema_json JSONB NOT NULL DEFAULT '{}'::jsonb,
test_result_json JSONB NOT NULL DEFAULT '{}'::jsonb,
publish_status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
published_time TIMESTAMP,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_skill_version_no UNIQUE (skill_id, version_no),
CONSTRAINT fk_skill_version_skill_id FOREIGN KEY (skill_id) REFERENCES skill_definition (id)
);
CREATE TABLE IF NOT EXISTS agent_session (
id BIGSERIAL PRIMARY KEY,
session_code VARCHAR(100) NOT NULL,
agent_id BIGINT NOT NULL,
workflow_run_id BIGINT,
title VARCHAR(200),
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_agent_session_code UNIQUE (session_code),
CONSTRAINT fk_agent_session_agent_id FOREIGN KEY (agent_id) REFERENCES agent_definition (id),
CONSTRAINT fk_agent_session_run_id FOREIGN KEY (workflow_run_id) REFERENCES workflow_run (id)
);
CREATE TABLE IF NOT EXISTS agent_message (
id BIGSERIAL PRIMARY KEY,
session_id BIGINT NOT NULL,
role VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
citation_json JSONB NOT NULL DEFAULT '[]'::jsonb,
token_count INTEGER,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT fk_agent_message_session_id FOREIGN KEY (session_id) REFERENCES agent_session (id)
);
CREATE TABLE IF NOT EXISTS agent_capability_binding (
id BIGSERIAL PRIMARY KEY,
owner_type VARCHAR(50) NOT NULL,
owner_id BIGINT NOT NULL,
capability_type VARCHAR(50) NOT NULL,
capability_id BIGINT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
version INTEGER NOT NULL DEFAULT 1,
create_time TIMESTAMP,
update_time TIMESTAMP,
remark VARCHAR(500) DEFAULT '',
create_by VARCHAR(64),
update_by VARCHAR(64),
CONSTRAINT uk_agent_capability_binding UNIQUE (owner_type, owner_id, capability_type, capability_id)
);

View File

@@ -0,0 +1,143 @@
export type PipelineStatus = 'done' | 'running' | 'blocked' | 'idle';
export interface LifecycleStep {
name: string;
description: string;
status: PipelineStatus;
}
export interface RecentRun {
id: string;
name: string;
type: string;
status: string;
latency: string;
cost: string;
}
export interface KnowledgeDocument {
id: string;
name: string;
parseStatus: string;
indexStatus: string;
chunks: number;
updatedAt: string;
}
export interface WorkflowNode {
id: string;
type: string;
label: string;
description: string;
x: number;
y: number;
}
export interface WorkflowEdge {
from: string;
to: string;
}
export interface TraceStep {
node: string;
status: string;
duration: string;
output: string;
}
export const lifecycleSteps: LifecycleStep[] = [
{ name: '知识接入', description: '上传、解析、切片、向量化', status: 'done' },
{ name: '能力编排', description: 'Workflow 连接模型、工具与 Skill', status: 'running' },
{ name: '对话调试', description: '验证引用、成本、延迟与回答质量', status: 'running' },
{ name: '发布观测', description: '版本快照、运行追踪、异常排查', status: 'idle' },
];
export const readinessChecklist = [
{ label: '知识库已绑定 Embedding 模型', done: true },
{ label: 'Workflow 草稿存在未发布节点变更', done: false },
{ label: 'Agent 已绑定默认知识库与 Skill', done: true },
{ label: '生产环境路由规则仍需压测', done: false },
];
export const recentRuns: RecentRun[] = [
{ id: 'run-1842', name: '售前问答 Agent', type: 'Agent', status: '成功', latency: '1.42s', cost: '¥0.018' },
{ id: 'run-1841', name: '合同知识召回', type: 'Workflow', status: '成功', latency: '860ms', cost: '¥0.006' },
{ id: 'run-1840', name: 'MCP: jira.search', type: 'MCP', status: '重试', latency: '2.8s', cost: '¥0.000' },
];
export const knowledgeStores = [
{ id: '1001', name: '产品制度库', docs: 128, health: 96, status: '可检索' },
{ id: '1002', name: '交付项目资料', docs: 64, health: 82, status: '索引中' },
{ id: '1003', name: '客服 FAQ', docs: 214, health: 91, status: '可检索' },
];
export const knowledgeDocuments: KnowledgeDocument[] = [
{ id: 'doc-01', name: '售前方案模板.pdf', parseStatus: 'PARSED', indexStatus: 'INDEXED', chunks: 42, updatedAt: '10分钟前' },
{ id: 'doc-02', name: '项目实施手册.docx', parseStatus: 'PARSED', indexStatus: 'INDEXING', chunks: 88, updatedAt: '23分钟前' },
{ id: 'doc-03', name: '服务条款更新.md', parseStatus: 'FAILED', indexStatus: 'PENDING', chunks: 0, updatedAt: '今天 09:12' },
{ id: 'doc-04', name: '客服高频问题.xlsx', parseStatus: 'PARSED', indexStatus: 'INDEXED', chunks: 119, updatedAt: '昨天' },
];
export const ingestionSteps: LifecycleStep[] = [
{ name: '上传', description: '4 个文件已入库 sys_attachment', status: 'done' },
{ name: '解析', description: 'Tika 抽取文本并保存快照', status: 'done' },
{ name: '切片', description: '固定长度 800 / overlap 120', status: 'running' },
{ name: '向量化', description: 'Qwen3 Embedding 1024 维', status: 'idle' },
{ name: '可检索', description: '等待索引任务完成', status: 'idle' },
];
export const workflowNodes: WorkflowNode[] = [
{ id: 'start', type: 'START', label: 'Start', description: '接收用户问题', x: 4, y: 42 },
{ id: 'retrieve', type: 'KNOWLEDGE_RETRIEVAL', label: 'Knowledge Retrieval', description: 'TopK=6 / score>0.72', x: 25, y: 18 },
{ id: 'llm', type: 'LLM', label: 'LLM', description: 'RAG_ANSWER 路由', x: 47, y: 42 },
{ id: 'mcp', type: 'MCP_TOOL', label: 'MCP Tool', description: 'jira.search / docs.lookup', x: 47, y: 70 },
{ id: 'skill', type: 'SKILL', label: 'Skill', description: '答案审校与引用整理', x: 69, y: 42 },
{ id: 'answer', type: 'ANSWER', label: 'Answer', description: '返回回答与引用', x: 88, y: 42 },
];
export const workflowEdges: WorkflowEdge[] = [
{ from: 'start', to: 'retrieve' },
{ from: 'retrieve', to: 'llm' },
{ from: 'llm', to: 'skill' },
{ from: 'mcp', to: 'skill' },
{ from: 'skill', to: 'answer' },
];
export const traceSteps: TraceStep[] = [
{ node: 'Start', status: '完成', duration: '4ms', output: '用户问题已标准化' },
{ node: 'Knowledge Retrieval', status: '完成', duration: '218ms', output: '召回 6 个切片' },
{ node: 'LLM', status: '完成', duration: '1.12s', output: '生成 612 tokens' },
{ node: 'Skill', status: '完成', duration: '88ms', output: '引用格式已校验' },
];
export const chatMessages = [
{ role: 'user', content: '如果客户要求私有化部署,售前方案里必须说明哪些内容?' },
{
role: 'assistant',
content: '建议说明部署拓扑、模型服务商、知识库索引策略、权限边界、日志留存周期和故障恢复方式。当前回答引用了 3 个知识切片。',
},
];
export const citations = [
{ title: '售前方案模板.pdf', score: '0.91', text: '私有化部署章节应覆盖基础设施、网络、安全与运维边界。' },
{ title: '项目实施手册.docx', score: '0.87', text: '交付计划需包含数据导入、索引重建与验收标准。' },
{ title: '服务条款更新.md', score: '0.82', text: '客户数据默认不出域,模型调用日志需保留审计字段。' },
];
export const mcpCapabilities = [
{ name: 'jira.search', type: 'tool', status: '已启用', description: '按项目、状态、负责人检索任务' },
{ name: 'docs.lookup', type: 'resource', status: '已启用', description: '读取外部文档中心条目' },
{ name: 'deploy.trigger', type: 'tool', status: '待授权', description: '触发测试环境部署流水线' },
];
export const skillVersions = [
{ version: 'v4', status: 'Draft', updatedAt: '刚刚', note: '增加引用一致性检查' },
{ version: 'v3', status: 'Published', updatedAt: '昨天', note: '生产环境当前版本' },
{ version: 'v2', status: 'Archived', updatedAt: '5天前', note: '旧版回答润色策略' },
];
export const modelRoutes = [
{ task: 'RAG_ANSWER', primary: 'qwen-plus', fallback: 'deepseek-v3', latency: '1800ms', status: '启用' },
{ task: 'RAG_EMBEDDING', primary: 'Qwen3-Embedding', fallback: '无', latency: '900ms', status: '启用' },
{ task: 'AGENT_PLAN', primary: 'gpt-4.1', fallback: 'qwen-max', latency: '3200ms', status: '草稿' },
];

View File

@@ -1,64 +1,71 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
Box, ChatDotRound,
Collection, Collection,
Connection,
Cpu,
DataBoard,
Document, Document,
Grid, MagicStick,
Histogram, Monitor,
List, Operation,
Setting, UploadFilled,
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
const systemMenuItems = [ const studioMenuItems = [
{ path: '/system/enums', label: '系统枚举', icon: Grid }, { path: '/studio', label: '工作台', icon: DataBoard },
{ path: '/system/model/providers', label: '模型服务商', icon: Setting }, { path: '/knowledge', label: '知识资产', icon: Collection },
{ path: '/system/model/configs', label: '模型配置', icon: Setting }, { path: '/knowledge/ingestion', label: '文件解析', icon: UploadFilled },
{ path: '/system/model/routes', label: '路由规则', icon: Setting }, { path: '/workflows', label: 'Workflow', icon: Connection },
{ path: '/system/model/call-logs', label: '调用日志', icon: Setting }, { path: '/agents', label: 'Agent 对话', icon: ChatDotRound },
]; { path: '/mcp', label: 'MCP', icon: Operation },
{ path: '/skills', label: 'Skills', icon: MagicStick },
const ragMenuItems = [ { path: '/models', label: '模型', icon: Cpu },
{ path: '/rag/stores', label: '知识库', icon: Collection }, { path: '/observability', label: '观测', icon: Monitor },
{ path: '/rag/workbench', label: 'RAG工作台', icon: Histogram },
{ path: '/rag/documents', label: '知识文档', icon: Document },
{ path: '/rag/tasks/chunk', label: '切片任务', icon: List },
]; ];
</script> </script>
<template> <template>
<el-container class="admin-layout"> <el-container class="admin-layout studio-shell">
<el-aside class="admin-sidebar" width="232px"> <el-aside class="admin-sidebar studio-sidebar" width="248px">
<div class="brand"> <div class="brand">
<el-icon :size="24"> <el-icon :size="24">
<Box /> <Document />
</el-icon> </el-icon>
<span>Common Agent</span> <div>
<span>Common Agent Studio</span>
<small>AI Agent Development Platform</small>
</div>
</div> </div>
<el-menu class="side-menu" :default-active="$route.path" router> <el-menu class="side-menu studio-menu" :default-active="$route.path" router>
<el-sub-menu index="system"> <el-menu-item v-for="item in studioMenuItems" :key="item.path" :index="item.path">
<template #title>系统管理</template>
<el-menu-item v-for="item in systemMenuItems" :key="item.path" :index="item.path">
<el-icon> <el-icon>
<component :is="item.icon" /> <component :is="item.icon" />
</el-icon> </el-icon>
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
</el-menu-item> </el-menu-item>
</el-sub-menu>
<el-sub-menu index="rag">
<template #title>RAG</template>
<el-menu-item v-for="item in ragMenuItems" :key="item.path" :index="item.path">
<el-icon>
<component :is="item.icon" />
</el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</el-sub-menu>
</el-menu> </el-menu>
<div class="sidebar-status">
<span>Dev 环境</span>
<strong>Draft / Published</strong>
</div>
</el-aside> </el-aside>
<el-container> <el-container class="studio-content-shell">
<el-main class="admin-main"> <header class="studio-topbar">
<div class="project-switcher">
<span>项目</span>
<strong>通用 AI Agent 开发平台</strong>
</div>
<div class="topbar-actions">
<span class="run-status">运行状态正常</span>
<el-button>版本快照</el-button>
<el-button type="primary">发布</el-button>
</div>
</header>
<el-main class="admin-main studio-main">
<RouterView /> <RouterView />
</el-main> </el-main>
</el-container> </el-container>

View File

@@ -6,16 +6,16 @@ import { describe, expect, it, vi } from 'vitest';
import AdminLayout from '../AdminLayout.vue'; import AdminLayout from '../AdminLayout.vue';
vi.mock('vue-router', () => ({ vi.mock('vue-router', () => ({
useRoute: () => ({ meta: { title: '系统枚举' } }), useRoute: () => ({ meta: { title: '工作台' } }),
})); }));
describe('AdminLayout', () => { describe('AdminLayout', () => {
it('does not render a duplicate page header above the main page content', () => { it('renders the Studio shell navigation without legacy admin groups', () => {
const wrapper = mount(AdminLayout, { const wrapper = mount(AdminLayout, {
global: { global: {
plugins: [createPinia(), ElementPlus], plugins: [createPinia(), ElementPlus],
mocks: { mocks: {
$route: { path: '/system/enums' }, $route: { path: '/studio' },
}, },
stubs: { stubs: {
RouterView: { template: '<main data-test="router-view" />' }, RouterView: { template: '<main data-test="router-view" />' },
@@ -25,5 +25,12 @@ describe('AdminLayout', () => {
expect(wrapper.find('.admin-header').exists()).toBe(false); expect(wrapper.find('.admin-header').exists()).toBe(false);
expect(wrapper.find('[data-test="router-view"]').exists()).toBe(true); expect(wrapper.find('[data-test="router-view"]').exists()).toBe(true);
expect(wrapper.text()).toContain('Common Agent Studio');
expect(wrapper.text()).toContain('知识资产');
expect(wrapper.text()).toContain('Workflow');
expect(wrapper.text()).toContain('观测');
expect(wrapper.text()).not.toContain('系统管理');
expect(wrapper.text()).not.toContain('RAG');
expect(wrapper.text()).not.toContain('Agent管理');
}); });
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<main class="not-found"> <main class="not-found">
<h1>404</h1> <h1>404</h1>
<RouterLink to="/rag/workbench">返回RAG工作台</RouterLink> <RouterLink to="/studio">返回工作台</RouterLink>
</main> </main>
</template> </template>

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import { ChatDotRound, Coin, Timer } from '@element-plus/icons-vue';
import { chatMessages, citations, traceSteps } from '@/data/studioMock';
</script>
<template>
<section class="studio-page agent-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">AgentWorkspaceView</p>
<h1>Agent 对话调试</h1>
</div>
<el-button type="primary">发布 Agent</el-button>
</header>
<div class="agent-layout">
<section class="studio-panel chat-panel">
<div class="panel-heading">
<div>
<h2>售前问答 Agent</h2>
<span>POST /api/agents/1001/runs</span>
</div>
<el-tag>Draft</el-tag>
</div>
<div class="message-list">
<article v-for="message in chatMessages" :key="message.content" :class="message.role">
<strong>{{ message.role === 'user' ? '用户' : 'Agent' }}</strong>
<p>{{ message.content }}</p>
</article>
</div>
<div class="chat-composer">
<span>输入调试问题运行会写入 agent_session / agent_message 草案</span>
<el-button type="primary"><el-icon><ChatDotRound /></el-icon> 发送</el-button>
</div>
</section>
<aside class="studio-panel citation-panel">
<div class="panel-heading compact">
<h2>引用切片</h2>
<span>3 个来源</span>
</div>
<article v-for="citation in citations" :key="citation.title" class="citation-card">
<strong>{{ citation.title }}</strong>
<el-tag type="success">score {{ citation.score }}</el-tag>
<p>{{ citation.text }}</p>
</article>
</aside>
<aside class="studio-panel run-inspector">
<div class="panel-heading compact">
<h2>运行追踪</h2>
<span>modelRequestId: f4215d</span>
</div>
<div class="metric-mini">
<span><el-icon><Timer /></el-icon> 1.42s</span>
<span><el-icon><Coin /></el-icon> ¥0.018</span>
<span>1,248 tokens</span>
</div>
<ol class="log-list">
<li v-for="step in traceSteps" :key="step.node">
<time>{{ step.duration }}</time>
<span>{{ step.node }} · {{ step.output }}</span>
</li>
</ol>
</aside>
</div>
</section>
</template>

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue';
import { ingestionSteps } from '@/data/studioMock';
</script>
<template>
<section class="studio-page ingestion-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">IngestionPipelineView</p>
<h1>文件解析管道</h1>
</div>
<el-button type="primary">启动索引任务</el-button>
</header>
<div class="ingestion-layout">
<section class="studio-panel upload-panel">
<div class="upload-dropzone">
<el-icon><UploadFilled /></el-icon>
<strong>拖拽文件到这里</strong>
<span>支持 PDF / Word / Excel / Markdown / TXT上传后自动创建 ingestion run</span>
<el-button type="primary">选择文件</el-button>
</div>
<div class="pipeline-timeline">
<article v-for="step in ingestionSteps" :key="step.name" :class="`is-${step.status}`">
<div class="timeline-dot" />
<strong>{{ step.name }}</strong>
<span>{{ step.description }}</span>
</article>
</div>
</section>
<section class="studio-panel preview-panel">
<div class="panel-heading">
<h2>解析与切片预览</h2>
<span>GET /api/knowledge/ingestion-runs/run-20260531</span>
</div>
<div class="preview-split">
<article>
<h3>解析文本</h3>
<p>私有化部署章节应覆盖基础设施网络安全与运维边界平台需说明模型服务商知识库索引策略与日志留存周期...</p>
</article>
<article>
<h3>切片 #24</h3>
<p>chunk_size=800, overlap=120, strategy=FIXED_LENGTH该切片将进入 rag_chunk 并在向量化后写入 rag_chunk_embedding</p>
</article>
</div>
<div class="pipeline-controls">
<label>切片策略 <strong>固定长度</strong></label>
<label>Chunk Size <strong>800</strong></label>
<label>Overlap <strong>120</strong></label>
<label>Embedding <strong>Qwen3 1024d</strong></label>
</div>
</section>
<aside class="studio-panel task-log-panel">
<div class="panel-heading compact">
<h2>任务日志</h2>
<span>run-20260531</span>
</div>
<ol class="log-list">
<li><time>23:08:12</time><span>上传 4 个文件并创建 rag_document</span></li>
<li><time>23:08:24</time><span>Tika 解析完成 3 个文件</span></li>
<li class="warn"><time>23:08:31</time><span>服务条款更新.md 编码检测失败等待重试</span></li>
<li><time>23:08:40</time><span>切片任务进行中 68 / 119</span></li>
</ol>
</aside>
</div>
</section>
</template>

View File

@@ -0,0 +1,94 @@
<script setup lang="ts">
import { DataAnalysis, Document, Setting } from '@element-plus/icons-vue';
import { knowledgeDocuments, knowledgeStores } from '@/data/studioMock';
</script>
<template>
<section class="studio-page workspace-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">KnowledgeWorkspaceView</p>
<h1>知识资产</h1>
</div>
<el-button type="primary">新建知识库</el-button>
</header>
<div class="three-column-layout">
<aside class="studio-panel collection-rail">
<div class="panel-heading compact">
<h2>知识集合</h2>
<span>{{ knowledgeStores.length }} 个库</span>
</div>
<button
v-for="store in knowledgeStores"
:key="store.id"
class="collection-item"
:class="{ active: store.id === '1001' }"
>
<strong>{{ store.name }}</strong>
<span>{{ store.docs }} 文档 · 健康度 {{ store.health }}%</span>
<em>{{ store.status }}</em>
</button>
</aside>
<main class="studio-panel knowledge-main">
<div class="panel-heading">
<div>
<h2>产品制度库</h2>
<span>绑定旧数据语义rag_store / rag_document / rag_chunk_embedding</span>
</div>
<el-tag type="success">可检索</el-tag>
</div>
<div class="config-grid">
<article>
<el-icon><Setting /></el-icon>
<strong>Embedding 模型</strong>
<span>Qwen3-Embedding · 1024 </span>
</article>
<article>
<el-icon><DataAnalysis /></el-icon>
<strong>检索配置</strong>
<span>TopK 6 · Score 0.72 · Rerank 关闭</span>
</article>
<article>
<el-icon><Document /></el-icon>
<strong>索引版本</strong>
<span>index_version 14 · Draft 快照</span>
</article>
</div>
<div class="document-table">
<div class="table-row table-head">
<span>文档</span><span>解析</span><span>索引</span><span>切片</span><span>更新</span>
</div>
<div v-for="doc in knowledgeDocuments" :key="doc.id" class="table-row">
<strong>{{ doc.name }}</strong>
<span>{{ doc.parseStatus }}</span>
<span>{{ doc.indexStatus }}</span>
<span>{{ doc.chunks }}</span>
<span>{{ doc.updatedAt }}</span>
</div>
</div>
</main>
<aside class="studio-panel inspector-panel">
<div class="panel-heading compact">
<h2>状态 Inspector</h2>
<span>聚合接口</span>
</div>
<dl class="inspector-list">
<dt>Workspace API</dt>
<dd>GET /api/knowledge/workspaces/1001</dd>
<dt>文档健康度</dt>
<dd>96% · 1 个解析失败</dd>
<dt>待处理任务</dt>
<dd>2 个文档等待向量化</dd>
<dt>发布影响</dt>
<dd>更新后需要 Workflow 重新验证引用质量</dd>
</dl>
</aside>
</div>
</section>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import { Link, Upload } from '@element-plus/icons-vue';
import { mcpCapabilities } from '@/data/studioMock';
</script>
<template>
<section class="studio-page mcp-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">McpImportView</p>
<h1>MCP 导入</h1>
</div>
<el-button type="primary"><el-icon><Upload /></el-icon> 导入 Server</el-button>
</header>
<div class="mcp-layout">
<section class="studio-panel import-panel">
<div class="panel-heading">
<h2>外部能力接入</h2>
<span>POST /api/mcp/import</span>
</div>
<div class="import-options">
<button class="active"><el-icon><Link /></el-icon><strong>URL</strong><span>https://mcp.example.com/sse</span></button>
<button><strong>npm package</strong><span>@acme/mcp-jira</span></button>
<button><strong>JSON Manifest</strong><span>粘贴 server 能力声明</span></button>
</div>
<div class="manifest-box">
<span>{ "server": "jira", "transport": "sse", "auth": "oauth2" }</span>
</div>
</section>
<section class="studio-panel capability-panel">
<div class="panel-heading">
<h2>能力预览</h2>
<span>GET /api/mcp/servers/jira/capabilities</span>
</div>
<div class="capability-grid">
<article v-for="item in mcpCapabilities" :key="item.name">
<el-tag>{{ item.type }}</el-tag>
<strong>{{ item.name }}</strong>
<p>{{ item.description }}</p>
<span>{{ item.status }}</span>
</article>
</div>
</section>
</div>
</section>
</template>

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import { modelRoutes } from '@/data/studioMock';
</script>
<template>
<section class="studio-page model-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">ModelRoutingView</p>
<h1>模型与路由</h1>
</div>
<el-button type="primary">新增路由</el-button>
</header>
<div class="studio-panel model-panel">
<div class="panel-heading">
<h2>任务路由规则</h2>
<span>保留 model_provider / model_config / model_route_rule 语义</span>
</div>
<div class="document-table">
<div class="table-row table-head">
<span>任务</span><span>主模型</span><span>Fallback</span><span>最大延迟</span><span>状态</span>
</div>
<div v-for="route in modelRoutes" :key="route.task" class="table-row">
<strong>{{ route.task }}</strong>
<span>{{ route.primary }}</span>
<span>{{ route.fallback }}</span>
<span>{{ route.latency }}</span>
<el-tag :type="route.status === '启用' ? 'success' : 'warning'">{{ route.status }}</el-tag>
</div>
</div>
</div>
</section>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import { recentRuns, traceSteps } from '@/data/studioMock';
</script>
<template>
<section class="studio-page observability-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">ObservabilityView</p>
<h1>运行观测</h1>
</div>
<el-button>导出日志</el-button>
</header>
<div class="observability-layout">
<section class="studio-panel">
<div class="panel-heading">
<h2>运行记录</h2>
<span>workflow_run / workflow_run_step / model_call_log</span>
</div>
<div class="run-table">
<div class="run-row run-head">
<span>名称</span><span>类型</span><span>状态</span><span>延迟</span><span>成本</span>
</div>
<div v-for="run in recentRuns" :key="run.id" class="run-row">
<strong>{{ run.name }}</strong>
<span>{{ run.type }}</span>
<el-tag :type="run.status === '成功' ? 'success' : 'warning'">{{ run.status }}</el-tag>
<span>{{ run.latency }}</span>
<span>{{ run.cost }}</span>
</div>
</div>
</section>
<aside class="studio-panel">
<div class="panel-heading compact">
<h2>步骤日志</h2>
<span>run-1842</span>
</div>
<ol class="log-list">
<li v-for="step in traceSteps" :key="step.node">
<time>{{ step.duration }}</time>
<span>{{ step.node }} · {{ step.status }} · {{ step.output }}</span>
</li>
</ol>
</aside>
</div>
</section>
</template>

View File

@@ -0,0 +1,56 @@
<script setup lang="ts">
import { skillVersions } from '@/data/studioMock';
</script>
<template>
<section class="studio-page skill-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">SkillWorkspaceView</p>
<h1>Skill 编辑与使用</h1>
</div>
<el-button type="primary">测试 Skill</el-button>
</header>
<div class="skill-layout">
<section class="studio-panel skill-editor">
<div class="panel-heading">
<h2>引用审校 Skill</h2>
<span>PUT /api/skills/skill-citation/draft</span>
</div>
<div class="editor-tabs">
<button class="active">Prompt</button>
<button>Code</button>
<button>Config</button>
</div>
<pre class="prompt-editor">你是回答审校器请检查答案是否完整引用知识库切片并输出
1. answer_quality
2. missing_citations
3. rewrite_suggestion</pre>
<div class="variable-grid">
<label>变量 <strong>answer</strong></label>
<label>变量 <strong>citations[]</strong></label>
<label>输出 <strong>quality_score</strong></label>
</div>
</section>
<aside class="studio-panel test-panel">
<div class="panel-heading compact">
<h2>测试面板</h2>
<span>POST /api/skills/skill-citation/test</span>
</div>
<div class="test-result">
<strong>quality_score: 0.86</strong>
<p>建议补充日志留存周期的引用来源并将私有化部署边界写得更明确</p>
</div>
<div class="version-list">
<article v-for="version in skillVersions" :key="version.version">
<strong>{{ version.version }}</strong>
<span>{{ version.status }}</span>
<em>{{ version.note }} · {{ version.updatedAt }}</em>
</article>
</div>
</aside>
</div>
</section>
</template>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import { ArrowRight, Check, Warning } from '@element-plus/icons-vue';
import { lifecycleSteps, readinessChecklist, recentRuns } from '@/data/studioMock';
</script>
<template>
<section class="studio-page dashboard-page">
<header class="studio-hero">
<div>
<p class="studio-kicker">项目 / Common Agent Studio</p>
<h1>从知识接入到 Agent 发布的一体化工作台</h1>
<p>
使用新的聚合 ViewModel 驱动原型知识资产WorkflowMCPSkillAgent 调试与观测都围绕一次发布旅程组织
</p>
</div>
<div class="hero-actions">
<el-button type="primary">新建 Workflow</el-button>
<el-button>导入 MCP</el-button>
</div>
</header>
<div class="lifecycle-strip">
<article v-for="(step, index) in lifecycleSteps" :key="step.name" class="lifecycle-step" :class="`is-${step.status}`">
<div class="step-index">{{ index + 1 }}</div>
<div>
<strong>{{ step.name }}</strong>
<span>{{ step.description }}</span>
</div>
<el-icon v-if="index < lifecycleSteps.length - 1"><ArrowRight /></el-icon>
</article>
</div>
<div class="dashboard-grid">
<section class="studio-panel readiness-panel">
<div class="panel-heading">
<div>
<h2>发布就绪检查</h2>
<span>ViewModel: StudioDashboardView</span>
</div>
<el-tag type="warning">Draft</el-tag>
</div>
<ul class="check-list">
<li v-for="item in readinessChecklist" :key="item.label" :class="{ done: item.done }">
<el-icon>
<Check v-if="item.done" />
<span v-else class="pending-dot" />
</el-icon>
<span>{{ item.label }}</span>
</li>
</ul>
</section>
<section class="studio-panel metrics-panel">
<div class="panel-heading">
<h2>运行概览</h2>
<span>环境: Dev</span>
</div>
<div class="metric-row">
<div><strong>27</strong><span>今日运行</span></div>
<div><strong>96.4%</strong><span>成功率</span></div>
<div><strong>1.28s</strong><span>P50 延迟</span></div>
<div><strong>¥4.82</strong><span>预估成本</span></div>
</div>
</section>
<section class="studio-panel recent-panel">
<div class="panel-heading">
<h2>最近运行</h2>
<span>Run Trace</span>
</div>
<div class="run-table">
<div class="run-row run-head">
<span>名称</span><span>类型</span><span>状态</span><span>延迟</span><span>成本</span>
</div>
<div v-for="run in recentRuns" :key="run.id" class="run-row">
<strong>{{ run.name }}</strong>
<span>{{ run.type }}</span>
<el-tag :type="run.status === '成功' ? 'success' : 'warning'">{{ run.status }}</el-tag>
<span>{{ run.latency }}</span>
<span>{{ run.cost }}</span>
</div>
</div>
</section>
<section class="studio-panel warning-panel">
<el-icon><Warning /></el-icon>
<div>
<h2>生产发布前仍需确认路由兜底</h2>
<p>AGENT_PLAN 任务当前只有草稿路由建议补齐 fallback 模型和最大延迟阈值</p>
</div>
</section>
</div>
</section>
</template>

View File

@@ -0,0 +1,112 @@
<script setup lang="ts">
import { Connection, Cpu, VideoPlay } from '@element-plus/icons-vue';
import { traceSteps, workflowEdges, workflowNodes } from '@/data/studioMock';
const nodeById = Object.fromEntries(workflowNodes.map((node) => [node.id, node]));
const canvasEdges = workflowEdges.flatMap((edge) => {
const from = nodeById[edge.from];
const to = nodeById[edge.to];
if (!from || !to) {
return [];
}
return [
{
id: `${edge.from}-${edge.to}`,
x1: from.x + 5,
y1: from.y + 4,
x2: to.x,
y2: to.y + 4,
},
];
});
</script>
<template>
<section class="studio-page workflow-page">
<header class="page-title-row">
<div>
<p class="studio-kicker">WorkflowBuilderView · Draft / Published</p>
<h1>Workflow 图形化编排</h1>
</div>
<div class="toolbar-actions">
<el-button>保存草稿</el-button>
<el-button type="primary"><el-icon><VideoPlay /></el-icon> 运行测试</el-button>
</div>
</header>
<div class="workflow-layout">
<aside class="studio-panel node-library">
<div class="panel-heading compact">
<h2>节点库</h2>
<span>JSON Graph</span>
</div>
<button>Start</button>
<button>LLM</button>
<button>Knowledge Retrieval</button>
<button>MCP Tool</button>
<button>Skill</button>
<button>Condition</button>
<button>Answer</button>
</aside>
<main class="studio-panel workflow-canvas">
<div class="canvas-toolbar">
<span><el-icon><Connection /></el-icon> workflow-support-rag</span>
<span>版本快照 v7</span>
<span>环境: Dev</span>
</div>
<div class="canvas-surface">
<svg class="edge-layer" viewBox="0 0 100 100" preserveAspectRatio="none">
<line
v-for="edge in canvasEdges"
:key="edge.id"
:x1="edge.x1"
:y1="edge.y1"
:x2="edge.x2"
:y2="edge.y2"
/>
</svg>
<article
v-for="node in workflowNodes"
:key="node.id"
class="workflow-node"
:class="{ selected: node.id === 'llm' }"
:style="{ left: `${node.x}%`, top: `${node.y}%` }"
>
<span>{{ node.type }}</span>
<strong>{{ node.label }}</strong>
<em>{{ node.description }}</em>
</article>
</div>
<div class="run-trace-drawer">
<strong>Run Trace</strong>
<div v-for="step in traceSteps" :key="step.node">
<span>{{ step.node }}</span>
<em>{{ step.status }} · {{ step.duration }} · {{ step.output }}</em>
</div>
</div>
</main>
<aside class="studio-panel inspector-panel">
<div class="panel-heading compact">
<h2>节点 Inspector</h2>
<span>LLM</span>
</div>
<dl class="inspector-list">
<dt>任务类型</dt>
<dd>RAG_ANSWER</dd>
<dt>输入 Schema</dt>
<dd>question, retrieved_chunks, conversation</dd>
<dt>输出 Schema</dt>
<dd>answer, citations, safety_flags</dd>
<dt>路由策略</dt>
<dd>primary qwen-plus / fallback deepseek-v3</dd>
</dl>
<button class="blue-command"><el-icon><Cpu /></el-icon> 打开模型路由</button>
</aside>
</div>
</section>
</template>

View File

@@ -3,19 +3,19 @@ import { describe, expect, it } from 'vitest';
import { routes } from '../index'; import { routes } from '../index';
describe('router', () => { describe('router', () => {
it('defines the admin shell routes', () => { it('defines the Studio product routes', () => {
const paths = routes.map((route) => route.path); const paths = routes.map((route) => route.path);
expect(paths).toContain('/'); expect(paths).toContain('/');
expect(paths).toContain('/rag/stores'); expect(paths).toContain('/studio');
expect(paths).toContain('/rag/workbench'); expect(paths).toContain('/knowledge');
expect(paths).toContain('/rag/documents'); expect(paths).toContain('/knowledge/ingestion');
expect(paths).toContain('/rag/tasks/chunk'); expect(paths).toContain('/workflows');
expect(paths).toContain('/system/enums'); expect(paths).toContain('/agents');
expect(paths).toContain('/system/model/providers'); expect(paths).toContain('/mcp');
expect(paths).toContain('/system/model/configs'); expect(paths).toContain('/skills');
expect(paths).toContain('/system/model/routes'); expect(paths).toContain('/models');
expect(paths).toContain('/system/model/call-logs'); expect(paths).toContain('/observability');
expect(paths).toContain('/:pathMatch(.*)*'); expect(paths).toContain('/:pathMatch(.*)*');
}); });
}); });

View File

@@ -1,76 +1,76 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import NotFoundPage from '@/pages/common/NotFoundPage.vue';
import RagDocumentsPage from '@/pages/rag/RagDocumentsPage.vue';
import RagStoresPage from '@/pages/rag/RagStoresPage.vue';
import RagChunkTasksPage from '@/pages/rag/tasks/RagChunkTasksPage.vue';
import RagWorkbenchPage from '@/pages/rag/workbench/RagWorkbenchPage.vue';
import SystemEnumsPage from '@/pages/system/SystemEnumsPage.vue';
import ModelProvidersPage from '@/pages/system/ModelProvidersPage.vue';
import ModelConfigsPage from '@/pages/system/ModelConfigsPage.vue';
import ModelRouteRulesPage from '@/pages/system/ModelRouteRulesPage.vue';
import ModelCallLogsPage from '@/pages/system/ModelCallLogsPage.vue';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import NotFoundPage from '@/pages/common/NotFoundPage.vue';
import AgentWorkspacePage from '@/pages/studio/AgentWorkspacePage.vue';
import IngestionPipelinePage from '@/pages/studio/IngestionPipelinePage.vue';
import KnowledgeWorkspacePage from '@/pages/studio/KnowledgeWorkspacePage.vue';
import McpImportPage from '@/pages/studio/McpImportPage.vue';
import ModelWorkspacePage from '@/pages/studio/ModelWorkspacePage.vue';
import ObservabilityPage from '@/pages/studio/ObservabilityPage.vue';
import SkillWorkspacePage from '@/pages/studio/SkillWorkspacePage.vue';
import StudioDashboardPage from '@/pages/studio/StudioDashboardPage.vue';
import WorkflowBuilderPage from '@/pages/studio/WorkflowBuilderPage.vue';
export const routes: RouteRecordRaw[] = [ export const routes: RouteRecordRaw[] = [
{ {
path: '/', path: '/',
redirect: '/rag/workbench', redirect: '/studio',
}, },
{ {
path: '/system/enums', path: '/studio',
name: 'system-enums', name: 'studio-dashboard',
component: SystemEnumsPage, component: StudioDashboardPage,
meta: { title: '系统枚举' }, meta: { title: '工作台' },
}, },
{ {
path: '/system/model/providers', path: '/knowledge',
name: 'system-model-providers', name: 'knowledge-workspace',
component: ModelProvidersPage, component: KnowledgeWorkspacePage,
meta: { title: '模型服务商' }, meta: { title: '知识资产' },
}, },
{ {
path: '/system/model/configs', path: '/knowledge/ingestion',
name: 'system-model-configs', name: 'knowledge-ingestion',
component: ModelConfigsPage, component: IngestionPipelinePage,
meta: { title: '模型配置' }, meta: { title: '文件解析' },
}, },
{ {
path: '/system/model/routes', path: '/workflows',
name: 'system-model-routes', name: 'workflow-builder',
component: ModelRouteRulesPage, component: WorkflowBuilderPage,
meta: { title: '路由规则' }, meta: { title: 'Workflow' },
}, },
{ {
path: '/system/model/call-logs', path: '/agents',
name: 'system-model-call-logs', name: 'agent-workspace',
component: ModelCallLogsPage, component: AgentWorkspacePage,
meta: { title: '调用日志' }, meta: { title: 'Agent 对话' },
}, },
{ {
path: '/rag/stores', path: '/mcp',
name: 'rag-stores', name: 'mcp-import',
component: RagStoresPage, component: McpImportPage,
meta: { title: '知识库' }, meta: { title: 'MCP 导入' },
}, },
{ {
path: '/rag/workbench', path: '/skills',
name: 'rag-workbench', name: 'skill-workspace',
component: RagWorkbenchPage, component: SkillWorkspacePage,
meta: { title: 'RAG工作台' }, meta: { title: 'Skill 编辑' },
}, },
{ {
path: '/rag/documents', path: '/models',
name: 'rag-documents', name: 'model-workspace',
component: RagDocumentsPage, component: ModelWorkspacePage,
meta: { title: '知识文档' }, meta: { title: '模型路由' },
}, },
{ {
path: '/rag/tasks/chunk', path: '/observability',
name: 'rag-chunk-tasks', name: 'observability',
component: RagChunkTasksPage, component: ObservabilityPage,
meta: { title: '切片任务' }, meta: { title: '观测' },
}, },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
@@ -81,73 +81,14 @@ export const routes: RouteRecordRaw[] = [
]; ];
const routerRoutes: RouteRecordRaw[] = [ const routerRoutes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/rag/workbench',
},
{ {
path: '/', path: '/',
component: AdminLayout, component: AdminLayout,
children: [ children: routes.filter((route) => route.path !== '/:pathMatch(.*)*'),
{
path: 'system/enums',
name: 'system-enums',
component: SystemEnumsPage,
meta: { title: '系统枚举' },
},
{
path: 'system/model/providers',
name: 'system-model-providers',
component: ModelProvidersPage,
meta: { title: '模型服务商' },
},
{
path: 'system/model/configs',
name: 'system-model-configs',
component: ModelConfigsPage,
meta: { title: '模型配置' },
},
{
path: 'system/model/routes',
name: 'system-model-routes',
component: ModelRouteRulesPage,
meta: { title: '路由规则' },
},
{
path: 'system/model/call-logs',
name: 'system-model-call-logs',
component: ModelCallLogsPage,
meta: { title: '调用日志' },
},
{
path: 'rag/stores',
name: 'rag-stores',
component: RagStoresPage,
meta: { title: '知识库' },
},
{
path: 'rag/workbench',
name: 'rag-workbench',
component: RagWorkbenchPage,
meta: { title: 'RAG工作台' },
},
{
path: 'rag/documents',
name: 'rag-documents',
component: RagDocumentsPage,
meta: { title: '知识文档' },
},
{
path: 'rag/tasks/chunk',
name: 'rag-chunk-tasks',
component: RagChunkTasksPage,
meta: { title: '切片任务' },
},
],
}, },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
name: 'not-found', name: 'not-found-shell',
component: NotFoundPage, component: NotFoundPage,
meta: { title: '页面不存在' }, meta: { title: '页面不存在' },
}, },

File diff suppressed because it is too large Load Diff