refactor: remove all emoji from source code
Test / backend-test (pull_request) Has been cancelled Details
Test / frontend-unit (pull_request) Has been cancelled Details
Test / api-e2e (pull_request) Has been cancelled Details
Test / frontend-e2e (pull_request) Has been cancelled Details

Replace emoji/glyph characters with:
- Ant Design Vue Outlined icons (frontend: ReviewResultCard, useMessageRenderer)
- Text labels with ANSI colors (CLI: chat.py, shell scripts)
- ASCII art (session_service.py docstring)

Add pre-commit guard (scripts/check-no-emoji.sh) and style guide
(docs/solutions/style/no-emoji-style-guide.md) to prevent regression.

Closes: emoji removal plan (docs/plans/2026-07-02-001)
This commit is contained in:
chiguyong 2026-07-03 02:38:06 +08:00
parent 6123b8feb2
commit 4085d7634f
11 changed files with 186 additions and 31 deletions

View File

@ -66,7 +66,7 @@ jobs:
echo "等待服务启动..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8001/api/v1/health > /dev/null 2>&1; then
echo " 服务健康检查通过"
echo "[OK] 服务健康检查通过"
curl -s http://localhost:8001/api/v1/health
exit 0
fi

10
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,10 @@
repos:
- repo: local
hooks:
- id: agentkit-no-emoji
name: agentkit-no-emoji
description: Prevent emoji and emoji-like characters in source code
entry: bash scripts/check-no-emoji.sh
language: system
pass_filenames: false
always_run: true

View File

@ -0,0 +1,83 @@
---
title: "No-Emoji Style Guide"
date: 2026-07-03
category: style
tags: [emoji, frontend, cli, terminal, icons]
problem_type: best_practice
component: cross-cutting
severity: convention
---
# No-Emoji Style Guide
## Problem
Emoji characters cause inconsistent rendering across terminals, OSes, and fonts. On Linux servers without Noto Color Emoji, they display as tofu blocks. They also clash with the project's Ant Design Vue Outlined icon family.
## Replacement Strategies
Three strategies based on context:
### KTD1: First-letter avatars (expert/persona avatars)
Use the first character of the expert name — uppercase for English, first CJK character for Chinese.
- `'🦊'``'F'` (Fox)
- `'🐼'``'P'` (Panda)
- `'🤖'``'A'` (AI)
Applies to: `configs/experts/*.yaml` avatar fields, test fixtures.
### KTD2: Ant Design Vue Outlined icons (UI banners/cards)
Use `@ant-design/icons-vue` components for visual icons in frontend components.
- `🏛️` → no icon (BoardBannerCard uses plain text)
- `⚖``<AuditOutlined />`
- `✓`/`✗` → `<CheckOutlined />`/`<CloseOutlined />`
- `↻``<ReloadOutlined />`
- `◆``<ApartmentOutlined />`
- `!``<WarningOutlined />`
Applies to: `useMessageRenderer.ts` shell.avatar, `ReviewResultCard.vue` statusIcon, `DebateBannerCard.vue`, `DebateConclusionCard.vue`, `UserBubble.vue`.
### KTD3: Text labels (CLI/shell/terminal output)
Use ASCII text labels with ANSI color codes for status markers.
- `✓``OK` (with `[bold green]` in Rich)
- `✗``FAIL` (with `[bold red]` in Rich)
- `⚠``WARN` (with `[bold yellow]` in Rich)
- `○``..` or `PENDING`
- `❌``[FAIL]`
- `✅``[OK]`
Applies to: `src/agentkit/cli/*.py` (Rich output), `scripts/*.sh` (shell output), `.gitea/workflows/*.yml` (CI logs).
## Unicode Ranges Covered
The `scripts/check-no-emoji.sh` pre-commit hook scans for:
| Range | Block |
|-------|-------|
| `1F000-1FFFF` | Emoji and Supplementary Symbols |
| `2600-27BF` | Misc Symbols and Dingbats (includes ✓ ✗) |
| `2300-23FF` | Miscellaneous Technical |
| `25A0-25FF` | Geometric Shapes (includes ◆ ○) |
| `2B00-2BFF` | Misc Symbols and Arrows |
**Excluded ranges** (pervasive in comments/docstrings, not emoji):
| Range | Block | Reason |
|-------|-------|--------|
| `2190-21FF` | Arrows | `→` is a pervasive docstring flow indicator |
| `2500-257F` | Box Drawings | `─` is a pervasive comment section separator |
| `2580-259F` | Block Elements | `█` `░` used in terminal progress bars |
Also detects escaped unicode sequences (`\u2713`, `\u2717`, `\u25c6`, etc.) in string literals, narrowed to emoji-like ranges only.
## Enforcement
- **Pre-commit hook**: `scripts/check-no-emoji.sh` runs on every commit
- **CI**: Add `bash scripts/check-no-emoji.sh` to `.gitea/workflows/` (deferred)
- **PR review**: Reviewers should check for emoji in code changes

