refactor: remove all emoji from agentkit
Deploy to Production / deploy (push) Waiting to run Details
Test / backend-test (push) Waiting to run Details
Test / frontend-unit (push) Waiting to run Details
Test / api-e2e (push) Waiting to run Details
Test / frontend-e2e (push) Waiting to run Details
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 across codebase: YAML avatars -> first char, frontend banners -> Ant Design Vue components, CLI status -> OK/FAIL/WARN labels, terminal -> [WARN]/[OK]/[PENDING], Bitable DB default -> table, App.vue font cleanup, test fixtures -> first char letters. shell.avatar type upgraded to string | Component.
This commit is contained in:
chiguyong 2026-07-02 01:33:28 +08:00
parent 36b0296730
commit 78a7faa17b
49 changed files with 113 additions and 108 deletions

View File

@ -73,7 +73,7 @@ jobs:
echo "尝试 $i/30: 服务未就绪,等待 5 秒..."
sleep 5
done
echo " 健康检查失败"
echo "[FAIL] 健康检查失败"
docker compose -f "$REPO_DIR/$COMPOSE_FILE" logs --tail=100
exit 1

View File

@ -15,7 +15,7 @@ config:
decision_framework: "用户价值优先 — 问'这会让用户觉得简单吗'和'它在 5 年后还有意义吗'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "💬"
avatar: "A"
color: "#07C160"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "系统设计 — 评估性能、安全性、可扩展性,遵循 SOLID 原则"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "⚙️"
avatar: "B"
color: "#fa8c16"
is_lead: false
task_mode: llm_generate

View File

@ -15,7 +15,7 @@ config:
decision_framework: "逆向思考 — 问'怎样做会必然失败',然后避免它"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🧠"
avatar: "C"
color: "#2C3E50"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "代码质量 — 规范性、安全性、可维护性、性能四维评估"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🛡️"
avatar: "C"
color: "#722ed1"
is_lead: false
task_mode: llm_generate

View File

@ -17,7 +17,7 @@ config:
- backend_engineer
- qa_engineer
- code_reviewer
avatar: "👥"
avatar: "D"
color: "#1890ff"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "第一性原理 — 问'这件事的物理学本质是什么',再推导可行性"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🚀"
avatar: "E"
color: "#E31937"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "用户体验优先 — 在技术实现和用户需求之间找到最佳平衡"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🎨"
avatar: "F"
color: "#52c41a"
is_lead: false
task_mode: llm_generate

View File

@ -15,7 +15,7 @@ config:
decision_framework: "客户至上 + 长期主义 — 问'什么对客户最好'和'这个决策 10 年后是否仍正确'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "📦"
avatar: "J"
color: "#FF9900"
is_lead: false
task_mode: llm_generate

View File

@ -16,7 +16,7 @@ config:
decision_framework: "用户价值 + 不寻常路 — 问'用户真的想要这个吗'和'这看起来像坏想法吗'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "📝"
avatar: "P"
color: "#FF6600"
is_lead: false
task_mode: llm_generate

View File

@ -17,7 +17,7 @@ config:
- allenzhang
- charlie_munger
- paul_graham
avatar: "🏛️"
avatar: "P"
color: "#8E44AD"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "质量保障 — 测试覆盖率、边界条件、回归测试三重保障"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🔍"
avatar: "Q"
color: "#eb2f96"
is_lead: false
task_mode: llm_generate

View File

@ -16,7 +16,7 @@ config:
decision_framework: "原则驱动 — 问'这符合哪条原则'和'最可信的人怎么看'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "⚖️"
avatar: "R"
color: "#1A5276"
is_lead: false
task_mode: llm_generate

View File

@ -16,7 +16,7 @@ config:
decision_framework: "用户体验 + 专注 — 问'这足够简单吗'和'这是我能做的最好的吗'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🍎"
avatar: "S"
color: "#555555"
is_lead: false
task_mode: llm_generate

View File

@ -14,7 +14,7 @@ config:
decision_framework: "架构决策 — 评估可行性、可维护性、扩展性,权衡短期与长期"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "🏗️"
avatar: "T"
color: "#1890ff"
is_lead: true
task_mode: llm_generate

View File

