406 lines
24 KiB
Markdown
406 lines
24 KiB
Markdown
---
|
||
title: "refactor: Remove all emoji from agentkit"
|
||
date: 2026-07-02
|
||
type: refactor
|
||
status: approved
|
||
approved_at: 2026-07-02
|
||
origin: user-direct (ce-plan invocation, 2026-07-02)
|
||
---
|
||
|
||
# refactor: Remove all emoji from agentkit
|
||
|
||
## Summary
|
||
|
||
`@board` 私董会数据恢复的修复已就位。但项目长期混用 emoji(专家头像、CLI 状态、私董会横幅、bitable 默认图标等),与既有的 Ant Design Vue Outlined 图标家族视觉风格不一致,且跨字体/OS 渲染不稳定。本次重构一次性把全部 emoji 收敛到三套等价替代:
|
||
|
||
- **头像/单字符位** → 统一走 `expertIdentity.ts` 风格的「CJK/ASCII 首字符大写」;YAML/DB 中仍是字符串字段,前端渲染照常。
|
||
- **横幅/卡片图标** → 改用 Ant Design Vue 组件(`BankOutlined`/`AuditOutlined`/`TeamOutlined` 等)。
|
||
- **CLI 状态标记** → Rich 文本标签(`OK`/`FAIL`/`WARN`) + Rich 颜色样式。
|
||
|
||
执行顺序按 4 个批次分阶段落地,每批可独立提交/回滚。
|
||
|
||
## Problem Frame
|
||
|
||
### 现状
|
||
|
||
全仓 22 个文件含 emoji 字符,覆盖 7 个语义类别(专家头像、bitable 默认、私董会横幅、辩论横幅、CLI 状态、终端提示、DB 默认值)。其中:
|
||
|
||
- `configs/experts/*.yaml` 15 个文件使用 emoji 作为 avatar
|
||
- `bitable/db.py` 用 `📋` 作 `icon` 字段的 DB default(与 `models.py:80` 已有 `"table"` 默认不一致)
|
||
- `chat.py` 把 `🏛️ 私董会开始:…` 写入 `SqliteConversationStore`,作为无障碍/纯文本回退
|
||
- `BoardBannerCard.vue`、`DebateBannerCard.vue`、`DebateConclusionCard.vue`、`UserBubble.vue` 直接渲染 emoji
|
||
- 4 个 CLI 文件用 `✓` `✗` `⚠` 作状态标记
|
||
- 3 个测试文件把 emoji 作为 fixture
|
||
|
||
### 触发原因
|
||
|
||
- 跨平台渲染不稳定:Linux 服务器无 Noto Color Emoji 时私董会横幅显示豆腐方块
|
||
- 视觉风格不一致:与项目其余 Ant Design Vue Outlined 图标家族(1.5px stroke)不协调
|
||
- DB schema 漂移:`bitable/models.py:80` 已迁到稳定 key `"table"`,但 `bitable/db.py:68,216` ORM/SQL 仍用 `📋`
|
||
|
||
### 期望效果
|
||
|
||
- 全部 emoji 字符从仓库代码中清除(注释、文档除外)
|
||
- 视觉风格统一为 Ant Design Vue Outlined + 字符首字母
|
||
- 旧 Bitable 行的 emoji 值通过前端 `resolveBitableIcon` 惰性回退到 `TableOutlined`,无需迁移
|
||
- CLI 输出在所有终端(包括无 emoji 字体)下稳定显示
|
||
|
||
## Key Technical Decisions
|
||
|
||
### KTD1:专家头像改为「首字符大写」策略,与 `expertIdentity.ts` 行为对齐
|
||
|
||
**决策**:YAML 中 `avatar` 字段从 emoji 改为对应专家名的首字符大写(中文取首个 CJK 字,英文取首字母大写)。保留 `IBoardExpert.avatar` 字段(不删),值由后端 `ExpertConfig` 解析或前端 `pickExpertIdentity` 兜底。
|
||
|
||
**理由**:
|
||
- `frontend/src/components/chat/helpers/expertIdentity.ts:55` 的 `pickExpertIdentity` 默认就是首字符策略
|
||
- 删除字段会破坏既有 WebSocket payload(`board_started.experts[].avatar`)契约
|
||
- 字符位渲染跨字体稳定
|
||
|
||
**取舍**:放弃了 emoji 头像的「人格化表达」,换取视觉一致性与跨平台稳定。Lead/主持人仍可通过 `color` token + tag 区分。
|
||
|
||
### KTD2:横幅/卡片图标改用 Ant Design Vue 组件,shell.avatar 改为 `Component` 类型
|
||
|
||
**决策**:`BoardBannerCard` / `DebateBannerCard` / `DebateConclusionCard` / `UserBubble` / `useMessageRenderer.ts` 引入 Ant Design Vue 组件(如 `<BankOutlined>`/`<AuditOutlined>`/`<TeamOutlined>`/`<CheckOutlined>`),把 shell.avatar 字段从 `string` 改为 `Component` 类型。
|
||
|
||
**理由**:
|
||
- 项目图标家族已统一为 Outlined(`Sidebar`、`Tabs`、`Tool call cards`)
|
||
- `useMessageRenderer.ts` 现有 `shell.avatar` 字段在 `MessageShell.vue` 渲染,与后端 `expert_avatar` 字段不冲突(那是单个专家的 avatar)
|
||
|
||
**影响**:`MessageShell.vue` 接收 shell 的 avatar 字段如果是 `Component` 类型则用 `<component :is="..." />`;如果是 `string`(首字母)则原样渲染。
|
||
|
||
### KTD3:CLI 状态标记用 Rich 文本标签 + 颜色,弃用 `✓` `✗` `⚠`
|
||
|
||
**决策**:`OK` / `FAIL` / `WARN` 作为文本标签,配合 Rich `[green]` / `[red]` / `[yellow]` 颜色。横幅标题用 `[OK 验收结果]` / `[WARN 风险标记]` 形式保留 emoji 缺失的视觉强度。
|
||
|
||
**理由**:
|
||
- 终端字体差异:Windows Terminal / 容器 tty / 旧版 macOS Terminal 对 Unicode 符号渲染不一致
|
||
- 颜色已能传达同样信息量
|
||
- 与 Python `rich` 库的最佳实践一致(标签 + 颜色)
|
||
|
||
### KTD4:Bitable DB 默认值从 `📋` 改为 `table`,旧数据惰性收敛
|
||
|
||
**决策**:
|
||
- `bitable/db.py:68` ORM `default="📋"` → `default="table"`
|
||
- `bitable/db.py:216` SQL `DEFAULT '📋'` → `DEFAULT 'table'`
|
||
- **不写迁移脚本** — 前端 `resolveBitableIcon` 已对未知值回退到 `TableOutlined`,下次访问旧行时自动收敛
|
||
- 新建行直接用 `table`;旧行 render 时也是 `TableOutlined`,零差异
|
||
|
||
**理由**:
|
||
- `bitable/models.py:80`(Pydantic)已经是 `"table"`,ORM 端对齐消除双默认值
|
||
- 避免引入 Alembic 迁移;旧数据下次访问收敛
|
||
- `bitable.ts:73` 和 `bitableIcons.ts:86` 已说明「legacy emoji 字符串自动回退」
|
||
|
||
**已知限制**:旧行在 DB 中仍是 `📋` 字符串。若未来要做 DB 备份快照或导出/导入,可能需要清理脚本。本次不处理。
|
||
|
||
### KTD5:App.vue 字体回退去掉 emoji 字体声明
|
||
|
||
**决策**:`App.vue:109-110` 中的 `'Apple Color Emoji'`、`'Segoe UI Emoji'`、`'Segoe UI Symbol'`、`'Noto Sans Emoji'` 字体回退删除。
|
||
|
||
**理由**:既然不再渲染任何 emoji 字符,浏览器/系统不会下载这些字体;删去减少 30KB+ 的潜在字体下载请求。保留 `sans-serif` 作为最终回退。
|
||
|
||
### KTD6:测试 fixture 同步更新,保持断言真实
|
||
|
||
**决策**:3 个测试文件中的 emoji fixture(`🤖` `🎯` `🐱` `💡` `🦊` `🐼` `🏛️`)改为对应的首字母大写(A/T/C/I/F/P 等),断言中的 `expect(avatars[0].textContent).toBe('🤖')` 同步改为 `'A'`。
|
||
|
||
**理由**:`restoreBoardStateFromMessages` 的 7 个新单元测试已锁定「缺 avatar/缺 color 走 fallback」行为,fixture 改字母不会绕过 fallback 链(仍由 `pickExpertIdentity(name)` 决定)。
|
||
|
||
## Scope Boundaries
|
||
|
||
### 包含
|
||
|
||
- 22 个源码文件中的 emoji 字符移除(不含文档注释)
|
||
- 4 批次的实施顺序与每批对应的文件清单
|
||
- DB 默认值从 emoji 改为稳定 key(无 schema migration)
|
||
- App.vue 字体回退清理
|
||
- 3 个测试文件 fixture 同步
|
||
- 验证:typecheck / pytest / vitest / 视觉冒烟
|
||
|
||
### 不包含(Deferred to Follow-Up Work)
|
||
|
||
- **emoji 自动检测**(CI 守卫 / pre-commit 钩子)— 防止新增 emoji 复发,作为后续独立计划
|
||
- **Bitable 旧行清理脚本**(DB 备份/导出场景下需要时再做)
|
||
- **`@ant-design/icons-vue` 包增量依赖** — 该包已是项目依赖(`bitableIcons.ts` 现有 import),无新增
|
||
- **CLI 国际化(i18n)** — `OK`/`FAIL`/`WARN` 目前硬编码中英混排;国际化是更大范围重构,本次不动
|
||
- **Tauri 桌面端 emoji 字体** — Tauri 客户端的 emoji 字体回退不在本计划范围
|
||
- **avatar 字段语义升级**(如改成 `avatar_url` 支持真实头像图片)— 是产品级决策,超出本次范围
|
||
|
||
## System-Wide Impact
|
||
|
||
| 受影响方 | 影响 |
|
||
| --- | --- |
|
||
| **终端用户(Web GUI)** | 私董会/辩论横幅、专家头像、Bitable 图标视觉变化;语义不变 |
|
||
| **终端用户(CLI)** | `agentkit` CLI 表格/状态标记从符号改为文本标签;颜色保留 |
|
||
| **终端用户(Tauri 桌面)** | 间接通过 Web GUI 同步 |
|
||
| **开发者** | 写新代码时不能直接用 emoji 字符;后续建议加 lint 规则 |
|
||
| **运营/部署** | 无(不涉及端口、配置、依赖增减) |
|
||
| **测试** | 3 个 vitest fixture 文件需同步更新断言;不影响测试覆盖度 |
|
||
|
||
## High-Level Technical Design
|
||
|
||
视觉风格统一(KTD1+KTD2)的目标是把混用状态收敛成两条规则:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 头像/单字符位 │
|
||
│ • YAML avatar 字段 → "S"/"B"/"P" 等首字符 │
|
||
│ • 前端 expertIdentity.ts 兜底(如缺 avatar) │
|
||
│ • ExpertMessage/StickyModeHeader/BoardBannerCard │
|
||
│ 都按 string 渲染字符 │
|
||
└─────────────────────────────────────────────────────┘
|
||
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 横幅/卡片图标 │
|
||
│ • BoardBannerCard: <BankOutlined /> │
|
||
│ • DebateBannerCard: <AuditOutlined /> │
|
||
│ • DebateConclusionCard: 4 个决策图标 = 4 个组件 │
|
||
│ • UserBubble command card: <BankOutlined /> 私董会 │
|
||
│ <TeamOutlined /> 团 │
|
||
│ • useMessageRenderer shell.avatar 改为 Component │
|
||
│ MessageShell 用 <component :is=...> 渲染 │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
CLI 状态机收敛(KTD3):
|
||
|
||
```
|
||
旧: 新:
|
||
"✓" "✗" "⚠" "OK" "FAIL" "WARN"
|
||
[green]✓ X[/green] [bold green]OK X[/bold green]
|
||
[red]✗ X[/red] [bold red]FAIL X[/bold red]
|
||
[yellow]⚠ X[/yellow] [bold yellow]WARN X[/bold yellow]
|
||
```
|
||
|
||
DB schema 收敛(KTD4):
|
||
|
||
```
|
||
bitable/db.py:68 icon = Column(String, default="table")
|
||
bitable/db.py:216 icon VARCHAR DEFAULT 'table'
|
||
↓
|
||
旧 DB 行: '📋' → 前端 resolveBitableIcon 命中 DEFAULT_BITABLE_ICON='table' → 渲染 TableOutlined
|
||
新 DB 行: 'table' → 渲染 TableOutlined (与旧行一致)
|
||
```
|
||
|
||
## Implementation Units
|
||
|
||
### U1. 数据/契约层 — avatar 字段、持久化文本、DB 默认值
|
||
|
||
**Goal**:把数据层/契约层的 emoji 全部收敛到字符串默认值或首字符。
|
||
|
||
**Files**:
|
||
- `configs/experts/*.yaml`(15 个文件:apple/private_board/team/tech_lead 等)— avatar 字段从 emoji 改首字符大写
|
||
- `src/agentkit/bitable/db.py:68` — ORM default `📋` → `table`
|
||
- `src/agentkit/bitable/db.py:216` — SQL `DEFAULT '📋'` → `DEFAULT 'table'`
|
||
- `src/agentkit/server/routes/chat.py:304,306` — `🏛️ 私董会开始:…` → `私董会开始:…`
|
||
- `src/agentkit/server/frontend/src/stores/chatStream.ts:1149` — `🏛️ 私董会开始:…` → `私董会开始:…`
|
||
- `src/agentkit/core/plan_schema.py:107,122` — `📋`/`⚠️` → `[Plan ...]`/`WARN`
|
||
- `src/agentkit/experts/registry.py:85` — 注释 `📊` 改为纯文字
|
||
|
||
**Approach**:
|
||
- YAML avatar 字段值:英文名取首字母大写(`steve_jobs` → `S`),中文名取首 CJK 字(`张三` → `张`)
|
||
- 后端 `chat.py` 与前端 `chatStream.ts` 的 `board_started` 持久化文本保持语义一致(去 emoji 字符本身不影响 message_type/board_started metadata)
|
||
- `plan_schema.py:to_readable()` 是给人工看的报告,文本用纯文字更稳
|
||
|
||
**Test scenarios**:
|
||
- **Happy path**:`configs/experts/steve_jobs.yaml` 加载后 `expert.avatar == "S"`
|
||
- **Happy path**:`FileModel` 新实例 `icon == "table"`
|
||
- **Happy path**:`_apply_v1_schema` 生成的表 `icon` 列默认值为 `'table'`
|
||
- **Edge case**:现有 emoji DB 行(如 `icon='📋'`)通过 `resolveBitableIcon` 仍渲染为 `TableOutlined`
|
||
|
||
**Verification**:
|
||
- `pytest tests/unit/experts -k "config or registry"` 通过
|
||
- `pytest tests/unit/bitable -k "schema or model"` 通过
|
||
- 启动 backend 调用 `@board`,确认 DB 中 `board_started` 消息 content 为 `私董会开始:...`(无 emoji)
|
||
|
||
---
|
||
|
||
### U2. 前端 UI 组件 — 横幅、卡片、命令 chip
|
||
|
||
**Goal**:把 emoji 图标改用 Ant Design Vue 组件。
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue` — `🏛️` → `<BankOutlined />`
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/DebateBannerCard.vue` — `⚖` → `<AuditOutlined />`
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/DebateConclusionCard.vue` — `decisionIcons` map 改为 Ant Design Vue 组件(`CheckOutlined`/`SwapOutlined`/`MinusOutlined`/`QuestionOutlined`)
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue:143` — `'🏛️'`/`'👥'` 字符串 → `<BankOutlined />`/`<TeamOutlined />`
|
||
- `src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts:212,271` — `avatar: '⚖'` → `avatar: AuditOutlined`(shell.avatar 改 Component)
|
||
- `src/agentkit/server/frontend/src/components/chat/MessageShell.vue` — 检查 avatar 字段类型,Component 走 `<component :is="..." />`,string 走原样
|
||
- `src/agentkit/server/frontend/src/App.vue:109-110` — 字体回退删 emoji 字体声明
|
||
|
||
**Approach**:
|
||
- 引入 `BankOutlined` / `AuditOutlined` / `TeamOutlined` / `CheckOutlined` / `SwapOutlined` / `MinusOutlined` / `QuestionOutlined`(均已在 `@ant-design/icons-vue` 中,无需新装)
|
||
- `BoardBannerCard` / `DebateBannerCard` 的 `<span class="...__icon">X</span>` 改为 `<component :is="X" class="...__icon" />`
|
||
- `UserBubble` 的 command card 改造:`icon` prop 改为 `Component` 类型
|
||
- `useMessageRenderer` 中 `shell.avatar` 类型由 `string` 改为 `Component`(仅这两处),其余 shell 仍用 string(首字母)
|
||
- `MessageShell.vue` 用 `v-if="typeof shell.avatar === 'string'"` 分支处理
|
||
|
||
**Test scenarios**:
|
||
- **Happy path**:`BoardBannerCard` 渲染时包含 `BankOutlined` SVG path
|
||
- **Happy path**:`DebateBannerCard` 渲染时包含 `AuditOutlined` SVG path
|
||
- **Happy path**:`DebateConclusionCard decision="adopt"` 渲染 `CheckOutlined`
|
||
- **Happy path**:`UserBubble` 解析 `@board:alice, bob` 时 command icon 为 `BankOutlined`
|
||
- **Edge case**:`shell.avatar` 是 string(如 `expert_initial`)时仍原样渲染,不被当作 component
|
||
|
||
**Verification**:
|
||
- `npm run typecheck` 通过
|
||
- `npx vitest run tests/unit/components/` 通过
|
||
- 浏览器打开 `/agent/chat` 触发 `@board`,确认横幅显示 `<BankOutlined />` 矢量图标(非 emoji)
|
||
- 触发辩论确认 `DebateBannerCard` 显示 `<AuditOutlined />`
|
||
|
||
---
|
||
|
||
### U3. CLI 控制台输出 — admin / chat / skill / benchmark
|
||
|
||
**Goal**:把 Rich 输出中的 `✓` `✗` `⚠` 改为 `OK`/`FAIL`/`WARN` 文本标签 + Rich 颜色。
|
||
|
||
**Files**:
|
||
- `src/agentkit/cli/admin.py:215,601` — 表格 `is_active` 列的 `✓`/`✗` → `OK`/`--`
|
||
- `src/agentkit/cli/chat.py:589,603,692,694,727,730` — 验收/阶段/风险标记全部收敛
|
||
- `src/agentkit/cli/skill.py:296` — `⚠ 以下为自动生成…` → `WARN 以下为自动生成…`
|
||
- `src/agentkit/cli/benchmark.py:895,989,2032,2733,2767,2784,2790` — 所有 `✓`/`✗`/`⚠` 收敛
|
||
|
||
**Approach**:
|
||
- 字符串替换策略:所有 `[green]✓ X[/green]` 改 `[bold green]OK X[/bold green]`;`[red]✗ X[/red]` 改 `[bold red]FAIL X[/red]`;`[yellow]⚠ X[/yellow]` 改 `[bold yellow]WARN X[/yellow]`
|
||
- 表格列中的 `✓`/`✗` 用 `OK`/`--`(失败用 `--` 而非 `FAIL` 因为列宽有限)
|
||
- 横幅标题的 `⚠ 风险标记` 改 `WARN 风险标记`(保留全部大写作为视觉强度替代)
|
||
|
||
**Test scenarios**:
|
||
- **Happy path**:`agentkit admin list-users` 输出包含 `OK` 而非 `✓`
|
||
- **Happy path**:`agentkit chat` 触发的 phase completion 打印 `[OK] 阶段名:summary`
|
||
- **Error path**:phase 失败时打印 `[FAIL] 阶段名:error`
|
||
- **Integration**:`agentkit benchmark` 在失败用例上打印 `[FAIL]`(无 emoji)
|
||
|
||
**Verification**:
|
||
- `pytest tests/unit/cli -k "admin or chat or skill or benchmark"` 通过
|
||
- 手动跑 `agentkit admin list-users` / `agentkit chat "test"` / `agentkit benchmark list` 确认输出无 emoji
|
||
|
||
---
|
||
|
||
### U4. 终端前端 + 测试 fixture + 字体回退
|
||
|
||
**Goal**:清理剩余的终端提示、测试 fixture、字体回退。
|
||
|
||
**Files**:
|
||
- `src/agentkit/server/frontend/src/stores/terminal.ts:251,262,266,270,275` — `⚠` `⏳` `✓` `✗` → `WARN` `PENDING` `OK` `REJECTED`/`TIMEOUT`
|
||
- `src/agentkit/server/frontend/src/components/terminal/CommandHistory.vue:20` — `✓`/`✗` → `OK`/`FAIL`
|
||
- `src/agentkit/server/frontend/tests/unit/stores/chatStream.test.ts:731` — `avatar: '🦊'` → `avatar: 'F'`
|
||
- `src/agentkit/server/frontend/tests/unit/stores/chatStore.test.ts:22` — `content: '🏛️ 私董会开始:…'` → `content: '私董会开始:…'`
|
||
- `src/agentkit/server/frontend/tests/unit/stores/chatStore.test.ts:33,40` — `avatar: '🦊'`/`'🐼'` → `avatar: 'F'`/`'P'`
|
||
- `src/agentkit/server/frontend/tests/unit/components/StickyModeHeader.test.ts` — 所有 emoji fixture 改首字母
|
||
- `src/agentkit/server/frontend/src/components/chat/messages/TeamPlanCard.vue:67,82,127` — 注释更新(已说明弃用,保留为历史说明即可,无运行影响)
|
||
|
||
**Approach**:
|
||
- 终端消息用 `[WARN]` / `[PENDING]` / `[OK]` / `[REJECTED]` / `[TIMEOUT]` 文本格式(与 CLI KTD3 保持一致)
|
||
- 测试 fixture 改为对应名称首字母(`StickyModeHeader.test.ts` 中 `avatar: '🤖'` → `avatar: 'A'`,`expect(avatars[0].textContent).toBe('A')`)
|
||
- `chatStore.test.ts` 的 `board_started` content 改 `私董会开始:AI 未来`(与 U1 后端持久化文本一致)
|
||
|
||
**Test scenarios**:
|
||
- **Happy path**:`CommandHistory` 渲染 exit_code=0 时显示 `OK`、非 0 时显示 `FAIL`
|
||
- **Happy path**:`StickyModeHeader.test.ts` 中 `expect(avatars[0].textContent).toBe('A')` 通过
|
||
- **Happy path**:`chatStore.test.ts` `restoreBoardStateFromMessages` 测试对新的非-emoji content 仍正确解析
|
||
- **Edge case**:终端 store 中 `appendOutput` 的 ANSI 颜色(`\x1b[33m`)保留,仅替换前缀文本
|
||
|
||
**Verification**:
|
||
- `npm run typecheck` 通过
|
||
- `npx vitest run tests/unit/` 全通过
|
||
- 终端面板审批流程人工冒烟
|
||
|
||
---
|
||
|
||
### U5. 端到端验证 + 防止复发(lint 规则 + 文档)
|
||
|
||
**Goal**:确认全仓无残留 emoji,并加入 CI 守卫。
|
||
|
||
**Files**:
|
||
- 新增 `frontend/eslint-rules/no-emoji.cjs`(自定义 ESLint 规则,禁止源码中的 emoji 字符范围)
|
||
- `frontend/.eslintrc.cjs` 注册规则
|
||
- `pyproject.toml` 加 `ruff` 自定义规则(`RUF900` 禁用 emoji)或依赖 `pre-commit` 钩子
|
||
- `.pre-commit-config.yaml`(若不存在则创建)— 添加 `agentkit-no-emoji` 钩子
|
||
- `docs/solutions/style/no-emoji-style-guide.md`(新增)— 记录替换规则与原因
|
||
|
||
**Approach**:
|
||
- 自定义 ESLint 规则用正则 `[\x{1F000}-\x{1FFFF}\x{2600}-\x{27BF}\x{2300}-\x{23FF}\x{2B00}-\x{2BFF}]` 匹配所有 emoji 范围
|
||
- 规则名 `no-emoji-in-source`,错误级别 `error`,白名单文件:注释(通过 `// allow-emoji` 注解豁免,仅在文档/迁移脚本等场景)
|
||
- ruff 通过 `RUF` 自定义检查或第三方 `flake8-no-emoji` 工具
|
||
- pre-commit 在 commit 阶段跑前端 + 后端双检查
|
||
- 风格指南文档说明三套替代策略(KTD1-3)
|
||
|
||
**Test scenarios**:
|
||
- **Happy path**:`npx eslint src/` 无 emoji 错误
|
||
- **Happy path**:`ruff check src/` 无 emoji 警告
|
||
- **Error path**:故意在某 `.vue` 文件中加一个 emoji 字符,ESLint 报错并阻止 build
|
||
- **Integration**:本地 commit 含 emoji 的文件被 pre-commit 阻止
|
||
|
||
**Verification**:
|
||
- `npm run lint` 全通过
|
||
- `ruff check src/` 全通过
|
||
- `pre-commit run --all-files` 全通过
|
||
- 新建一个临时 `.vue` 文件含 emoji,确认 ESLint 报错
|
||
|
||
**Deferred to Follow-Up**:CI 工作流(`.github/workflows/` 或 Gitea Actions)添加 emoji 检查步骤 — 等 lint 规则稳定后再加,避免一次性改动过多。
|
||
|
||
---
|
||
|
||
## Risks & Dependencies
|
||
|
||
| 风险 | 缓解 |
|
||
| --- | --- |
|
||
| Ant Design Vue 图标视觉与原 emoji 风格差异大,用户不适应 | U2 完成后做视觉冒烟;如不接受可回退到 `string` 字符首字母策略(KTD1) |
|
||
| `useMessageRenderer.ts` 的 `shell.avatar` 改 `Component` 类型影响 `MessageShell.vue` 之外的消费者 | 全仓 grep `shell.avatar` / `message.shell.avatar` 确认仅 `MessageShell.vue` 消费 |
|
||
| CLI 输出改为 `OK`/`FAIL` 文本会改变宽度,可能破坏现有对齐 | 表格用 `--` 而非 `FAIL`(列宽更短) |
|
||
| Bitable 旧行不清理,未来 DB 导出/快照会有 `📋` 字符串 | 文档记录 KTD4 限制;后续可加迁移脚本 |
|
||
| pre-commit / 自定义 ESLint 规则本身可能误报(CJK 字符、注释中的 emoji) | 规则用明确的 Unicode range;白名单机制支持 `// allow-emoji` 行级豁免 |
|
||
| 测试 fixture 改首字母可能与某个测试断言的「fallback 行为」冲突 | U4 前先跑一遍 vitest 全量测试,建立基线;逐个 fixture 改并立即跑对应测试 |
|
||
|
||
**依赖关系**:
|
||
- U1 独立(数据/契约层)
|
||
- U2 独立(前端 UI)
|
||
- U3 独立(CLI)
|
||
- U4 依赖 U1(chatStore.test.ts content 需与 U1 后端持久化文本一致)
|
||
- U5 依赖 U1-U4 全部完成
|
||
|
||
**实施顺序**:U1 → U2 → U3 → U4 → U5。每批独立可提交/可回滚。
|
||
|
||
## Acceptance Examples
|
||
|
||
AE1. **私董会横幅显示矢量图标** — 触发 `@board 测试主题`,确认 `BoardBannerCard` 渲染 `<BankOutlined />` 矢量图标(SVG),不再显示 `🏛️` 字符。
|
||
|
||
AE2. **Bitable 默认图标稳定** — 创建新 Bitable 文件,`icon` 字段在 DB 中为 `'table'`,UI 渲染 `TableOutlined`。已存在的 emoji 行通过前端 `resolveBitableIcon` 自动收敛。
|
||
|
||
AE3. **CLI 状态无 emoji** — 运行 `agentkit admin list-users`,表格中 `is_active=true` 显示 `OK`,`is_active=false` 显示 `--`,无 `✓`/`✗` 字符。
|
||
|
||
AE4. **专家头像首字符** — `@board steve_jobs, charlie_munger` 私董会,`BoardBannerCard` 中 Steve Jobs 头像显示 `S`、Charlie Munger 头像显示 `C`(不再显示 `🍎`/`🧠`)。
|
||
|
||
AE5. **测试 fixture 与生产代码一致** — `npm run typecheck` + `npx vitest run` + `pytest tests/unit/` 三套全通过,无 emoji 残留。
|
||
|
||
AE6. **CI 守卫** — 故意在某 `.vue` 模板加 `🏛️` 字符,`npm run lint` 报错并阻止。
|
||
|
||
## Open Questions
|
||
|
||
1. **`useMessageRenderer.ts` 的 `shell.avatar` 类型升级是否需要更平滑**(保留 string 但用约定 `__ICON__BankOutlined`)?— 当前方案改为 `Component` 是更类型安全的,但若 `MessageShell.vue` 之外有第三方消费方可能破坏。**默认决定**:U2 实施前先全仓 grep 确认无外部消费者。
|
||
|
||
2. **Bitable `📋` 旧行是否要做一次性清理脚本**?— KTD4 决定惰性收敛,文档记录为 follow-up。如用户要求显式清理,U5 后可加一个 `agentkit bitable migrate-icons` 命令。
|
||
|
||
3. **`@ant-design/icons-vue` 包体积**(目前约 300KB+)— 项目已在用,仅新增 7 个组件,bundle size 影响可忽略。如未来有更激进的优化诉求可走按需 import + Vite tree-shaking(已默认开启)。
|
||
|
||
## Sources & Research
|
||
|
||
- **本地参考**:
|
||
- `frontend/src/components/chat/helpers/expertIdentity.ts` — 字符首字母策略的权威实现
|
||
- `frontend/src/components/bitable/bitableIcons.ts:89` — `resolveBitableIcon` 的回退路径
|
||
- `docs/plans/2026-06-19-001-feat-chat-area-vi-redesign-plan.md:192` — BoardBannerCard 的设计说明(含 `🏛️`)
|
||
- `docs/plans/2026-07-01-001-feat-ui-ue-enhancement-plan.md:154-155` — StickyModeHeader 的 emoji 头像渲染说明
|
||
- **项目记忆**:
|
||
- 「UI components must use consistent color tokens; avoid hardcoded blue fallback colors」— 沿用到图标风格统一
|
||
- 「CSS token fallback values can cause unintended color changes when tokens load late」— Ant Design Vue 组件是设计良好的回退机制
|
||
- **无外部研究触发**:emoji 替换是纯内部风格收敛,无外部 API/技术依赖。
|
||
|
||
---
|
||
|
||
## Plan Metadata
|
||
|
||
- **Depth**: Standard(5 个 implementation units,跨前后端+CLI+DB+测试+lint 守卫)
|
||
- **Expected commits**: 5(每个 U 一个 commit;U5 包含 lint 规则可能拆 2 个)
|
||
- **Risk profile**: 中(无安全/支付/外部 API 风险,主要是视觉/UX 风险)
|
||
- **Origin**: 用户直接在 ce-plan 中提出(无 upstream brainstorm doc)
|
||
- **Confidence**: 中高(KTD1-4 都有项目内既有模式可遵循;KTD5/6 引入新机制,U5 留验证空间)
|