60
scripts/check-no-emoji.sh Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# =============================================================================
# check-no-emoji.sh — Scan source code for emoji and emoji-like characters
#
# Detects two patterns:
# 1. Literal emoji/glyph characters (Unicode ranges)
# 2. Escaped unicode sequences in string literals (\u2713, \u21bb, etc.)
#
# Exits 0 if clean, 1 if violations found.
# =============================================================================
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"
# Scan paths (exclude docs, markdown, and vendored files)
SCAN_PATHS="src/ configs/ tests/ scripts/"
# Unicode ranges: Emoji, Misc Symbols/Dingbats, Misc Technical, Geometric Shapes (excl. Box Drawings), Misc Symbols/Arrows
# Excluded: Arrows (2190-21FF) — "→" is a pervasive docstring flow indicator, not emoji
# Excluded: Box Drawings (2500-257F) — "─" is a pervasive comment section separator, not emoji
# ponytail: narrower ranges than the plan's U5 spec; plan scope says "注释、文档除外" and these two ranges
# exist almost exclusively in comments/docstrings. Upgrade path: add a comment-aware filter.
LITERAL_PATTERN='[\x{1F000}-\x{1FFFF}\x{2600}-\x{27BF}\x{2300}-\x{23FF}\x{25A0}-\x{25FF}\x{2B00}-\x{2BFF}]'
# Escaped sequences: \u2713, \u2717, \u25c6, etc. (narrowed to match LITERAL_PATTERN ranges only)
# ponytail: original plan spec used 2[0-5][0-9a-fA-F]{2} which matched \u2000-\u25FF (punctuation, math, box drawing)
# causing false positives in minified JS bundles. Narrowed to emoji-like ranges only.
ESCAPE_PATTERN='\\u(271[0-9a-fA-F]|26[0-9a-fA-F]{2}|23[0-9a-fA-F]{2}|25[a-fA-F][0-9a-fA-F]|2[b-fB-F][0-9a-fA-F]{2})'
VIOLATIONS=0
# Check literal emoji characters (exclude minified bundles, test reports, and this script)
LITERAL_HITS=$(rg -n --no-heading -P "$LITERAL_PATTERN" \
-g '!**/static/assets/**' -g '!**/playwright-report/**' -g '!check-no-emoji.sh' \
$SCAN_PATHS 2>/dev/null || true)
if [[ -n "$LITERAL_HITS" ]]; then
echo "[FAIL] Literal emoji/glyph characters found:"
echo "$LITERAL_HITS"
echo ""
VIOLATIONS=1
fi
# Check escaped unicode sequences (same exclusions)
ESCAPE_HITS=$(rg -n --no-heading -P "$ESCAPE_PATTERN" \
-g '!**/static/assets/**' -g '!**/playwright-report/**' -g '!check-no-emoji.sh' \
$SCAN_PATHS 2>/dev/null || true)
if [[ -n "$ESCAPE_HITS" ]]; then
echo "[FAIL] Escaped unicode emoji sequences found:"
echo "$ESCAPE_HITS"
echo ""
VIOLATIONS=1
fi
if [[ $VIOLATIONS -eq 0 ]]; then
echo "[OK] No emoji or emoji-like characters found in $SCAN_PATHS"
exit 0
else
exit 1
fi

View File

@ -13,12 +13,12 @@ DEPLOY_DIR="${DEPLOY_DIR:-/opt/agentkit}"
cd "$PROJECT_ROOT"
if [ ! -f "$COMPOSE_FILE" ]; then
echo " 未找到 $COMPOSE_FILE"
echo "[FAIL] 未找到 $COMPOSE_FILE"
exit 1
fi
if [ ! -f ".env" ]; then
echo " 未找到 .env 文件,请先通过 Gitea Secrets 生成"
echo "[FAIL] 未找到 .env 文件,请先通过 Gitea Secrets 生成"
exit 1
fi
@ -33,4 +33,4 @@ docker compose -f "$COMPOSE_FILE" up -d --remove-orphans
echo "==> 当前服务状态:"
docker compose -f "$COMPOSE_FILE" ps
echo "==> 部署完成 "
echo "==> 部署完成 [OK]"

View File

@ -85,10 +85,10 @@ done
# ── 日志函数 ────────────────────────────────────────────────────────────────
ok() { echo -e " ${GREEN}${NC} $*"; }
ok() { echo -e " ${GREEN}OK${NC} $*"; }
info() { echo -e " ${CYAN}${NC} $*"; }
warn() { echo -e " ${YELLOW}!${NC} $*"; }
fail() { echo -e " ${RED}${NC} $*" >&2; }
fail() { echo -e " ${RED}FAIL${NC} $*" >&2; }
section() {
echo ""
@ -120,16 +120,16 @@ print_status() {
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo -e "${CYAN} 启动状态总览${NC}"
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
echo -e "$([[ $S_DEPS -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") 依赖检查"
echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") 环境配置"
echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") Redis (:6381)"
echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") PostgreSQL (:5435)"
echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") 后端服务 (:18001)"
echo -e "$([[ $S_DEPS -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 依赖检查"
echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 环境配置"
echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") Redis (:6381)"
echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") PostgreSQL (:5435)"
echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 后端服务 (:18001)"
if [[ $MODE == "gui" || $MODE == "tauri" ]]; then
echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") 前端服务 (:18002)"
echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 前端服务 (:18002)"
fi
if [[ $MODE == "tauri" ]]; then
echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}${NC}" || echo " ${YELLOW}${NC}") Tauri 客户端"
echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") Tauri 客户端"
fi
}