@ -16,7 +16,7 @@ config:
decision_framework: "能力圈 + 内在价值 — 问'我理解这个业务吗'和'它的内在价值是多少'"
collaboration_strategy: "cooperative"
bound_skills: []
avatar: "💰"
avatar: "W"
color: "#1E8449"
is_lead: false
task_mode: llm_generate

View File

@ -65,7 +65,7 @@ class FileModel(BitableBase):
id = Column(String, primary_key=True, default=_uuid_str)
name = Column(String, nullable=False)
icon = Column(String, default="📋")
icon = Column(String, default="table")
description = Column(Text, default="")
owner_user_id = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), default=_utcnow)
@ -213,7 +213,7 @@ async def _apply_v2_migration(conn: object) -> None:
"CREATE TABLE IF NOT EXISTS bitable.bitable_files ("
" id VARCHAR PRIMARY KEY,"
" name VARCHAR NOT NULL,"
" icon VARCHAR DEFAULT '📋',"
" icon VARCHAR DEFAULT 'table',"
" description TEXT DEFAULT '',"
" owner_user_id VARCHAR,"
" created_at TIMESTAMPTZ DEFAULT NOW(),"

View File

@ -212,7 +212,7 @@ def dept_list(
str(d.get("id", "")),
str(d.get("name", "")),
str(d.get("description", "")),
"" if d.get("is_active") else "",
"OK" if d.get("is_active") else "--",
str(d.get("created_at", "")),
)
console.print(table)
@ -598,7 +598,7 @@ def user_list(
str(u.get("username", "")),
str(u.get("email", "")),
str(u.get("role", "")),
"" if u.get("is_active") else "",
"OK" if u.get("is_active") else "--",
)
console.print(table)

View File

