From fd2c8d1d6451b6331fafec13b067954b74b4a5ce Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Mon, 25 May 2026 21:21:54 +0800 Subject: [PATCH 1/3] =?UTF-8?q?ui=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/docs/ui修改-深蓝.md | 600 +++++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) diff --git a/frontend-web/docs/ui修改-深蓝.md b/frontend-web/docs/ui修改-深蓝.md index 357c7b31..3aaa8028 100644 --- a/frontend-web/docs/ui修改-深蓝.md +++ b/frontend-web/docs/ui修改-深蓝.md @@ -233,3 +233,603 @@ |------|----------| | 16px Regular | 表格横纵轴数字 | | 14px Regular | 表格横轴数字(空间有限) | + +--- + +## 3. 阴影 + +### 3.1 容器与卡片配色 + +阴影组通过深浅层级叠加形成空间感:外层容器最深,内层卡片次之,边框与高亮文字使用主色或字体色。 + +#### 3.1.1 外层容器 + +| 角色 | 色值 | 来源 | +|------|------|------| +| 页面 / 最外层背景 | `#060E24` | ZS-004-10 | +| 分栏容器背景 | `#0C1836` | ZS-004-09 | + +#### 3.1.2 卡片(标题 + 操作) + +| 角色 | 色值 | 来源 | +|------|------|------| +| 卡片背景 | `#122347` | ZS-004-08 | +| 卡片边框 | `#1A2F5A` | ZS-004-07 | +| 卡片阴影(外发光) | `rgba(22, 119, 255, 0.12)` | 基于 ZS-002-06 主色蓝 12% 透明度 | +| 标题文字(16px Bold) | `#C5CFE0` | ZS-004-02(正常字体色) | +| 操作按钮文字 | `#4096FF` | ZS-002-05(标准蓝,链接) | +| 次级操作图标(✏️ / ⋯) | `#6E88B0` | ZS-004-04(次级字体色) | + +#### 3.1.3 卡片新增(占位卡片) + +虚线描边的"新增入口"卡片,用于引导用户添加内容。 + +| 角色 | 色值 | 来源 | +|------|------|------| +| 卡片背景 | `#0C1836` | ZS-004-09(与外层同色,营造凹陷感) | +| 虚线边框 | `#4096FF` | ZS-002-05 | +| 加号图标 | `#4096FF` | ZS-002-05 | + +#### 3.1.4 阴影投影规则 + +| 层级 | 阴影值 | 使用场景 | +|------|--------|----------| +| 一级阴影(卡片悬浮) | `0 2px 8px rgba(22, 119, 255, 0.12)` | 普通卡片 | +| 二级阴影(hover / 强调) | `0 4px 16px rgba(22, 119, 255, 0.24)` | 卡片 hover、弹出层 | +| 三级阴影(弹窗 / Modal) | `0 8px 32px rgba(6, 14, 36, 0.6)` | Dialog、Drawer | + +--- + +## 4. 圆角 + +### 4.1 圆角统一规范 + +| 元素 | 圆角值 | 说明 | +|------|--------|------| +| 按钮 | `2px` | 所有按钮(主按钮 / 次按钮 / 文本按钮)统一使用 | +| 卡片 | `4px` | 内容卡片、标题+操作卡片、卡片新增占位 | +| 页面分栏框 | `4px` | 外层容器、分栏区域 | +| 输入框 / 选择器 | `2px` | 与按钮一致,保持表单内对齐 | +| Tag / Badge | `2px` | 与按钮一致 | +| Modal / Drawer | `4px` | 与卡片一致 | + +### 4.2 按钮配色(圆角 2px) + +#### 4.2.1 主按钮(Primary) + +| 角色 | 色值 | 来源 | +|------|------|------| +| 背景 | `#1677FF` | ZS-002-06 | +| 文字 | `#FFFFFF` | — | +| Hover 背景 | `#0958D9` | ZS-002-07 | +| Active 背景 | `#003EB3` | ZS-002-08 | +| 禁用背景 | `#4A6491` | ZS-004-05 | +| 禁用文字 | `#6E88B0` | ZS-004-04 | + +#### 4.2.2 次按钮(Outline / Default) + +| 角色 | 色值 | 来源 | +|------|------|------| +| 背景 | `transparent` | — | +| 边框 | `#6E88B0` | ZS-004-04 | +| 文字 | `#C5CFE0` | ZS-004-02 | +| Hover 边框 | `#4096FF` | ZS-002-05 | +| Hover 文字 | `#4096FF` | ZS-002-05 | +| Active 边框 | `#1677FF` | ZS-002-06 | + +### 4.3 卡片与分栏(圆角 4px) + +| 角色 | 色值 | 来源 | +|------|------|------| +| 卡片背景 | `#122347` | ZS-004-08 | +| 卡片边框 | `#1A2F5A` | ZS-004-07 | +| 分栏框背景 | `#0C1836` | ZS-004-09 | +| 分栏框边框 | `#1A2F5A` | ZS-004-07 | + +--- + +## 5. 按钮 + +> 全部圆角 `2px`,文字字号 14px(小尺寸)/ 14px(正常尺寸),字体 Noto Sans S Chinese Regular。 + +### 5.1 按钮尺寸 + +| 规格 | 高度 | 内边距 | 使用场景 | +|------|------|--------|----------| +| 小 | `28px` | `0 10px` | 空间小的板块内部,如表格中、卡片标题等 | +| 正常 | `32px` | `0 14px` | 默认尺寸,所有常用按钮 | + +### 5.2 按钮类型与配色 + +#### 5.2.1 主按钮(Primary) + +> 一个操作区域只能有一个主按钮。 + +| 状态 | 背景 | 边框 | 文字 | +|------|------|------|------| +| 默认 | `#1677FF` (ZS-002-06) | — | `#FFFFFF` | +| Hover | `#0958D9` (ZS-002-07) | — | `#FFFFFF` | +| Active | `#003EB3` (ZS-002-08) | — | `#FFFFFF` | +| 失效 | `rgba(22, 119, 255, 0.35)` | — | `#6E88B0` (ZS-004-04) | + +#### 5.2.2 默认按钮(Default / Outline) + +用于"取消 / 返回"等次要操作,与主按钮成对出现时主按钮在右。 + +| 状态 | 背景 | 边框 | 文字 | +|------|------|------|------| +| 默认 | `transparent` | `#6E88B0` (ZS-004-04) | `#C5CFE0` (ZS-004-02) | +| Hover | `rgba(22, 119, 255, 0.08)` | `#4096FF` (ZS-002-05) | `#4096FF` (ZS-002-05) | +| Active | `rgba(22, 119, 255, 0.16)` | `#1677FF` (ZS-002-06) | `#1677FF` (ZS-002-06) | +| 失效 | `transparent` | `#4A6491` (ZS-004-05) | `#4A6491` (ZS-004-05) | + +#### 5.2.3 文本按钮(Text) + +用于表格操作、文件下载等。无背景与边框,靠文字色识别。 + +| 状态 | 文字 | 装饰 | +|------|------|------| +| 默认 | `#4096FF` (ZS-002-05) | 无下划线 | +| Hover | `#1677FF` (ZS-002-06) | 显示下划线(与文字同色) | +| Active | `#0958D9` (ZS-002-07) | 下划线 | +| 失效 | `#4A6491` (ZS-004-05) | 无装饰 | + +#### 5.2.4 图标按钮(Icon-only) + +用于增强辨识度或节省空间。仅图标,无文字。 + +| 变体 | 背景 | 图标颜色 | +|------|------|----------| +| 主色(常用) | `#1677FF` (ZS-002-06) | `#FFFFFF` | +| 主色 Hover | `#0958D9` (ZS-002-07) | `#FFFFFF` | +| 危险(删除等) | `#F5222D` (ZS-001-06) | `#FFFFFF` | +| 危险 Hover | `#CF1322` (ZS-001-07) | `#FFFFFF` | +| 透明(仅图标) | `transparent` | `#6E88B0` (ZS-004-04) → hover `#4096FF` (ZS-002-05) | + +### 5.3 按钮组合 + +#### 5.3.1 文字 + 下拉按钮(含 ▾) + +两种填充: + +| 形态 | 配色规则 | +|------|----------| +| 默认(Outline) | 沿用 §5.2.2 默认按钮,箭头 ▾ 与文字同色 `#C5CFE0` | +| 主色(Filled) | 沿用 §5.2.1 主按钮,箭头 ▾ 同步白色 `#FFFFFF` | + +#### 5.3.2 图标 + 文字按钮(如 `+ 正常`) + +| 形态 | 配色规则 | +|------|----------| +| 默认(Outline) | 沿用 §5.2.2,左侧图标颜色与文字一致 `#C5CFE0` | +| 主色(Filled) | 沿用 §5.2.1,左侧图标与文字一致 `#FFFFFF` | + +#### 5.3.3 文本按钮 + 图标("更多 »"、"展开 ⌄"、"下一页 »"、"« 上一页") + +| 角色 | 色值 | +|------|------| +| 文字 + 图标(默认) | `#4096FF` (ZS-002-05) | +| 文字 + 图标(Hover) | `#1677FF` (ZS-002-06) | +| 失效 | `#4A6491` (ZS-004-05) | + +> 图标可在文字前或后;多个文本按钮排成一行时,间距 `16px`,无分隔符。 + +#### 5.3.4 主按钮 + 默认按钮组合(如"确定 / 取消 / 返回") + +- **排列顺序**:默认按钮在左,主按钮在右(最右侧为主操作) +- **按钮间距**:`8px` +- **示例**:`[ 返回 ] [ 取消 ] [ 确定 ]` +- **配色**:主按钮沿用 §5.2.1,其余沿用 §5.2.2 + +### 5.4 其他按钮 + +#### 5.4.1 悬浮状态(Floating) + +用于 Banner / 登录 / 视觉冲击强等展示场景。配色与主按钮一致,叠加阴影抬升。 + +| 状态 | 阴影 | +|------|------| +| 默认 | `0 2px 8px rgba(22, 119, 255, 0.12)`(§3.1.4 一级阴影) | +| Hover | `0 4px 16px rgba(22, 119, 255, 0.24)`(§3.1.4 二级阴影) | +| Active | 阴影消失,按下感 | + +#### 5.4.2 加载中(Loading) + +用于异步操作等待反馈。点击瞬间将按钮锁定,文字前显示旋转图标。 + +| 角色 | 色值 | +|------|------| +| 背景 | `#1677FF` (ZS-002-06) | +| 文字 | `#FFFFFF` | +| 加载图标 | `#FFFFFF`,旋转动画 `1.2s linear infinite` | +| 不可点击 | 鼠标 `cursor: not-allowed` | + +> 与失效态的区别:失效是"不可操作",加载是"操作进行中"——保留品牌主色,只是不可再次触发。 + +--- + +## 6. 分割线 + +> 统一线宽 `1px`,颜色全部从 ZS-004 深蓝系取色,与深色背景保持低对比度但仍可识别。 + +### 6.1 水平分割线 + +水平分割线常用来对不同元素内容进行分割。 + +| 类型 | 色值 | 来源 | 样式 | 使用场景 | +|------|------|------|------|----------| +| 实线(默认) | `#4A6491` | ZS-004-05 | `border-top: 1px solid` | 模块间、列表项间分割 | +| 实线(强调) | `#6E88B0` | ZS-004-04 | `border-top: 1px solid` | 主区块标题下方、卡片头部分割 | +| 实线(弱化) | `#2D4473` | ZS-004-06 | `border-top: 1px solid` | 卡片内部、密集列表分隔 | +| 虚线 | `#4A6491` | ZS-004-05 | `border-top: 1px dashed` | 次级 / 层级内分割,提示"非主结构" | +| 主色实线(高亮) | `#1677FF` | ZS-002-06 | `border-top: 1px solid` | 强调分隔,少量使用(如当前激活区块底边) | + +### 6.2 垂直分割线 + +垂直分割线常用来做行内分割(如导航项之间、状态指示之间、表格列头之间)。 + +| 类型 | 色值 | 来源 | 样式 | 使用场景 | +|------|------|------|------|----------| +| 实线(默认) | `#4A6491` | ZS-004-05 | `border-left: 1px solid` | 行内 inline 元素分割(面包屑、操作按钮组) | +| 实线(弱化) | `#2D4473` | ZS-004-06 | `border-left: 1px solid` | 表格列分割、紧凑布局 | +| 虚线 | `#4A6491` | ZS-004-05 | `border-left: 1px dashed` | 多列对齐辅助、辅助参考线 | + +### 6.3 规格与间距 + +| 规则 | 取值 | +|------|------| +| 线宽 | `1px` | +| 水平分割线与上下元素间距 | `≥ 8px`(紧凑布局可用 `4px`) | +| 垂直分割线与左右元素间距 | `≥ 8px`(行内紧凑 `4px`) | +| 垂直分割线高度 | 与文字行高一致,约 `12–14px`(也可全高跟随容器) | +| 透明度 | 必要时叠 `opacity: 0.6` 实现"更弱"层级 | + +### 6.4 CSS 参考 + +```css +/* 默认水平分割线 */ +.divider-h { border-top: 1px solid #4A6491; } +.divider-h-strong { border-top: 1px solid #6E88B0; } +.divider-h-weak { border-top: 1px solid #2D4473; } +.divider-h-dashed { border-top: 1px dashed #4A6491; } +.divider-h-accent { border-top: 1px solid #1677FF; } + +/* 垂直分割线 */ +.divider-v { border-left: 1px solid #4A6491; height: 14px; display: inline-block; vertical-align: middle; } +.divider-v-weak { border-left: 1px solid #2D4473; height: 14px; display: inline-block; vertical-align: middle; } +.divider-v-dashed { border-left: 1px dashed #4A6491; height: 14px; display: inline-block; vertical-align: middle; } +``` + +--- + +## 7. 间距 + +> 全站以 `8px (0.5rem)` 为对照基准单位,划分"密 / 疏"两个层次。所有间距取值均来自下列阶梯,禁止使用非阶梯值(如 5px、13px)。 + +### 7.1 间距阶梯 + +| 阶 | px | rem | 分组 | 典型场景 | +|----|------|--------|------|----------| +| 1 | `4px` | `0.25rem` | 密 | 图标与文字间距、Tag 内边距、紧凑徽标 | +| 2 | `8px` | `0.5rem` | 密 | 行内元素相邻、按钮组间距、表单 label 与控件 | +| 3 | `12px` | `0.75rem` | 密 | 表单项垂直间距、列表项内边距 | +| 4 | `16px` | `1rem` | 密 | 卡片内容内边距、模块内分隔 | +| 5 | `24px` | `1.5rem` | 疏 | 模块之间、Section 内边距 | +| 6 | `32px` | `2rem` | 疏 | 卡片之间、表单组之间 | +| 7 | `40px` | `2.5rem` | 疏 | 主区块之间、页面 Header / Footer 内边距 | +| 8 | `56px` | `3.5rem` | 疏 | 页面段落级隔断、Banner 区域 | + +> "密"对应紧凑信息密度(表格、表单、卡片内部);"疏"对应视觉呼吸(模块之间、页面整体留白)。**16px 是"密"的上限,24px 是"疏"的下限**,两者之间不应再插入自定义值。 + +### 7.2 使用原则 + +- **同层级一致**:同一布局层级内的间距必须使用同一阶值;多种取值会破坏节奏。 +- **就近取阶**:相邻区块的间距尽量从相邻阶梯取值(如 `16px → 24px`),跨越过多阶(如 `8px → 56px`)会让视觉断裂。 +- **密疏不混用**:一个组件内部全部用"密",外部用"疏",避免内/外间距颠倒。 +- **优先 8 的倍数**:除最小阶 `4px` 外,所有阶皆为 8 的倍数,方便 4/8 网格对齐。 +- **响应式按比例衰减**:移动端可整体降一阶(如 `24px → 16px`),但仍在阶梯内。 + +### 7.3 CSS Token 建议 + +```css +:root { + --spacing-1: 4px; /* 0.25rem */ + --spacing-2: 8px; /* 0.5rem */ + --spacing-3: 12px; /* 0.75rem */ + --spacing-4: 16px; /* 1rem */ + --spacing-5: 24px; /* 1.5rem */ + --spacing-6: 32px; /* 2rem */ + --spacing-7: 40px; /* 2.5rem */ + --spacing-8: 56px; /* 3.5rem */ +} +``` + +### 7.4 与已有规范的对齐 + +| 已有规则 | 对应阶 | +|----------|--------| +| §4.x 卡片圆角内边距 | `--spacing-4` (16px) | +| §5.3.4 主+默认按钮组合间距 `8px` | `--spacing-2` | +| §5.3.3 文本按钮间距 `16px` | `--spacing-4` | +| §6.3 分割线与上下元素间距 `≥ 8px` | `--spacing-2` 起 | + +--- + +## 8. 导航组件 + +> 适用于"大屏后台 / 控制台"类深蓝主题界面。整体结构 = 顶部导航 + 左侧分级菜单 + 多级下拉。所有颜色取自 ZS-002 / ZS-004 两个色系。 + +### 8.1 整体结构 + +``` +┌─ 顶部导航(Header)─ 高 56px ──────────────────────────────────────────────┐ +│ ≡ 二级菜单 二级菜单 二级菜单 〈一级菜单〉〈一级菜单〉 ⟪ XXXX系统 ⟫ 〈一级菜单〉 ⟳ 时间 ⌂ 返回首页 │ +├──┬──────────────────────────────────────────────────────────────────────────┤ +│ 三│ ┌─ 四级菜单(下拉面板)──┐ │ +│ 级│ │ ▾ 四级菜单 │ │ +│ 菜│ │ ▾ 五级菜单 │ │ +│ 单│ │ - 六级菜单 (active)│ │ +│ 三│ │ - 六级菜单 │ │ +│ 级│ │ ▸ 五级菜单 │ │ +│ 菜│ │ ▸ 四级菜单 │ │ +│ 单│ └────────────────────────┘ │ +│ …│ │ +└──┴──────────────────────────────────────────────────────────────────────────┘ +``` + +### 8.2 顶部导航(Header) + +#### 8.2.1 规格 + +| 属性 | 取值 | +|------|------| +| 高度 | `56px` | +| 左右 padding | `--spacing-4` (16px) | +| 背景 | 渐变 `linear-gradient(180deg, #122347 0%, #0C1836 100%)`(ZS-004-08 → ZS-004-09) | +| 底边 | `border-bottom: 1px solid #1A2F5A` (ZS-004-07) | +| 阴影 | `0 2px 8px rgba(6, 14, 36, 0.6)`(§3.1.4 一级阴影变体) | +| 文字基线 | `Noto Sans S Chinese`,垂直居中 | + +#### 8.2.2 二级菜单(左侧 Tabs,小号 14px) + +| 状态 | 文字 | 背景 | 装饰 | +|------|------|------|------| +| 默认 | `#6E88B0` (ZS-004-04) | `transparent` | — | +| Hover | `#C5CFE0` (ZS-004-02) | `rgba(22,119,255,0.08)` | — | +| 激活 | `#4096FF` (ZS-002-05) | `transparent` | 底部 `2px solid #4096FF` 高亮线 | + +- 字号 `14px Regular`,间距 `--spacing-3` (12px) +- 与最左侧的"折叠菜单 ≡"图标间距 `--spacing-4` (16px) + +#### 8.2.3 一级菜单(中部主导航,16px 带切角装饰) + +| 状态 | 文字 | 装饰 | +|------|------|------| +| 默认 | `#C5CFE0` (ZS-004-02) | 两端切角斜边,`border: 1px solid #1A2F5A` | +| Hover | `#4096FF` (ZS-002-05) | 切角线变 `#4096FF` | +| 激活 | `#FFFFFF` | 切角线 `#1677FF` (ZS-002-06),背景 `rgba(22,119,255,0.16)` | + +- 字号 `16px Medium` +- 切角推荐 `clip-path: polygon(8px 0, calc(100% - 8px) 0, 100% 100%, 0 100%)` +- 项间距 `--spacing-2` (8px) + +#### 8.2.4 系统标题(XXXX系统) + +- 字号 `20px Bold`,颜色 `#FFFFFF` +- 字体 `DOUYUFont` 或 Noto Sans Medium +- 左右装饰用主色蓝渐变线条:`linear-gradient(90deg, transparent 0%, #1677FF 50%, transparent 100%)`,线宽 `1px` +- 居中相对 Header 几何中心,左右最少留 `--spacing-6` (32px) 间距给一级菜单 + +#### 8.2.5 右侧工具区(时间 / 图标 / 返回首页) + +| 元素 | 字号/尺寸 | 颜色 | +|------|----------|------| +| 更新时间文字 | `12px Regular` | `#6E88B0` (ZS-004-04) | +| 时间数值(数字) | `12px D-DIN Regular` | `#C5CFE0` (ZS-004-02) | +| 工具图标(刷新/上/家) | `16px` | `#6E88B0`,hover → `#4096FF` | +| 返回首页文字 | `14px Regular` | `#C5CFE0`,hover → `#4096FF` | + +- 元素间距 `--spacing-3` (12px) +- 整体右 padding `--spacing-4` (16px) + +### 8.3 侧边栏(三级菜单) + +#### 8.3.1 规格 + +| 属性 | 取值 | +|------|------| +| 宽度 | `64px`(仅图标 / 竖排文字) | +| 背景 | `#0C1836` (ZS-004-09) | +| 右边界 | `border-right: 1px solid #1A2F5A` (ZS-004-07) | +| 项高度 | `64px`(与宽度一致,呈正方形) | +| 项内间距 | 上下 `--spacing-3` (12px) | +| 字号 | `14px Medium`,竖排(`writing-mode: vertical-rl`)或正常排两行 | + +#### 8.3.2 状态配色 + +| 状态 | 背景 | 文字 | 左侧指示条 | +|------|------|------|-----------| +| 默认 | `transparent` | `#6E88B0` (ZS-004-04) | 无 | +| Hover | `rgba(22,119,255,0.08)` | `#C5CFE0` (ZS-004-02) | 无 | +| 激活 | `#1A2F5A` (ZS-004-07) | `#FFFFFF` | 左侧 `2px solid #1677FF` (ZS-002-06) | + +### 8.4 下拉菜单面板(四级 / 五级 / 六级) + +#### 8.4.1 面板规格 + +| 属性 | 取值 | +|------|------| +| 背景 | `#122347` (ZS-004-08) | +| 边框 | `1px solid #1A2F5A` (ZS-004-07) | +| 阴影 | `0 4px 16px rgba(6, 14, 36, 0.6)`(§3.1.4 三级阴影变体) | +| 圆角 | `4px` | +| 内边距 | 上下 `--spacing-2` (8px) | +| 最小宽度 | `150px` | + +#### 8.4.2 菜单项规格 + +| 层级 | 缩进(padding-left) | 字号 | 展开图标 | 前缀 | +|------|---------------------|------|----------|------| +| 四级菜单 | `--spacing-3` (12px) | `14px Medium` | `▾ / ▸` | — | +| 五级菜单 | `--spacing-5` (24px) | `14px Regular` | `▾ / ▸` | — | +| 六级菜单 | `--spacing-6` (32px) | `14px Regular` | — | `-` 短横线 | + +- 行高 `32px` +- 项之间无分割线(靠缩进与图标区分层级) + +#### 8.4.3 菜单项配色 + +| 状态 | 背景 | 文字 | 展开图标 | +|------|------|------|----------| +| 默认 | `transparent` | `#C5CFE0` (ZS-004-02) | `#6E88B0` (ZS-004-04) | +| Hover | `rgba(22,119,255,0.08)` | `#4096FF` (ZS-002-05) | `#4096FF` | +| 激活(当前选中) | `rgba(22,119,255,0.16)` | `#4096FF` (ZS-002-05) | `#1677FF` (ZS-002-06) | +| 父级(含激活子项) | `transparent` | `#FFFFFF` | `#1677FF` | +| 失效 | `transparent` | `#4A6491` (ZS-004-05) | `#4A6491` | + +### 8.5 交互与动画 + +| 行为 | 动效 | +|------|------| +| 下拉面板展开 | `transform-origin: top; transform: scaleY(0.95) → scaleY(1); opacity 0 → 1`,`200ms ease-out` | +| 展开图标旋转 | `transform: rotate(0 → 90deg)`,`200ms ease-out` | +| Hover 背景过渡 | `background-color 120ms ease-out` | +| 激活底部高亮线(二级菜单) | `width 0% → 100%`,`240ms ease-out` | + +### 8.6 间距引用 + +| 位置 | 取值 | +|------|------| +| Header 内部元素间距 | `--spacing-2 / --spacing-3` | +| Header 与下方内容间距 | `0`(贴合,靠底边线与阴影区分) | +| 侧边栏与下拉面板间距 | `--spacing-1` (4px) | +| 下拉面板与触发元素间距 | `--spacing-1` (4px) | +| 同级菜单项之间 | `0`(紧贴排列,靠 hover/active 背景区分) | + +--- + +## 9. 锚点与页签 + +> 对应"4.2 导航组件-锚点"。三类组件共用主色蓝指示当前位置:**页签 / 锚点 / 回到顶部**。 + +### 9.1 页签(Tabs) + +#### 9.1.1 横向页签(下划线-横向) + +| 状态 | 文字 | 下划线 | +|------|------|--------| +| 默认 | `#C5CFE0` (ZS-004-02) | 无 | +| Hover | `#4096FF` (ZS-002-05) | 无 | +| 激活 | `#4096FF` (ZS-002-05) | `2px solid #4096FF`,宽度与文字齐 | +| 失效 | `#4A6491` (ZS-004-05) | 无 | + +- 字号 `14px Medium` +- 项间距 `--spacing-5` (24px) +- 下划线与文字底部间距 `--spacing-2` (8px) +- 容器底边可叠一条 `1px solid #1A2F5A` (ZS-004-07) 作为基线,激活态的高亮线压在上面 + +#### 9.1.2 纵向页签(下划线-纵向) + +| 状态 | 文字 | 右侧指示线 | +|------|------|-----------| +| 默认 | `#C5CFE0` (ZS-004-02) | 无 | +| Hover | `#4096FF` (ZS-002-05) | 无 | +| 激活 | `#4096FF` (ZS-002-05) | 右侧 `2px solid #4096FF`,高度与文字齐 | +| 失效 | `#4A6491` (ZS-004-05) | 无 | + +- 字号 `14px Medium`,项高度 `32px` +- 项间距 `--spacing-2` (8px) +- 容器右边可叠一条 `1px solid #1A2F5A` 基线,激活指示线压在上面 + +### 9.2 锚点(Anchor) + +用于长内容页(如详情页、文档页)的章节快速跳转。 + +#### 9.2.1 结构 + +``` +│ 一级 +│ 一级 (active) +│ 二级 +│ 二级 +│ 一级 +│ 一级 +``` + +#### 9.2.2 规格 + +| 属性 | 取值 | +|------|------| +| 容器背景 | `#122347` (ZS-004-08) 或 `transparent`(嵌入页面侧栏时) | +| 左侧基线 | `1px solid #1A2F5A` (ZS-004-07) 通栏 | +| 项高度 | `32px` | +| 一级项左 padding | `--spacing-3` (12px) | +| 二级项左 padding | `--spacing-5` (24px) | +| 项间距 | `0`(贴合排列) | +| 字号 | `14px Regular` | + +#### 9.2.3 状态配色 + +| 状态 | 一级文字 | 二级文字 | 左侧指示条 | +|------|----------|----------|-----------| +| 默认 | `#C5CFE0` (ZS-004-02) | `#6E88B0` (ZS-004-04) | 无 | +| Hover | `#4096FF` (ZS-002-05) | `#4096FF` (ZS-002-05) | 无 | +| 激活 | `#4096FF` (ZS-002-05) | — | 左侧 `2px solid #4096FF`,覆盖容器基线 | +| 父级(含激活子项) | `#FFFFFF` | — | — | + +- 激活态指示条与基线重合,宽 `2px`,色压主色蓝 `#4096FF` +- 二级项**不单独支持激活态**:当二级被点中时,其父级一级显示激活态,左侧指示条按父级位置渲染 + +#### 9.2.4 滚动同步 + +| 行为 | 规则 | +|------|------| +| 页面滚动 | 监听锚点对应区块进入视口的相对位置,自动切换激活态 | +| 点击锚点 | `behavior: smooth` 滚动到目标,激活指示条 `top` 过渡 `200ms ease-out` | +| 边界处理 | 滚动到页面顶部 → 第一个锚点激活;到页尾 → 最后一个锚点激活 | + +### 9.3 回到顶部(BackToTop) + +固定在视口右下角的悬浮按钮,向上滚动到页面顶部。 + +#### 9.3.1 规格 + +| 属性 | 取值 | +|------|------| +| 形状 | 圆形 `40px × 40px`(`border-radius: 50%`) | +| 图标 | `↑` 上箭头,`16px`,`#FFFFFF` | +| 距视口右边 | `--spacing-5` (24px) | +| 距视口下边 | `--spacing-6` (32px) | +| 出现时机 | 页面 `scrollTop > 200px` 时淡入;回到顶部后淡出 | +| 淡入/淡出 | `opacity 0 → 1` / `1 → 0`,`200ms ease-out` | + +#### 9.3.2 状态配色 + +| 状态 | 背景 | 阴影 | 图标 | +|------|------|------|------| +| 正常(默认) | `#1677FF` (ZS-002-06) | `0 2px 8px rgba(22,119,255,0.12)` (§3.1.4 一级阴影) | `#FFFFFF` | +| 悬停(Hover) | `#0958D9` (ZS-002-07) | `0 4px 16px rgba(22,119,255,0.24)` (§3.1.4 二级阴影) | `#FFFFFF` | +| 点按(Active) | `#003EB3` (ZS-002-08) | 阴影消失,按下感 | `#FFFFFF` | +| 失效 / 不显示 | — | — | 整体 `opacity: 0`,`pointer-events: none` | + +#### 9.3.3 悬停 Tooltip + +Hover 状态下方显示 `回到顶部` 提示气泡: + +| 属性 | 取值 | +|------|------| +| 文字 | `回到顶部`,`12px Regular` | +| 文字颜色 | `#C5CFE0` (ZS-004-02) | +| 背景 | `#1677FF` (ZS-002-06) | +| 圆角 | `2px` | +| 内边距 | 上下 `4px`,左右 `--spacing-2` (8px) | +| 与按钮间距 | `--spacing-2` (8px) | +| 出现时机 | Hover 进入 `300ms` 后显示,离开立即消失 | + +### 9.4 三者的差异与选用 + +| 组件 | 主要用途 | 视觉特征 | +|------|----------|---------| +| 页签 | 同层级内容切换(多 Tab 互斥) | 文字 + 下划线(横/纵) | +| 锚点 | 长页面内章节跳转 | 文字 + 左侧指示条 + 层级缩进 | +| 回到顶部 | 长页面快速返回起点 | 圆形悬浮按钮 + 上箭头 | From e3d8e534f6e5ac79a661dedad4ef9e81b2060765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Mon, 25 May 2026 22:39:50 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/docs/multi-agent-backend-dev.md | 405 ++++++++++++++++++ frontend-web/docs/multi-agent-frontend-dev.md | 4 +- .../src/roundtable-planning/api/intent.ts | 96 ++++- .../pages/RoundtablePlanningPage.tsx | 134 ++++-- .../backend/app/gateway/routers/intent.py | 52 ++- .../app/gateway/routers/multi_agent.py | 96 +++++ 6 files changed, 739 insertions(+), 48 deletions(-) diff --git a/frontend-web/docs/multi-agent-backend-dev.md b/frontend-web/docs/multi-agent-backend-dev.md index fa254fa1..c4724d83 100644 --- a/frontend-web/docs/multi-agent-backend-dev.md +++ b/frontend-web/docs/multi-agent-backend-dev.md @@ -13,6 +13,7 @@ - 三个圆桌 Router(`intent / recommend / multi_agent`)**全部走 HTTP loopback**:在同进程内通过 `httpx.AsyncClient` 调本机 `127.0.0.1` 的 `/api/threads`、`/api/agents`、`/api/threads/{id}/runs/stream`。 - **所有 loopback `httpx.AsyncClient` 必须 `trust_env=False`**。开发机若设了 `HTTP_PROXY` / `HTTPS_PROXY`,httpx 默认会把 loopback 也走代理 → 不可达 → 单次请求多出 40 秒超时(典型症状日志:`headers_in=42.160s`)。 - **席位 agent 持久注册**(磁盘 `.deer-flow/agents/` + `_sync_legacy_agents`),**协调智能体每次新建**(命名 `roundtable-coordinator-{timestamp36}`,DB 累积,需后续 GC)。 +- **`.deer-flow/` 整个目录被 git ignore**(仓库根 `.gitignore` 第 64 / 74 行),`git clone` 后内置 agent 目录在部署机上**不会存在**——任何环境都必须把 `roundtable-intent` + `roundtable-recommender` 两个**功能型 agent** 持久化进发布物,否则 `/api/intent/stream` 报 500。**互联网 demo 还需要 6 个示例研讨席位**(`roundtable-intelligence` 等),**内网部署用客户自有 agent 替代**这些席位,无需打包。详见 §11.7。 - **`init` 是 SSE 而非一次性 JSON**:用 `asyncio.as_completed` 按真实完成顺序推送 `seat_ready`,前端因此能逐个点亮席位。 - **`init_done.thread_ids` 包含协调智能体自身**(`agent_threads[main_agent_name] = coord_thread`),前端 Step 3 列文件时需自行过滤。 - **澄清协议统一**:Step 1 与 Step 2 leader 都借 `ask_clarification` + 终态 `clarification` 帧驱动同一套选项卡片组件;`allow_multiple` 默认 false(单选、点击即发送)。 @@ -679,6 +680,235 @@ grep "special-timing" .runtime/logs/*.log # payload_built / first_upstream_l 2. 前端候选池 / 推荐逻辑纳入该 id 3. Step 2 `agent_names` 传入即可 — **multi_agent 路由无需改** +### 11.7 内置 agent 部署持久化(必读,关系到内网部署能否启动) + +#### 11.7.1 问题背景 + +圆桌功能涉及的 agent 都靠**磁盘文件**激活(`.deer-flow/agents//{config.yaml,SOUL.md}`),但仓库根 `.gitignore` 第 64 / 74 行有: + +``` +offline-backend-20260512/backend/.deer-flow/* +``` + +整个 `.deer-flow/` 被 git 排除。新机器 `git clone` 后 `.deer-flow/agents/` 不存在 → 圆桌相关 agent 缺失 → 进入 `/api/intent/init` 后 `_create_thread` 本身能成功,但下一步 `/api/intent/stream` 真正调用 LangGraph 时会因为 agent 解析失败抛 500(典型表现:前端 Step 1 init 一次成功,下一句话发出去就 500)。 + +#### 11.7.1.1 哪些 agent **必须**打包,哪些**不需要** + +圆桌涉及的 agent 分**两类**,部署侧需求完全不同: + +| Agent | 角色 | 是否必须打包发布 | +|-------|------|------------------| +| `roundtable-intent` | **功能型**:Step 1 任务理解,所有部署都用它 | ✅ **必须**(任何环境) | +| `roundtable-recommender` | **功能型**:Step 1→2 之间从候选池里挑推荐,所有部署都用它 | ✅ **必须**(任何环境) | +| `roundtable-intelligence` | 示例研讨席位(情报收集) | ⚠️ **互联网 demo 用**,内网不需要 | +| `roundtable-environment` | 示例研讨席位(环境评估) | ⚠️ 同上 | +| `roundtable-solution-design` | 示例研讨席位(方案设计) | ⚠️ 同上 | +| `roundtable-risk-review` | 示例研讨席位(风险审查) | ⚠️ 同上 | +| `roundtable-execution-plan` | 示例研讨席位(执行规划) | ⚠️ 同上 | +| `roundtable-summary` | 示例研讨席位(总结陈述) | ⚠️ 同上 | +| `roundtable-coordinator-*` | 协调智能体壳,每次会话动态新建 | ❌ **不打包**(运行时生成) | + +**关键区分**: +- `roundtable-intent` / `roundtable-recommender` 是**圆桌功能本身的一部分**——没有它们,整个 Step 1 + 推荐流程跑不起来,跟客户业务无关 +- 6 个 `roundtable-<领域>` 席位只是**互联网 demo 用的示例研讨者**,让访客打开页面就能看到完整三步流程。内网部署面对的是客户自己的业务场景,会把客户自有的领域 agent(行业专家、合规审查员、业务规则引擎……)注册成候选 agent;这时 6 个示例席位反而成噪声 + +**对应的部署组合**: + +| 部署场景 | 打包内容 | +|----------|----------| +| **互联网 demo** | `roundtable-intent` + `roundtable-recommender` + **6 个示例席位** | +| **内网客户部署** | `roundtable-intent` + `roundtable-recommender`(仅此 2 个,**不带** 6 个示例席位) | +| **本地开发** | 全要(方便快速跑通) | + +**内网部署的额外注意**(去掉 6 个示例席位时): + +1. **客户自有的候选 agent 必须先存在**:内网部署落地时,客户的业务 agent 应该已经注册进 `.deer-flow/agents/`(手工建目录或通过 `POST /api/agents` 创建)。否则候选池为空,推荐拿不到任何 picks。 +2. **前端 `FALLBACK_AGENT_IDS` 需要适配**:前端 `RecommendAgentsDialog.tsx` 里硬编码了 6 个示例席位 id 作为 fallback;若推荐失败回退路径会拿这 6 个去 `candidates` 里交集,内网部署该交集恒为空,**fallback 会失败**。建议要么: + - **方案 1**(最稳):内网客户的候选池足够丰富 + recommender SOUL 调好,让推荐成功率 ~100%,不依赖 fallback + - **方案 2**:把 `FALLBACK_AGENT_IDS` 改成从后端 `/api/agents` 取前 6 个 `published=true` 的候选 agent(动态 fallback)。改动点见前端文档 §6.2 `RecommendAgentsDialog` +3. **推荐 SOUL(`roundtable-recommender/SOUL.md`)通常无需改**:SOUL 本身没写死候选 id,只描述「从给的候选池里挑」;候选池由前端传入,模型读到客户自有 agent 后会照样工作。 + +下文给出三种修复方案,**推荐方案 A**(最简单,无需写代码)。 + +#### 11.7.2 方案 A:`.gitignore` 白名单(推荐) + +让 git 只跟踪 **`roundtable-intent` + `roundtable-recommender` 两个功能型 agent** 的 `config.yaml` 与 `SOUL.md`,其它一律忽略。6 个示例席位**不进白名单**——互联网 demo 部署机如果需要它们,按 §11.7.2.A 在该分支单独追加白名单;内网部署机直接享受「干净的 `.deer-flow/`」。 + +**步骤 1:编辑仓库根 `.gitignore`** + +在最后追加白名单段(位置很重要,**必须在 `offline-backend-20260512/backend/.deer-flow/*` 之后**,否则被前面的通配符吃掉): + +```gitignore +# === 圆桌功能型 agent 跟随仓库发布(任何部署都需要) === +# 父目录先 unignore,git 才能往下递归 +!offline-backend-20260512/backend/.deer-flow/ +!offline-backend-20260512/backend/.deer-flow/agents/ +# 仅放行 2 个功能型 agent;6 个示例席位由互联网分支自行放行 +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-recommender/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-recommender/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-recommender/SOUL.md +``` + +⚠️ **不要**直接写 `!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/**`——`git` 不会进入还在 ignore 的父目录,必须逐层 un-ignore,再精确放行文件。 + +**步骤 2:强制 add 并提交** + +```bash +# 验证白名单生效 +git check-ignore -v offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/config.yaml +# 期望输出无任何匹配,或最后一条匹配是上面的 ! 行 + +# 加入索引(只加 2 个功能型 agent) +git add offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent +git add offline-backend-20260512/backend/.deer-flow/agents/roundtable-recommender + +git status # 应只看到 2 × 2 = 4 个新文件,绝不出现 users/、threads/、data/、6 个示例席位 + +git commit -m "chore: 把圆桌功能型 agent(intent + recommender)纳入版本管理" +``` + +**步骤 3:部署机验证** + +```bash +# clone 之后立刻 +ls offline-backend-20260512/backend/.deer-flow/agents/ +# 内网部署:应看到 roundtable-intent/ + roundtable-recommender/(+ 客户自有 agent) +# 互联网 demo:还应看到 6 个 roundtable-<领域>/ 示例席位 +``` + +启动后端,前端打开圆桌规划页,Step 1 第一句话发出去能正常拿到 streaming 响应而不是 500,就说明 OK。 + +##### 11.7.2.A 互联网 demo 分支:追加 6 个示例席位 + +互联网 demo 维护一条独立分支(或独立 `.gitignore` 段,用 build flag 切),在白名单后追加: + +```gitignore +# === 仅互联网 demo 需要的 6 个示例研讨席位 === +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intelligence/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intelligence/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-intelligence/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-environment/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-environment/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-environment/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-solution-design/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-solution-design/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-solution-design/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-risk-review/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-risk-review/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-risk-review/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-execution-plan/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-execution-plan/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-execution-plan/SOUL.md +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-summary/ +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-summary/config.yaml +!offline-backend-20260512/backend/.deer-flow/agents/roundtable-summary/SOUL.md +``` + +`git add` 对应 6 个目录、提交,互联网分支即拿到全部 8 个内置 agent。两条分支共享同一份 `roundtable-intent / roundtable-recommender`,SOUL 调整对两边都生效,避免双维护。 + +**优点**: + +- 零代码改动;新机器 `git clone` 即可,部署文档不用单独说「记得拷一下 `.deer-flow/agents/`」 +- 修改 SOUL 直接走 git 走 review 流程 +- 多人协作时 SOUL 改动可见 + +**注意事项**: + +- `git status` 检查时确保**只**有 16 个内置文件进了索引,**绝对不能**包括 `users/`、`threads/`、`data/*.db`、`roundtable-coordinator-*/` 这些运行时数据 +- `roundtable-coordinator-mp*/` 这类历史会话残留目录会被 ignore 规则自动忽略(白名单没放行它们),无需手动清理 +- 部署机如果之前手动建过 `.deer-flow/agents/roundtable-intent/`,`git pull` 会把仓库版本覆盖上去——这是期望行为 + +#### 11.7.3 方案 B:源码内置 + 启动时种子化 + +如果不想让 `.deer-flow/` 这种「运行时目录」出现在 git 里,可以把 SOUL/config 放进 `packages/harness/` 里作为只读资源,启动时检查 `.deer-flow/agents//` 不存在就从源拷过去。 + +**步骤大纲**: + +1. 在 `packages/harness/deerflow/agents/builtin_personas/` 下建 8 个目录(`roundtable-intent/` 等),把 SOUL.md + config.yaml 搬过去 +2. 在 `app/gateway/app.py` 的 `lifespan` 启动阶段新增 `_seed_builtin_roundtable_agents()`: + ```python + def _seed_builtin_roundtable_agents() -> None: + from importlib.resources import files + src_root = files("deerflow.agents.builtin_personas") + dst_root = get_paths().agents_dir() # .deer-flow/agents/ + for agent_id in BUILTIN_ROUNDTABLE_AGENT_IDS: # 8 个常量 + dst = dst_root / agent_id + if dst.exists(): + continue # 已有(可能是用户改过的),不覆盖 + dst.mkdir(parents=True) + for fname in ("config.yaml", "SOUL.md"): + (dst / fname).write_bytes((src_root / agent_id / fname).read_bytes()) + ``` +3. 紧接着调一次 `_sync_legacy_agents(store)`,把这些刚落地的目录 upsert 进 `agents` 表 +4. 把 `BUILTIN_ROUNDTABLE_AGENT_IDS` 常量也导出给 `recommend.py` / `multi_agent.py` 共享 + +**优点**: + +- `.deer-flow/` 仍然作为「运行时数据目录」语义干净 +- 运维可以手动改 `.deer-flow/agents/roundtable-intent/SOUL.md` 临时调试,重启不被覆盖(`if dst.exists(): continue`) +- 升级时如果想强制刷新,加个 `--force-reseed-builtins` 启动参数即可 + +**缺点**: + +- 多一段 bootstrap 代码 + 一份 `pyproject.toml` 的 `package-data` 声明(确保 SOUL.md 跟着 wheel 一起发布) +- 「现场改 SOUL 不被覆盖」既是优点也是坑——A 改了 SOUL 但忘了同步源码,下次新机器部署又回到旧版本 + +#### 11.7.4 方案 C:纯运维流程(最弱,不推荐) + +不动代码、不动 .gitignore,发布说明里写一行: + +> 部署前请将 `<开发机>:offline-backend-20260512/backend/.deer-flow/agents/` 整个目录 `scp` 到部署机同位置。 + +只在「不允许改仓库结构」或「初期试跑」时用。长期维护几乎一定会有人忘记拷而部署失败。 + +#### 11.7.5 方案选择决策 + +| 场景 | 推荐方案 | +|------|----------| +| 单仓库多人协作、SOUL 经常调整 | **A**(.gitignore 白名单) | +| SOUL 极少改,希望严格区分代码与运行时数据 | **B**(源码 + lifespan 种子化) | +| 一次性 POC / 即将切换到 B | C | + +无论选哪种,部署机第一次启动后都建议跑这条 smoke test 验证: + +```bash +curl -X POST http://127.0.0.1:8001/api/intent/init \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{}' +# 期望: 200 + {"status":"success","agent_id":"roundtable-intent","thread_id":"..."} + +curl -X POST http://127.0.0.1:8001/api/intent/stream \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"thread_id":"<上一步返回的>","message":"测试一下"}' +# 期望: 200 + SSE 长连接,最后一帧 status 为 asking 或 clarification +# 若 500 → 看后端日志,大概率是 SOUL.md 找不到或 LLM 外网不通(看上一条排查项) +``` + +#### 11.7.6 推荐智能体(`roundtable-recommender`)与研讨席位的关系(注:完整定制流程见 §11.8) + +推荐路由 `recommend.py` 用到的 `roundtable-recommender` 与「研讨席位」是**两件事**: + +- **`roundtable-recommender`**:负责从候选池里挑 picks 的「推荐者」本身,是个 LLM agent,**功能型**,所有部署必须有 +- **研讨席位**(无论是 6 个示例还是客户自有 agent):是「被挑选的候选」,前端 `filterRecommendCandidates` 过滤后送进 `/api/recommend/stream` 的 `candidates` 数组;recommender 在 SOUL 里看到候选池后才能输出 `[RECOMMEND_READY]` JSON + +「推荐改成动态」改的是**从池子里挑哪几个**,不是「池子可以没有」。**池子的来源在两类部署里不同**: + +| 部署 | 候选池来源 | +|------|------------| +| 互联网 demo | 仓库自带的 6 个 `roundtable-<领域>` 示例席位(+ 用户自建的) | +| 内网客户 | 客户自有的业务 agent(通过 `POST /api/agents` 或 `.deer-flow/agents//` 注册)| + +`roundtable-recommender/SOUL.md` 本身没写死任何候选 id(只描述「从给的候选池里挑」),所以内网部署即使候选池从「6 个示例」换成「客户自有 agent」,**recommender 的 SOUL 无需改动**,候选池由前端传入。 + +**前端 fallback 行为差异**: + +互联网 demo 因为有这 6 个内置示例席位垫底,推荐失败时 `FALLBACK_AGENT_IDS ∩ candidates` 还能拿到回退选项;**内网部署该交集恒为空**,回退路径失效——所以内网部署应通过「候选池足够丰富 + recommender SOUL 调好」让推荐成功率接近 100%,或改造前端 `FALLBACK_AGENT_IDS` 为动态从 `/api/agents` 取前 N 个(见前端文档 §6.2)。 + ### 11.4 新增 Step 1 工具 若需新工具,须同步修改 `intent.py` 的 `excluded_tools` 黑名单(当前默认禁 15 个工具,仅 `ask_clarification` 实际可用)。 @@ -691,6 +921,181 @@ grep "special-timing" .runtime/logs/*.log # payload_built / first_upstream_l `_append_thread_message` 通过 `GET state → mutate → POST state` 实现,是为了与 LangGraph checkpoint 一致;如果将来 Gateway 提供 `POST /api/threads/{id}/messages/append` 这类原子接口,可换上,降低并发竞争(虽然圆桌当前不会并发广播同一 thread)。 +### 11.8 添加 / 替换任务推荐智能体(`roundtable-recommender`) + +推荐智能体 id 在后端是**硬编码**常量(`recommend.py:63` → `RECOMMENDER_AGENT_ID = "roundtable-recommender"`),路由不接收 `agent_id` 参数。因此「添加推荐智能体」**不是新增一个 id**,而是落地这个固定 id 对应的文件(首次部署)或**就地改 SOUL**(定制行为)。 + +#### 11.8.1 何时需要动它 + +| 场景 | 是否需要动 SOUL | +|------|-----------------| +| 全新部署,目录还不存在 | ✅ 必须先把目录搭起来(§11.8.2) | +| 候选池从 6 个互联网示例换成客户自有 agent | ❌ **不用改**——SOUL 没写死候选 id,候选池由前端传入 | +| 想改变推荐风格(更激进 / 更保守 / 偏好某领域) | ✅ 改 SOUL(§11.8.4) | +| 想让模型偏好你新加的领域 agent | ⚠️ **优先**改新 agent 的 `description`(候选池 prompt 用的就是它),其次才改 recommender SOUL | +| 想改输出 JSON 格式 | ❌ **不要**——后端正则 `_RECOMMEND_READY_PATTERN` 写死了 `[RECOMMEND_READY]` + ```json ``` 块;改 SOUL 输出会让后端解析失败走 fallback | + +#### 11.8.2 标准目录结构 + +``` +.deer-flow/agents/roundtable-recommender/ +├── config.yaml # id / name / description(只是 DB 元数据,不进 prompt) +└── SOUL.md # 行为契约,详见 11.8.4 +``` + +**`config.yaml` 模板**(**id 必须严格等于 `roundtable-recommender`**,否则 `recommend.py` 找不到): + +```yaml +description: 智能体推荐专员,基于用户的任务目标与约束,从候选智能体池中挑选最相关的 2-6 个智能体并给出推荐理由,为多智能体圆桌研讨准备最终参会名单 +id: roundtable-recommender +name: 智能体推荐 +``` + +字段说明: +- `id`:**不可改**,与 `RECOMMENDER_AGENT_ID` 常量绑死 +- `name`:DB 显示名,候选池过滤时不参与(`filterRecommendCandidates` 按 id 排除) +- `description`:DB 元数据,不进 prompt;改了不会影响推荐效果 + +**SOUL.md** 内容见 §11.8.4 模板。新增之后按 §11.7.2 把这两个文件加入 git 白名单 + commit 即可。 + +#### 11.8.3 后端给推荐智能体喂的 prompt 长什么样 + +`recommend.py:_build_prompt()` 把前端传的 intent + candidates 拼成下面这段,作为**唯一一条 user message** 投递: + +``` +任务目标:对生产系统进行安全改造,满足等保 2.0 合规要求 +约束:预算 ≤ 500 万元; 工期 ≤ 6 个月; 必须满足等保 2.0 +关键假设:现有系统具备基础数字化能力; 允许预定窗口停机 + +候选智能体池: +- roundtable-intelligence (情报收集): 检索同类历史案例、行业最佳实践与外部情报 +- roundtable-solution-design (方案设计): 产出主备方案与关键路径 +- my-cost-calc (成本测算): 基于预算和资源单价计算总成本 +- ... + +请按 SOUL 中规定的格式输出推荐结果(自然语言思路 + [RECOMMEND_READY] + json 块)。 +``` + +**关键事实**: +- **候选 agent 的 `name` 与 `description` 在 prompt 里就是它的「介绍」**——这是模型判断「这个 agent 适不适合本任务」的全部信息。客户自有 agent 想被推荐准确,`description` 必须写**任务导向**的能力描述,不要写 "由 XX 团队维护" 这种元信息 +- **候选池由前端过滤后传入**,后端不读 DB;前端 `filterRecommendCandidates` 已剔除 `roundtable-intent / roundtable-recommender / roundtable-coordinator-*` +- SOUL 看不到「intent agent 上一轮说了什么 / 用户原始 message」,只看到结构化的 `objective / constraints / assumptions`——这是设计选择,让推荐稳定 + +#### 11.8.4 SOUL 设计要点(模板 + 注解) + +SOUL **必须**满足三件事,其它都可定制: + +1. **输出格式严格**:自然语言思路 → `[RECOMMEND_READY]` 单独成行 → ```json {"picks": [{"agent_id": "...", "reason": "..."}, ...]} ``` +2. **`agent_id` 严格来自候选池**:模型造词出来会被 `_validate_picks` 过滤掉 +3. **2 ≤ picks ≤ 6**:少于 2 → 整轮判 `asking` 走 fallback;多于 6 → 后端截断 + +下面是一份可直接用的最小 SOUL,重点行带注释(**真正落盘时去掉 ``**): + +```markdown +# 智能体推荐专员 + +## 角色定位 +你是多智能体圆桌研讨的"智能体推荐专员"。任务理解专员已经把用户需求整理成 +「任务目标 + 约束 + 关键假设」交给你,你的工作是从一个**候选智能体池**里, +挑出**最适合**参加这次圆桌研讨的 2-6 个智能体,并为每一位写一句推荐理由。 + +## 核心职责 +- 仔细阅读用户的任务目标与约束 +- 浏览候选池中每个智能体的 `name` 与 `description` +- 选出与本次任务**最相关**的 2-6 个智能体(数量动态,不必凑齐 6 个) +- 为每个推荐的智能体写一句**具体、可验证**的推荐理由 +- 不推荐重复职能 / 不推荐与任务明显无关的智能体(宁缺毋滥) + +## 数量约束 +- **下限**:至少 2 个 / **上限**:最多 6 个 +- 候选池不足 2 个时,有几个推几个 + +## 输出协议(后端用正则识别,严格执行) +1. 先用自然语言简短说明推荐思路(1-3 句话) +2. 输出标记 `[RECOMMEND_READY]` 单独成行 +3. 紧接一个 ```json 代码块: + + ```json + { + "picks": [ + {"agent_id": "<候选池中的 id>", "reason": "<一句推荐理由>"}, + ... + ] + } + ``` + +格式注意: +- `[RECOMMEND_READY]` **单独成行**,前后无其他字符 +- `agent_id` **严格**来自候选池,不要造词 +- `reason` **具体**,不要"很合适"这种空话 + +## 边界 +- 不调用任何工具(已被后端 excluded_tools 屏蔽) +- 不评价候选 agent 优劣,只判断"是否适合本次任务" +- 不发起对话、不反问用户 + +## 示例 + +``` + +##### 定制方向 + +- **偏好新加的 agent**:在「示例」里把它列进候选池并出现在推荐结果中,模型会学到这种 id 也可推荐。比单纯在「核心职责」里加文字约束有效 +- **行业偏向**:在「角色定位」加一句「优先考虑能直接对接 <行业> 合规要求的智能体」 +- **激进 vs 保守**:调「数量约束」——例如「能挑出强相关的 3 个就不要凑 6 个」会让 picks 缩到 3-4 个 +- **不要做的事**: + - ❌ 改 `[RECOMMEND_READY]` 为别的标记 → 解析失败 + - ❌ 把 JSON 结构改成 `{"recommendations": [...]}` 之类 → 解析失败 + - ❌ 让模型只输出 JSON 不带前面的自然语言 → 前端 `parseRecommendStreamContent` 把整段当 `rationale`,弹窗顶部为空白 + +#### 11.8.5 验证流程 + +部署完成后跑 smoke test 确认推荐路径通: + +```bash +# 1. 确认 agent 文件落地 +ls .deer-flow/agents/roundtable-recommender/ +# 期望: SOUL.md config.yaml + +# 2. 确认 DB 已 upsert(任意 /api/agents 请求会触发 _sync_legacy_agents) +curl -H "Authorization: Bearer " http://127.0.0.1:8001/api/agents | \ + python -c "import sys,json; ids=[a['id'] for a in json.load(sys.stdin).get('agents',[])]; print('roundtable-recommender' in ids)" +# 期望: True + +# 3. 触发一次推荐(用最小 intent + 候选池) +curl -X POST http://127.0.0.1:8001/api/recommend/stream \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "intent": { + "objective": "测试推荐链路", + "constraints": ["响应时间 < 30s"], + "assumptions": [] + }, + "candidates": [ + {"agent_id": "agent-a", "name": "A", "description": "做 X"}, + {"agent_id": "agent-b", "name": "B", "description": "做 Y"}, + {"agent_id": "agent-c", "name": "C", "description": "做 Z"} + ] + }' +# 期望(SSE 流):最后一帧 data: {"status":"done","content":"...","picks":[...]} +# 若 picks 是空 → SOUL 没产生合法 [RECOMMEND_READY] / agent_id 不在候选池 +# 若整轮 status=error → 看后端 _diagnose_exception 输出的 hint +``` + +#### 11.8.6 故障排查清单 + +| 现象 | 排查方向 | +|------|----------| +| 推荐永远走 fallback(status=asking) | 后端日志 grep `recommend stream`,看 `last_ai_content` 是不是不含 `[RECOMMEND_READY]`——SOUL 没遵守协议 | +| `picks` 数量被截断到 6 | 后端 `_MAX_PICKS=6` 故意限制,前端用户可再手加到 8 | +| 模型推荐了候选池里没有的 id | `_validate_picks` 会丢掉,但若有效项不足 2 个会整轮走 fallback;改 SOUL 强调「严格来自候选池」 | +| 推荐结果不偏向客户自有 agent | 先检查该 agent 的 `description` 是不是写得「任务导向」;再考虑在 SOUL 示例里加它 | +| 推荐稳定性差(同 intent 重跑结果飘) | 推荐 model 用低 temperature(在 `roundtable-recommender/config.yaml` 加 `model: <某个低 temp 模型>` 或直接前端选 deterministic 模型) | +| 前端弹窗顶部 rationale 区空白 | SOUL 让模型只输出 JSON 没有自然语言;恢复成「先思路 + 后 JSON」 | + --- ## 12. 故障排查 diff --git a/frontend-web/docs/multi-agent-frontend-dev.md b/frontend-web/docs/multi-agent-frontend-dev.md index f4336e7c..6b7de99d 100644 --- a/frontend-web/docs/multi-agent-frontend-dev.md +++ b/frontend-web/docs/multi-agent-frontend-dev.md @@ -370,9 +370,9 @@ isOrchestrating = false - 追加一条「⚡ [模型偏好已保存] ...」对话气泡 - **不**立即重启编排;下一轮该席位被派活时 `runOrchestration` 内会读 `agentConfigs[meta.speakerId]?.model || selectedModel || undefined` 作为 special 调用的 `model` 参数 -### 5.6 全局模型选择器(顶栏) +### 5.6 全局模型选择器(右侧 sidebar 顶部) -- **位置**:顶栏右侧「新建任务」按钮左边,` setSelectedModel(e.target.value)} - disabled={modelsLoading || availableModels.length === 0} - title={ - modelsLoading - ? "正在加载模型列表…" - : availableModels.length === 0 - ? "未在 config.yaml 中配置任何模型" - : "切换全局推理模型;不影响 Persona 抽屉里已设置的 per-seat 覆盖" - } - className="bg-transparent border-none outline-none text-xs font-bold text-slate-700 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed dark:text-foreground" - > - {availableModels.length === 0 && ( - - )} - {availableModels.map((m) => ( - - ))} - - -