View File

@ -690,13 +690,13 @@ async def _execute_team_cli(
phases = message.get("plan_phases", [])
icon_map = {
"completed": ("OK", "green"),
"in_progress": ("", "blue"),
"in_progress": ("RUN", "blue"),
"failed": ("FAIL", "red"),
}
lines = []
for ph in phases:
status = ph.get("status", "pending")
icon, color = icon_map.get(status, ("", "dim"))
icon, color = icon_map.get(status, ("--", "dim"))
lines.append(
f" [{color}]{icon}[/{color}] {ph.get('name', '?')}{ph.get('assigned_expert', '?')}"
)
@ -719,7 +719,7 @@ async def _execute_team_cli(
_render_collaboration_contracts(all_contracts)
elif etype == "phase_started":
rprint(
f"\n[bold blue] {message.get('phase_name', '?')}[/bold blue] "
f"\n[bold blue]>> {message.get('phase_name', '?')}[/bold blue] "
f"{message.get('assigned_expert', '?')}"
)
elif etype == "phase_completed":

View File

@ -9,9 +9,9 @@ Lifecycle
---------
::
create rotate rotate ...
create -> rotate -> rotate -> ...
| | |
v v v
active active active
revoke (user logout) revoked

View File

@ -1,5 +1,5 @@
import { computed, type Component } from 'vue'
import { AuditOutlined } from '@ant-design/icons-vue'
import { AuditOutlined, ApartmentOutlined, CheckOutlined, CloseOutlined, WarningOutlined } from '@ant-design/icons-vue'
import type { IChatMessage } from '@/api/types'
import UserBubble from '@/components/chat/messages/UserBubble.vue'
import AssistantText from '@/components/chat/messages/AssistantText.vue'
@ -283,7 +283,7 @@ export function useMessageRenderer(message: IChatMessage) {
type,
shell: {
name: '协作关系图',
avatar: '◆',
avatar: ApartmentOutlined,
color: '#1890ff',
meta: time,
},
@ -304,7 +304,7 @@ export function useMessageRenderer(message: IChatMessage) {
type,
shell: {
name: '验收结果',
avatar: review.passed ? '\u2713' : '\u2717',
avatar: review.passed ? CheckOutlined : CloseOutlined,
color: review.passed ? '#52c41a' : '#ff4d4f',
meta: review.phase_name || time,
},
@ -325,7 +325,7 @@ export function useMessageRenderer(message: IChatMessage) {
type,
shell: {
name: '风险标记',
avatar: '!',
avatar: WarningOutlined,
color: '#fa8c16',
meta: risk.phase_name || time,
},

View File

@ -1,7 +1,7 @@
<template>
<div class="review-card" :class="reviewClass">
<div class="review-card__header">
<span class="review-card__icon">{{ statusIcon }}</span>
<span class="review-card__icon"><component :is="statusIcon" /></span>
<span class="review-card__status">{{ statusLabel }}</span>
<a-tag v-if="review.rework_count !== undefined && review.rework_count > 0" :color="review.passed ? 'green' : 'red'">
返工 {{ review.rework_count }}
@ -18,7 +18,8 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, type Component } from 'vue'
import { CheckOutlined, CloseOutlined, ReloadOutlined } from '@ant-design/icons-vue'
import AssistantText from './AssistantText.vue'
import type { IChatMessage, IReviewResult } from '@/api/types'
@ -38,10 +39,10 @@ const statusLabel = computed(() => {
return '要求返工'
})
const statusIcon = computed(() => {
if (props.review.passed) return '\u2713'
if (props.review.final_status === 'failed') return '\u2717'
return '\u21bb'
const statusIcon = computed<Component>(() => {
if (props.review.passed) return CheckOutlined
if (props.review.final_status === 'failed') return CloseOutlined
return ReloadOutlined
})
const feedbackMessage = computed<IChatMessage>(() => ({
@ -85,7 +86,8 @@ const feedbackMessage = computed<IChatMessage>(() => ({
.review-card__icon {
font-size: 16px;
font-weight: bold;
display: inline-flex;
align-items: center;
}
.review-card--passed .review-card__icon {

View File

@ -1419,7 +1419,7 @@ class MetricsReporter:
for rc in report.root_causes:
type_label = cause_type_labels.get(rc.cause_type, rc.cause_type)
conf_bar = "" * int(rc.confidence * 10) + "" * (10 - int(rc.confidence * 10))
lines.append(f" [{type_label}] 置信度: {conf_bar} {rc.confidence:.0%}")
lines.append(f" > [{type_label}] 置信度: {conf_bar} {rc.confidence:.0%}")
lines.append(f" 原因: {rc.cause_description}")
if rc.detail:
lines.append(f" 详情: {rc.detail}")