@ -892,7 +892,7 @@ async def _run_llm_reasoning(
)
cases.append(case)
if verbose:
status = "[green][/green]" if case.passed else "[red][/red]"
status = "[green]OK[/green]" if case.passed else "[red]FAIL[/red]"
console.print(
f" {status} {task.task_id}: {result.actual} ({result.duration_ms:.2f}ms)"
)
@ -986,7 +986,7 @@ async def _run_gui_integration(
def _log(tid: str, passed: bool, label: str) -> None:
if verbose:
status = "[green]✓[/green]" if passed else "[red]✗[/red]"
status = "[green]OK[/green]" if passed else "[red]FAIL[/red]"
console.print(f" {status} {tid}: {label}")
all_runs_cases: list[list[CaseResult]] = []
@ -2029,7 +2029,7 @@ async def _run_dimension(
cases.append(case)
if verbose:
status = "[green][/green]" if case.passed else "[red][/red]"
status = "[green]OK[/green]" if case.passed else "[red]FAIL[/red]"
console.print(
f" {status} {task.task_id}: {result.actual} ({result.duration_ms:.2f}ms)"
)
@ -2730,7 +2730,7 @@ def benchmark(
components = _build_real_components()
if components is None:
console.print(
"[yellow] LLM mode skipped — no valid agentkit.yaml or API key.[/yellow]"
"[yellow]WARN LLM mode skipped — no valid agentkit.yaml or API key.[/yellow]"
)
else:
preprocessor, _skill_registry, llm_gateway = components
@ -2764,7 +2764,7 @@ def benchmark(
progress.update(task, completed=True, total=1)
if not results:
console.print("[yellow] No dimensions were run.[/yellow]")
console.print("[yellow]WARN No dimensions were run.[/yellow]")
return
# Display summary table
@ -2781,13 +2781,13 @@ def benchmark(
if fail_all == 0:
summary = f"All {pass_all} tests passed across {len(results)} dimensions."
console.print(f"[bold green] {summary}[/bold green]")
console.print(f"[bold green]OK {summary}[/bold green]")
else:
summary = (
f"{pass_all}/{total_all} tests passed ({fail_all} failed) "
f"across {len(results)} dimensions."
)
console.print(f"[bold yellow] {summary}[/bold yellow]")
console.print(f"[bold yellow]WARN {summary}[/bold yellow]")
console.print()

View File

@ -586,7 +586,7 @@ def _render_pm_collaboration_event(message: dict) -> bool:
rprint(
Panel(
"\n".join(lines),
title=f"[bold]{'' if passed else ''} 验收结果[/bold]",
title=f"[bold]{'OK' if passed else 'FAIL'} 验收结果[/bold]",
border_style=color,
)
)
@ -600,7 +600,7 @@ def _render_pm_collaboration_event(message: dict) -> bool:
f"[bold]专家:[/bold] {expert}\n"
f"[bold]阶段:[/bold] {phase_name}\n"
f"[bold]风险:[/bold] {risk_desc}",
title="[bold] 风险标记[/bold]",
title="[bold]WARN 风险标记[/bold]",
border_style="yellow",
)
)
@ -689,9 +689,9 @@ async def _execute_team_cli(
elif etype == "plan_update":
phases = message.get("plan_phases", [])
icon_map = {
"completed": ("", "green"),
"completed": ("OK", "green"),
"in_progress": ("", "blue"),
"failed": ("", "red"),
"failed": ("FAIL", "red"),
}
lines = []
for ph in phases:
@ -724,10 +724,10 @@ async def _execute_team_cli(
)
elif etype == "phase_completed":
summary = message.get("result_summary", "")
rprint(f" [green] {message.get('phase_name', '?')}[/green]: {summary[:120]}")
rprint(f" [green]OK {message.get('phase_name', '?')}[/green]: {summary[:120]}")
elif etype == "phase_failed":
rprint(
f" [red] {message.get('phase_name', '?')}[/red]: {message.get('error', '')}"
f" [red]FAIL {message.get('phase_name', '?')}[/red]: {message.get('error', '')}"
)
elif etype == "debate_started":
rprint(

View File

@ -293,7 +293,7 @@ def _render_risk_guard_suggestions(suggestions: list) -> None:
return
rprint(
"[bold yellow] 以下为自动生成的风险守卫建议,"
"[bold yellow]WARN 以下为自动生成的风险守卫建议,"
"必须人工审查后手动编辑 YAML 应用,不会自动生效。[/bold yellow]\n"
)
table = Table(title="Risk Guard Suggestions (待人工审查)")

View File

@ -104,7 +104,7 @@ class ExecutionPlan:
def to_readable(self) -> str:
"""序列化为可读格式,用于人工确认"""
lines = [f"📋 执行计划 [{self.plan_id}]", f"目标: {self.goal}", ""]
lines = [f"[Plan] 执行计划 [{self.plan_id}]", f"目标: {self.goal}", ""]
for group_idx, group in enumerate(self.parallel_groups):
lines.append(f"── 并行组 {group_idx + 1} ──")
@ -119,7 +119,7 @@ class ExecutionPlan:
lines.append("")
if self.skill_gaps:
lines.append("⚠️ 能力缺口:")
lines.append("[WARN] 能力缺口:")
for gap in self.skill_gaps:
lines.append(f" - {gap.step_name}: 缺少 '{gap.required_skill}' ({gap.level.value})")
if gap.suggestion:

View File

@ -82,7 +82,7 @@ class ExpertTemplateRegistry:
bound_skills:
- data_query
- chart_gen
avatar: "📊"
avatar: "首字符"
color: "#52c41a"
is_lead: false

View File

@ -106,8 +106,7 @@ html, body, #app {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Sans Emoji';
'Noto Sans', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--text-primary, #1a1a1a);

View File

@ -3,7 +3,7 @@
*
* History
* -------
* Originally, the Bitable file ``icon`` field stored emoji glyphs (📋 📊 📝
* Originally, the Bitable file ``icon`` field stored emoji glyphs (table, chart, note
* etc.). Those look fine in isolation but clash with the project's line-icon
* aesthetic everywhere else (Ant Design Vue ``Outlined`` family Tabs,
* Sidebar, Tool call cards all use stroke-based 1.5px icons). The mixed

View File

@ -1,4 +1,5 @@
import { computed, type Component } from 'vue'
import { AuditOutlined } 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'
@ -36,7 +37,7 @@ export type MessageViewType =
export interface MessageShellMeta {
name: string
meta?: string
avatar?: string
avatar?: string | Component
color?: string
}
@ -209,7 +210,7 @@ export function useMessageRenderer(message: IChatMessage) {
type,
shell: {
name: '辩论',
avatar: '⚖',
avatar: AuditOutlined,
color: '#722ed1',
meta: message.debate_topic || '',
},
@ -268,7 +269,7 @@ export function useMessageRenderer(message: IChatMessage) {
type,
shell: {
name: '辩论裁决',
avatar: '⚖',
avatar: AuditOutlined,
color: '#fa8c16',
meta: decisionLabels[decision] || decision,
},

View File

@ -3,7 +3,7 @@
<div class="board-banner-card__bar" />
<div class="board-banner-card__body">
<div class="board-banner-card__title">
<span class="board-banner-card__icon">🏛</span>
<BankOutlined class="board-banner-card__icon" />
<span>私董会 {{ topic }}</span>
</div>
<div class="board-banner-card__experts">
@ -32,6 +32,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { BankOutlined } from '@ant-design/icons-vue'
import type { IBoardExpert } from '@/api/types'
interface Props {

View File

@ -1,6 +1,6 @@
<template>
<div class="debate-banner">
<div class="debate-banner__icon"></div>
<div class="debate-banner__icon"><AuditOutlined /></div>
<div class="debate-banner__body">
<div class="debate-banner__title">辩论开始</div>
<div class="debate-banner__topic">{{ topic }}</div>
@ -17,6 +17,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { AuditOutlined } from '@ant-design/icons-vue'
import AssistantText from './AssistantText.vue'
import type { IChatMessage } from '@/api/types'

View File

@ -1,7 +1,7 @@
<template>
<div class="debate-conclusion" :class="`debate-conclusion--${decision}`">
<div class="debate-conclusion__header">
<span class="debate-conclusion__icon">{{ decisionIcon }}</span>
<span class="debate-conclusion__icon"><component :is="decisionIcon" /></span>
<span class="debate-conclusion__decision">{{ decisionLabel }}</span>
</div>
<div class="debate-conclusion__body">
@ -15,7 +15,8 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, type Component } from 'vue'
import { CheckOutlined, SwapOutlined, MinusOutlined, QuestionOutlined } from '@ant-design/icons-vue'
import AssistantText from './AssistantText.vue'
import type { IChatMessage } from '@/api/types'
@ -31,15 +32,15 @@ const decisionLabels: Record<string, string> = {
shelve: '搁置',
inconclusive: '未决',
}
const decisionIcons: Record<string, string> = {
adopt: '✓',
compromise: '⇄',
shelve: '○',
inconclusive: '?',
const decisionIcons: Record<string, Component> = {
adopt: CheckOutlined,
compromise: SwapOutlined,
shelve: MinusOutlined,
inconclusive: QuestionOutlined,
}
const decisionLabel = computed(() => decisionLabels[props.decision] || props.decision)
const decisionIcon = computed(() => decisionIcons[props.decision] || '?')
const decisionIcon = computed(() => decisionIcons[props.decision] || QuestionOutlined)
const textMessage = computed<IChatMessage>(() => ({
id: 'debate-conclusion',

View File

@ -7,7 +7,8 @@
class="message-shell__custom-avatar"
:style="{ backgroundColor: color || '#1a1a1a' }"
>
{{ avatar || (name ? name.charAt(0).toUpperCase() : '?') }}
<component :is="avatar" v-if="typeof avatar !== 'string' && avatar" />
<template v-else>{{ avatar || (name ? name.charAt(0).toUpperCase() : '?') }}</template>
</div>
<a-avatar v-else-if="role === 'assistant'" :size="28" class="message-shell__avatar--assistant">
<template #icon><RobotOutlined /></template>
@ -43,12 +44,13 @@
<script setup lang="ts">
import { Avatar as AAvatar } from 'ant-design-vue'
import { RobotOutlined, UserOutlined } from '@ant-design/icons-vue'
import type { Component } from 'vue'
interface Props {
role: 'user' | 'assistant'
name?: string
meta?: string
avatar?: string
avatar?: string | Component
color?: string
/** U4 R10: 专家身份 badge 名称 — 存在时渲染为彩色 badge 替代普通 name 文本 */
expertName?: string

View File

@ -80,7 +80,7 @@ const props = withDefaults(defineProps<Props>(), {
* Avatar glyph shown inside the 22x22 pill in the card header.
*
* 2026-07-01: switched the default from the business-person emoji
* (``🧑💼``) to a clean initials-based fallback to keep the
* (business-person glyph) to a clean initials-based fallback to keep the
* visual language consistent with the rest of the app (Ant Design
* Vue outlined icons + monospace text). Backend payloads that supply
* a non-empty ``leadAvatar`` string (e.g. expert avatars streamed over
@ -124,10 +124,10 @@ function statusLabel(status: string): string {
/**
* Phase status dot icon. 2026-07-01: replaced the ad-hoc Unicode
* glyphs (`` ``) with the matching Ant Design Vue components
* glyphs (hollow dot, solid dot, check, cross) with the matching Ant Design Vue components
* so the dot matches the line-icon family used in the rest of the
* app. The dotted ``MinusOutlined`` for pending is the closest
* outlined equivalent of the old hollow ````; the spinning /
* outlined equivalent of the old hollow dot; the spinning /
* pulse feel of ``in_progress`` is left to the existing colour
* treatment in the scoped CSS below.
*/

View File

@ -18,7 +18,7 @@
/>
<div v-else-if="commandBubble" class="user-bubble__command" :class="`user-bubble__command--${commandBubble.kind}`">
<div class="user-bubble__command-header">
<span class="user-bubble__command-icon">{{ commandBubble.icon }}</span>
<span class="user-bubble__command-icon"><component :is="commandBubble.icon" /></span>
<span class="user-bubble__command-label">{{ commandBubble.label }}</span>
<span class="user-bubble__command-sep">·</span>
<span class="user-bubble__command-topic">{{ commandBubble.topic }}</span>
@ -92,8 +92,8 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'
import { ref, computed, onMounted, onUnmounted, type Component } from 'vue'
import { CopyOutlined, DeleteOutlined, EditOutlined, BankOutlined, TeamOutlined } from '@ant-design/icons-vue'
import FileAttachment from './FileAttachment.vue'
import { useChatStore, nextMessageIsAssistant } from '@/stores/chatStore'
@ -119,7 +119,7 @@ const COMMAND_RE = /^@(board|team)(?::([^\s]+))?\s+([\s\S]+)$/
interface CommandBubble {
kind: 'board' | 'team'
icon: string
icon: Component
label: string
topic: string
experts: string[]
@ -140,7 +140,7 @@ const commandBubble = computed<CommandBubble | null>(() => {
: []
return {
kind,
icon: kind === 'board' ? '🏛️' : '👥',
icon: kind === 'board' ? BankOutlined : TeamOutlined,
label: kind === 'board' ? '私董会' : '专家团',
topic: topic || rest,
experts,

View File

@ -17,7 +17,7 @@
<span
:class="['command-history__exit-code', record.exit_code === 0 ? 'command-history__exit-code--success' : 'command-history__exit-code--error']"
>
{{ record.exit_code === 0 ? '✓' : '✗' }}
{{ record.exit_code === 0 ? 'OK' : 'FAIL' }}
</span>
<span class="command-history__command">{{ record.command }}</span>
</div>

View File

@ -1146,7 +1146,7 @@ export function dispatchWsEvent(
const startMsg: IChatMessage = {
id: generateId(),
role: "assistant",
content: `🏛️ 私董会开始:${boardData.topic}`,
content: `私董会开始:${boardData.topic}`,
timestamp: new Date().toISOString(),
status: "completed",
message_type: "board_started",

View File

@ -248,7 +248,7 @@ export const useTerminalStore = defineStore('terminal', () => {
command: data.command as string,
reason: (data.reason as string) || '需要用户确认',
}
appendOutput(`\x1b[33m 需要确认: ${data.command}\x1b[0m`)
appendOutput(`\x1b[33m[WARN] 需要确认: ${data.command}\x1b[0m`)
appendOutput(`\x1b[33m 原因: ${data.reason}\x1b[0m`)
break
// ── Server terminal: admin approval ────────────────────────
@ -259,20 +259,20 @@ export const useTerminalStore = defineStore('terminal', () => {
reason: (data.reason as string) || '需要管理员审批',
expires_in: (data.expires_in as number) || 300,
}
appendOutput(`\x1b[33m 等待管理员审批: ${data.command}\x1b[0m`)
appendOutput(`\x1b[33m[PENDING] 等待管理员审批: ${data.command}\x1b[0m`)
appendOutput(`\x1b[33m 原因: ${data.reason}\x1b[0m`)
break
case 'approval_approved':
appendOutput(`\x1b[32m 审批已通过,正在执行...\x1b[0m`)
appendOutput(`\x1b[32m[OK] 审批已通过,正在执行...\x1b[0m`)
pendingApproval.value = null
break
case 'approval_rejected':
appendOutput(`\x1b[31m 审批被拒绝: ${data.reason || ''}\x1b[0m`)
appendOutput(`\x1b[31m[REJECTED] 审批被拒绝: ${data.reason || ''}\x1b[0m`)
pendingApproval.value = null
isExecuting.value = false
break
case 'approval_expired':
appendOutput(`\x1b[31m 审批超时未响应\x1b[0m`)
appendOutput(`\x1b[31m[TIMEOUT] 审批超时未响应\x1b[0m`)
pendingApproval.value = null
isExecuting.value = false
break

View File

@ -109,7 +109,7 @@ const renaming = ref(false)
/**
* ``icon`` accepts any string here because legacy DB rows may still
* contain the old emoji values (``📋`` etc.). The select options only
* contain the old emoji values (clipboard-emoji etc.). The select options only
* show the new Bitable icon keys; the resolver maps unknown values to
* the default ``table`` icon at render time.
*/

View File

@ -91,7 +91,7 @@ function makeExpert(overrides: Partial<IExpertInfo> = {}): IExpertInfo {
id: 'e1',
name: '专家A',
persona: '资深架构师',
avatar: '🤖',
avatar: 'A',
color: '#3b82f6',
is_lead: false,
bound_skills: ['react'],
@ -120,7 +120,7 @@ function makeBoardState(overrides: Partial<BoardState> = {}): BoardState {
experts: [
{
name: '主持人',
avatar: '🎯',
avatar: 'T',
color: '#a855f7',
is_moderator: true,
persona: '引导讨论',
@ -177,8 +177,8 @@ describe('StickyModeHeader (U2)', () => {
teamState.value = makeTeamState({
task_description: '实现用户登录功能',
experts: [
makeExpert({ id: 'e1', name: '专家A', avatar: '🤖', is_lead: true }),
makeExpert({ id: 'e2', name: '专家B', avatar: '🐱' }),
makeExpert({ id: 'e1', name: '专家A', avatar: 'A', is_lead: true }),
makeExpert({ id: 'e2', name: '专家B', avatar: 'C' }),
],
})
const { container, unmount } = mountStickyHeader()
@ -196,7 +196,7 @@ describe('StickyModeHeader (U2)', () => {
const avatars = container.querySelectorAll('.sticky-mode-header__avatar')
expect(avatars.length).toBe(2)
expect(avatars[0].textContent).toBe('🤖')
expect(avatars[0].textContent).toBe('A')
unmount()
})
@ -229,14 +229,14 @@ describe('StickyModeHeader (U2)', () => {
experts: [
{
name: '主持人',
avatar: '🎯',
avatar: 'T',
color: '#a855f7',
is_moderator: true,
persona: '引导讨论',
},
{
name: '专家1',
avatar: '💡',
avatar: 'I',
color: '#3b82f6',
is_moderator: false,
persona: '市场分析',
@ -285,7 +285,7 @@ describe('StickyModeHeader (U2)', () => {
makeExpert({
id: 'e1',
name: '架构师',
avatar: '🤖',
avatar: 'A',
persona: '专注系统设计',
bound_skills: ['react', 'vue'],
is_lead: true,
@ -435,7 +435,7 @@ describe('StickyModeHeader (U2)', () => {
experts: [
{
name: '主持人',
avatar: '🎯',
avatar: 'T',
color: '#a855f7',
is_moderator: true,
persona: '引导者',

View File

@ -19,7 +19,7 @@ function boardStartedMsg(round: number = 0): IChatMessage {
return {
id: 'msg-start',
role: 'assistant',
content: '🏛️ 私董会开始AI 未来',
content: '私董会开始AI 未来',
timestamp: '2026-07-01T10:00:00Z',
status: 'completed',
message_type: 'board_started',
@ -30,14 +30,14 @@ function boardStartedMsg(round: number = 0): IChatMessage {
experts: [
{
name: 'Alice',
avatar: '🦊',
avatar: 'F',
color: '#7a5af8',
is_moderator: true,
persona: '主持人',
},
{
name: 'Bob',
avatar: '🐼',
avatar: 'P',
color: '#22c55e',
is_moderator: false,
persona: '工程师',

View File

@ -728,7 +728,7 @@ describe('dispatchWsEvent', () => {
experts: [
{
name: 'Mod',
avatar: '🦊',
avatar: 'F',
color: '#f00',
is_moderator: true,
persona: 'moderator',

View File

@ -301,9 +301,9 @@ async def _execute_board_meeting(
# proper board_speech card instead of a plain assistant bubble.
experts_data = event_data.get("experts")
board_started_text = (
f"🏛️ 私董会开始:{event_data.get('topic', '')}"
f"私董会开始:{event_data.get('topic', '')}"
if event_data.get("topic")
else "🏛️ 私董会开始"
else "私董会开始"
)
persistable: dict[str, tuple[str, str, dict[str, object] | None]] = {
"board_started": (

View File

@ -1331,8 +1331,8 @@ class MetricsReporter:
if report.overfitting_results:
lines.append("── 过拟合分析 ────────────────────────────────────────────")
for r in report.overfitting_results:
status = " 过拟合" if r.is_overfitted else " 正常"
orig_label = "" if r.original_correct else ""
status = "WARN 过拟合" if r.is_overfitted else "OK 正常"
orig_label = "OK" if r.original_correct else "FAIL"
lines.append(
f" [{status}] {r.benchmark_id}: "
f"原始输入={orig_label}, "
@ -1386,8 +1386,8 @@ class MetricsReporter:
if report.weaknesses:
lines.append("── 智能化短板识别 ────────────────────────────────────────")
for w in report.weaknesses:
icon = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢"}.get(
w.severity, ""
icon = {"critical": "[C]", "high": "[H]", "medium": "[M]", "low": "[L]"}.get(
w.severity, "[?]"
)
severity_label = {
"critical": "严重",
@ -1438,8 +1438,8 @@ class MetricsReporter:
lines.append(f" │ 总体策略: {plan.overall_strategy}")
lines.append("")
for action in plan.actions:
priority_icon = {"P0": "🔴", "P1": "🟠", "P2": "🟡", "P3": "🟢"}.get(
action.priority, ""
priority_icon = {"P0": "[P0]", "P1": "[P1]", "P2": "[P2]", "P3": "[P3]"}.get(
action.priority, "[?]"
)
effort_label = {"small": "", "medium": "", "large": ""}.get(
action.effort, action.effort

View File

@ -1434,7 +1434,7 @@ def _generate_text_report(json_report: dict[str, Any]) -> str:
for dim, score in json_report["dimension_scores"].items():
name = dim_names.get(dim, dim)
detail = json_report["dimension_details"][dim]
status = "" if score == 100 else ""
status = "OK" if score == 100 else "FAIL"
lines.append(f" {status} {name}: {score:.1f}% ({detail['passed']}/{detail['total']})")
lines.append("")
@ -1446,7 +1446,7 @@ def _generate_text_report(json_report: dict[str, Any]) -> str:
name = dim_names.get(dim, dim)
lines.append(f"\n[{name}]")
for case in details["cases"]:
status = "" if case["passed"] else ""
status = "OK" if case["passed"] else "FAIL"
lines.append(f" {status} {case['case_id']}")
lines.append("")

View File

@ -257,7 +257,7 @@ class TestRequestPreprocessorMetrics:
print(f"Total: {total}, Correct: {correct}, Accuracy: {accuracy:.1f}%")
print(f"{'-'*60}")
for r in results:
status = "" if r["correct"] else ""
status = "OK" if r["correct"] else "FAIL"
print(f" {status} {r['id']}: '{r['input']}'{r['actual']} (expected {r['expected']})")
print(f"{'='*60}")

View File

@ -25,7 +25,7 @@ async def test_create_file_returns_with_defaults(bitable_service) -> None:
file = await bitable_service.create_file(name="销售管线", owner_user_id="u1")
assert file.id
assert file.name == "销售管线"
assert file.icon == "📋"
assert file.icon == "table"
assert file.description == ""
assert file.owner_user_id == "u1"
assert file.created_at is not None
@ -75,11 +75,11 @@ async def test_update_file_changes_name_and_icon(bitable_service) -> None:
"""update_file patches name/icon/description."""
file = await bitable_service.create_file(name="Old", owner_user_id="u1")
updated = await bitable_service.update_file(
file.id, name="New", icon="🚀", description="updated desc"
file.id, name="New", icon="rocket", description="updated desc"
)
assert updated is not None
assert updated.name == "New"
assert updated.icon == "🚀"
assert updated.icon == "rocket"
assert updated.description == "updated desc"

View File

@ -31,7 +31,7 @@ def _make_expert_config(
is_lead=is_lead,
task_mode="llm_generate",
prompt={"identity": name},
avatar="🎭",
avatar="T",
color="#FF0000",
)

View File

@ -26,7 +26,7 @@ def _make_expert_configs(count: int = 3) -> list[ExpertConfig]:
is_lead=(i == 0),
task_mode="llm_generate",
prompt={"identity": f"Expert {i}"},
avatar="🎭",
avatar="T",
color=f"#FF{i:02d}000",
))
return configs

View File

@ -55,7 +55,7 @@ class TestExpertConfig:
thinking_style="逻辑推理",
collaboration_strategy="cooperative",
bound_skills=["data_query", "chart_gen"],
avatar="📊",
avatar="C",
color="#52c41a",
is_lead=True,
task_mode="llm_generate",
@ -67,7 +67,7 @@ class TestExpertConfig:
assert config.thinking_style == "逻辑推理"
assert config.collaboration_strategy == "cooperative"
assert config.bound_skills == ["data_query", "chart_gen"]
assert config.avatar == "📊"
assert config.avatar == "C"
assert config.color == "#52c41a"
assert config.is_lead is True
@ -99,7 +99,7 @@ class TestExpertConfig:
thinking_style="创造性思维",
collaboration_strategy="lead",
bound_skills=["skill_a", "skill_b"],
avatar="🧠",
avatar="B",
color="#ff4d4f",
is_lead=True,
task_mode="llm_generate",
@ -193,7 +193,7 @@ class TestExpertTemplate:
thinking_style="分析型",
collaboration_strategy="cooperative",
bound_skills=["skill_x"],
avatar="🔬",
avatar="M",
color="#722ed1",
is_lead=False,
task_mode="llm_generate",

View File

@ -358,7 +358,7 @@ class TestExpertGetCapabilitiesSummary:
bound_skills=["data_query", "chart_gen"],
is_lead=True,
color="#52c41a",
avatar="📊",
avatar="C",
)
agent = _make_mock_agent()
@ -373,7 +373,7 @@ class TestExpertGetCapabilitiesSummary:
"bound_skills": ["data_query", "chart_gen"],
"is_lead": True,
"color": "#52c41a",
"avatar": "📊",
"avatar": "C",
}

View File

@ -150,7 +150,7 @@ class TestExpertTemplateRegistry:
"thinking_style": "结构化思维",
"collaboration_strategy": "cooperative",
"bound_skills": ["skill_a", "skill_b"],
"avatar": "🤖",
"avatar": "A",
"color": "#fa8c16",
"is_lead": False,
"task_mode": "llm_generate",
@ -167,7 +167,7 @@ class TestExpertTemplateRegistry:
assert template.config.persona == "YAML 专家"
assert template.config.thinking_style == "结构化思维"
assert template.config.bound_skills == ["skill_a", "skill_b"]
assert template.config.avatar == "🤖"
assert template.config.avatar == "A"
assert template.config.color == "#fa8c16"
# 同时注册到 registry
assert registry.get("yaml_expert") is template