Merge branch 'refactor/gui-redesign': Agent-First GUI redesign with Design Token system
- U1: Design Token system (tokens.css + theme.ts) - U2: Four-quadrant Agent-First layout with TopNav - U3: Chat panel refactor (markdown-it + ToolCallIndicator) - U4: Code/preview panel (CodeDiffViewer + FileTree) - U5: Terminal panel refactor (One Dark Pro + Ant Design Modal) - U6: Evolution panel simplification + grouped settings - U7: Transitions + responsive breakpoints - Color migration: 15+ components migrated to Design Tokens - Code review fixes: ARIA a11y, XSS protection, touch/keyboard support, path traversal protection, lazy-loading, ANSI span balance
This commit is contained in:
commit
b89da90fd9
|
|
@ -0,0 +1,417 @@
|
|||
---
|
||||
title: "refactor: Agent-First GUI Redesign"
|
||||
status: completed
|
||||
created: 2026-06-13
|
||||
origin: docs/brainstorms/2026-06-13-gui-redesign-requirements.md
|
||||
---
|
||||
|
||||
# Plan: Agent-First GUI Redesign
|
||||
|
||||
## Summary
|
||||
|
||||
将 Fischer AgentKit GUI 从 SideNav 多页面布局重构为 Agent-First 四象限全屏布局,建立统一 Design Token 体系,采用浅色极简视觉风格。分 3 个迭代渐进式完成,每个迭代可独立部署。
|
||||
|
||||
## Problem Frame
|
||||
|
||||
当前 GUI 处于"功能可用但视觉粗糙"状态:300 处硬编码颜色、无设计系统、SideNav 多页面布局无法同时展示 Agent 活动、无响应式支持。竞品(Devin/Cursor/v0.dev)已普遍采用 Agent-First 全屏布局 + 统一设计系统。
|
||||
|
||||
## Requirements Trace
|
||||
|
||||
| R-ID | Requirement | Priority |
|
||||
|------|-------------|----------|
|
||||
| R1 | Design Token 体系 | P0 |
|
||||
| R2 | Agent-First 全屏布局 | P0 |
|
||||
| R3 | 对话面板重构 | P1 |
|
||||
| R4 | 代码/预览面板 | P1 |
|
||||
| R5 | 终端面板重构 | P1 |
|
||||
| R6 | 状态/监控面板(进化精简) | P1 |
|
||||
| R7 | 工作流单页化 | P2 |
|
||||
| R8 | 设置分组化 | P2 |
|
||||
| R9 | 过渡动画与微交互 | P2 |
|
||||
| R10 | 响应式断点 | P2 |
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
### KTD1: Design Token 双轨制 — CSS 变量 + Ant Design Theme Token
|
||||
|
||||
**决策:** 同时建立 CSS 自定义属性(`var(--color-primary)`)和 Ant Design Vue ConfigProvider theme token,两者通过映射表保持同步。
|
||||
|
||||
**理由:** Ant Design Vue 4.x 的 CSS-in-JS 架构通过 `theme.token` 控制组件内部样式,但自定义组件和 ECharts 无法读取 antd token。CSS 变量作为通用层,antd token 作为组件层,映射表桥接两者。
|
||||
|
||||
### KTD2: 四象限布局实现 — CSS Grid + 可拖拽分隔线
|
||||
|
||||
**决策:** 使用 CSS Grid 实现四象限基础布局,自定义 `SplitPane` 组件实现可拖拽分隔线,比例持久化到 localStorage。
|
||||
|
||||
**理由:** CSS Grid 天然支持二维布局和 `fr` 单位,比 Flexbox 更适合四象限。自定义 SplitPane 比引入第三方库(如 splitpanes)更轻量,且可精确控制样式。
|
||||
|
||||
### KTD3: 路由策略 — 保留 Vue Router,页面切换变为象限内容切换
|
||||
|
||||
**决策:** 保留 Vue Router 但重构路由结构。顶级路由改为象限内容路由(`/chat`、`/code`、`/terminal`、`/monitor`),通过路由参数控制各象限显示内容。旧路由(`/workflow`、`/knowledge`、`/skills`、`/evolution`、`/settings`)重定向到新的象限路由。
|
||||
|
||||
**理由:** 保留路由可维持 URL 可访问性、浏览器前进/后退、深层链接。象限内容切换比整页跳转更流畅。
|
||||
|
||||
### KTD4: 视觉风格 — 浅色极简 + Vercel 式紫黑渐变
|
||||
|
||||
**决策:** 主背景白色/极浅灰,强调色使用紫黑渐变(`#7c3aed` → `#1e1b4b`),代码/终端区域使用深色背景(One Dark Pro 配色)。
|
||||
|
||||
**理由:** 用户选择浅色极简风格。Vercel/v0.dev 的紫黑渐变是成熟的浅色极简强调色方案,与白色背景形成强对比。
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
### In Scope
|
||||
- Design Token 体系建立
|
||||
- 四象限全屏布局重构
|
||||
- 所有现有功能迁移
|
||||
- 浅色极简视觉风格
|
||||
- 过渡动画和微交互
|
||||
- 1280px+ 响应式断点
|
||||
|
||||
### Deferred to Follow-Up Work
|
||||
- 暗色模式(需 Design Token 暗色变体)
|
||||
- 移动端适配
|
||||
- Computer Use 功能实现
|
||||
- Cmd+K 内联编辑
|
||||
- @-mention 上下文引用
|
||||
- 代码 Diff Accept/Reject 实际回滚
|
||||
|
||||
### Outside This Product's Identity
|
||||
- 多用户协作/实时协同编辑
|
||||
- 插件市场
|
||||
- 代码编辑器(只读预览)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Units
|
||||
|
||||
### U1. Design Token 体系 + 主题配置
|
||||
|
||||
**Goal:** 建立统一的设计令牌系统,消除所有硬编码颜色/间距/圆角值。
|
||||
|
||||
**Requirements:** R1
|
||||
|
||||
**Dependencies:** 无
|
||||
|
||||
**Files:**
|
||||
- Create: `src/agentkit/server/frontend/src/styles/tokens.css`
|
||||
- Create: `src/agentkit/server/frontend/src/styles/theme.ts`
|
||||
- Create: `src/agentkit/server/frontend/src/styles/index.ts`
|
||||
- Modify: `src/agentkit/server/frontend/src/App.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/main.ts`
|
||||
|
||||
**Approach:**
|
||||
1. 创建 `tokens.css`,定义 CSS 自定义属性:颜色(primary/secondary/success/error/warning/灰阶/背景/边框)、间距(4/8/12/16/24/32px)、圆角(4/6/8/12px)、字体(12/13/14/16/20/24px)、阴影(sm/md/lg)
|
||||
2. 创建 `theme.ts`,定义 Ant Design Vue theme token 映射(将 CSS 变量值映射到 antd token)
|
||||
3. 在 `App.vue` 的 ConfigProvider 中注入 theme 配置
|
||||
4. 在 `main.ts` 中引入 `tokens.css`
|
||||
5. 统一主色为 `#7c3aed`(紫黑渐变起始色),消除 `#1677ff`/`#1890ff` 混用
|
||||
|
||||
**Patterns to follow:** Ant Design Vue 4.x theme customization API (ConfigProvider theme prop)
|
||||
|
||||
**Test scenarios:**
|
||||
- 验证 CSS 变量在浏览器中正确计算(`getComputedStyle(document.documentElement).getPropertyValue('--color-primary')` 返回 `#7c3aed`)
|
||||
- 验证 Ant Design 组件使用新主题色(Button primary 颜色为 `#7c3aed`)
|
||||
- 验证自定义组件可通过 `var(--color-primary)` 引用主题色
|
||||
|
||||
**Verification:** 所有 CSS 变量可计算,Ant Design 组件使用新主题色,无硬编码 `#1677ff`/`#1890ff`。
|
||||
|
||||
---
|
||||
|
||||
### U2. 四象限全屏布局 + 顶部导航
|
||||
|
||||
**Goal:** 将 SideNav 多页面布局重构为四象限全屏布局,顶部极简导航栏。
|
||||
|
||||
**Requirements:** R2
|
||||
|
||||
**Dependencies:** U1
|
||||
|
||||
**Files:**
|
||||
- Create: `src/agentkit/server/frontend/src/components/layout/AgentLayout.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/layout/SplitPane.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/layout/TopNav.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/layout/QuadrantPanel.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/App.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/router/index.ts`
|
||||
- Preserve: `src/agentkit/server/frontend/src/components/layout/AppLayout.vue`(旧布局保留,路由切换)
|
||||
|
||||
**Approach:**
|
||||
1. 创建 `AgentLayout.vue`:CSS Grid 四象限布局,每个象限是一个 `<QuadrantPanel>`,支持折叠/展开
|
||||
2. 创建 `SplitPane.vue`:可拖拽分隔线组件,支持水平/垂直方向,比例持久化到 localStorage
|
||||
3. 创建 `TopNav.vue`:48px 高度顶部导航栏,包含 Logo、任务选择器、Agent 状态指示、模式切换、设置入口
|
||||
4. 创建 `QuadrantPanel.vue`:象限容器组件,支持标题栏、折叠按钮、Tab 切换
|
||||
5. 修改路由:新增 `/agent` 路由使用 AgentLayout,旧路由保留 AppLayout 作为兼容
|
||||
6. 默认路由 `/` 重定向到 `/agent`
|
||||
|
||||
**Patterns to follow:** Devin 四象限布局模式,CSS Grid `grid-template-rows/columns` + `fr` 单位
|
||||
|
||||
**Test scenarios:**
|
||||
- 四象限正确渲染,各象限可独立折叠/展开
|
||||
- 分隔线可拖拽调整比例,刷新后比例保持
|
||||
- 顶部导航栏正确显示 Logo、状态指示、设置入口
|
||||
- 1280px 以下右下象限自动折叠
|
||||
|
||||
**Verification:** 四象限布局可交互,分隔线可拖拽,比例持久化,响应式断点生效。
|
||||
|
||||
---
|
||||
|
||||
### U3. 对话面板重构(左上象限)
|
||||
|
||||
**Goal:** 将 ChatView 重构为对话面板,支持 Markdown 渲染和工具调用指示器。
|
||||
|
||||
**Requirements:** R3
|
||||
|
||||
**Dependencies:** U1, U2
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/agentkit/server/frontend/src/views/ChatView.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/chat/ChatMessage.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/chat/ChatInput.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/chat/ChatSidebar.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/chat/ToolCallIndicator.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/chat/ContextPill.vue`
|
||||
|
||||
**Approach:**
|
||||
1. ChatMessage:替换 `v-html` 为 Markdown 渲染(使用 `markdown-it` 或 `marked`),添加工具调用指示器(`[Read]`/`[Edit]`/`[Bash]` 彩色标签)
|
||||
2. ChatInput:添加上下文胶囊(Context Pills),显示当前关联的文件/技能
|
||||
3. ChatSidebar:改为可折叠侧栏,默认折叠
|
||||
4. 流式输出添加打字机效果
|
||||
5. 所有硬编码颜色替换为 Design Token 引用
|
||||
|
||||
**Patterns to follow:** Claude Code 的 Tool Use Indicators,Trae 的 Context Pills
|
||||
|
||||
**Test scenarios:**
|
||||
- Markdown 正确渲染(标题、代码块、列表、链接)
|
||||
- 工具调用指示器正确显示类型和颜色
|
||||
- 上下文胶囊显示关联文件名
|
||||
- 流式输出打字机效果平滑
|
||||
|
||||
**Verification:** 对话面板功能完整,Markdown 渲染正确,工具调用可视化,无 `v-html`。
|
||||
|
||||
---
|
||||
|
||||
### U4. 代码/预览面板(右上象限)
|
||||
|
||||
**Goal:** 新增代码 Diff 查看和文件预览能力,集成工作流画布和知识库管理。
|
||||
|
||||
**Requirements:** R4, R7
|
||||
|
||||
**Dependencies:** U1, U2
|
||||
|
||||
**Files:**
|
||||
- Create: `src/agentkit/server/frontend/src/components/code/CodeDiffViewer.vue`
|
||||
- Create: `src/agentkit/server/frontend/src/components/code/FileTree.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/views/WorkflowView.vue`(单页化)
|
||||
- Modify: `src/agentkit/server/frontend/src/views/KnowledgeBaseView.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/workflow/FlowCanvas.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/workflow/NodePalette.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/workflow/PropertyPanel.vue`
|
||||
|
||||
**Approach:**
|
||||
1. 创建 `CodeDiffViewer.vue`:只读代码 Diff 展示,支持逐行高亮(红/绿),语法高亮
|
||||
2. 创建 `FileTree.vue`:文件树浏览器,展示 Agent 修改的文件列表
|
||||
3. 工作流单页化:列表和编辑在同一象限内通过 Tab 切换
|
||||
4. 知识库管理集成为此象限的一个 Tab
|
||||
5. 象限 Tab 切换:代码 / 工作流 / 知识库
|
||||
6. 所有硬编码颜色替换为 Design Token
|
||||
|
||||
**Patterns to follow:** Cursor Composer 的 Diff 展示,v0.dev 的 Tab 切换
|
||||
|
||||
**Test scenarios:**
|
||||
- 代码 Diff 正确高亮删除/新增行
|
||||
- 文件树正确展示文件结构
|
||||
- 工作流列表/编辑 Tab 切换流畅
|
||||
- 知识库 Tab 正常工作
|
||||
- 象限 Tab 切换无闪烁
|
||||
|
||||
**Verification:** 右上象限支持三种内容切换,代码 Diff 可视化,工作流单页化完成。
|
||||
|
||||
---
|
||||
|
||||
### U5. 终端面板重构(左下象限)
|
||||
|
||||
**Goal:** 将 TerminalView 重构为终端面板,使用 Ant Design 组件替代原生 HTML。
|
||||
|
||||
**Requirements:** R5
|
||||
|
||||
**Dependencies:** U1, U2
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/agentkit/server/frontend/src/views/TerminalView.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/terminal/TerminalEmulator.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/terminal/CommandHistory.vue`
|
||||
|
||||
**Approach:**
|
||||
1. 终端背景改为 One Dark Pro 配色(深色背景 + 语法高亮色)
|
||||
2. 命令确认弹窗从原生 HTML 改为 Ant Design Modal
|
||||
3. 命令历史侧栏改为可折叠
|
||||
4. 输入框从原生 `<input>` 改为 Ant Design Input
|
||||
5. 所有硬编码颜色替换为 Design Token
|
||||
|
||||
**Patterns to follow:** One Dark Pro 终端配色,Ant Design Modal 组件
|
||||
|
||||
**Test scenarios:**
|
||||
- 终端输出 ANSI 颜色正确渲染
|
||||
- 命令确认弹窗使用 Ant Design Modal 样式
|
||||
- 命令历史可折叠展开
|
||||
- WebSocket 连接/断开正常
|
||||
|
||||
**Verification:** 终端面板视觉统一,无原生 HTML 弹窗,One Dark Pro 配色生效。
|
||||
|
||||
---
|
||||
|
||||
### U6. 状态/监控面板(右下象限)+ 进化精简
|
||||
|
||||
**Goal:** 将 EvolutionView 精简后集成为右下象限,集成技能和设置。
|
||||
|
||||
**Requirements:** R6, R8
|
||||
|
||||
**Dependencies:** U1, U2
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/agentkit/server/frontend/src/views/EvolutionView.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/evolution/DashboardOverview.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/evolution/MetricsChart.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/evolution/UsagePanel.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/views/SkillsView.vue`
|
||||
- Modify: `src/agentkit/server/frontend/src/views/SettingsView.vue`
|
||||
- Delete: `src/agentkit/server/frontend/src/components/evolution/PitfallRoutePanel.vue`
|
||||
- Delete: `src/agentkit/server/frontend/src/components/evolution/OptimizationPanel.vue`
|
||||
- Delete: `src/agentkit/server/frontend/src/components/evolution/MetricsPanel.vue`
|
||||
- Delete: `src/agentkit/server/frontend/src/components/evolution/ExperiencePanel.vue`
|
||||
|
||||
**Approach:**
|
||||
1. 进化面板精简:6 个子面板合并为 3 个 Tab — 概览+指标、经验+坑点、用量
|
||||
2. DashboardOverview:4 列统计卡片改为 2 列,增加趋势迷你图
|
||||
3. 设置分组化:4 个 Tab(LLM/技能/知识库/系统),每组独立保存
|
||||
4. 象限 Tab 切换:监控 / 技能 / 设置
|
||||
5. 删除不再需要的包装组件(PitfallRoutePanel/OptimizationPanel/MetricsPanel/ExperiencePanel)
|
||||
6. 所有硬编码颜色替换为 Design Token
|
||||
|
||||
**Patterns to follow:** Devin 的 Action Timeline,Ant Design Tabs 分组
|
||||
|
||||
**Test scenarios:**
|
||||
- 进化概览+指标 Tab 正确展示
|
||||
- 经验+坑点 Tab 合并后功能完整
|
||||
- 设置分组 Tab 切换正常,每组独立保存
|
||||
- 技能 Tab 正常展示和安装
|
||||
|
||||
**Verification:** 右下象限支持三种内容切换,进化面板精简完成,设置分组化完成。
|
||||
|
||||
---
|
||||
|
||||
### U7. 过渡动画 + 微交互 + 响应式
|
||||
|
||||
**Goal:** 为所有交互添加过渡动画,实现 1280px+ 响应式断点。
|
||||
|
||||
**Requirements:** R9, R10
|
||||
|
||||
**Dependencies:** U1, U2, U3, U4, U5, U6
|
||||
|
||||
**Files:**
|
||||
- Create: `src/agentkit/server/frontend/src/styles/transitions.css`
|
||||
- Create: `src/agentkit/server/frontend/src/styles/responsive.css`
|
||||
- Modify: `src/agentkit/server/frontend/src/components/layout/AgentLayout.vue`
|
||||
- Modify: all view and component files (transition additions)
|
||||
|
||||
**Approach:**
|
||||
1. 创建 `transitions.css`:定义 Vue transition 类(fade/slide/collapse/stagger),统一时长(150ms/200ms/300ms)
|
||||
2. 象限折叠/展开:平滑过渡 200ms ease
|
||||
3. Tab 切换:淡入淡出 150ms
|
||||
4. 列表项加载:交错渐入 stagger 50ms
|
||||
5. 空状态:品牌化插图 + 引导文案(替代 `<a-empty>`)
|
||||
6. 加载态:骨架屏替代 `<a-spin>`
|
||||
7. 创建 `responsive.css`:定义断点(≥1440px 四象限完整,1280-1440px 右下折叠,<1280px 提示)
|
||||
8. 象限比例记忆:localStorage 保存用户调整的比例
|
||||
|
||||
**Patterns to follow:** Vue `<Transition>` 组件,CSS `@media` 断点
|
||||
|
||||
**Test scenarios:**
|
||||
- 象限折叠/展开动画平滑无卡顿
|
||||
- Tab 切换淡入淡出效果正确
|
||||
- 列表项交错渐入效果
|
||||
- 1440px+ 四象限完整展示
|
||||
- 1280-1440px 右下象限自动折叠
|
||||
- <1280px 显示提示信息
|
||||
- 刷新后象限比例保持
|
||||
|
||||
**Verification:** 所有过渡动画生效,响应式断点正确,比例持久化。
|
||||
|
||||
---
|
||||
|
||||
## High-Level Technical Design
|
||||
|
||||
### 四象限布局架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ TopNav (48px) │
|
||||
│ [Logo] [TaskSelector] [AgentStatus] [ModeToggle] [⚙] │
|
||||
├──────────────────────────┬──────────────────────────────┤
|
||||
│ │ │
|
||||
│ QuadrantPanel │ QuadrantPanel │
|
||||
│ position="top-left" │ position="top-right" │
|
||||
│ ┌─ Tabs ────────────┐ │ ┌─ Tabs ────────────────┐ │
|
||||
│ │ Chat (default) │ │ │ Code/Diff (default) │ │
|
||||
│ │ Agent Log │ │ │ Workflow │ │
|
||||
│ └────────────────────┘ │ │ Knowledge Base │ │
|
||||
│ │ └────────────────────────┘ │
|
||||
│ ← SplitPane (vertical) →│ │
|
||||
├──────────────────────────┼──────────────────────────────┤
|
||||
│ │ │
|
||||
│ QuadrantPanel │ QuadrantPanel │
|
||||
│ position="bottom-left" │ position="bottom-right" │
|
||||
│ ┌─ Tabs ────────────┐ │ ┌─ Tabs ────────────────┐ │
|
||||
│ │ Terminal (default) │ │ │ Monitor (default) │ │
|
||||
│ │ Command History │ │ │ Skills │ │
|
||||
│ └────────────────────┘ │ │ Settings │ │
|
||||
│ │ └────────────────────────┘ │
|
||||
└──────────────────────────┴──────────────────────────────┘
|
||||
```
|
||||
|
||||
### Design Token 映射架构
|
||||
|
||||
```
|
||||
tokens.css (CSS Custom Properties)
|
||||
│
|
||||
├─→ 自定义组件 (via var(--xxx))
|
||||
│
|
||||
└─→ theme.ts (Ant Design Theme Token Mapping)
|
||||
│
|
||||
└─→ ConfigProvider :theme
|
||||
│
|
||||
└─→ Ant Design 组件 (via CSS-in-JS)
|
||||
```
|
||||
|
||||
### 路由重构
|
||||
|
||||
```
|
||||
/ → /agent (AgentLayout)
|
||||
/agent/chat → 左上象限显示 Chat
|
||||
/agent/code → 右上象限显示 Code/Diff
|
||||
/agent/terminal → 左下象限显示 Terminal
|
||||
/agent/monitor → 右下象限显示 Monitor
|
||||
|
||||
旧路由兼容:
|
||||
/workflow → /agent/code?tab=workflow
|
||||
/knowledge → /agent/code?tab=knowledge
|
||||
/skills → /agent/monitor?tab=skills
|
||||
/evolution → /agent/monitor?tab=monitor
|
||||
/settings → /agent/monitor?tab=settings
|
||||
/terminal → /agent/terminal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| 300 处硬编码颜色迁移遗漏 | 视觉不一致 | U1 完成后全局搜索 `#[0-9a-fA-F]{3,6}` 验证零残留 |
|
||||
| Vue Flow 在象限内 resize 问题 | 画布渲染异常 | SplitPane 拖拽结束时触发 window resize 事件 |
|
||||
| ECharts 颜色需单独处理 | 图表颜色不跟随主题 | 在 ECharts 初始化时从 CSS 变量读取颜色 |
|
||||
| 大型组件迁移引入回归 | 功能损坏 | 每个单元完成后运行现有测试 + 手动验证 |
|
||||
| 四象限布局在小屏幕下体验差 | 不可用 | R10 响应式断点确保 1280px+ 可用 |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Code Diff Viewer 是否需要引入第三方库(如 diff2html)还是手写简单 diff 展示?→ 建议先用简单行级高亮,后续迭代引入 diff2html
|
||||
- 骨架屏组件是自建还是使用 Ant Design Vue 的 Skeleton?→ 建议使用 antd Skeleton,减少维护成本
|
||||
|
|
@ -680,6 +680,7 @@ def create_app(
|
|||
if gui_mode:
|
||||
from pathlib import Path as _Path
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
_static_dir = _Path(__file__).parent / "static"
|
||||
|
||||
|
|
@ -691,4 +692,29 @@ def create_app(
|
|||
return FileResponse(str(index_path))
|
||||
return HTMLResponse("<h1>AgentKit GUI not found</h1>", status_code=404)
|
||||
|
||||
# SPA fallback: serve index.html for all non-API, non-static routes
|
||||
@app.get("/{path:path}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def spa_fallback(path: str):
|
||||
"""Serve index.html for SPA client-side routing."""
|
||||
# Don't intercept API routes or docs
|
||||
if path.startswith("api/") or path.startswith("docs") or path == "openapi.json":
|
||||
return HTMLResponse("<h1>Not Found</h1>", status_code=404)
|
||||
# Try to serve a real static file first (with path traversal protection)
|
||||
file_path = (_static_dir / path).resolve()
|
||||
if not str(file_path).startswith(str(_static_dir.resolve())):
|
||||
return HTMLResponse("<h1>Forbidden</h1>", status_code=403)
|
||||
if file_path.is_file():
|
||||
return FileResponse(str(file_path))
|
||||
# Fallback to index.html for SPA routing
|
||||
index_path = _static_dir / "index.html"
|
||||
if index_path.exists():
|
||||
return FileResponse(str(index_path))
|
||||
return HTMLResponse("<h1>Not Found</h1>", status_code=404)
|
||||
|
||||
# Mount static assets last (js, css, images, etc.)
|
||||
# mount() is checked after route matching, so API routes take priority
|
||||
assets_dir = _static_dir / "assets"
|
||||
if assets_dir.exists():
|
||||
app.mount("/assets", StaticFiles(directory=str(assets_dir)), name="static-assets")
|
||||
|
||||
return app
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -14,11 +14,15 @@
|
|||
"@vue-flow/controls": "^1.1.0",
|
||||
"@vue-flow/core": "^1.41.0",
|
||||
"ant-design-vue": "^4.2.0",
|
||||
"dompurify": "^3.4.10",
|
||||
"markdown-it": "^14.2.0",
|
||||
"pinia": "^2.2.0",
|
||||
"vue": "^3.5.0",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"echarts": "^6.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<a-config-provider :locale="zhCN">
|
||||
<AppLayout />
|
||||
<a-config-provider :locale="zhCN" :theme="themeConfig">
|
||||
<router-view />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ConfigProvider as AConfigProvider } from 'ant-design-vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import AppLayout from './components/layout/AppLayout.vue'
|
||||
import { themeConfig } from './styles'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export class BaseApiClient {
|
|||
}
|
||||
|
||||
protected async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const url = `${this.baseUrl}${path}`
|
||||
// If path starts with /api/, it's an absolute path — don't prepend baseUrl
|
||||
const url = path.startsWith('/api/') ? path : `${this.baseUrl}${path}`
|
||||
const headers: Record<string, string> = {
|
||||
...options.headers as Record<string, string>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export interface IKbSource {
|
|||
status: string
|
||||
document_count: number
|
||||
last_synced: string | null
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface IAddSourceRequest {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,29 @@ class SkillsApiClient extends BaseApiClient {
|
|||
return this.request<{ capabilities: ICapabilityInfo[] }>('/capabilities')
|
||||
}
|
||||
|
||||
/** Install a skill by name (searches GitHub) or from a source URL */
|
||||
async installSkill(
|
||||
name: string,
|
||||
source?: string
|
||||
): Promise<{ status: string; name: string; path: string }> {
|
||||
// The install endpoint is on /api/v1/skills/install, not under skill-management
|
||||
return this.request<{ status: string; name: string; path: string }>(
|
||||
'/api/v1/skills/install',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, source }),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Uninstall a skill */
|
||||
async uninstallSkill(skillName: string): Promise<{ status: string; name: string }> {
|
||||
return this.request<{ status: string; name: string }>(
|
||||
`/api/v1/skills/${encodeURIComponent(skillName)}`,
|
||||
{ method: 'DELETE' }
|
||||
)
|
||||
}
|
||||
|
||||
/** Reload a skill */
|
||||
async reloadSkill(
|
||||
skillName: string
|
||||
|
|
|
|||
|
|
@ -1,33 +1,51 @@
|
|||
<template>
|
||||
<div class="chat-input">
|
||||
<a-textarea
|
||||
v-model:value="inputText"
|
||||
:placeholder="placeholder"
|
||||
:auto-size="{ minRows: 1, maxRows: 4 }"
|
||||
:disabled="disabled"
|
||||
@pressEnter="handlePressEnter"
|
||||
class="chat-input__textarea"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!canSend"
|
||||
:loading="disabled"
|
||||
@click="handleSend"
|
||||
class="chat-input__send"
|
||||
>
|
||||
<template #icon><SendOutlined /></template>
|
||||
发送
|
||||
</a-button>
|
||||
<div v-if="contextPills.length > 0" class="chat-input__pills">
|
||||
<ContextPill
|
||||
v-for="(pill, idx) in contextPills"
|
||||
:key="idx"
|
||||
:label="pill.label"
|
||||
:icon="pill.icon"
|
||||
:removable="pill.removable"
|
||||
@remove="removePill(idx)"
|
||||
/>
|
||||
</div>
|
||||
<div class="chat-input__row">
|
||||
<a-textarea
|
||||
v-model:value="inputText"
|
||||
:placeholder="placeholder"
|
||||
:auto-size="{ minRows: 1, maxRows: 4 }"
|
||||
:disabled="disabled"
|
||||
@pressEnter="handlePressEnter"
|
||||
class="chat-input__textarea"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!canSend"
|
||||
:loading="disabled"
|
||||
@click="handleSend"
|
||||
class="chat-input__send"
|
||||
>
|
||||
<template #icon><SendOutlined /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, type Component } from 'vue'
|
||||
import { Input as AInput, Button as AButton } from 'ant-design-vue'
|
||||
import { SendOutlined } from '@ant-design/icons-vue'
|
||||
import ContextPill from './ContextPill.vue'
|
||||
|
||||
const ATextarea = AInput.TextArea
|
||||
|
||||
interface ContextPillData {
|
||||
label: string
|
||||
icon?: Component
|
||||
removable?: boolean
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
|
|
@ -43,6 +61,7 @@ const emit = defineEmits<{
|
|||
}>()
|
||||
|
||||
const inputText = ref('')
|
||||
const contextPills = ref<ContextPillData[]>([])
|
||||
|
||||
const canSend = computed(() => {
|
||||
return inputText.value.trim().length > 0 && !props.disabled
|
||||
|
|
@ -56,20 +75,36 @@ function handleSend(): void {
|
|||
}
|
||||
|
||||
function handlePressEnter(event: KeyboardEvent): void {
|
||||
if (event.shiftKey) return // Shift+Enter for new line
|
||||
if (event.shiftKey) return
|
||||
event.preventDefault()
|
||||
handleSend()
|
||||
}
|
||||
|
||||
function removePill(idx: number): void {
|
||||
contextPills.value.splice(idx, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--bg-primary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.chat-input__pills {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-input__row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-input__textarea {
|
||||
|
|
|
|||
|
|
@ -3,26 +3,38 @@
|
|||
<div class="chat-message__avatar">
|
||||
<a-avatar
|
||||
v-if="message.role === 'assistant'"
|
||||
:size="36"
|
||||
style="background-color: #1677ff"
|
||||
:size="32"
|
||||
class="chat-message__avatar--assistant"
|
||||
>
|
||||
<template #icon><RobotOutlined /></template>
|
||||
</a-avatar>
|
||||
<a-avatar
|
||||
v-else
|
||||
:size="36"
|
||||
style="background-color: #52c41a"
|
||||
:size="32"
|
||||
class="chat-message__avatar--user"
|
||||
>
|
||||
<template #icon><UserOutlined /></template>
|
||||
</a-avatar>
|
||||
</div>
|
||||
<div class="chat-message__body">
|
||||
<!-- Tool call indicators -->
|
||||
<div v-if="toolCalls.length > 0" class="chat-message__tools">
|
||||
<ToolCallIndicator
|
||||
v-for="(tc, idx) in toolCalls"
|
||||
:key="idx"
|
||||
:type="tc.type"
|
||||
:name="tc.name"
|
||||
/>
|
||||
</div>
|
||||
<!-- Message content -->
|
||||
<div class="chat-message__content" :class="[`chat-message__content--${message.role}`]">
|
||||
<span v-html="renderedContent"></span>
|
||||
<div v-if="message.role === 'assistant'" class="chat-message__markdown" v-html="renderedContent"></div>
|
||||
<span v-else>{{ message.content }}</span>
|
||||
<a-spin v-if="isLoading" size="small" class="chat-message__loading" />
|
||||
</div>
|
||||
<!-- Routing info -->
|
||||
<div v-if="showRouting" class="chat-message__routing">
|
||||
<a-tag color="blue">
|
||||
<a-tag color="purple">
|
||||
<ThunderboltOutlined /> {{ message.matched_skill }}
|
||||
</a-tag>
|
||||
<a-tag v-if="message.confidence !== undefined" color="green">
|
||||
|
|
@ -41,9 +53,36 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { Avatar as AAvatar, Tag as ATag, Spin as ASpin } from 'ant-design-vue'
|
||||
import { RobotOutlined, UserOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
|
||||
import type { IChatMessage } from '@/api/types'
|
||||
import ToolCallIndicator from './ToolCallIndicator.vue'
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: false,
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
})
|
||||
|
||||
// Sanitize markdown output to prevent XSS (javascript: links, data: URIs, etc.)
|
||||
function sanitize(html: string): string {
|
||||
return DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: [
|
||||
'p', 'br', 'strong', 'em', 'del', 'code', 'pre', 'a',
|
||||
'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'blockquote',
|
||||
'table', 'thead', 'tbody', 'tr', 'th', 'td', 'span',
|
||||
],
|
||||
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
})
|
||||
}
|
||||
|
||||
interface ToolCall {
|
||||
type: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
message: IChatMessage
|
||||
|
|
@ -65,20 +104,32 @@ const formattedTime = computed(() => {
|
|||
})
|
||||
|
||||
const renderedContent = computed(() => {
|
||||
// Simple rendering: escape HTML and convert newlines
|
||||
const escaped = props.message.content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
return escaped.replace(/\n/g, '<br/>')
|
||||
if (!props.message.content) return ''
|
||||
return sanitize(md.render(props.message.content))
|
||||
})
|
||||
|
||||
const toolCalls = computed<ToolCall[]>(() => {
|
||||
// Extract tool calls from message metadata or content patterns
|
||||
const calls: ToolCall[] = []
|
||||
const content = props.message.content || ''
|
||||
|
||||
// Detect tool use patterns like [Read], [Edit], [Bash] at line start only
|
||||
const toolPattern = /^\[(Read|Edit|Bash|Write|Search|Grep|Glob)\]/gm
|
||||
let match
|
||||
while ((match = toolPattern.exec(content)) !== null) {
|
||||
const toolName = match[1].toLowerCase()
|
||||
calls.push({ type: toolName, name: match[1] })
|
||||
}
|
||||
|
||||
return calls
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
|
||||
.chat-message--user {
|
||||
|
|
@ -89,50 +140,111 @@ const renderedContent = computed(() => {
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-message__avatar--assistant {
|
||||
background: var(--gradient-brand) !important;
|
||||
}
|
||||
|
||||
.chat-message__avatar--user {
|
||||
background-color: var(--color-success) !important;
|
||||
}
|
||||
|
||||
.chat-message__body {
|
||||
max-width: 70%;
|
||||
max-width: 75%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.chat-message--user .chat-message__body {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.chat-message__tools {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chat-message__content {
|
||||
padding: 10px 14px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-lg);
|
||||
line-height: var(--leading-normal);
|
||||
font-size: var(--font-base);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.chat-message__content--user {
|
||||
background: #1677ff;
|
||||
color: #ffffff;
|
||||
border-bottom-right-radius: 4px;
|
||||
background: var(--color-primary);
|
||||
color: var(--text-inverse);
|
||||
border-bottom-right-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.chat-message__content--assistant {
|
||||
background: #ffffff;
|
||||
color: #333333;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-bottom-left-radius: 4px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom-left-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.chat-message__markdown {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(p) {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(pre) {
|
||||
background: var(--code-bg);
|
||||
color: var(--code-fg);
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
overflow-x: auto;
|
||||
margin: var(--space-2) 0;
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(code) {
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace;
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(:not(pre) > code) {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
padding: 1px var(--space-1);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(ul),
|
||||
.chat-message__markdown :deep(ol) {
|
||||
padding-left: var(--space-5);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-message__markdown :deep(h1),
|
||||
.chat-message__markdown :deep(h2),
|
||||
.chat-message__markdown :deep(h3) {
|
||||
margin-top: var(--space-3);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-message__loading {
|
||||
margin-left: 8px;
|
||||
margin-left: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-message__routing {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.chat-message__time {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
<template>
|
||||
<div class="chat-sidebar">
|
||||
<div class="chat-sidebar__header">
|
||||
<a-button type="primary" block @click="handleCreate">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建对话
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="chat-sidebar__list">
|
||||
<a-empty v-if="conversations.length === 0" description="暂无对话" />
|
||||
<div
|
||||
v-for="conv in conversations"
|
||||
:key="conv.id"
|
||||
class="chat-sidebar__item"
|
||||
:class="{ 'chat-sidebar__item--active': conv.id === currentId }"
|
||||
@click="handleSelect(conv.id)"
|
||||
>
|
||||
<MessageOutlined class="chat-sidebar__item-icon" />
|
||||
<span class="chat-sidebar__item-title">{{ conv.title }}</span>
|
||||
<span class="chat-sidebar__item-time">{{ formatRelativeTime(conv.updated_at) }}</span>
|
||||
<div :class="['chat-sidebar', { 'chat-sidebar--collapsed': collapsed }]">
|
||||
<div v-if="!collapsed" class="chat-sidebar__content">
|
||||
<div class="chat-sidebar__header">
|
||||
<a-button type="primary" block size="small" @click="handleCreate">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建对话
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="chat-sidebar__list">
|
||||
<a-empty v-if="conversations.length === 0" description="暂无对话" :image-style="{ height: '40px' }" />
|
||||
<div
|
||||
v-for="conv in conversations"
|
||||
:key="conv.id"
|
||||
class="chat-sidebar__item"
|
||||
:class="{ 'chat-sidebar__item--active': conv.id === currentId }"
|
||||
@click="handleSelect(conv.id)"
|
||||
>
|
||||
<MessageOutlined class="chat-sidebar__item-icon" />
|
||||
<span class="chat-sidebar__item-title">{{ conv.title }}</span>
|
||||
<span class="chat-sidebar__item-time">{{ formatRelativeTime(conv.updated_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="chat-sidebar__toggle" @click="collapsed = !collapsed" :title="collapsed ? '展开' : '折叠'">
|
||||
<RightOutlined v-if="collapsed" />
|
||||
<LeftOutlined v-else />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button as AButton, Empty as AEmpty } from 'ant-design-vue'
|
||||
import { PlusOutlined, MessageOutlined } from '@ant-design/icons-vue'
|
||||
import { PlusOutlined, MessageOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
|
||||
import type { IConversation } from '@/api/types'
|
||||
|
||||
interface IProps {
|
||||
|
|
@ -40,6 +47,8 @@ const emit = defineEmits<{
|
|||
select: [id: string]
|
||||
}>()
|
||||
|
||||
const collapsed = ref(true)
|
||||
|
||||
function handleCreate(): void {
|
||||
emit('create')
|
||||
}
|
||||
|
|
@ -69,69 +78,102 @@ function formatRelativeTime(dateStr: string): string {
|
|||
|
||||
<style scoped>
|
||||
.chat-sidebar {
|
||||
width: 280px;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
transition: width var(--transition-normal);
|
||||
}
|
||||
|
||||
.chat-sidebar--collapsed {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.chat-sidebar:not(.chat-sidebar--collapsed) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.chat-sidebar__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-sidebar__header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: var(--space-3);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.chat-sidebar__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-sidebar__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.chat-sidebar__item:hover {
|
||||
background: #f5f5f5;
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.chat-sidebar__item--active {
|
||||
background: #e6f4ff;
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.chat-sidebar__item--active:hover {
|
||||
background: #e6f4ff;
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.chat-sidebar__item-icon {
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
color: var(--text-placeholder);
|
||||
font-size: var(--font-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-sidebar__item--active .chat-sidebar__item-icon {
|
||||
color: #1677ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.chat-sidebar__item-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-sidebar__item-time {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-placeholder);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-sidebar__toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.chat-sidebar__toggle:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<span class="context-pill">
|
||||
<component :is="icon" class="context-pill__icon" />
|
||||
<span class="context-pill__label">{{ label }}</span>
|
||||
<button v-if="removable" class="context-pill__remove" @click.stop="$emit('remove')">
|
||||
<CloseOutlined />
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Component } from 'vue'
|
||||
import { CloseOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
defineProps<{
|
||||
label: string
|
||||
icon?: Component
|
||||
removable?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
remove: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.context-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: 2px var(--space-2);
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-secondary);
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.context-pill__icon {
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.context-pill__label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.context-pill__remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-placeholder);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-full);
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.context-pill__remove:hover {
|
||||
color: var(--color-error);
|
||||
background: var(--color-error-light);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<span :class="['tool-call-indicator', `tool-call-indicator--${type}`]">
|
||||
<component :is="icon" class="tool-call-indicator__icon" />
|
||||
<span class="tool-call-indicator__label">{{ name }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, type Component } from 'vue'
|
||||
import {
|
||||
ReadOutlined,
|
||||
EditOutlined,
|
||||
CodeOutlined,
|
||||
FileAddOutlined,
|
||||
SearchOutlined,
|
||||
FolderOpenOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const props = defineProps<{
|
||||
type: string
|
||||
name: string
|
||||
}>()
|
||||
|
||||
const iconMap: Record<string, Component> = {
|
||||
read: ReadOutlined,
|
||||
edit: EditOutlined,
|
||||
bash: CodeOutlined,
|
||||
write: FileAddOutlined,
|
||||
search: SearchOutlined,
|
||||
grep: SearchOutlined,
|
||||
glob: FolderOpenOutlined,
|
||||
}
|
||||
|
||||
const icon = computed(() => iconMap[props.type] || CodeOutlined)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tool-call-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 1px var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: 11px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tool-call-indicator__icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.tool-call-indicator--read {
|
||||
background: var(--color-info-light);
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
.tool-call-indicator--edit {
|
||||
background: var(--color-warning-light);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.tool-call-indicator--bash {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tool-call-indicator--write {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.tool-call-indicator--search,
|
||||
.tool-call-indicator--grep,
|
||||
.tool-call-indicator--glob {
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="code-diff-viewer">
|
||||
<div v-if="!diff" class="code-diff-viewer__empty">
|
||||
<FileTextOutlined style="font-size: 32px; color: var(--text-placeholder)" />
|
||||
<p>暂无代码变更</p>
|
||||
</div>
|
||||
<div v-else class="code-diff-viewer__content">
|
||||
<div class="code-diff-viewer__header">
|
||||
<span class="code-diff-viewer__file">{{ diff.file }}</span>
|
||||
<span class="code-diff-viewer__stats">
|
||||
<span class="code-diff-viewer__added">+{{ diff.added }}</span>
|
||||
<span class="code-diff-viewer__removed">-{{ diff.removed }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="code-diff-viewer__lines">
|
||||
<div
|
||||
v-for="(line, idx) in diff.lines"
|
||||
:key="idx"
|
||||
:class="['code-diff-viewer__line', `code-diff-viewer__line--${line.type}`]"
|
||||
>
|
||||
<span class="code-diff-viewer__num">{{ line.num }}</span>
|
||||
<span class="code-diff-viewer__prefix">{{ line.prefix }}</span>
|
||||
<span class="code-diff-viewer__text">{{ line.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FileTextOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export interface DiffLine {
|
||||
num: number
|
||||
type: 'context' | 'added' | 'removed'
|
||||
prefix: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface DiffData {
|
||||
file: string
|
||||
added: number
|
||||
removed: number
|
||||
lines: DiffLine[]
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
diff: DiffData | null
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.code-diff-viewer {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace;
|
||||
font-size: var(--font-sm);
|
||||
background: var(--code-bg);
|
||||
color: var(--code-fg);
|
||||
}
|
||||
|
||||
.code-diff-viewer__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: var(--space-3);
|
||||
color: var(--text-placeholder);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.code-diff-viewer__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.code-diff-viewer__file {
|
||||
color: var(--code-fg);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.code-diff-viewer__stats {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.code-diff-viewer__added {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.code-diff-viewer__removed {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.code-diff-viewer__line {
|
||||
display: flex;
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.code-diff-viewer__line--added {
|
||||
background: var(--code-added-bg);
|
||||
}
|
||||
|
||||
.code-diff-viewer__line--removed {
|
||||
background: var(--code-removed-bg);
|
||||
}
|
||||
|
||||
.code-diff-viewer__num {
|
||||
width: 48px;
|
||||
text-align: right;
|
||||
padding-right: var(--space-2);
|
||||
color: var(--code-comment);
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.code-diff-viewer__prefix {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.code-diff-viewer__line--added .code-diff-viewer__prefix {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.code-diff-viewer__line--removed .code-diff-viewer__prefix {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.code-diff-viewer__text {
|
||||
flex: 1;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div class="file-tree">
|
||||
<div v-if="files.length === 0" class="file-tree__empty">
|
||||
<FolderOpenOutlined style="font-size: 24px; color: var(--text-placeholder)" />
|
||||
<p>暂无文件</p>
|
||||
</div>
|
||||
<div v-else class="file-tree__list">
|
||||
<div
|
||||
v-for="file in files"
|
||||
:key="file.path"
|
||||
:class="['file-tree__item', { 'file-tree__item--active': selectedPath === file.path }]"
|
||||
@click="selectedPath = file.path; $emit('select', file.path)"
|
||||
>
|
||||
<component :is="getFileIcon(file.status)" class="file-tree__icon" :class="[`file-tree__icon--${file.status}`]" />
|
||||
<span class="file-tree__name">{{ file.name }}</span>
|
||||
<span v-if="file.status" :class="['file-tree__badge', `file-tree__badge--${file.status}`]">
|
||||
{{ getStatusLabel(file.status) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, type Component } from 'vue'
|
||||
import {
|
||||
FolderOpenOutlined,
|
||||
FileOutlined,
|
||||
FileAddOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
export interface FileEntry {
|
||||
path: string
|
||||
name: string
|
||||
status?: 'added' | 'modified' | 'deleted'
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
files: FileEntry[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
select: [path: string]
|
||||
}>()
|
||||
|
||||
const selectedPath = ref('')
|
||||
|
||||
function getFileIcon(status?: string): Component {
|
||||
switch (status) {
|
||||
case 'added': return FileAddOutlined
|
||||
case 'deleted': return DeleteOutlined
|
||||
case 'modified': return EditOutlined
|
||||
default: return FileOutlined
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status?: string): string {
|
||||
switch (status) {
|
||||
case 'added': return 'A'
|
||||
case 'deleted': return 'D'
|
||||
case 'modified': return 'M'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-tree {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.file-tree__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: var(--space-2);
|
||||
color: var(--text-placeholder);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.file-tree__list {
|
||||
padding: var(--space-1);
|
||||
}
|
||||
|
||||
.file-tree__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background var(--transition-fast);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.file-tree__item:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.file-tree__item--active {
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.file-tree__icon {
|
||||
font-size: 14px;
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-tree__icon--added {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.file-tree__icon--modified {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.file-tree__icon--deleted {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.file-tree__name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.file-tree__badge {
|
||||
font-size: 10px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-tree__badge--added {
|
||||
color: var(--color-success);
|
||||
background: var(--color-success-light);
|
||||
}
|
||||
|
||||
.file-tree__badge--modified {
|
||||
color: var(--color-warning);
|
||||
background: var(--color-warning-light);
|
||||
}
|
||||
|
||||
.file-tree__badge--deleted {
|
||||
color: var(--color-error);
|
||||
background: var(--color-error-light);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<a-statistic
|
||||
title="总任务数"
|
||||
:value="metrics?.total_tasks ?? 0"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
:value-style="{ color: '#7c3aed' }"
|
||||
>
|
||||
<template #prefix><CheckCircleOutlined /></template>
|
||||
</a-statistic>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<a-statistic
|
||||
title="Agent 活跃数"
|
||||
:value="activeAgentCount"
|
||||
:value-style="{ color: '#722ed1' }"
|
||||
:value-style="{ color: '#7c3aed' }"
|
||||
>
|
||||
<template #prefix><TeamOutlined /></template>
|
||||
</a-statistic>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<a-statistic
|
||||
title="LLM 用量"
|
||||
:value="usageSummary.total_tokens"
|
||||
:value-style="{ color: '#13c2c2' }"
|
||||
:value-style="{ color: '#3b82f6' }"
|
||||
>
|
||||
<template #prefix><CloudServerOutlined /></template>
|
||||
</a-statistic>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
title="质量通过率"
|
||||
:value="metrics ? (metrics.success_rate * 100).toFixed(1) : '0.0'"
|
||||
suffix="%"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
:value-style="{ color: '#10b981' }"
|
||||
>
|
||||
<template #prefix><SafetyCertificateOutlined /></template>
|
||||
</a-statistic>
|
||||
|
|
@ -183,7 +183,7 @@ function riskLabel(level: string): string {
|
|||
|
||||
.overview-card__footer {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.overview-sections {
|
||||
|
|
@ -195,8 +195,8 @@ function riskLabel(level: string): string {
|
|||
}
|
||||
|
||||
.overview-section {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
|
|
@ -235,7 +235,7 @@ function riskLabel(level: string): string {
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
border-bottom: 1px solid var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.experience-item:last-child {
|
||||
|
|
@ -250,11 +250,11 @@ function riskLabel(level: string): string {
|
|||
}
|
||||
|
||||
.experience-item__dot--success {
|
||||
background: #52c41a;
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.experience-item__dot--failure {
|
||||
background: #ff4d4f;
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
.experience-item__info {
|
||||
|
|
@ -274,7 +274,7 @@ function riskLabel(level: string): string {
|
|||
|
||||
.experience-item__meta {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.pitfall-item {
|
||||
|
|
@ -282,7 +282,7 @@ function riskLabel(level: string): string {
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
border-bottom: 1px solid var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.pitfall-item:last-child {
|
||||
|
|
@ -297,7 +297,7 @@ function riskLabel(level: string): string {
|
|||
|
||||
.pitfall-item__rate {
|
||||
font-size: 12px;
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ function onExperienceFilter(outcome: string) {
|
|||
<style scoped>
|
||||
.experience-panel {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { Select as ASelect, SelectOption as ASelectOption, Tag as ATag, Empty as AEmpty } from 'ant-design-vue'
|
||||
import type { Experience } from '@/api/evolution'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
experiences: Experience[]
|
||||
}>()
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ function formatTime(isoStr: string): string {
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: #e8e8e8;
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
|
|
@ -153,21 +153,21 @@ function formatTime(isoStr: string): string {
|
|||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
border: 2px solid var(--bg-primary);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-dot--success {
|
||||
background: #52c41a;
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.timeline-dot--failure {
|
||||
background: #ff4d4f;
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
.timeline-card {
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
|
|
@ -199,17 +199,17 @@ function formatTime(isoStr: string): string {
|
|||
gap: 12px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.timeline-card__type {
|
||||
color: #1890ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.timeline-card__detail {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid var(--border-color-split);
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
|
|
@ -219,13 +219,13 @@ function formatTime(isoStr: string): string {
|
|||
.detail-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #595959;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ function formatTime(isoStr: string): string {
|
|||
margin: 0;
|
||||
padding-left: 16px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,19 +18,19 @@
|
|||
<div class="summary-row">
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">成功率</div>
|
||||
<div class="summary-value" style="color: #52c41a">
|
||||
<div class="summary-value" style="color: #10b981">
|
||||
{{ metrics ? (metrics.success_rate * 100).toFixed(1) + '%' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">平均耗时</div>
|
||||
<div class="summary-value" style="color: #1890ff">
|
||||
<div class="summary-value" style="color: #7c3aed">
|
||||
{{ metrics ? formatDuration(metrics.avg_duration) : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="summary-label">重试率</div>
|
||||
<div class="summary-value" style="color: #fa8c16">
|
||||
<div class="summary-value" style="color: #f59e0b">
|
||||
{{ metrics ? (metrics.retry_rate * 100).toFixed(1) + '%' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -129,7 +129,7 @@ function updateChart() {
|
|||
type: 'line',
|
||||
data: props.trends.map(t => +(t.success_rate * 100).toFixed(1)),
|
||||
smooth: true,
|
||||
itemStyle: { color: '#52c41a' },
|
||||
itemStyle: { color: '#10b981' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
},
|
||||
|
|
@ -138,7 +138,7 @@ function updateChart() {
|
|||
type: 'line',
|
||||
data: props.trends.map(t => +(t.retry_rate * 100).toFixed(1)),
|
||||
smooth: true,
|
||||
itemStyle: { color: '#fa8c16' },
|
||||
itemStyle: { color: '#f59e0b' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
},
|
||||
|
|
@ -148,7 +148,7 @@ function updateChart() {
|
|||
yAxisIndex: 1,
|
||||
data: props.trends.map(t => +(t.avg_duration).toFixed(0)),
|
||||
smooth: true,
|
||||
itemStyle: { color: '#1890ff' },
|
||||
itemStyle: { color: '#7c3aed' },
|
||||
lineStyle: { type: 'dashed' },
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
|
|
@ -220,7 +220,7 @@ watch(() => [props.trends, props.period], updateChart, { deep: true })
|
|||
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
background: #fafafa;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
text-align: center;
|
||||
|
|
@ -228,7 +228,7 @@ watch(() => [props.trends, props.period], updateChart, { deep: true })
|
|||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ function onPeriodChange(period: string) {
|
|||
<style scoped>
|
||||
.metrics-panel {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const store = useEvolutionStore()
|
|||
<style scoped>
|
||||
.optimization-panel {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ import { ref } from 'vue'
|
|||
import { Tag as ATag, Empty as AEmpty } from 'ant-design-vue'
|
||||
import type { PathOptimization } from '@/api/evolution'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
optimizations: PathOptimization[]
|
||||
}>()
|
||||
|
||||
|
|
@ -112,8 +112,8 @@ function formatTime(isoStr: string | null): string {
|
|||
}
|
||||
|
||||
.optimizer-item {
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 8px;
|
||||
|
|
@ -135,18 +135,18 @@ function formatTime(isoStr: string | null): string {
|
|||
.optimizer-item__type {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1890ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.optimizer-item__time {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.optimizer-item__detail {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-top: 1px solid var(--border-color-split);
|
||||
}
|
||||
|
||||
.path-comparison {
|
||||
|
|
@ -157,7 +157,7 @@ function formatTime(isoStr: string | null): string {
|
|||
|
||||
.path-arrow {
|
||||
font-size: 16px;
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
padding-top: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ function formatTime(isoStr: string | null): string {
|
|||
.path-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #595959;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
|
|
@ -192,12 +192,12 @@ function formatTime(isoStr: string | null): string {
|
|||
}
|
||||
|
||||
.path-step--old {
|
||||
background: #fff2f0;
|
||||
color: #cf1322;
|
||||
background: var(--color-error-light);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.path-step--new {
|
||||
background: #f6ffed;
|
||||
color: #389e0d;
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import { ref } from 'vue'
|
|||
import { Input as AInput, Button as AButton, Tag as ATag, Empty as AEmpty } from 'ant-design-vue'
|
||||
import type { PitfallWarning } from '@/api/evolution'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
warnings: PitfallWarning[]
|
||||
loading?: boolean
|
||||
}>()
|
||||
|
|
@ -124,8 +124,8 @@ function riskLabel(level: string): string {
|
|||
}
|
||||
|
||||
.pitfall-item {
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 8px;
|
||||
|
|
@ -145,20 +145,20 @@ function riskLabel(level: string): string {
|
|||
|
||||
.pitfall-item__rate {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pitfall-item__reason {
|
||||
font-size: 12px;
|
||||
color: #595959;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pitfall-item__suggestion {
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
color: var(--color-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ function onPitfallCheck(taskType: string) {
|
|||
<style scoped>
|
||||
.pitfall-route-panel {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -12,25 +12,25 @@
|
|||
<div class="usage-summary">
|
||||
<div class="usage-summary__card">
|
||||
<div class="usage-summary__label">请求成功率</div>
|
||||
<div class="usage-summary__value" style="color: #52c41a">
|
||||
<div class="usage-summary__value" style="color: #10b981">
|
||||
{{ (summary.success_rate * 100).toFixed(1) }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-summary__card">
|
||||
<div class="usage-summary__label">平均响应延迟</div>
|
||||
<div class="usage-summary__value" style="color: #1890ff">
|
||||
<div class="usage-summary__value" style="color: #7c3aed">
|
||||
{{ summary.avg_latency_ms.toFixed(0) }} ms
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-summary__card">
|
||||
<div class="usage-summary__label">总 Token 数</div>
|
||||
<div class="usage-summary__value" style="color: #722ed1">
|
||||
<div class="usage-summary__value" style="color: #7c3aed">
|
||||
{{ formatNumber(summary.total_tokens) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="usage-summary__card">
|
||||
<div class="usage-summary__label">总请求数</div>
|
||||
<div class="usage-summary__value" style="color: #fa8c16">
|
||||
<div class="usage-summary__value" style="color: #f59e0b">
|
||||
{{ formatNumber(summary.total_requests) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -72,7 +72,7 @@ const PROVIDER_COLORS: Record<string, string> = {
|
|||
azure: '#0078d4',
|
||||
deepseek: '#4d6bfe',
|
||||
zhipu: '#3b5cff',
|
||||
default: '#8c8c8c',
|
||||
default: '#737373',
|
||||
}
|
||||
|
||||
async function loadUsage() {
|
||||
|
|
@ -203,8 +203,8 @@ watch(usageData, updateChart, { deep: true })
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ watch(usageData, updateChart, { deep: true })
|
|||
}
|
||||
|
||||
.usage-summary__card {
|
||||
background: #fafafa;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
text-align: center;
|
||||
|
|
@ -240,7 +240,7 @@ watch(usageData, updateChart, { deep: true })
|
|||
|
||||
.usage-summary__label {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.search-result-item {
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 12px;
|
||||
|
|
@ -136,19 +136,19 @@ onMounted(() => {
|
|||
|
||||
.search-result-item__index {
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.search-result-item__score {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.search-result-item__content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
white-space: pre-wrap;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<div class="agent-layout">
|
||||
<TopNav />
|
||||
<div class="agent-layout__body">
|
||||
<SplitPane
|
||||
direction="horizontal"
|
||||
:default-ratio="0.5"
|
||||
storage-key="agent-h-split"
|
||||
>
|
||||
<template #first>
|
||||
<SplitPane
|
||||
direction="vertical"
|
||||
:default-ratio="0.6"
|
||||
storage-key="agent-left-v-split"
|
||||
>
|
||||
<template #first>
|
||||
<QuadrantPanel
|
||||
ref="tlPanel"
|
||||
:tabs="topLeftTabs"
|
||||
default-tab="chat"
|
||||
storage-key="quadrant-tl-tab"
|
||||
>
|
||||
<template #chat>
|
||||
<ChatView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
<template #second>
|
||||
<QuadrantPanel
|
||||
ref="blPanel"
|
||||
:tabs="bottomLeftTabs"
|
||||
default-tab="terminal"
|
||||
storage-key="quadrant-bl-tab"
|
||||
>
|
||||
<template #terminal>
|
||||
<TerminalView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</template>
|
||||
<template #second>
|
||||
<SplitPane
|
||||
direction="vertical"
|
||||
:default-ratio="0.6"
|
||||
storage-key="agent-right-v-split"
|
||||
>
|
||||
<template #first>
|
||||
<QuadrantPanel
|
||||
ref="trPanel"
|
||||
:tabs="topRightTabs"
|
||||
default-tab="code"
|
||||
storage-key="quadrant-tr-tab"
|
||||
>
|
||||
<template #code>
|
||||
<div class="agent-layout__placeholder">
|
||||
<FileTextOutlined style="font-size: 32px; color: var(--text-placeholder)" />
|
||||
<p>代码预览</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #workflow>
|
||||
<WorkflowView />
|
||||
</template>
|
||||
<template #knowledge>
|
||||
<KnowledgeBaseView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
<template #second>
|
||||
<QuadrantPanel
|
||||
ref="brPanel"
|
||||
:tabs="bottomRightTabs"
|
||||
default-tab="monitor"
|
||||
storage-key="quadrant-br-tab"
|
||||
>
|
||||
<template #monitor>
|
||||
<EvolutionView />
|
||||
</template>
|
||||
<template #skills>
|
||||
<SkillsView />
|
||||
</template>
|
||||
<template #settings>
|
||||
<SettingsView />
|
||||
</template>
|
||||
</QuadrantPanel>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</div>
|
||||
<div class="agent-layout__small-screen">
|
||||
<DesktopOutlined style="font-size: 48px; color: var(--text-placeholder)" />
|
||||
<h2>请使用更大的屏幕</h2>
|
||||
<p>Fischer AgentKit 四象限布局需要至少 1280px 宽度的屏幕才能正常使用。</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, defineAsyncComponent, type Component } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
MessageOutlined,
|
||||
CodeOutlined,
|
||||
FileTextOutlined,
|
||||
ApartmentOutlined,
|
||||
BookOutlined,
|
||||
DashboardOutlined,
|
||||
AppstoreOutlined,
|
||||
SettingOutlined,
|
||||
DesktopOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import TopNav from './TopNav.vue'
|
||||
import SplitPane from './SplitPane.vue'
|
||||
import QuadrantPanel from './QuadrantPanel.vue'
|
||||
import type { QuadrantTab } from './QuadrantPanel.vue'
|
||||
|
||||
// Lazy-load views to avoid bundling all into initial chunk
|
||||
const ChatView = defineAsyncComponent(() => import('@/views/ChatView.vue'))
|
||||
const TerminalView = defineAsyncComponent(() => import('@/views/TerminalView.vue'))
|
||||
const WorkflowView = defineAsyncComponent(() => import('@/views/WorkflowView.vue'))
|
||||
const KnowledgeBaseView = defineAsyncComponent(() => import('@/views/KnowledgeBaseView.vue'))
|
||||
const EvolutionView = defineAsyncComponent(() => import('@/views/EvolutionView.vue'))
|
||||
const SkillsView = defineAsyncComponent(() => import('@/views/SkillsView.vue'))
|
||||
const SettingsView = defineAsyncComponent(() => import('@/views/SettingsView.vue'))
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const topLeftTabs: QuadrantTab[] = [
|
||||
{ key: 'chat', label: '对话', icon: MessageOutlined as Component },
|
||||
]
|
||||
|
||||
const bottomLeftTabs: QuadrantTab[] = [
|
||||
{ key: 'terminal', label: '终端', icon: CodeOutlined as Component },
|
||||
]
|
||||
|
||||
const topRightTabs: QuadrantTab[] = [
|
||||
{ key: 'code', label: '代码', icon: FileTextOutlined as Component },
|
||||
{ key: 'workflow', label: '工作流', icon: ApartmentOutlined as Component },
|
||||
{ key: 'knowledge', label: '知识库', icon: BookOutlined as Component },
|
||||
]
|
||||
|
||||
const bottomRightTabs: QuadrantTab[] = [
|
||||
{ key: 'monitor', label: '监控', icon: DashboardOutlined as Component },
|
||||
{ key: 'skills', label: '技能', icon: AppstoreOutlined as Component },
|
||||
{ key: 'settings', label: '设置', icon: SettingOutlined as Component },
|
||||
]
|
||||
|
||||
// Quadrant refs for route-driven tab switching
|
||||
const tlPanel = ref<InstanceType<typeof QuadrantPanel> | null>(null)
|
||||
const blPanel = ref<InstanceType<typeof QuadrantPanel> | null>(null)
|
||||
const trPanel = ref<InstanceType<typeof QuadrantPanel> | null>(null)
|
||||
const brPanel = ref<InstanceType<typeof QuadrantPanel> | null>(null)
|
||||
|
||||
// Watch route changes to sync quadrant tabs with URL
|
||||
watch(() => route.meta, (meta) => {
|
||||
const quadrant = meta.quadrant as string | undefined
|
||||
const tab = meta.tab as string | undefined
|
||||
if (quadrant && tab) {
|
||||
const panelMap: Record<string, typeof tlPanel> = {
|
||||
tl: tlPanel,
|
||||
bl: blPanel,
|
||||
tr: trPanel,
|
||||
br: brPanel,
|
||||
}
|
||||
const panel = panelMap[quadrant]
|
||||
if (panel?.value) {
|
||||
panel.value.setActiveTab(tab)
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agent-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.agent-layout__body {
|
||||
flex: 1;
|
||||
padding: var(--space-2);
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-layout__placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: var(--space-3);
|
||||
color: var(--text-placeholder);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -21,6 +21,6 @@ import SideNav from './SideNav.vue'
|
|||
.app-layout__main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
<template>
|
||||
<div class="quadrant-panel" :class="{ 'quadrant-panel--collapsed': collapsed }">
|
||||
<div class="quadrant-panel__header">
|
||||
<div class="quadrant-panel__tabs" role="tablist" :aria-label="`${storageKey || 'panel'} tabs`">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['quadrant-panel__tab', { 'quadrant-panel__tab--active': activeTab === tab.key }]"
|
||||
role="tab"
|
||||
:aria-selected="activeTab === tab.key"
|
||||
:tabindex="activeTab === tab.key ? 0 : -1"
|
||||
@click="activeTab = tab.key"
|
||||
@keydown="onTabKeydown"
|
||||
>
|
||||
<component :is="tab.icon" v-if="tab.icon" class="quadrant-panel__tab-icon" />
|
||||
<span>{{ tab.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="quadrant-panel__collapse-btn"
|
||||
@click="collapsed = !collapsed"
|
||||
:title="collapsed ? '展开' : '折叠'"
|
||||
:aria-label="collapsed ? '展开面板' : '折叠面板'"
|
||||
>
|
||||
<MinusOutlined v-if="!collapsed" />
|
||||
<PlusOutlined v-else />
|
||||
</button>
|
||||
</div>
|
||||
<div v-show="!collapsed" class="quadrant-panel__body">
|
||||
<div
|
||||
v-for="tab in activeTabList"
|
||||
:key="tab.key"
|
||||
role="tabpanel"
|
||||
class="quadrant-panel__content"
|
||||
>
|
||||
<slot :name="tab.key" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed, type Component } from 'vue'
|
||||
import { MinusOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
export interface QuadrantTab {
|
||||
key: string
|
||||
label: string
|
||||
icon?: Component
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
tabs: QuadrantTab[]
|
||||
defaultTab?: string
|
||||
storageKey?: string
|
||||
}>(), {
|
||||
defaultTab: '',
|
||||
})
|
||||
|
||||
const savedTab = props.storageKey
|
||||
? localStorage.getItem(props.storageKey) || props.defaultTab
|
||||
: props.defaultTab
|
||||
|
||||
const activeTab = ref(savedTab || (props.tabs[0]?.key ?? ''))
|
||||
const collapsed = ref(props.storageKey ? localStorage.getItem(props.storageKey + '-collapsed') === 'true' : false)
|
||||
|
||||
// Only render the active tab (v-if via computed filter)
|
||||
const activeTabList = computed(() => props.tabs.filter(t => t.key === activeTab.value))
|
||||
|
||||
watch(activeTab, (val) => {
|
||||
if (props.storageKey) localStorage.setItem(props.storageKey, val)
|
||||
})
|
||||
|
||||
watch(collapsed, (val) => {
|
||||
if (props.storageKey) localStorage.setItem(props.storageKey + '-collapsed', String(val))
|
||||
})
|
||||
|
||||
function setActiveTab(tabKey: string) {
|
||||
if (props.tabs.some(t => t.key === tabKey)) {
|
||||
activeTab.value = tabKey
|
||||
}
|
||||
}
|
||||
|
||||
function onTabKeydown(e: KeyboardEvent) {
|
||||
const currentIndex = props.tabs.findIndex(t => t.key === activeTab.value)
|
||||
let nextIndex = currentIndex
|
||||
|
||||
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
nextIndex = (currentIndex + 1) % props.tabs.length
|
||||
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
nextIndex = (currentIndex - 1 + props.tabs.length) % props.tabs.length
|
||||
} else if (e.key === 'Home') {
|
||||
e.preventDefault()
|
||||
nextIndex = 0
|
||||
} else if (e.key === 'End') {
|
||||
e.preventDefault()
|
||||
nextIndex = props.tabs.length - 1
|
||||
}
|
||||
|
||||
if (nextIndex !== currentIndex) {
|
||||
activeTab.value = props.tabs[nextIndex].key
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ setActiveTab })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quadrant-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.quadrant-panel--collapsed {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.quadrant-panel__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
padding: 0 var(--space-2);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quadrant-panel__tabs {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.quadrant-panel__tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quadrant-panel__tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-xs);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab--active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-light);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.quadrant-panel__tab-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.quadrant-panel__collapse-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.quadrant-panel__collapse-btn:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.quadrant-panel__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quadrant-panel__content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -88,8 +88,8 @@ watch(
|
|||
}
|
||||
)
|
||||
|
||||
function handleMenuClick({ key }: { key: string }): void {
|
||||
router.push(key)
|
||||
function handleMenuClick({ key }: { key: string | number }): void {
|
||||
router.push(String(key))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
:class="['split-pane', `split-pane--${direction}`, { 'split-pane--dragging': isDragging }]"
|
||||
>
|
||||
<div
|
||||
class="split-pane__first"
|
||||
:style="firstStyle"
|
||||
>
|
||||
<slot name="first" />
|
||||
</div>
|
||||
<div
|
||||
class="split-pane__handle"
|
||||
role="separator"
|
||||
:aria-orientation="direction === 'horizontal' ? 'vertical' : 'horizontal'"
|
||||
:aria-valuenow="Math.round(ratio * 100)"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
tabindex="0"
|
||||
@mousedown="onMouseDown"
|
||||
@touchstart.passive="onTouchStart"
|
||||
@keydown="onHandleKeydown"
|
||||
>
|
||||
<div class="split-pane__handle-line" />
|
||||
</div>
|
||||
<div
|
||||
class="split-pane__second"
|
||||
:style="secondStyle"
|
||||
>
|
||||
<slot name="second" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
defaultRatio?: number
|
||||
minRatio?: number
|
||||
maxRatio?: number
|
||||
storageKey?: string
|
||||
}>(), {
|
||||
direction: 'horizontal',
|
||||
defaultRatio: 0.5,
|
||||
minRatio: 0.2,
|
||||
maxRatio: 0.8,
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const isDragging = ref(false)
|
||||
|
||||
const savedRatio = props.storageKey
|
||||
? parseFloat(localStorage.getItem(props.storageKey) || String(props.defaultRatio))
|
||||
: props.defaultRatio
|
||||
|
||||
const ratio = ref(
|
||||
Math.min(props.maxRatio, Math.max(props.minRatio, savedRatio))
|
||||
)
|
||||
|
||||
const firstStyle = computed(() => {
|
||||
if (props.direction === 'horizontal') {
|
||||
return { width: `${ratio.value * 100}%` }
|
||||
}
|
||||
return { height: `${ratio.value * 100}%` }
|
||||
})
|
||||
|
||||
const secondStyle = computed(() => {
|
||||
if (props.direction === 'horizontal') {
|
||||
return { width: `${(1 - ratio.value) * 100}%` }
|
||||
}
|
||||
return { height: `${(1 - ratio.value) * 100}%` }
|
||||
})
|
||||
|
||||
function clampRatio(val: number) {
|
||||
return Math.min(props.maxRatio, Math.max(props.minRatio, val))
|
||||
}
|
||||
|
||||
function updateRatioFromPosition(clientPos: number, startPos: number, startRatio: number, containerSize: number) {
|
||||
const delta = (clientPos - startPos) / containerSize
|
||||
ratio.value = clampRatio(startRatio + delta)
|
||||
}
|
||||
|
||||
function finishDrag() {
|
||||
isDragging.value = false
|
||||
if (props.storageKey) {
|
||||
localStorage.setItem(props.storageKey, String(ratio.value))
|
||||
}
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
if (!containerRef.value) return
|
||||
isDragging.value = true
|
||||
|
||||
const startPos = props.direction === 'horizontal' ? e.clientX : e.clientY
|
||||
const containerSize = props.direction === 'horizontal'
|
||||
? containerRef.value.offsetWidth
|
||||
: containerRef.value.offsetHeight
|
||||
const startRatio = ratio.value
|
||||
|
||||
function onMouseMove(ev: MouseEvent) {
|
||||
const currentPos = props.direction === 'horizontal' ? ev.clientX : ev.clientY
|
||||
updateRatioFromPosition(currentPos, startPos, startRatio, containerSize)
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
finishDrag()
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}
|
||||
|
||||
function onTouchStart(e: TouchEvent) {
|
||||
if (!containerRef.value) return
|
||||
isDragging.value = true
|
||||
|
||||
const touch = e.touches[0]
|
||||
const startPos = props.direction === 'horizontal' ? touch.clientX : touch.clientY
|
||||
const containerSize = props.direction === 'horizontal'
|
||||
? containerRef.value.offsetWidth
|
||||
: containerRef.value.offsetHeight
|
||||
const startRatio = ratio.value
|
||||
|
||||
function onTouchMove(ev: TouchEvent) {
|
||||
const t = ev.touches[0]
|
||||
const currentPos = props.direction === 'horizontal' ? t.clientX : t.clientY
|
||||
updateRatioFromPosition(currentPos, startPos, startRatio, containerSize)
|
||||
}
|
||||
|
||||
function onTouchEnd() {
|
||||
finishDrag()
|
||||
document.removeEventListener('touchmove', onTouchMove)
|
||||
document.removeEventListener('touchend', onTouchEnd)
|
||||
}
|
||||
|
||||
document.addEventListener('touchmove', onTouchMove, { passive: true })
|
||||
document.addEventListener('touchend', onTouchEnd)
|
||||
}
|
||||
|
||||
function onHandleKeydown(e: KeyboardEvent) {
|
||||
const step = 0.02 // 2% per key press
|
||||
if (props.direction === 'horizontal') {
|
||||
if (e.key === 'ArrowLeft') { e.preventDefault(); ratio.value = clampRatio(ratio.value - step) }
|
||||
else if (e.key === 'ArrowRight') { e.preventDefault(); ratio.value = clampRatio(ratio.value + step) }
|
||||
} else {
|
||||
if (e.key === 'ArrowUp') { e.preventDefault(); ratio.value = clampRatio(ratio.value - step) }
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); ratio.value = clampRatio(ratio.value + step) }
|
||||
}
|
||||
if (e.key === 'Home') { e.preventDefault(); ratio.value = props.minRatio }
|
||||
else if (e.key === 'End') { e.preventDefault(); ratio.value = props.maxRatio }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.split-pane {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.split-pane--horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.split-pane--vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split-pane__first,
|
||||
.split-pane__second {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.split-pane__handle {
|
||||
flex-shrink: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.split-pane--horizontal > .split-pane__handle {
|
||||
width: 6px;
|
||||
cursor: col-resize;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split-pane--vertical > .split-pane__handle {
|
||||
height: 6px;
|
||||
cursor: row-resize;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.split-pane__handle:hover,
|
||||
.split-pane--dragging .split-pane__handle {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.split-pane__handle-line {
|
||||
background-color: var(--border-color);
|
||||
border-radius: var(--radius-full);
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.split-pane--horizontal > .split-pane__handle > .split-pane__handle-line {
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.split-pane--vertical > .split-pane__handle > .split-pane__handle-line {
|
||||
height: 2px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.split-pane__handle:hover .split-pane__handle-line,
|
||||
.split-pane--dragging .split-pane__handle-line {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.split-pane--dragging {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<header class="top-nav">
|
||||
<div class="top-nav__left">
|
||||
<div class="top-nav__logo" @click="router.push('/agent')">
|
||||
<span class="top-nav__logo-text">Fischer</span>
|
||||
<span class="top-nav__logo-badge">AgentKit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="top-nav__center">
|
||||
</div>
|
||||
|
||||
<div class="top-nav__right">
|
||||
<div class="top-nav__status">
|
||||
<a-badge
|
||||
:status="wsConnected ? 'success' : 'error'"
|
||||
:text="wsConnected ? '已连接' : '未连接'"
|
||||
/>
|
||||
</div>
|
||||
<a-tooltip title="设置">
|
||||
<button class="top-nav__icon-btn" @click="router.push('/agent/monitor?tab=settings')">
|
||||
<SettingOutlined />
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Badge as ABadge, Tooltip as ATooltip } from 'ant-design-vue'
|
||||
import { SettingOutlined } from '@ant-design/icons-vue'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
|
||||
const router = useRouter()
|
||||
const chatStore = useChatStore()
|
||||
const wsConnected = computed(() => chatStore.isWsConnected)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--topnav-height);
|
||||
padding: 0 var(--space-4);
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
.top-nav__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.top-nav__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.top-nav__logo-text {
|
||||
font-size: var(--font-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
background: var(--gradient-brand);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.top-nav__logo-badge {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-inverse);
|
||||
background: var(--gradient-brand);
|
||||
padding: 1px var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.top-nav__center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-nav__task-select {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.top-nav__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.top-nav__status {
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.top-nav__status :deep(.ant-badge-status-text) {
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.top-nav__icon-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.top-nav__icon-btn:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -60,12 +60,12 @@ defineEmits<{
|
|||
}
|
||||
|
||||
.skill-card__icon {
|
||||
color: #1677ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.skill-card__desc {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
|
|
@ -90,12 +90,12 @@ defineEmits<{
|
|||
|
||||
.skill-card__deps-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.skill-card__more {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.skill-card__footer {
|
||||
|
|
@ -106,6 +106,6 @@ defineEmits<{
|
|||
|
||||
.skill-card__version {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -103,12 +103,12 @@ async function handleHealthCheck(): Promise<void> {
|
|||
}
|
||||
|
||||
.skill-detail__empty {
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.skill-detail__config {
|
||||
background: #f5f5f5;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
>
|
||||
<div class="command-history__item-header">
|
||||
<span
|
||||
class="command-history__exit-code"
|
||||
:class="record.exit_code === 0 ? 'success' : 'error'"
|
||||
:class="['command-history__exit-code', record.exit_code === 0 ? 'command-history__exit-code--success' : 'command-history__exit-code--error']"
|
||||
>
|
||||
{{ record.exit_code === 0 ? '✓' : '✗' }}
|
||||
</span>
|
||||
|
|
@ -37,6 +36,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Button as AButton } from 'ant-design-vue'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
|
||||
const terminalStore = useTerminalStore()
|
||||
|
|
@ -60,61 +60,60 @@ function formatTime(timestamp: number): string {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fafafa;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.command-history__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-base);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.command-history__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
.command-history__item {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
transition: background 0.2s;
|
||||
margin-bottom: var(--space-1);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.command-history__item:hover {
|
||||
background: #e6f4ff;
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.command-history__item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.command-history__exit-code {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-size: var(--font-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.command-history__exit-code.success {
|
||||
color: #52c41a;
|
||||
.command-history__exit-code--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.command-history__exit-code.error {
|
||||
color: #f5222d;
|
||||
.command-history__exit-code--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.command-history__command {
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace;
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
|
@ -122,20 +121,20 @@ function formatTime(timestamp: number): string {
|
|||
|
||||
.command-history__item-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: var(--space-2);
|
||||
margin-top: 2px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.command-history__duration {
|
||||
color: #1677ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.command-history__empty {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
padding: var(--space-6);
|
||||
color: var(--text-placeholder);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -77,16 +77,65 @@ function historyDown(): void {
|
|||
}
|
||||
|
||||
function ansiToHtml(text: string): string {
|
||||
// Basic ANSI color code conversion
|
||||
return text
|
||||
// Basic ANSI color code conversion using One Dark Pro palette
|
||||
// Track open spans to ensure balanced HTML on reset
|
||||
let html = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\x1b\[32m/g, '<span style="color:#4caf50">')
|
||||
.replace(/\x1b\[33m/g, '<span style="color:#ff9800">')
|
||||
.replace(/\x1b\[31m/g, '<span style="color:#f44336">')
|
||||
.replace(/\x1b\[36m/g, '<span style="color:#00bcd4">')
|
||||
.replace(/\x1b\[0m/g, '</span>')
|
||||
|
||||
// Replace color codes with close-previous + open-new pattern
|
||||
// and track span depth for balanced closing on reset
|
||||
let spanDepth = 0
|
||||
const result: string[] = []
|
||||
let i = 0
|
||||
|
||||
while (i < html.length) {
|
||||
// Check for ANSI escape sequence
|
||||
if (html[i] === '\x1b' && i + 1 < html.length && html[i + 1] === '[') {
|
||||
const endIdx = html.indexOf('m', i + 2)
|
||||
if (endIdx !== -1) {
|
||||
const code = html.substring(i + 2, endIdx)
|
||||
if (code === '0' || code === '') {
|
||||
// Reset: close all open spans
|
||||
for (let s = 0; s < spanDepth; s++) {
|
||||
result.push('</span>')
|
||||
}
|
||||
spanDepth = 0
|
||||
} else {
|
||||
// Color code: close previous span if any, open new one
|
||||
const colorMap: Record<string, string> = {
|
||||
'32': 'ansi-green',
|
||||
'33': 'ansi-yellow',
|
||||
'31': 'ansi-red',
|
||||
'36': 'ansi-cyan',
|
||||
'34': 'ansi-blue',
|
||||
'35': 'ansi-magenta',
|
||||
'1': 'ansi-bold',
|
||||
}
|
||||
const cls = colorMap[code]
|
||||
if (cls) {
|
||||
result.push(`<span class="${cls}">`)
|
||||
spanDepth++
|
||||
}
|
||||
}
|
||||
i = endIdx + 1
|
||||
} else {
|
||||
result.push(html[i])
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
result.push(html[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Close any remaining open spans
|
||||
for (let s = 0; s < spanDepth; s++) {
|
||||
result.push('</span>')
|
||||
}
|
||||
|
||||
return result.join('')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -95,38 +144,38 @@ function ansiToHtml(text: string): string {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #1e1e1e;
|
||||
border-radius: 6px;
|
||||
background: var(--code-bg);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.terminal-emulator__output {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #d4d4d4;
|
||||
padding: var(--space-3);
|
||||
font-size: var(--font-sm);
|
||||
line-height: var(--leading-normal);
|
||||
color: var(--code-fg);
|
||||
}
|
||||
|
||||
.terminal-emulator__welcome {
|
||||
color: #888;
|
||||
color: var(--code-comment);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.terminal-emulator__input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid #333;
|
||||
background: #252526;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.terminal-emulator__prompt {
|
||||
color: #4caf50;
|
||||
margin-right: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--code-string);
|
||||
margin-right: var(--space-2);
|
||||
font-size: var(--font-sm);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
@ -135,17 +184,25 @@ function ansiToHtml(text: string): string {
|
|||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #d4d4d4;
|
||||
color: var(--code-fg);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.terminal-emulator__input-field::placeholder {
|
||||
color: #555;
|
||||
color: var(--code-comment);
|
||||
}
|
||||
|
||||
.terminal-line {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* ANSI color classes using One Dark Pro palette */
|
||||
.terminal-line :deep(.ansi-green) { color: var(--code-string); }
|
||||
.terminal-line :deep(.ansi-yellow) { color: var(--code-number); }
|
||||
.terminal-line :deep(.ansi-red) { color: var(--code-variable); }
|
||||
.terminal-line :deep(.ansi-cyan) { color: var(--code-function); }
|
||||
.terminal-line :deep(.ansi-blue) { color: var(--code-function); }
|
||||
.terminal-line :deep(.ansi-magenta) { color: var(--code-keyword); }
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ const isPaused = computed(() => {
|
|||
|
||||
<style scoped>
|
||||
.approval-node {
|
||||
background: #fff;
|
||||
border: 2px solid #722ed1;
|
||||
background: var(--bg-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
min-width: 160px;
|
||||
|
|
@ -59,32 +59,32 @@ const isPaused = computed(() => {
|
|||
}
|
||||
|
||||
.approval-node.selected {
|
||||
border-color: #531dab;
|
||||
border-color: var(--color-primary-hover);
|
||||
box-shadow: 0 0 0 3px rgba(114, 46, 209, 0.2);
|
||||
}
|
||||
|
||||
.approval-node.paused {
|
||||
border-color: #fa8c16;
|
||||
border-color: var(--color-warning);
|
||||
animation: pulse-border 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Execution status styles */
|
||||
.approval-node.status-running {
|
||||
border-color: #1890ff;
|
||||
border-color: var(--color-info);
|
||||
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.approval-node.status-completed {
|
||||
border-color: #52c41a;
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.approval-node.status-failed {
|
||||
border-color: #ff4d4f;
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.approval-node.status-waiting_approval {
|
||||
border-color: #faad14;
|
||||
border-color: var(--color-warning);
|
||||
box-shadow: 0 0 8px rgba(250, 173, 20, 0.5);
|
||||
animation: pulse-waiting 2s ease-in-out infinite;
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ const isPaused = computed(() => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
|
|
@ -124,20 +124,20 @@ const isPaused = computed(() => {
|
|||
}
|
||||
|
||||
.running-icon {
|
||||
color: #1890ff;
|
||||
color: var(--color-info);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.completed-icon {
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.failed-icon {
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.waiting-icon {
|
||||
color: #faad14;
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
|
@ -153,13 +153,13 @@ const isPaused = computed(() => {
|
|||
}
|
||||
|
||||
.node-icon {
|
||||
color: #722ed1;
|
||||
color: var(--color-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
|
@ -179,15 +179,15 @@ const isPaused = computed(() => {
|
|||
.node-detail {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -43,46 +43,46 @@ defineProps<{
|
|||
}
|
||||
|
||||
.diamond-shape {
|
||||
background: #fff;
|
||||
border: 2px solid #faad14;
|
||||
background: var(--bg-primary);
|
||||
border: 2px solid var(--color-warning);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transform: none;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.condition-node:hover .diamond-shape {
|
||||
box-shadow: 0 4px 12px rgba(250, 173, 20, 0.25);
|
||||
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.25);
|
||||
}
|
||||
|
||||
.condition-node.selected .diamond-shape {
|
||||
border-color: #d48806;
|
||||
box-shadow: 0 0 0 3px rgba(250, 173, 20, 0.2);
|
||||
border-color: var(--color-warning);
|
||||
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.2);
|
||||
}
|
||||
|
||||
/* Execution status styles */
|
||||
.condition-node.status-running .diamond-shape {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
|
||||
border-color: var(--color-info);
|
||||
box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.condition-node.status-completed .diamond-shape {
|
||||
border-color: #52c41a;
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.condition-node.status-failed .diamond-shape {
|
||||
border-color: #ff4d4f;
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.condition-node.status-waiting_approval .diamond-shape {
|
||||
border-color: #faad14;
|
||||
border-color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(24, 144, 255, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(24, 144, 255, 0.7); }
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(59, 130, 246, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(59, 130, 246, 0.7); }
|
||||
}
|
||||
|
||||
/* Status indicator icon */
|
||||
|
|
@ -96,7 +96,7 @@ defineProps<{
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
@ -106,20 +106,20 @@ defineProps<{
|
|||
}
|
||||
|
||||
.running-icon {
|
||||
color: #1890ff;
|
||||
color: var(--color-info);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.completed-icon {
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.failed-icon {
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.waiting-icon {
|
||||
color: #faad14;
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
|
@ -134,24 +134,24 @@ defineProps<{
|
|||
}
|
||||
|
||||
.node-icon {
|
||||
color: #faad14;
|
||||
color: var(--color-warning);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.condition-expr {
|
||||
margin-top: 4px;
|
||||
padding: 2px 8px;
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
background: var(--color-warning-light);
|
||||
border: 1px solid var(--color-warning-light);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: #8c6900;
|
||||
color: var(--color-warning);
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -159,11 +159,11 @@ defineProps<{
|
|||
}
|
||||
|
||||
.handle-true {
|
||||
background: #52c41a;
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.handle-false {
|
||||
background: #ff4d4f;
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
.label-true {
|
||||
|
|
@ -171,7 +171,7 @@ defineProps<{
|
|||
right: -22px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ defineProps<{
|
|||
bottom: -18px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
</a-space>
|
||||
</div>
|
||||
<VueFlow
|
||||
v-model:nodes="nodes"
|
||||
v-model:edges="edges"
|
||||
v-model:nodes="store.flowNodes"
|
||||
v-model:edges="store.flowEdges"
|
||||
:node-types="nodeTypes"
|
||||
:default-edge-options="defaultEdgeOptions"
|
||||
:snap-to-grid="true"
|
||||
|
|
@ -58,9 +58,7 @@ import { useWorkflowStore } from '@/stores/workflow'
|
|||
|
||||
const store = useWorkflowStore()
|
||||
|
||||
const props = defineProps<{
|
||||
nodes: any[]
|
||||
edges: any[]
|
||||
defineProps<{
|
||||
saving?: boolean
|
||||
executing?: boolean
|
||||
}>()
|
||||
|
|
@ -69,15 +67,13 @@ const emit = defineEmits<{
|
|||
save: []
|
||||
execute: []
|
||||
clear: []
|
||||
'update:nodes': [nodes: any[]]
|
||||
'update:edges': [edges: any[]]
|
||||
'node-select': [nodeId: string | null]
|
||||
'node-drop': [nodeType: string, position: { x: number; y: number }]
|
||||
}>()
|
||||
|
||||
const canvasRef = ref<HTMLElement>()
|
||||
|
||||
const nodeTypes = {
|
||||
const nodeTypes: Record<string, any> = {
|
||||
skill: markRaw(SkillNode),
|
||||
condition: markRaw(ConditionNode),
|
||||
approval: markRaw(ApprovalNode),
|
||||
|
|
@ -128,7 +124,7 @@ function onConnect(params: any) {
|
|||
sourceHandle: params.sourceHandle,
|
||||
targetHandle: params.targetHandle,
|
||||
animated: true,
|
||||
style: { stroke: '#1890ff', strokeWidth: 2 },
|
||||
style: { stroke: '#7c3aed', strokeWidth: 2 },
|
||||
}
|
||||
store.addEdge(newEdge)
|
||||
}
|
||||
|
|
@ -164,17 +160,17 @@ onUnmounted(() => {
|
|||
|
||||
function onValidate() {
|
||||
// Basic validation
|
||||
if (props.nodes.length === 0) {
|
||||
if (store.flowNodes.length === 0) {
|
||||
message.warning('工作流为空,请添加节点')
|
||||
return
|
||||
}
|
||||
// Check for nodes without connections (except the first)
|
||||
const connectedSources = new Set(props.edges.map((e: any) => e.source))
|
||||
const connectedTargets = new Set(props.edges.map((e: any) => e.target))
|
||||
const connectedSources = new Set(store.flowEdges.map((e: any) => e.source))
|
||||
const connectedTargets = new Set(store.flowEdges.map((e: any) => e.target))
|
||||
const allConnected = new Set([...connectedSources, ...connectedTargets])
|
||||
|
||||
const orphanNodes = props.nodes.filter((n: any) => !allConnected.has(n.id))
|
||||
if (orphanNodes.length > 0 && props.nodes.length > 1) {
|
||||
const orphanNodes = store.flowNodes.filter((n: any) => !allConnected.has(n.id))
|
||||
if (orphanNodes.length > 0 && store.flowNodes.length > 1) {
|
||||
message.warning(`存在未连接的节点: ${orphanNodes.map((n: any) => n.data?.label).join(', ')}`)
|
||||
return
|
||||
}
|
||||
|
|
@ -194,8 +190,8 @@ function onValidate() {
|
|||
|
||||
.canvas-toolbar {
|
||||
padding: 8px 12px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color-split);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
|
|
|
|||
|
|
@ -33,28 +33,28 @@ const nodeTypes = [
|
|||
name: '技能节点',
|
||||
desc: '引用已注册的技能',
|
||||
icon: ThunderboltOutlined,
|
||||
color: '#1890ff',
|
||||
color: '#7c3aed',
|
||||
},
|
||||
{
|
||||
type: 'condition',
|
||||
name: '条件节点',
|
||||
desc: 'If/else 条件分支',
|
||||
icon: BranchesOutlined,
|
||||
color: '#faad14',
|
||||
color: '#f59e0b',
|
||||
},
|
||||
{
|
||||
type: 'approval',
|
||||
name: '审批节点',
|
||||
desc: '人工审批关卡',
|
||||
icon: UserOutlined,
|
||||
color: '#722ed1',
|
||||
color: '#7c3aed',
|
||||
},
|
||||
{
|
||||
type: 'parallel',
|
||||
name: '并行节点',
|
||||
desc: '并行执行组',
|
||||
icon: ForkOutlined,
|
||||
color: '#52c41a',
|
||||
color: '#10b981',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -69,8 +69,8 @@ function onDragStart(event: DragEvent, nodeType: string) {
|
|||
<style scoped>
|
||||
.node-palette {
|
||||
width: 200px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
border-right: 1px solid var(--border-color-split);
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
|
@ -81,8 +81,8 @@ function onDragStart(event: DragEvent, nodeType: string) {
|
|||
padding: 12px 16px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color-split);
|
||||
}
|
||||
|
||||
.palette-list {
|
||||
|
|
@ -97,15 +97,15 @@ function onDragStart(event: DragEvent, nodeType: string) {
|
|||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
cursor: grab;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.palette-item:hover {
|
||||
border-color: #1890ff;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
||||
}
|
||||
|
||||
|
|
@ -127,11 +127,11 @@ function onDragStart(event: DragEvent, nodeType: string) {
|
|||
.item-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -33,48 +33,48 @@ defineProps<{
|
|||
|
||||
<style scoped>
|
||||
.parallel-node {
|
||||
background: #fff;
|
||||
border: 2px solid #52c41a;
|
||||
background: var(--bg-primary);
|
||||
border: 2px solid var(--color-success);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
min-width: 160px;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.parallel-node:hover {
|
||||
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.25);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
|
||||
.parallel-node.selected {
|
||||
border-color: #389e0d;
|
||||
box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.2);
|
||||
border-color: var(--color-success);
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
/* Execution status styles */
|
||||
.parallel-node.status-running {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
|
||||
border-color: var(--color-info);
|
||||
box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.parallel-node.status-completed {
|
||||
border-color: #52c41a;
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.parallel-node.status-failed {
|
||||
border-color: #ff4d4f;
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.parallel-node.status-waiting_approval {
|
||||
border-color: #faad14;
|
||||
border-color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(24, 144, 255, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(24, 144, 255, 0.7); }
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(59, 130, 246, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(59, 130, 246, 0.7); }
|
||||
}
|
||||
|
||||
/* Status indicator icon */
|
||||
|
|
@ -88,7 +88,7 @@ defineProps<{
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
|
|
@ -97,20 +97,20 @@ defineProps<{
|
|||
}
|
||||
|
||||
.running-icon {
|
||||
color: #1890ff;
|
||||
color: var(--color-info);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.completed-icon {
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.failed-icon {
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.waiting-icon {
|
||||
color: #faad14;
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
|
@ -126,13 +126,13 @@ defineProps<{
|
|||
}
|
||||
|
||||
.node-icon {
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
|
@ -145,15 +145,15 @@ defineProps<{
|
|||
.node-detail {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ function updateConfig(key: string, value: unknown) {
|
|||
<style scoped>
|
||||
.property-panel {
|
||||
width: 300px;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
background: #fff;
|
||||
border-left: 1px solid var(--border-color-split);
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
|
@ -190,13 +190,13 @@ function updateConfig(key: string, value: unknown) {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--border-color-split);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
|
|
|
|||
|
|
@ -37,48 +37,48 @@ defineProps<{
|
|||
|
||||
<style scoped>
|
||||
.skill-node {
|
||||
background: #fff;
|
||||
border: 2px solid #1890ff;
|
||||
background: var(--bg-primary);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
min-width: 160px;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.skill-node:hover {
|
||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.25);
|
||||
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.25);
|
||||
}
|
||||
|
||||
.skill-node.selected {
|
||||
border-color: #096dd9;
|
||||
box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.2);
|
||||
border-color: var(--color-primary-hover);
|
||||
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
|
||||
/* Execution status styles */
|
||||
.skill-node.status-running {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
|
||||
border-color: var(--color-info);
|
||||
box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.skill-node.status-completed {
|
||||
border-color: #52c41a;
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.skill-node.status-failed {
|
||||
border-color: #ff4d4f;
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.skill-node.status-waiting_approval {
|
||||
border-color: #faad14;
|
||||
border-color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(24, 144, 255, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(24, 144, 255, 0.7); }
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(59, 130, 246, 0.3); }
|
||||
50% { box-shadow: 0 0 12px rgba(59, 130, 246, 0.7); }
|
||||
}
|
||||
|
||||
/* Status indicator icon */
|
||||
|
|
@ -92,7 +92,7 @@ defineProps<{
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
|
|
@ -101,20 +101,20 @@ defineProps<{
|
|||
}
|
||||
|
||||
.running-icon {
|
||||
color: #1890ff;
|
||||
color: var(--color-info);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.completed-icon {
|
||||
color: #52c41a;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.failed-icon {
|
||||
color: #ff4d4f;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.waiting-icon {
|
||||
color: #faad14;
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
|
@ -130,13 +130,13 @@ defineProps<{
|
|||
}
|
||||
|
||||
.node-icon {
|
||||
color: #1890ff;
|
||||
color: var(--color-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
|
@ -149,15 +149,15 @@ defineProps<{
|
|||
.node-detail {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
color: #999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #666;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
import './styles'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,96 +2,139 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
// Agent-First 四象限布局 (新)
|
||||
{
|
||||
path: '/agent',
|
||||
name: 'agent',
|
||||
component: () => import('@/components/layout/AgentLayout.vue'),
|
||||
meta: { title: 'AgentKit' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/agent/chat',
|
||||
},
|
||||
{
|
||||
path: 'chat',
|
||||
name: 'agent-chat',
|
||||
meta: { title: '对话', quadrant: 'tl', tab: 'chat' },
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'code',
|
||||
name: 'agent-code',
|
||||
meta: { title: '代码', quadrant: 'tr', tab: 'code' },
|
||||
component: () => import('@/views/WorkflowView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'terminal',
|
||||
name: 'agent-terminal',
|
||||
meta: { title: '终端', quadrant: 'bl', tab: 'terminal' },
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'monitor',
|
||||
name: 'agent-monitor',
|
||||
meta: { title: '监控', quadrant: 'br', tab: 'monitor' },
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Default redirect to agent layout
|
||||
{
|
||||
path: '/',
|
||||
name: 'chat',
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
meta: { title: '智能对话' },
|
||||
redirect: '/agent',
|
||||
},
|
||||
|
||||
// Legacy route redirects → agent quadrant routes
|
||||
{
|
||||
path: '/workflow',
|
||||
name: 'workflow',
|
||||
component: () => import('@/views/WorkflowView.vue'),
|
||||
meta: { title: '工作流' },
|
||||
redirect: '/agent/code?tab=workflow',
|
||||
},
|
||||
{
|
||||
path: '/knowledge',
|
||||
name: 'knowledge',
|
||||
component: () => import('@/views/KnowledgeBaseView.vue'),
|
||||
meta: { title: '知识库' },
|
||||
redirect: '/agent/code?tab=knowledge',
|
||||
},
|
||||
{
|
||||
path: '/skills',
|
||||
name: 'skills',
|
||||
component: () => import('@/views/SkillsView.vue'),
|
||||
meta: { title: '技能' },
|
||||
redirect: '/agent/monitor?tab=skills',
|
||||
},
|
||||
{
|
||||
path: '/evolution',
|
||||
redirect: '/agent/monitor?tab=monitor',
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
redirect: '/agent/monitor?tab=settings',
|
||||
},
|
||||
{
|
||||
path: '/terminal',
|
||||
name: 'terminal',
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
meta: { title: '终端' },
|
||||
redirect: '/agent/terminal',
|
||||
},
|
||||
|
||||
// Computer Use (保留独立路由,显示"即将推出")
|
||||
{
|
||||
path: '/computer-use',
|
||||
name: 'computer-use',
|
||||
component: () => import('@/views/ComputerUseView.vue'),
|
||||
meta: { title: 'Computer Use' },
|
||||
},
|
||||
|
||||
// Legacy layout (fallback)
|
||||
{
|
||||
path: '/evolution',
|
||||
name: 'evolution',
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
meta: { title: '自进化' },
|
||||
path: '/legacy',
|
||||
name: 'legacy',
|
||||
component: () => import('@/components/layout/AppLayout.vue'),
|
||||
meta: { title: 'Fischer AgentKit (Legacy)' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/evolution/overview',
|
||||
redirect: '/legacy/chat',
|
||||
},
|
||||
{
|
||||
path: 'overview',
|
||||
name: 'evolution-overview',
|
||||
component: () => import('@/components/evolution/DashboardOverview.vue'),
|
||||
meta: { title: '概览 - 自进化' },
|
||||
path: 'chat',
|
||||
name: 'legacy-chat',
|
||||
component: () => import('@/views/ChatView.vue'),
|
||||
meta: { title: '智能对话' },
|
||||
},
|
||||
{
|
||||
path: 'experiences',
|
||||
name: 'evolution-experiences',
|
||||
component: () => import('@/components/evolution/ExperiencePanel.vue'),
|
||||
meta: { title: '经验记录 - 自进化' },
|
||||
path: 'workflow',
|
||||
name: 'legacy-workflow',
|
||||
component: () => import('@/views/WorkflowView.vue'),
|
||||
meta: { title: '工作流' },
|
||||
},
|
||||
{
|
||||
path: 'metrics',
|
||||
name: 'evolution-metrics',
|
||||
component: () => import('@/components/evolution/MetricsPanel.vue'),
|
||||
meta: { title: '指标趋势 - 自进化' },
|
||||
path: 'knowledge',
|
||||
name: 'legacy-knowledge',
|
||||
component: () => import('@/views/KnowledgeBaseView.vue'),
|
||||
meta: { title: '知识库' },
|
||||
},
|
||||
{
|
||||
path: 'pitfalls',
|
||||
name: 'evolution-pitfalls',
|
||||
component: () => import('@/components/evolution/PitfallRoutePanel.vue'),
|
||||
meta: { title: '避坑预警 - 自进化' },
|
||||
path: 'skills',
|
||||
name: 'legacy-skills',
|
||||
component: () => import('@/views/SkillsView.vue'),
|
||||
meta: { title: '技能' },
|
||||
},
|
||||
{
|
||||
path: 'optimizations',
|
||||
name: 'evolution-optimizations',
|
||||
component: () => import('@/components/evolution/OptimizationPanel.vue'),
|
||||
meta: { title: '路径优化 - 自进化' },
|
||||
path: 'terminal',
|
||||
name: 'legacy-terminal',
|
||||
component: () => import('@/views/TerminalView.vue'),
|
||||
meta: { title: '终端' },
|
||||
},
|
||||
{
|
||||
path: 'usage',
|
||||
name: 'evolution-usage',
|
||||
component: () => import('@/components/evolution/UsagePanel.vue'),
|
||||
meta: { title: '用量统计 - 自进化' },
|
||||
path: 'evolution',
|
||||
name: 'legacy-evolution',
|
||||
component: () => import('@/views/EvolutionView.vue'),
|
||||
meta: { title: '自进化' },
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: 'legacy-settings',
|
||||
component: () => import('@/views/SettingsView.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('@/views/SettingsView.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Vue Flow state
|
||||
const flowNodes = ref<Node<WorkflowNodeData>[]>([])
|
||||
const flowEdges = ref<Edge[]>([])
|
||||
// Vue Flow state — use any[] to avoid @vue-flow/core deep type recursion with Vue 3.5+
|
||||
const flowNodes = ref<any[]>([])
|
||||
const flowEdges = ref<any[]>([])
|
||||
const selectedNodeId = ref<string | null>(null)
|
||||
|
||||
// Undo/Redo state
|
||||
|
|
@ -51,9 +51,9 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||
const executionHistoryTotal = ref(0)
|
||||
|
||||
// --- Getters ---
|
||||
const selectedNode = computed<Node<WorkflowNodeData> | null>(() => {
|
||||
const selectedNode = computed<any | null>(() => {
|
||||
if (!selectedNodeId.value) return null
|
||||
return flowNodes.value.find((n) => n.id === selectedNodeId.value) || null
|
||||
return flowNodes.value.find((n: any) => n.id === selectedNodeId.value) || null
|
||||
})
|
||||
|
||||
const selectedNodeData = computed<WorkflowNodeData | null>(() => {
|
||||
|
|
@ -65,12 +65,12 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||
|
||||
// --- Internal mutation methods (no command tracking) ---
|
||||
|
||||
function _addNodeDirect(node: Node<WorkflowNodeData>): void {
|
||||
flowNodes.value = [...flowNodes.value, node]
|
||||
function _addNodeDirect(node: any): void {
|
||||
flowNodes.value.push(node)
|
||||
}
|
||||
|
||||
function _removeNodeDirect(nodeId: string): { node: Node<WorkflowNodeData>; edges: Edge[] } | null {
|
||||
const node = flowNodes.value.find((n) => n.id === nodeId)
|
||||
function _removeNodeDirect(nodeId: string): { node: any; edges: any[] } | null {
|
||||
const node = flowNodes.value.find((n: any) => n.id === nodeId)
|
||||
if (!node) return null
|
||||
const removedEdges = flowEdges.value.filter(
|
||||
(e) => e.source === nodeId || e.target === nodeId
|
||||
|
|
@ -97,11 +97,12 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
|||
}
|
||||
|
||||
function _updateNodeDataDirect(nodeId: string, data: Partial<WorkflowNodeData>): void {
|
||||
const index = flowNodes.value.findIndex((n) => n.id === nodeId)
|
||||
const index = flowNodes.value.findIndex((n: any) => n.id === nodeId)
|
||||
if (index !== -1) {
|
||||
const existing = flowNodes.value[index]
|
||||
flowNodes.value[index] = {
|
||||
...flowNodes.value[index],
|
||||
data: { ...flowNodes.value[index].data, ...data },
|
||||
...existing,
|
||||
data: { ...existing.data, ...data },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Fischer AgentKit Styles Entry
|
||||
*
|
||||
* Import this module to load all design tokens and theme configuration.
|
||||
*/
|
||||
|
||||
import './tokens.css'
|
||||
import './transitions.css'
|
||||
import './responsive.css'
|
||||
export { themeConfig } from './theme'
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Fischer AgentKit Responsive Breakpoints
|
||||
*
|
||||
* ≥1440px: Four quadrants fully visible
|
||||
* 1280-1440px: Bottom-right quadrant auto-collapsed
|
||||
* <1280px: Prompt to use larger screen
|
||||
*/
|
||||
|
||||
/* ── Full four-quadrant layout ── */
|
||||
@media (min-width: 1440px) {
|
||||
.agent-layout__body {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Compact: bottom-right quadrant collapsed ── */
|
||||
@media (min-width: 1280px) and (max-width: 1439px) {
|
||||
.agent-layout__body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Auto-collapse bottom-right quadrant at medium widths */
|
||||
.agent-layout__body .split-pane--horizontal > .split-pane__second .split-pane--vertical > .split-pane__second .quadrant-panel {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Too small: show prompt ── */
|
||||
@media (max-width: 1279px) {
|
||||
.agent-layout__body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.agent-layout__small-screen {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Default: hide small screen prompt ── */
|
||||
.agent-layout__small-screen {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
gap: var(--space-4);
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
|
||||
.agent-layout__small-screen h2 {
|
||||
font-size: var(--font-lg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.agent-layout__small-screen p {
|
||||
font-size: var(--font-base);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* ── Quadrant min-size for readability ── */
|
||||
.quadrant-panel {
|
||||
min-width: var(--quadrant-min-size);
|
||||
min-height: var(--quadrant-min-size);
|
||||
}
|
||||
|
||||
/* ── TopNav responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
.top-nav__center {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Chat sidebar responsive ── */
|
||||
@media (max-width: 1024px) {
|
||||
.chat-sidebar:not(.chat-sidebar--collapsed) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Print: hide interactive elements ── */
|
||||
@media print {
|
||||
.top-nav,
|
||||
.split-pane__handle,
|
||||
.quadrant-panel__collapse-btn {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* Ant Design Vue Theme Token Mapping
|
||||
*
|
||||
* Reads CSS custom properties at runtime from tokens.css to ensure
|
||||
* single source of truth. Falls back to hardcoded values if CSS
|
||||
* variables are not yet available (SSR / build time).
|
||||
*/
|
||||
|
||||
import type { ThemeConfig } from 'ant-design-vue/es/config-provider/context'
|
||||
|
||||
function readToken(varName: string, fallback: string): string {
|
||||
if (typeof document === 'undefined') return fallback
|
||||
const val = getComputedStyle(document.documentElement).getPropertyValue(varName).trim()
|
||||
return val || fallback
|
||||
}
|
||||
|
||||
export const themeConfig: ThemeConfig = {
|
||||
token: {
|
||||
// Brand — read from CSS variables
|
||||
colorPrimary: readToken('--color-primary', '#7c3aed'),
|
||||
colorInfo: readToken('--color-primary', '#7c3aed'),
|
||||
|
||||
// Semantic
|
||||
colorSuccess: readToken('--color-success', '#10b981'),
|
||||
colorWarning: readToken('--color-warning', '#f59e0b'),
|
||||
colorError: readToken('--color-error', '#ef4444'),
|
||||
|
||||
// Text
|
||||
colorText: readToken('--text-primary', '#171717'),
|
||||
colorTextSecondary: readToken('--text-secondary', '#525252'),
|
||||
colorTextTertiary: readToken('--text-tertiary', '#737373'),
|
||||
colorTextQuaternary: readToken('--text-placeholder', '#a3a3a3'),
|
||||
|
||||
// Background
|
||||
colorBgContainer: readToken('--bg-primary', '#ffffff'),
|
||||
colorBgLayout: readToken('--bg-secondary', '#fafafa'),
|
||||
colorBgElevated: readToken('--bg-primary', '#ffffff'),
|
||||
|
||||
// Border
|
||||
colorBorder: readToken('--border-color', '#e5e5e5'),
|
||||
colorBorderSecondary: readToken('--border-color-split', '#f0f0f0'),
|
||||
|
||||
// Font
|
||||
fontSize: 14,
|
||||
fontSizeSM: 12,
|
||||
fontSizeLG: 16,
|
||||
fontSizeXL: 20,
|
||||
|
||||
// Radius
|
||||
borderRadius: 8,
|
||||
borderRadiusSM: 6,
|
||||
borderRadiusLG: 12,
|
||||
|
||||
// Spacing
|
||||
marginXS: 4,
|
||||
marginSM: 8,
|
||||
margin: 16,
|
||||
marginMD: 16,
|
||||
marginLG: 24,
|
||||
marginXL: 32,
|
||||
|
||||
// Shadow
|
||||
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||
boxShadowSecondary: '0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05)',
|
||||
|
||||
// Control
|
||||
controlHeight: 32,
|
||||
controlHeightSM: 24,
|
||||
controlHeightLG: 40,
|
||||
},
|
||||
components: {
|
||||
Menu: {
|
||||
itemSelectedBg: readToken('--color-primary-light', '#ede9fe'),
|
||||
itemSelectedColor: readToken('--color-primary', '#7c3aed'),
|
||||
itemHoverBg: '#f5f3ff',
|
||||
itemHoverColor: readToken('--color-primary', '#7c3aed'),
|
||||
itemColor: readToken('--text-secondary', '#525252'),
|
||||
} as Record<string, unknown>,
|
||||
Tabs: {
|
||||
itemSelectedColor: readToken('--color-primary', '#7c3aed'),
|
||||
itemHoverColor: readToken('--color-primary-hover', '#6d28d9'),
|
||||
} as Record<string, unknown>,
|
||||
Select: {
|
||||
colorPrimary: readToken('--color-primary', '#7c3aed'),
|
||||
colorPrimaryHover: readToken('--color-primary-hover', '#6d28d9'),
|
||||
} as Record<string, unknown>,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Fischer AgentKit Design Tokens
|
||||
*
|
||||
* CSS Custom Properties as the single source of truth.
|
||||
* Ant Design Vue theme tokens are mapped from these values in theme.ts.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ── Brand Colors ── */
|
||||
--color-primary: #7c3aed;
|
||||
--color-primary-hover: #6d28d9;
|
||||
--color-primary-active: #5b21b6;
|
||||
--color-primary-light: #ede9fe;
|
||||
--color-primary-bg: #f5f3ff;
|
||||
|
||||
/* ── Gradient ── */
|
||||
--gradient-brand: linear-gradient(135deg, #7c3aed 0%, #1e1b4b 100%);
|
||||
|
||||
/* ── Semantic Colors ── */
|
||||
--color-success: #10b981;
|
||||
--color-success-light: #d1fae5;
|
||||
--color-warning: #f59e0b;
|
||||
--color-warning-light: #fef3c7;
|
||||
--color-error: #ef4444;
|
||||
--color-error-light: #fee2e2;
|
||||
--color-info: #3b82f6;
|
||||
--color-info-light: #dbeafe;
|
||||
|
||||
/* ── Neutral / Gray Scale ── */
|
||||
--color-gray-50: #fafafa;
|
||||
--color-gray-100: #f5f5f5;
|
||||
--color-gray-200: #e5e5e5;
|
||||
--color-gray-300: #d4d4d4;
|
||||
--color-gray-400: #a3a3a3;
|
||||
--color-gray-500: #737373;
|
||||
--color-gray-600: #525252;
|
||||
--color-gray-700: #404040;
|
||||
--color-gray-800: #262626;
|
||||
--color-gray-900: #171717;
|
||||
|
||||
/* ── Background ── */
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #fafafa;
|
||||
--bg-tertiary: #f5f5f5;
|
||||
--bg-elevated: #ffffff;
|
||||
--bg-code: #282c34;
|
||||
|
||||
/* ── Foreground / Text ── */
|
||||
--text-primary: #171717;
|
||||
--text-secondary: #525252;
|
||||
--text-tertiary: #737373;
|
||||
--text-placeholder: #a3a3a3;
|
||||
--text-inverse: #ffffff;
|
||||
--text-code: #abb2bf;
|
||||
|
||||
/* ── Border ── */
|
||||
--border-color: #e5e5e5;
|
||||
--border-color-hover: #d4d4d4;
|
||||
--border-color-active: var(--color-primary);
|
||||
--border-color-split: #f0f0f0;
|
||||
|
||||
/* ── Spacing ── */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
|
||||
/* ── Border Radius ── */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* ── Font Size ── */
|
||||
--font-xs: 12px;
|
||||
--font-sm: 13px;
|
||||
--font-base: 14px;
|
||||
--font-md: 16px;
|
||||
--font-lg: 20px;
|
||||
--font-xl: 24px;
|
||||
|
||||
/* ── Font Weight ── */
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* ── Line Height ── */
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.75;
|
||||
|
||||
/* ── Shadow ── */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.07), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.04);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* ── Transition ── */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
|
||||
/* ── Z-Index ── */
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-fixed: 1030;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
|
||||
/* ── Layout ── */
|
||||
--topnav-height: 48px;
|
||||
--sidebar-width: 280px;
|
||||
--quadrant-min-size: 320px;
|
||||
|
||||
/* ── Code Theme (One Dark Pro) ── */
|
||||
--code-bg: #282c34;
|
||||
--code-fg: #abb2bf;
|
||||
--code-keyword: #c678dd;
|
||||
--code-string: #98c379;
|
||||
--code-number: #d19a66;
|
||||
--code-comment: #5c6370;
|
||||
--code-function: #61afef;
|
||||
--code-variable: #e06c75;
|
||||
--code-type: #e5c07b;
|
||||
--code-added-bg: rgba(16, 185, 129, 0.15);
|
||||
--code-removed-bg: rgba(239, 68, 68, 0.15);
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* Fischer AgentKit Transition Animations
|
||||
*
|
||||
* Unified transition classes for Vue <Transition> components.
|
||||
* All durations reference Design Token variables for consistency.
|
||||
*/
|
||||
|
||||
/* ── Fade ── */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Slide Up ── */
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
transition: transform var(--transition-normal), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.slide-up-enter-from,
|
||||
.slide-up-leave-to {
|
||||
transform: translateY(8px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Slide Down ── */
|
||||
.slide-down-enter-active,
|
||||
.slide-down-leave-active {
|
||||
transition: transform var(--transition-normal), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.slide-down-enter-from,
|
||||
.slide-down-leave-to {
|
||||
transform: translateY(-8px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Slide Right ── */
|
||||
.slide-right-enter-active,
|
||||
.slide-right-leave-active {
|
||||
transition: transform var(--transition-normal), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.slide-right-enter-from,
|
||||
.slide-right-leave-to {
|
||||
transform: translateX(-8px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Collapse (height) ── */
|
||||
.collapse-enter-active,
|
||||
.collapse-leave-active {
|
||||
transition: max-height var(--transition-slow) ease, opacity var(--transition-fast);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.collapse-enter-from,
|
||||
.collapse-leave-to {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.collapse-enter-to,
|
||||
.collapse-leave-from {
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ── Scale ── */
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: transform var(--transition-fast), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Stagger list items ── */
|
||||
.stagger-list-enter-active {
|
||||
transition: transform var(--transition-normal), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.stagger-list-leave-active {
|
||||
transition: transform var(--transition-fast), opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.stagger-list-enter-from,
|
||||
.stagger-list-leave-to {
|
||||
transform: translateY(8px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.stagger-list-move {
|
||||
transition: transform var(--transition-normal);
|
||||
}
|
||||
|
||||
/* ── Skeleton pulse ── */
|
||||
@keyframes skeleton-pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.skeleton-loading {
|
||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bg-tertiary) 25%,
|
||||
var(--border-color) 50%,
|
||||
var(--bg-tertiary) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ── Pulse dot (running status) ── */
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
.pulse-dot {
|
||||
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ function handleSend(message: string): void {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.chat-view__empty {
|
||||
|
|
@ -132,7 +132,7 @@ function handleSend(message: string): void {
|
|||
.chat-view__messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px 0;
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
.chat-view__welcome {
|
||||
|
|
@ -141,45 +141,48 @@ function handleSend(message: string): void {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #999999;
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.chat-view__welcome-icon {
|
||||
font-size: 48px;
|
||||
color: #1677ff;
|
||||
margin-bottom: 16px;
|
||||
background: var(--gradient-brand);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.chat-view__welcome h2 {
|
||||
color: #333333;
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-primary);
|
||||
font-size: var(--font-xl);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.chat-view__welcome p {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
font-size: var(--font-base);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.chat-view__steps {
|
||||
padding: 8px 16px;
|
||||
margin: 0 16px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
padding: var(--space-2) var(--space-4);
|
||||
margin: 0 var(--space-4);
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.chat-view__step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: var(--space-1);
|
||||
padding: 2px 0;
|
||||
font-size: 13px;
|
||||
color: #666666;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.chat-view__step-icon {
|
||||
font-size: 10px;
|
||||
color: #1677ff;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,32 @@
|
|||
<template>
|
||||
<div class="evolution-container">
|
||||
<div class="evolution-sidebar">
|
||||
<div class="evolution-sidebar__header">
|
||||
<h2>自进化</h2>
|
||||
<a-spin v-if="store.isLoading" size="small" />
|
||||
</div>
|
||||
<a-menu mode="inline" :selected-keys="[currentRoute]" class="evolution-sidebar__menu">
|
||||
<a-menu-item key="overview" @click="navigate('overview')">
|
||||
<DashboardOutlined /> 概览
|
||||
</a-menu-item>
|
||||
<a-menu-item key="experiences" @click="navigate('experiences')">
|
||||
<HistoryOutlined /> 经验记录
|
||||
</a-menu-item>
|
||||
<a-menu-item key="metrics" @click="navigate('metrics')">
|
||||
<LineChartOutlined /> 指标趋势
|
||||
</a-menu-item>
|
||||
<a-menu-item key="pitfalls" @click="navigate('pitfalls')">
|
||||
<WarningOutlined /> 避坑预警
|
||||
</a-menu-item>
|
||||
<a-menu-item key="optimizations" @click="navigate('optimizations')">
|
||||
<RocketOutlined /> 路径优化
|
||||
</a-menu-item>
|
||||
<a-menu-item key="usage" @click="navigate('usage')">
|
||||
<CloudServerOutlined /> 用量统计
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="evolution-content">
|
||||
<router-view />
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeTab" class="evolution-tabs">
|
||||
<a-tab-pane key="overview" tab="概览+指标">
|
||||
<DashboardOverview />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="experiences" tab="经验+坑点">
|
||||
<div class="evolution-panels">
|
||||
<ExperienceTimeline :experiences="store.experiences" />
|
||||
<PitfallPanel :warnings="store.pitfalls" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="usage" tab="用量">
|
||||
<UsagePanel />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { Spin as ASpin, Menu as AMenu, MenuItem as AMenuItem } from 'ant-design-vue'
|
||||
import {
|
||||
DashboardOutlined,
|
||||
HistoryOutlined,
|
||||
LineChartOutlined,
|
||||
WarningOutlined,
|
||||
RocketOutlined,
|
||||
CloudServerOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useEvolutionStore } from '@/stores/evolution'
|
||||
import DashboardOverview from '@/components/evolution/DashboardOverview.vue'
|
||||
import ExperienceTimeline from '@/components/evolution/ExperienceTimeline.vue'
|
||||
import PitfallPanel from '@/components/evolution/PitfallPanel.vue'
|
||||
import UsagePanel from '@/components/evolution/UsagePanel.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const store = useEvolutionStore()
|
||||
|
||||
const currentRoute = computed(() => {
|
||||
const path = route.path
|
||||
if (path === '/evolution' || path === '/evolution/') return 'overview'
|
||||
const segment = path.split('/').pop()
|
||||
return segment || 'overview'
|
||||
})
|
||||
|
||||
function navigate(key: string) {
|
||||
router.push(`/evolution/${key}`)
|
||||
}
|
||||
const activeTab = ref('overview')
|
||||
|
||||
onMounted(() => {
|
||||
store.loadExperiences()
|
||||
|
|
@ -76,40 +43,18 @@ onUnmounted(() => {
|
|||
<style scoped>
|
||||
.evolution-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.evolution-sidebar {
|
||||
width: 180px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
.evolution-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.evolution-panels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.evolution-sidebar__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 16px 8px;
|
||||
}
|
||||
|
||||
.evolution-sidebar__header h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.evolution-sidebar__menu {
|
||||
flex: 1;
|
||||
border-inline-end: none !important;
|
||||
}
|
||||
|
||||
.evolution-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ onMounted(() => {
|
|||
<style scoped>
|
||||
.kb-view {
|
||||
height: 100%;
|
||||
padding: 16px 24px;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,144 +1,144 @@
|
|||
<template>
|
||||
<div class="settings-view">
|
||||
<a-form layout="vertical" class="settings-form">
|
||||
<!-- LLM 配置 -->
|
||||
<a-divider orientation="left">LLM 配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Provider">
|
||||
<a-select v-model:value="settingsStore.llm.provider" placeholder="选择 LLM 提供商">
|
||||
<a-select-option value="anthropic">Anthropic</a-select-option>
|
||||
<a-select-option value="openai">OpenAI</a-select-option>
|
||||
<a-select-option value="gemini">Gemini</a-select-option>
|
||||
<a-select-option value="custom">自定义</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="模型">
|
||||
<a-input v-model:value="settingsStore.llm.model" placeholder="例如: claude-sonnet-4-20250514" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="API Key">
|
||||
<a-input-password v-model:value="settingsStore.llm.api_key" placeholder="输入 API Key" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Base URL">
|
||||
<a-input v-model:value="settingsStore.llm.base_url" placeholder="自定义 API 地址(可选)" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-tabs v-model:activeKey="activeTab" class="settings-tabs">
|
||||
<a-tab-pane key="llm" tab="LLM 配置">
|
||||
<a-form layout="vertical" class="settings-form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Provider">
|
||||
<a-select v-model:value="settingsStore.llm.provider" placeholder="选择 LLM 提供商">
|
||||
<a-select-option value="anthropic">Anthropic</a-select-option>
|
||||
<a-select-option value="openai">OpenAI</a-select-option>
|
||||
<a-select-option value="gemini">Gemini</a-select-option>
|
||||
<a-select-option value="custom">自定义</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="模型">
|
||||
<a-input v-model:value="settingsStore.llm.model" placeholder="例如: claude-sonnet-4-20250514" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="API Key">
|
||||
<a-input-password v-model:value="settingsStore.llm.api_key" placeholder="输入 API Key" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Base URL">
|
||||
<a-input v-model:value="settingsStore.llm.base_url" placeholder="自定义 API 地址(可选)" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存 LLM 配置</a-button>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 技能配置 -->
|
||||
<a-divider orientation="left">技能配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="默认技能">
|
||||
<a-input v-model:value="settingsStore.skillSettings.default_skill" placeholder="留空则使用自动路由" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="自动路由">
|
||||
<a-switch v-model:checked="settingsStore.skillSettings.auto_routing" />
|
||||
<span style="margin-left: 8px; color: #999">启用后自动将消息路由到最匹配的技能</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-tab-pane key="skills" tab="技能管理">
|
||||
<a-form layout="vertical" class="settings-form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="默认技能">
|
||||
<a-input v-model:value="settingsStore.skillSettings.default_skill" placeholder="留空则使用自动路由" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="自动路由">
|
||||
<a-switch v-model:checked="settingsStore.skillSettings.auto_routing" />
|
||||
<span class="settings-form__hint">启用后自动将消息路由到最匹配的技能</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存技能配置</a-button>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 知识库配置 -->
|
||||
<a-divider orientation="left">知识库配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="默认信息源">
|
||||
<a-select
|
||||
v-model:value="settingsStore.kbSettings.default_sources"
|
||||
mode="multiple"
|
||||
placeholder="选择默认信息源"
|
||||
>
|
||||
<a-select-option value="local">本地文档</a-select-option>
|
||||
<a-select-option value="feishu">飞书</a-select-option>
|
||||
<a-select-option value="confluence">Confluence</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="检索数量 (Top K)">
|
||||
<a-input-number v-model:value="settingsStore.kbSettings.top_k" :min="1" :max="50" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="检索模式">
|
||||
<a-select v-model:value="settingsStore.kbSettings.retrieval_mode">
|
||||
<a-select-option value="standard">标准</a-select-option>
|
||||
<a-select-option value="rerank">重排序</a-select-option>
|
||||
<a-select-option value="compression">压缩</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-tab-pane key="knowledge" tab="知识库设置">
|
||||
<a-form layout="vertical" class="settings-form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="默认信息源">
|
||||
<a-select
|
||||
v-model:value="settingsStore.kbSettings.default_sources"
|
||||
mode="multiple"
|
||||
placeholder="选择默认信息源"
|
||||
>
|
||||
<a-select-option value="local">本地文档</a-select-option>
|
||||
<a-select-option value="feishu">飞书</a-select-option>
|
||||
<a-select-option value="confluence">Confluence</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="检索数量 (Top K)">
|
||||
<a-input-number v-model:value="settingsStore.kbSettings.top_k" :min="1" :max="50" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="检索模式">
|
||||
<a-select v-model:value="settingsStore.kbSettings.retrieval_mode">
|
||||
<a-select-option value="standard">标准</a-select-option>
|
||||
<a-select-option value="rerank">重排序</a-select-option>
|
||||
<a-select-option value="compression">压缩</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存知识库配置</a-button>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 系统配置 -->
|
||||
<a-divider orientation="left">系统配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="速率限制 (次/分钟)">
|
||||
<a-input-number v-model:value="settingsStore.system.rate_limit" :min="1" :max="1000" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日志级别">
|
||||
<a-select v-model:value="settingsStore.system.logging_level">
|
||||
<a-select-option value="DEBUG">DEBUG</a-select-option>
|
||||
<a-select-option value="INFO">INFO</a-select-option>
|
||||
<a-select-option value="WARNING">WARNING</a-select-option>
|
||||
<a-select-option value="ERROR">ERROR</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="CORS 来源">
|
||||
<a-select
|
||||
v-model:value="settingsStore.system.cors_origins"
|
||||
mode="tags"
|
||||
placeholder="输入 CORS 来源"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-tab-pane key="system" tab="系统设置">
|
||||
<a-form layout="vertical" class="settings-form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="速率限制 (次/分钟)">
|
||||
<a-input-number v-model:value="settingsStore.system.rate_limit" :min="1" :max="1000" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日志级别">
|
||||
<a-select v-model:value="settingsStore.system.logging_level">
|
||||
<a-select-option value="DEBUG">DEBUG</a-select-option>
|
||||
<a-select-option value="INFO">INFO</a-select-option>
|
||||
<a-select-option value="WARNING">WARNING</a-select-option>
|
||||
<a-select-option value="ERROR">ERROR</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="CORS 来源">
|
||||
<a-select
|
||||
v-model:value="settingsStore.system.cors_origins"
|
||||
mode="tags"
|
||||
placeholder="输入 CORS 来源"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存系统配置</a-button>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<div class="settings-form__actions">
|
||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">
|
||||
保存设置
|
||||
</a-button>
|
||||
<a-alert
|
||||
v-if="settingsStore.saveSuccess"
|
||||
message="设置已保存"
|
||||
type="success"
|
||||
show-icon
|
||||
style="margin-left: 12px"
|
||||
/>
|
||||
<a-alert
|
||||
v-if="settingsStore.error"
|
||||
:message="settingsStore.error"
|
||||
type="error"
|
||||
show-icon
|
||||
style="margin-left: 12px"
|
||||
/>
|
||||
</div>
|
||||
</a-form>
|
||||
<div v-if="settingsStore.saveSuccess" class="settings-view__alert">
|
||||
<a-alert message="设置已保存" type="success" show-icon />
|
||||
</div>
|
||||
<div v-if="settingsStore.error" class="settings-view__alert">
|
||||
<a-alert :message="settingsStore.error" type="error" show-icon />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const activeTab = ref('llm')
|
||||
|
||||
onMounted(() => {
|
||||
settingsStore.fetchSettings()
|
||||
|
|
@ -157,18 +157,27 @@ async function handleSave(): Promise<void> {
|
|||
<style scoped>
|
||||
.settings-view {
|
||||
height: 100%;
|
||||
padding: 16px 24px;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.settings-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
max-width: 900px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.settings-form__actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.settings-form__hint {
|
||||
margin-left: var(--space-2);
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.settings-view__alert {
|
||||
margin-top: var(--space-3);
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,16 @@
|
|||
{{ cap.display_name }} ({{ cap.skill_count }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button @click="handleRefresh" :loading="skillsStore.isLoading">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="showInstallModal = true">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
安装技能
|
||||
</a-button>
|
||||
<a-button @click="handleRefresh" :loading="skillsStore.isLoading">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="skillsStore.isLoading">
|
||||
|
|
@ -35,13 +41,35 @@
|
|||
:skill="skillsStore.selectedSkill"
|
||||
@close="handleDetailClose"
|
||||
/>
|
||||
|
||||
<a-modal
|
||||
v-model:open="showInstallModal"
|
||||
title="安装技能"
|
||||
:confirm-loading="installing"
|
||||
@ok="handleInstall"
|
||||
ok-text="安装"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="技能名称" required>
|
||||
<a-input v-model:value="installName" placeholder="例如: find-skills" />
|
||||
</a-form-item>
|
||||
<a-form-item label="来源 URL(可选)">
|
||||
<a-input v-model:value="installSource" placeholder="https://... 或留空自动搜索" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-alert v-if="installError" :message="installError" type="error" show-icon style="margin-top: 8px" />
|
||||
<a-alert v-if="installSuccess" :message="`技能 ${installSuccess} 安装成功!`" type="success" show-icon style="margin-top: 8px" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import { ReloadOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useSkillsStore } from '@/stores/skills'
|
||||
import { skillsApi } from '@/api/skills'
|
||||
import SkillCard from '@/components/skills/SkillCard.vue'
|
||||
import SkillDetail from '@/components/skills/SkillDetail.vue'
|
||||
|
||||
|
|
@ -49,6 +77,14 @@ const skillsStore = useSkillsStore()
|
|||
const selectedCapability = ref<string | undefined>(undefined)
|
||||
const showDetail = ref(false)
|
||||
|
||||
// Install state
|
||||
const showInstallModal = ref(false)
|
||||
const installName = ref('')
|
||||
const installSource = ref('')
|
||||
const installing = ref(false)
|
||||
const installError = ref('')
|
||||
const installSuccess = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
skillsStore.fetchSkills(),
|
||||
|
|
@ -77,26 +113,50 @@ function handleDetailClose(): void {
|
|||
showDetail.value = false
|
||||
skillsStore.clearSelectedSkill()
|
||||
}
|
||||
|
||||
async function handleInstall(): Promise<void> {
|
||||
if (!installName.value.trim()) {
|
||||
installError.value = '请输入技能名称'
|
||||
return
|
||||
}
|
||||
installing.value = true
|
||||
installError.value = ''
|
||||
installSuccess.value = ''
|
||||
try {
|
||||
const result = await skillsApi.installSkill(
|
||||
installName.value.trim(),
|
||||
installSource.value.trim() || undefined
|
||||
)
|
||||
installSuccess.value = result.name
|
||||
message.success(`技能 ${result.name} 安装成功!`)
|
||||
// Refresh skill list
|
||||
await handleRefresh()
|
||||
} catch (e: any) {
|
||||
installError.value = e?.message || '安装失败'
|
||||
} finally {
|
||||
installing.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.skills-view {
|
||||
height: 100%;
|
||||
padding: 16px 24px;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.skills-view__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.skills-view__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,59 +2,56 @@
|
|||
<div class="terminal-view">
|
||||
<div class="terminal-view__main">
|
||||
<TerminalEmulator />
|
||||
<!-- Confirmation dialog -->
|
||||
<div v-if="terminalStore.pendingConfirmation" class="terminal-view__confirmation">
|
||||
<div class="terminal-view__confirmation-content">
|
||||
<div class="terminal-view__confirmation-header">
|
||||
<span class="terminal-view__confirmation-icon">⚠</span>
|
||||
<span>命令确认</span>
|
||||
</div>
|
||||
<div class="terminal-view__confirmation-command">
|
||||
{{ terminalStore.pendingConfirmation.command }}
|
||||
</div>
|
||||
<div class="terminal-view__confirmation-reason">
|
||||
{{ terminalStore.pendingConfirmation.reason }}
|
||||
</div>
|
||||
<div class="terminal-view__confirmation-actions">
|
||||
<label class="terminal-view__whitelist-check">
|
||||
<input
|
||||
v-model="addToWhitelist"
|
||||
type="checkbox"
|
||||
/>
|
||||
添加到会话白名单
|
||||
</label>
|
||||
<div class="terminal-view__confirmation-buttons">
|
||||
<button
|
||||
class="terminal-view__btn terminal-view__btn--reject"
|
||||
@click="rejectConfirmation"
|
||||
>
|
||||
拒绝
|
||||
</button>
|
||||
<button
|
||||
class="terminal-view__btn terminal-view__btn--approve"
|
||||
@click="approveConfirmation"
|
||||
>
|
||||
确认执行
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['terminal-view__sidebar', { 'terminal-view__sidebar--collapsed': sidebarCollapsed }]">
|
||||
<button class="terminal-view__sidebar-toggle" @click="sidebarCollapsed = !sidebarCollapsed">
|
||||
<HistoryOutlined />
|
||||
</button>
|
||||
<div v-if="!sidebarCollapsed" class="terminal-view__sidebar-content">
|
||||
<CommandHistory @select="handleHistorySelect" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="terminal-view__sidebar">
|
||||
<CommandHistory @select="handleHistorySelect" />
|
||||
</div>
|
||||
<!-- Confirmation dialog using Ant Design Modal -->
|
||||
<a-modal
|
||||
v-model:open="showConfirmation"
|
||||
title="命令确认"
|
||||
:ok-text="'确认执行'"
|
||||
:cancel-text="'拒绝'"
|
||||
@ok="approveConfirmation"
|
||||
@cancel="rejectConfirmation"
|
||||
:ok-button-props="{ danger: true }"
|
||||
>
|
||||
<div class="terminal-view__modal-body">
|
||||
<div class="terminal-view__modal-command">
|
||||
{{ terminalStore.pendingConfirmation?.command }}
|
||||
</div>
|
||||
<div class="terminal-view__modal-reason">
|
||||
{{ terminalStore.pendingConfirmation?.reason }}
|
||||
</div>
|
||||
<a-checkbox v-model:checked="addToWhitelist">
|
||||
添加到会话白名单
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { Modal as AModal, Checkbox as ACheckbox } from 'ant-design-vue'
|
||||
import { HistoryOutlined } from '@ant-design/icons-vue'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
import TerminalEmulator from '@/components/terminal/TerminalEmulator.vue'
|
||||
import CommandHistory from '@/components/terminal/CommandHistory.vue'
|
||||
|
||||
const terminalStore = useTerminalStore()
|
||||
const addToWhitelist = ref(false)
|
||||
const sidebarCollapsed = ref(true)
|
||||
|
||||
const showConfirmation = computed({
|
||||
get: () => !!terminalStore.pendingConfirmation,
|
||||
set: () => { /* modal handles close via cancel */ },
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
terminalStore.disconnectWebSocket()
|
||||
|
|
@ -91,110 +88,65 @@ function rejectConfirmation(): void {
|
|||
.terminal-view__main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
padding: var(--space-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal-view__sidebar {
|
||||
width: 280px;
|
||||
display: flex;
|
||||
border-left: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: width var(--transition-normal);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation {
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
z-index: 10;
|
||||
.terminal-view__sidebar--collapsed {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-content {
|
||||
background: #2d2d2d;
|
||||
border: 1px solid #ff9800;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
.terminal-view__sidebar:not(.terminal-view__sidebar--collapsed) {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-header {
|
||||
.terminal-view__sidebar-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #ff9800;
|
||||
margin-bottom: 8px;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-icon {
|
||||
font-size: 16px;
|
||||
.terminal-view__sidebar-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-command {
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
color: #d4d4d4;
|
||||
background: #1e1e1e;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
.terminal-view__sidebar-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.terminal-view__modal-command {
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, Consolas, monospace;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-3);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-reason {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.terminal-view__whitelist-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.terminal-view__whitelist-check input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.terminal-view__confirmation-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.terminal-view__btn {
|
||||
padding: 6px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.terminal-view__btn--reject {
|
||||
background: #555;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.terminal-view__btn--reject:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.terminal-view__btn--approve {
|
||||
background: #ff9800;
|
||||
color: #1e1e1e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.terminal-view__btn--approve:hover {
|
||||
background: #ffa726;
|
||||
.terminal-view__modal-reason {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -315,55 +315,54 @@ function handleBack() {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.list-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-size: var(--font-md);
|
||||
}
|
||||
|
||||
.list-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.workflow-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-lg);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
gap: 12px;
|
||||
transition: all var(--transition-fast);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.workflow-item:hover {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.12);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: var(--font-base);
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--font-xs);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.editor-main {
|
||||
|
|
@ -382,8 +381,8 @@ function handleBack() {
|
|||
}
|
||||
|
||||
.execution-history {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
@ -391,20 +390,20 @@ function handleBack() {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
padding: var(--space-2) var(--space-4);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-secondary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.history-header:hover {
|
||||
background: #f0f0f0;
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.history-body {
|
||||
padding: 0 16px 8px;
|
||||
padding: 0 var(--space-4) var(--space-2);
|
||||
}
|
||||
|
||||
.back-bar {
|
||||
|
|
@ -414,16 +413,16 @@ function handleBack() {
|
|||
right: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.workflow-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
font-size: var(--font-base);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.side-nav[data-v-ffaa5764]{height:100vh;overflow-y:auto;display:flex;flex-direction:column}.side-nav[data-v-ffaa5764] .ant-layout-sider-children{display:flex;flex-direction:column;height:100%}.side-nav__logo[data-v-ffaa5764]{height:64px;display:flex;align-items:center;justify-content:center;border-bottom:1px solid rgba(255,255,255,.1)}.side-nav__title[data-v-ffaa5764]{color:#fff;font-size:18px;font-weight:600;margin:0;white-space:nowrap}.side-nav__footer[data-v-ffaa5764]{margin-top:auto;padding:16px 24px;border-top:1px solid rgba(255,255,255,.1)}.side-nav__footer[data-v-ffaa5764] .ant-badge-status-text{color:#ffffffa6;font-size:12px}.app-layout[data-v-1f8febf9]{height:100vh;width:100vw}.app-layout__main[data-v-1f8febf9]{flex:1;overflow:hidden;background:var(--bg-tertiary)}
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c as i,I as u}from"./index-Cdm90D30.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"};function a(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(c){return Object.getOwnPropertyDescriptor(e,c).enumerable}))),n.forEach(function(c){p(r,c,e[c])})}return r}function p(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=a({},t,e.attrs);return i(u,a({},n,{icon:l}),null)};o.displayName="AppstoreOutlined";o.inheritAttrs=!1;export{o as A};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{d as E,r as f,i as V,z as o,R,c as h,J as W,P as z}from"./index-Cdm90D30.js";import{i as F}from"./_plugin-vue_export-helper-BpJgGuqH.js";var I=function(t,l){var c={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&l.indexOf(n)<0&&(c[n]=t[n]);if(t!=null&&typeof Object.getOwnPropertySymbols=="function")for(var a=0,n=Object.getOwnPropertySymbols(t);a<n.length;a++)l.indexOf(n[a])<0&&Object.prototype.propertyIsEnumerable.call(t,n[a])&&(c[n[a]]=t[n[a]]);return c};const J={prefixCls:String,name:String,id:String,type:String,defaultChecked:{type:[Boolean,Number],default:void 0},checked:{type:[Boolean,Number],default:void 0},disabled:Boolean,tabindex:{type:[Number,String]},readonly:Boolean,autofocus:Boolean,value:z.any,required:Boolean},G=E({compatConfig:{MODE:3},name:"Checkbox",inheritAttrs:!1,props:F(J,{prefixCls:"rc-checkbox",type:"checkbox",defaultChecked:!1}),emits:["click","change"],setup(t,l){let{attrs:c,emit:n,expose:a}=l;const d=f(t.checked===void 0?t.defaultChecked:t.checked),s=f();V(()=>t.checked,()=>{d.value=t.checked}),a({focus(){var e;(e=s.value)===null||e===void 0||e.focus()},blur(){var e;(e=s.value)===null||e===void 0||e.blur()}});const i=f(),y=e=>{if(t.disabled)return;t.checked===void 0&&(d.value=e.target.checked),e.shiftKey=i.value;const u={target:o(o({},t),{checked:e.target.checked}),stopPropagation(){e.stopPropagation()},preventDefault(){e.preventDefault()},nativeEvent:e};t.checked!==void 0&&(s.value.checked=!!t.checked),n("change",u),i.value=!1},k=e=>{n("click",e),i.value=e.shiftKey};return()=>{const{prefixCls:e,name:u,id:g,type:m,disabled:b,readonly:x,tabindex:C,autofocus:O,value:P,required:S}=t,_=I(t,["prefixCls","name","id","type","disabled","readonly","tabindex","autofocus","value","required"]),{class:j,onFocus:B,onBlur:K,onKeydown:N,onKeypress:w,onKeyup:D}=c,v=o(o({},_),c),$=Object.keys(v).reduce((p,r)=>((r.startsWith("data-")||r.startsWith("aria-")||r==="role")&&(p[r]=v[r]),p),{}),q=R(e,j,{[`${e}-checked`]:d.value,[`${e}-disabled`]:b}),A=o(o({name:u,id:g,type:m,readonly:x,disabled:b,tabindex:C,class:`${e}-input`,checked:!!d.value,autofocus:O,value:P},$),{onChange:y,onClick:k,onFocus:B,onBlur:K,onKeydown:N,onKeypress:w,onKeyup:D,required:S});return h("span",{class:q},[h("input",W({ref:s},A),null),h("span",{class:`${e}-inner`},null)])}}});export{G as V};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.placeholder-view[data-v-baa4efd2]{display:flex;align-items:center;justify-content:center;height:100%}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{c as i,I as u}from"./index-Cdm90D30.js";var l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 140H96c-17.7 0-32 14.3-32 32v496c0 17.7 14.3 32 32 32h380v112H304c-8.8 0-16 7.2-16 16v48c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-48c0-8.8-7.2-16-16-16H548V700h380c17.7 0 32-14.3 32-32V172c0-17.7-14.3-32-32-32zm-40 488H136V212h752v416z"}}]},name:"desktop",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=c({},t,e.attrs);return i(u,c({},n,{icon:l}),null)};o.displayName="DesktopOutlined";o.inheritAttrs=!1;export{o as D};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{u}from"./responsiveObserve-Dze5mRNR.js";import{E as s,a2 as i,K as c,L as f,c as p,I as v}from"./index-Cdm90D30.js";const h=e=>({color:e.colorLink,textDecoration:"none",outline:"none",cursor:"pointer",transition:`color ${e.motionDurationSlow}`,"&:focus, &:hover":{color:e.colorLinkHover},"&:active":{color:e.colorLinkActive}});function g(){const e=c({});let t=null;const r=u();return s(()=>{t=r.value.subscribe(n=>{e.value=n})}),i(()=>{r.value.unsubscribe(t)}),e}function H(e){const t=c();return f(()=>{t.value=e()},{flush:"sync"}),t}var d={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 00-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z"}}]},name:"folder-open",theme:"outlined"};function a(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(r);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(r).filter(function(o){return Object.getOwnPropertyDescriptor(r,o).enumerable}))),n.forEach(function(o){O(e,o,r[o])})}return e}function O(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var l=function(t,r){var n=a({},t,r.attrs);return p(v,a({},n,{icon:d}),null)};l.displayName="FolderOpenOutlined";l.inheritAttrs=!1;export{l as F,H as e,h as o,g as u};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{z as I,T as p,B as i,d as c,ab as C,i as f,f as F,r as x,C as a}from"./index-Cdm90D30.js";import{e as y}from"./index-DlUg5G7X.js";function w(n,o){const e=I({},n);for(let t=0;t<o.length;t+=1){const l=o[t];delete e[l]}return e}const r=Symbol("ContextProps"),s=Symbol("InternalContextProps"),S=function(n){let o=arguments.length>1&&arguments[1]!==void 0?arguments[1]:F(()=>!0);const e=x(new Map),t=(m,v)=>{e.value.set(m,v),e.value=new Map(e.value)},l=m=>{e.value.delete(m),e.value=new Map(e.value)};f([o,e],()=>{}),a(r,n),a(s,{addFormItemField:t,removeFormItemField:l})},d={id:F(()=>{}),onFieldBlur:()=>{},onFieldChange:()=>{},clearValidate:()=>{}},u={addFormItemField:()=>{},removeFormItemField:()=>{}},_=()=>{const n=i(s,u),o=Symbol("FormItemFieldKey"),e=C();return n.addFormItemField(o,e.type),p(()=>{n.removeFormItemField(o)}),a(s,u),a(r,d),i(r,d)},K=c({compatConfig:{MODE:3},name:"AFormItemRest",setup(n,o){let{slots:e}=o;return a(s,u),a(r,d),()=>{var t;return(t=e.default)===null||t===void 0?void 0:t.call(e)}}}),g=y({}),M=c({name:"NoFormStatus",setup(n,o){let{slots:e}=o;return g.useProvide({}),()=>{var t;return(t=e.default)===null||t===void 0?void 0:t.call(e)}}});export{g as F,M as N,S as a,K as b,w as o,_ as u};
|
||||
|
|
@ -0,0 +1 @@
|
|||
const F={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,CAPS_LOCK:20,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,N:78,P:80,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,SEMICOLON:186,EQUALS:187,WIN_KEY:224};export{F as K};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.document-upload[data-v-55410552]{padding:8px 0}.upload-spin[data-v-55410552]{display:block;text-align:center;padding:16px}.document-list[data-v-55410552]{margin-top:16px}.source-config[data-v-752313e6]{padding:8px 0}.source-config__header[data-v-752313e6]{margin-bottom:16px;display:flex;justify-content:flex-end}.search-test[data-v-bfaf0801]{padding:8px 0}.advanced-options[data-v-bfaf0801]{margin-top:12px}.advanced-form[data-v-bfaf0801]{flex-wrap:wrap;gap:8px}.search-results[data-v-bfaf0801]{margin-top:16px}.search-result-item[data-v-bfaf0801]{background:var(--bg-secondary);border:1px solid var(--border-color-split);border-radius:6px;padding:12px 16px;margin-bottom:12px}.search-result-item__header[data-v-bfaf0801]{display:flex;align-items:center;gap:8px;margin-bottom:8px}.search-result-item__index[data-v-bfaf0801]{font-weight:600;color:var(--color-primary)}.search-result-item__score[data-v-bfaf0801]{margin-left:auto;font-size:12px;color:var(--text-placeholder)}.search-result-item__content[data-v-bfaf0801]{font-size:14px;line-height:1.6;color:var(--text-primary);white-space:pre-wrap;max-height:200px;overflow-y:auto}.search-result-item__meta[data-v-bfaf0801]{margin-top:8px;display:flex;flex-wrap:wrap;gap:4px}.kb-view[data-v-e4e6375c]{height:100%;padding:var(--space-4) var(--space-6);overflow-y:auto;background:var(--bg-primary)}
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c,I as u}from"./index-Cdm90D30.js";var s={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"};function i(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){O(r,a,e[a])})}return r}function O(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var f=function(t,e){var n=i({},t,e.attrs);return c(u,i({},n,{icon:s}),null)};f.displayName="RightOutlined";f.inheritAttrs=!1;var g={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"}}]},name:"left",theme:"outlined"};function l(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){p(r,a,e[a])})}return r}function p(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var o=function(t,e){var n=l({},t,e.attrs);return c(u,l({},n,{icon:g}),null)};o.displayName="LeftOutlined";o.inheritAttrs=!1;export{o as L,f as R};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c as i,I as c}from"./index-Cdm90D30.js";var o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"}},{tag:"path",attrs:{d:"M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z"}}]},name:"plus",theme:"outlined"};function u(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){s(r,a,e[a])})}return r}function s(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var l=function(t,e){var n=u({},t,e.attrs);return i(c,u({},n,{icon:o}),null)};l.displayName="PlusOutlined";l.inheritAttrs=!1;export{l as P};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c as l,I as c}from"./index-Cdm90D30.js";var p={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M908 640H804V488c0-4.4-3.6-8-8-8H548v-96h108c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16H368c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h108v96H228c-4.4 0-8 3.6-8 8v152H116c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16H292v-88h440v88H620c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16zm-564 76v168H176V716h168zm84-408V140h168v168H428zm420 576H680V716h168v168z"}}]},name:"apartment",theme:"outlined"};function i(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){v(r,a,e[a])})}return r}function v(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var f=function(t,e){var n=i({},t,e.attrs);return l(c,i({},n,{icon:p}),null)};f.displayName="ApartmentOutlined";f.inheritAttrs=!1;var O={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-260 72h96v209.9L621.5 312 572 347.4V136zm220 752H232V136h280v296.9c0 3.3 1 6.6 3 9.3a15.9 15.9 0 0022.3 3.7l83.8-59.9 81.4 59.4c2.7 2 6 3.1 9.4 3.1 8.8 0 16-7.2 16-16V136h64v752z"}}]},name:"book",theme:"outlined"};function u(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){g(r,a,e[a])})}return r}function g(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var s=function(t,e){var n=u({},t,e.attrs);return l(c,u({},n,{icon:O}),null)};s.displayName="BookOutlined";s.inheritAttrs=!1;var b={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"}}]},name:"setting",theme:"outlined"};function o(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){d(r,a,e[a])})}return r}function d(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var m=function(t,e){var n=o({},t,e.attrs);return l(c,o({},n,{icon:b}),null)};m.displayName="SettingOutlined";m.inheritAttrs=!1;export{f as A,s as B,m as S};
|
||||
|
|
@ -0,0 +1 @@
|
|||
.settings-view[data-v-69defdaa]{height:100%;padding:var(--space-4) var(--space-6);overflow-y:auto;background:var(--bg-primary)}.settings-tabs[data-v-69defdaa]{height:100%}.settings-form[data-v-69defdaa]{max-width:600px}.settings-form__hint[data-v-69defdaa]{margin-left:var(--space-2);color:var(--text-tertiary);font-size:var(--font-sm)}.settings-view__alert[data-v-69defdaa]{margin-top:var(--space-3);max-width:600px}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.skill-card[data-v-f7a7f9c2]{cursor:pointer}.skill-card__title[data-v-f7a7f9c2]{display:flex;align-items:center;gap:6px}.skill-card__icon[data-v-f7a7f9c2]{color:var(--color-primary)}.skill-card__desc[data-v-f7a7f9c2]{font-size:13px;color:var(--text-secondary);margin-bottom:8px;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.skill-card__tags[data-v-f7a7f9c2]{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}.skill-card__deps[data-v-f7a7f9c2]{display:flex;align-items:center;gap:4px;flex-wrap:wrap}.skill-card__deps-label[data-v-f7a7f9c2],.skill-card__more[data-v-f7a7f9c2]{font-size:12px;color:var(--text-placeholder)}.skill-card__footer[data-v-f7a7f9c2]{margin-top:8px;display:flex;justify-content:flex-end}.skill-card__version[data-v-f7a7f9c2]{font-size:12px;color:var(--text-placeholder)}.skill-detail__tags[data-v-3ddcc970]{display:flex;flex-wrap:wrap;gap:6px}.skill-detail__empty[data-v-3ddcc970]{color:var(--text-placeholder);font-size:13px}.skill-detail__config[data-v-3ddcc970]{background:var(--bg-tertiary);border-radius:6px;padding:12px;overflow-x:auto}.skill-detail__config pre[data-v-3ddcc970]{margin:0;font-size:12px;line-height:1.5}.skill-detail__actions[data-v-3ddcc970]{margin-top:24px;display:flex;gap:12px}.skills-view[data-v-50d34770]{height:100%;padding:var(--space-4) var(--space-6);overflow-y:auto;background:var(--bg-primary)}.skills-view__header[data-v-50d34770]{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-4)}.skills-view__grid[data-v-50d34770]{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-4)}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.terminal-emulator[data-v-e0e9656d]{display:flex;flex-direction:column;height:100%;background:var(--code-bg);border-radius:var(--radius-md);overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace}.terminal-emulator__output[data-v-e0e9656d]{flex:1;overflow-y:auto;padding:var(--space-3);font-size:var(--font-sm);line-height:var(--leading-normal);color:var(--code-fg)}.terminal-emulator__welcome[data-v-e0e9656d]{color:var(--code-comment);font-style:italic}.terminal-emulator__input[data-v-e0e9656d]{display:flex;align-items:center;padding:var(--space-2) var(--space-3);border-top:1px solid rgba(255,255,255,.1);background:#0003}.terminal-emulator__prompt[data-v-e0e9656d]{color:var(--code-string);margin-right:var(--space-2);font-size:var(--font-sm);white-space:nowrap}.terminal-emulator__input-field[data-v-e0e9656d]{flex:1;background:transparent;border:none;outline:none;color:var(--code-fg);font-family:inherit;font-size:var(--font-sm)}.terminal-emulator__input-field[data-v-e0e9656d]::placeholder{color:var(--code-comment)}.terminal-line[data-v-e0e9656d]{white-space:pre-wrap;word-break:break-all}.terminal-line[data-v-e0e9656d] .ansi-green{color:var(--code-string)}.terminal-line[data-v-e0e9656d] .ansi-yellow{color:var(--code-number)}.terminal-line[data-v-e0e9656d] .ansi-red{color:var(--code-variable)}.terminal-line[data-v-e0e9656d] .ansi-cyan,.terminal-line[data-v-e0e9656d] .ansi-blue{color:var(--code-function)}.terminal-line[data-v-e0e9656d] .ansi-magenta{color:var(--code-keyword)}.command-history[data-v-d8bc7055]{display:flex;flex-direction:column;height:100%;background:var(--bg-primary)}.command-history__header[data-v-d8bc7055]{display:flex;justify-content:space-between;align-items:center;padding:var(--space-3) var(--space-4);font-weight:var(--font-weight-semibold);font-size:var(--font-base);border-bottom:1px solid var(--border-color)}.command-history__list[data-v-d8bc7055]{flex:1;overflow-y:auto;padding:var(--space-2)}.command-history__item[data-v-d8bc7055]{padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);cursor:pointer;margin-bottom:var(--space-1);transition:background var(--transition-fast)}.command-history__item[data-v-d8bc7055]:hover{background:var(--color-primary-light)}.command-history__item-header[data-v-d8bc7055]{display:flex;align-items:center;gap:var(--space-1)}.command-history__exit-code[data-v-d8bc7055]{font-size:var(--font-xs);font-weight:var(--font-weight-semibold)}.command-history__exit-code--success[data-v-d8bc7055]{color:var(--color-success)}.command-history__exit-code--error[data-v-d8bc7055]{color:var(--color-error)}.command-history__command[data-v-d8bc7055]{font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace;font-size:var(--font-xs);color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-history__item-meta[data-v-d8bc7055]{display:flex;gap:var(--space-2);margin-top:2px;font-size:11px;color:var(--text-placeholder)}.command-history__duration[data-v-d8bc7055]{color:var(--color-primary)}.command-history__empty[data-v-d8bc7055]{text-align:center;padding:var(--space-6);color:var(--text-placeholder);font-size:var(--font-sm)}.terminal-view[data-v-e0e2611b]{display:flex;height:100%;overflow:hidden}.terminal-view__main[data-v-e0e2611b]{flex:1;overflow:hidden;padding:var(--space-2);position:relative}.terminal-view__sidebar[data-v-e0e2611b]{display:flex;border-left:1px solid var(--border-color);background:var(--bg-primary);transition:width var(--transition-normal);overflow:hidden}.terminal-view__sidebar--collapsed[data-v-e0e2611b]{width:32px}.terminal-view__sidebar[data-v-e0e2611b]:not(.terminal-view__sidebar--collapsed){width:240px}.terminal-view__sidebar-toggle[data-v-e0e2611b]{display:flex;align-items:center;justify-content:center;width:32px;height:100%;border:none;background:transparent;color:var(--text-tertiary);cursor:pointer;flex-shrink:0;transition:all var(--transition-fast)}.terminal-view__sidebar-toggle[data-v-e0e2611b]:hover{color:var(--text-primary);background:var(--bg-tertiary)}.terminal-view__sidebar-content[data-v-e0e2611b]{flex:1;overflow:hidden;min-width:0}.terminal-view__modal-command[data-v-e0e2611b]{font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace;font-size:var(--font-sm);color:var(--text-primary);background:var(--bg-tertiary);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);margin-bottom:var(--space-3);word-break:break-all}.terminal-view__modal-reason[data-v-e0e2611b]{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:var(--space-3)}
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c as u,I as i}from"./index-Cdm90D30.js";var s={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z"}}]},name:"thunderbolt",theme:"outlined"};function c(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(t).filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable}))),n.forEach(function(a){d(r,a,t[a])})}return r}function d(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var o=function(e,t){var n=c({},e,t.attrs);return u(i,c({},n,{icon:s}),null)};o.displayName="ThunderboltOutlined";o.inheritAttrs=!1;var b={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M858.5 763.6a374 374 0 00-80.6-119.5 375.63 375.63 0 00-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 00-80.6 119.5A371.7 371.7 0 00136 901.8a8 8 0 008 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 008-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"}}]},name:"user",theme:"outlined"};function l(r){for(var e=1;e<arguments.length;e++){var t=arguments[e]!=null?Object(arguments[e]):{},n=Object.keys(t);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(t).filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable}))),n.forEach(function(a){O(r,a,t[a])})}return r}function O(r,e,t){return e in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}var f=function(e,t){var n=l({},e,t.attrs);return u(i,l({},n,{icon:b}),null)};f.displayName="UserOutlined";f.inheritAttrs=!1;export{o as T,f as U};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
let i={};function t(a,n){}function c(a,n,e){!n&&!i[e]&&(i[e]=!0)}function d(a,n){c(t,a,n)}const o=(a,n,e)=>{d(a,`[ant-design-vue: ${n}] ${e}`)};export{d as a,o as d,t as w};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import{x as K,y as Q,z as _,A as U,aH as Y,d as Z,D as oo,bo as eo,bp as no,bq as lo,ap as to,az as io,ao,bh as so,an as ro,R as co,c as s,a1 as uo,aF as go,aP as po,p as mo,v as fo,J as N,aG as vo,K as w,f as $o,P as v,aA as yo}from"./index-Cdm90D30.js";import{c as ho}from"./zoom-DFzZ47uz.js";const B=(o,e,n,i,a)=>({backgroundColor:o,border:`${i.lineWidth}px ${i.lineType} ${e}`,[`${a}-icon`]:{color:n}}),Co=o=>{const{componentCls:e,motionDurationSlow:n,marginXS:i,marginSM:a,fontSize:u,fontSizeLG:r,lineHeight:g,borderRadiusLG:$,motionEaseInOutCirc:c,alertIconSizeLG:d,colorText:m,paddingContentVerticalSM:f,alertPaddingHorizontal:y,paddingMD:C,paddingContentHorizontalLG:x}=o;return{[e]:_(_({},U(o)),{position:"relative",display:"flex",alignItems:"center",padding:`${f}px ${y}px`,wordWrap:"break-word",borderRadius:$,[`&${e}-rtl`]:{direction:"rtl"},[`${e}-content`]:{flex:1,minWidth:0},[`${e}-icon`]:{marginInlineEnd:i,lineHeight:0},"&-description":{display:"none",fontSize:u,lineHeight:g},"&-message":{color:m},[`&${e}-motion-leave`]:{overflow:"hidden",opacity:1,transition:`max-height ${n} ${c}, opacity ${n} ${c},
|
||||
padding-top ${n} ${c}, padding-bottom ${n} ${c},
|
||||
margin-bottom ${n} ${c}`},[`&${e}-motion-leave-active`]:{maxHeight:0,marginBottom:"0 !important",paddingTop:0,paddingBottom:0,opacity:0}}),[`${e}-with-description`]:{alignItems:"flex-start",paddingInline:x,paddingBlock:C,[`${e}-icon`]:{marginInlineEnd:a,fontSize:d,lineHeight:0},[`${e}-message`]:{display:"block",marginBottom:i,color:m,fontSize:r},[`${e}-description`]:{display:"block"}},[`${e}-banner`]:{marginBottom:0,border:"0 !important",borderRadius:0}}},xo=o=>{const{componentCls:e,colorSuccess:n,colorSuccessBorder:i,colorSuccessBg:a,colorWarning:u,colorWarningBorder:r,colorWarningBg:g,colorError:$,colorErrorBorder:c,colorErrorBg:d,colorInfo:m,colorInfoBorder:f,colorInfoBg:y}=o;return{[e]:{"&-success":B(a,i,n,o,e),"&-info":B(y,f,m,o,e),"&-warning":B(g,r,u,o,e),"&-error":_(_({},B(d,c,$,o,e)),{[`${e}-description > pre`]:{margin:0,padding:0}})}}},So=o=>{const{componentCls:e,iconCls:n,motionDurationMid:i,marginXS:a,fontSizeIcon:u,colorIcon:r,colorIconHover:g}=o;return{[e]:{"&-action":{marginInlineStart:a},[`${e}-close-icon`]:{marginInlineStart:a,padding:0,overflow:"hidden",fontSize:u,lineHeight:`${u}px`,backgroundColor:"transparent",border:"none",outline:"none",cursor:"pointer",[`${n}-close`]:{color:r,transition:`color ${i}`,"&:hover":{color:g}}},"&-close-text":{color:r,transition:`color ${i}`,"&:hover":{color:g}}}}},bo=o=>[Co(o),xo(o),So(o)],Io=K("Alert",o=>{const{fontSizeHeading3:e}=o,n=Q(o,{alertIconSizeLG:e,alertPaddingHorizontal:12});return[bo(n)]}),wo={success:ro,info:so,error:ao,warning:io},Bo={success:to,info:lo,error:no,warning:eo},_o=yo("success","info","warning","error"),Ho=()=>({type:v.oneOf(_o),closable:{type:Boolean,default:void 0},closeText:v.any,message:v.any,description:v.any,afterClose:Function,showIcon:{type:Boolean,default:void 0},prefixCls:String,banner:{type:Boolean,default:void 0},icon:v.any,closeIcon:v.any,onClose:Function}),To=Z({compatConfig:{MODE:3},name:"AAlert",inheritAttrs:!1,props:Ho(),setup(o,e){let{slots:n,emit:i,attrs:a,expose:u}=e;const{prefixCls:r,direction:g}=oo("alert",o),[$,c]=Io(r),d=w(!1),m=w(!1),f=w(),y=t=>{t.preventDefault();const p=f.value;p.style.height=`${p.offsetHeight}px`,p.style.height=`${p.offsetHeight}px`,d.value=!0,i("close",t)},C=()=>{var t;d.value=!1,m.value=!0,(t=o.afterClose)===null||t===void 0||t.call(o)},x=$o(()=>{const{type:t}=o;return t!==void 0?t:o.banner?"warning":"info"});u({animationEnd:C});const V=w({});return()=>{var t,p,H,T,z,A,E,O,F,L;const{banner:M,closeIcon:G=(t=n.closeIcon)===null||t===void 0?void 0:t.call(n)}=o;let{closable:P,showIcon:h}=o;const D=(p=o.closeText)!==null&&p!==void 0?p:(H=n.closeText)===null||H===void 0?void 0:H.call(n),S=(T=o.description)!==null&&T!==void 0?T:(z=n.description)===null||z===void 0?void 0:z.call(n),R=(A=o.message)!==null&&A!==void 0?A:(E=n.message)===null||E===void 0?void 0:E.call(n),b=(O=o.icon)!==null&&O!==void 0?O:(F=n.icon)===null||F===void 0?void 0:F.call(n),W=(L=n.action)===null||L===void 0?void 0:L.call(n);h=M&&h===void 0?!0:h;const j=(S?Bo:wo)[x.value]||null;D&&(P=!0);const l=r.value,k=co(l,{[`${l}-${x.value}`]:!0,[`${l}-closing`]:d.value,[`${l}-with-description`]:!!S,[`${l}-no-icon`]:!h,[`${l}-banner`]:!!M,[`${l}-closable`]:P,[`${l}-rtl`]:g.value==="rtl",[c.value]:!0}),X=P?s("button",{type:"button",onClick:y,class:`${l}-close-icon`,tabindex:0},[D?s("span",{class:`${l}-close-text`},[D]):G===void 0?s(uo,null,null):G]):null,q=b&&(go(b)?ho(b,{class:`${l}-icon`}):s("span",{class:`${l}-icon`},[b]))||s(j,{class:`${l}-icon`},null),J=po(`${l}-motion`,{appear:!1,css:!0,onAfterLeave:C,onBeforeLeave:I=>{I.style.maxHeight=`${I.offsetHeight}px`},onLeave:I=>{I.style.maxHeight="0px"}});return $(m.value?null:s(vo,J,{default:()=>[mo(s("div",N(N({role:"alert"},a),{},{style:[a.style,V.value],class:[a.class,k],"data-show":!d.value,ref:f}),[h?q:null,s("div",{class:`${l}-content`},[R?s("div",{class:`${l}-message`},[R]):null,S?s("div",{class:`${l}-description`},[S]):null]),W?s("div",{class:`${l}-action`},[W]):null,X]),[[fo,!d.value]])]}))}}}),Eo=Y(To);export{Eo as _};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{x as b,y as S,z as d,A as w,aH as B,d as T,U as k,D as A,R as z,c as f,J as x,aJ as D,r as N,f as _,aR as $,F as I,av as C}from"./index-Cdm90D30.js";import{g as W,P as H,A as R,t as E,a as M}from"./base-BzUb4EcV.js";import{o as j}from"./FormItemContext-C7duC9vx.js";import{i as F}from"./zoom-DFzZ47uz.js";import{i as J}from"./_plugin-vue_export-helper-BpJgGuqH.js";const L=t=>{const{componentCls:o,popoverBg:r,popoverColor:e,width:a,fontWeightStrong:s,popoverPadding:l,boxShadowSecondary:c,colorTextHeading:g,borderRadiusLG:u,zIndexPopup:p,marginXS:m,colorBgElevated:n}=t;return[{[o]:d(d({},w(t)),{position:"absolute",top:0,left:{_skip_check_:!0,value:0},zIndex:p,fontWeight:"normal",whiteSpace:"normal",textAlign:"start",cursor:"auto",userSelect:"text","--antd-arrow-background-color":n,"&-rtl":{direction:"rtl"},"&-hidden":{display:"none"},[`${o}-content`]:{position:"relative"},[`${o}-inner`]:{backgroundColor:r,backgroundClip:"padding-box",borderRadius:u,boxShadow:c,padding:l},[`${o}-title`]:{minWidth:a,marginBottom:m,color:g,fontWeight:s},[`${o}-inner-content`]:{color:e}})},W(t,{colorBg:"var(--antd-arrow-background-color)"}),{[`${o}-pure`]:{position:"relative",maxWidth:"none",[`${o}-content`]:{display:"inline-block"}}}]},O=t=>{const{componentCls:o}=t;return{[o]:H.map(r=>{const e=t[`${r}-6`];return{[`&${o}-${r}`]:{"--antd-arrow-background-color":e,[`${o}-inner`]:{backgroundColor:e},[`${o}-arrow`]:{background:"transparent"}}}})}},G=t=>{const{componentCls:o,lineWidth:r,lineType:e,colorSplit:a,paddingSM:s,controlHeight:l,fontSize:c,lineHeight:g,padding:u}=t,p=l-Math.round(c*g),m=p/2,n=p/2-r,i=u;return{[o]:{[`${o}-inner`]:{padding:0},[`${o}-title`]:{margin:0,padding:`${m}px ${i}px ${n}px`,borderBottom:`${r}px ${e} ${a}`},[`${o}-inner-content`]:{padding:`${s}px ${i}px`}}}},U=b("Popover",t=>{const{colorBgElevated:o,colorText:r,wireframe:e}=t,a=S(t,{popoverBg:o,popoverColor:r,popoverPadding:12});return[L(a),O(a),e&&G(a),F(a,"zoom-big")]},t=>{let{zIndexPopupBase:o}=t;return{zIndexPopup:o+30,width:177}}),V=()=>d(d({},M()),{content:C(),title:C()}),X=T({compatConfig:{MODE:3},name:"APopover",inheritAttrs:!1,props:J(V(),d(d({},E()),{trigger:"hover",placement:"top",mouseEnterDelay:.1,mouseLeaveDelay:.1})),setup(t,o){let{expose:r,slots:e,attrs:a}=o;const s=N();k(t.visible===void 0),r({getPopupDomNode:()=>{var n,i;return(i=(n=s.value)===null||n===void 0?void 0:n.getPopupDomNode)===null||i===void 0?void 0:i.call(n)}});const{prefixCls:l,configProvider:c}=A("popover",t),[g,u]=U(l),p=_(()=>c.getPrefixCls()),m=()=>{var n,i;const{title:v=$((n=e.title)===null||n===void 0?void 0:n.call(e)),content:h=$((i=e.content)===null||i===void 0?void 0:i.call(e))}=t,P=!!(Array.isArray(v)?v.length:v),y=!!(Array.isArray(h)?h.length:v);return!P&&!y?null:f(I,null,[P&&f("div",{class:`${l.value}-title`},[v]),f("div",{class:`${l.value}-inner-content`},[h])])};return()=>{const n=z(t.overlayClassName,u.value);return g(f(R,x(x(x({},j(t,["title","content"])),a),{},{prefixCls:l.value,ref:s,overlayClassName:n,transitionName:D(p.value,"zoom-big",t.transitionName),"data-popover-inject":!0}),{title:m,default:e.default}))}}}),oo=B(X);export{oo as P};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{x as b,y as w,z as s,A as z,aH as y,d as M,D as C,M as B,c as f,J as u,f as d}from"./index-Cdm90D30.js";const H=t=>{const{componentCls:e,sizePaddingEdgeHorizontal:o,colorSplit:r,lineWidth:i}=t;return{[e]:s(s({},z(t)),{borderBlockStart:`${i}px solid ${r}`,"&-vertical":{position:"relative",top:"-0.06em",display:"inline-block",height:"0.9em",margin:`0 ${t.dividerVerticalGutterMargin}px`,verticalAlign:"middle",borderTop:0,borderInlineStart:`${i}px solid ${r}`},"&-horizontal":{display:"flex",clear:"both",width:"100%",minWidth:"100%",margin:`${t.dividerHorizontalGutterMargin}px 0`},[`&-horizontal${e}-with-text`]:{display:"flex",alignItems:"center",margin:`${t.dividerHorizontalWithTextGutterMargin}px 0`,color:t.colorTextHeading,fontWeight:500,fontSize:t.fontSizeLG,whiteSpace:"nowrap",textAlign:"center",borderBlockStart:`0 ${r}`,"&::before, &::after":{position:"relative",width:"50%",borderBlockStart:`${i}px solid transparent`,borderBlockStartColor:"inherit",borderBlockEnd:0,transform:"translateY(50%)",content:"''"}},[`&-horizontal${e}-with-text-left`]:{"&::before":{width:"5%"},"&::after":{width:"95%"}},[`&-horizontal${e}-with-text-right`]:{"&::before":{width:"95%"},"&::after":{width:"5%"}},[`${e}-inner-text`]:{display:"inline-block",padding:"0 1em"},"&-dashed":{background:"none",borderColor:r,borderStyle:"dashed",borderWidth:`${i}px 0 0`},[`&-horizontal${e}-with-text${e}-dashed`]:{"&::before, &::after":{borderStyle:"dashed none none"}},[`&-vertical${e}-dashed`]:{borderInlineStartWidth:i,borderInlineEnd:0,borderBlockStart:0,borderBlockEnd:0},[`&-plain${e}-with-text`]:{color:t.colorText,fontWeight:"normal",fontSize:t.fontSize},[`&-horizontal${e}-with-text-left${e}-no-default-orientation-margin-left`]:{"&::before":{width:0},"&::after":{width:"100%"},[`${e}-inner-text`]:{paddingInlineStart:o}},[`&-horizontal${e}-with-text-right${e}-no-default-orientation-margin-right`]:{"&::before":{width:"100%"},"&::after":{width:0},[`${e}-inner-text`]:{paddingInlineEnd:o}}})}},I=b("Divider",t=>{const e=w(t,{dividerVerticalGutterMargin:t.marginXS,dividerHorizontalWithTextGutterMargin:t.margin,dividerHorizontalGutterMargin:t.marginLG});return[H(e)]},{sizePaddingEdgeHorizontal:0}),G=()=>({prefixCls:String,type:{type:String,default:"horizontal"},dashed:{type:Boolean,default:!1},orientation:{type:String,default:"center"},plain:{type:Boolean,default:!1},orientationMargin:[String,Number]}),W=M({name:"ADivider",inheritAttrs:!1,compatConfig:{MODE:3},props:G(),setup(t,e){let{slots:o,attrs:r}=e;const{prefixCls:i,direction:m}=C("divider",t),[v,h]=I(i),g=d(()=>t.orientation==="left"&&t.orientationMargin!=null),c=d(()=>t.orientation==="right"&&t.orientationMargin!=null),x=d(()=>{const{type:n,dashed:l,plain:S}=t,a=i.value;return{[a]:!0,[h.value]:!!h.value,[`${a}-${n}`]:!0,[`${a}-dashed`]:!!l,[`${a}-plain`]:!!S,[`${a}-rtl`]:m.value==="rtl",[`${a}-no-default-orientation-margin-left`]:g.value,[`${a}-no-default-orientation-margin-right`]:c.value}}),$=d(()=>{const n=typeof t.orientationMargin=="number"?`${t.orientationMargin}px`:t.orientationMargin;return s(s({},g.value&&{marginLeft:n}),c.value&&{marginRight:n})}),p=d(()=>t.orientation.length>0?"-"+t.orientation:t.orientation);return()=>{var n;const l=B((n=o.default)===null||n===void 0?void 0:n.call(o));return v(f("div",u(u({},r),{},{class:[x.value,l.length?`${i.value}-with-text ${i.value}-with-text${p.value}`:"",r.class],role:"separator"}),[l.length?f("span",{class:`${i.value}-inner-text`,style:$.value},[l]):null]))}}}),E=y(W);export{E as _};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue