优化: 前端u-agent令牌对齐与组件拆分续批

- 全局样式类(df-sec-h/df-toast族)替换散装实现,主题token去硬编码兜底
- 拆分子组件:ai-chat/queue-panel(队列时钟与编辑随迁)、
  conversation-sidebar/ConversationList、settings/provider-panel
- 父组件死副本清理(队列编辑/时钟/键位表,TS6133清零)并补
  fill-draft父侧转发(重构半成品断链修复)
- 验收:vue-tsc EXIT 0
This commit is contained in:
lxy
2026-10-18 23:15:57 +08:00
parent 302c4bcf52
commit 25ec9cf69a
62 changed files with 3475 additions and 2006 deletions
+144 -165
View File
@@ -45,7 +45,7 @@ const emit = defineEmits<{
}>()
const store = useAiStore()
const { t } = useI18n()
const { t, locale } = useI18n()
// ── 消息任务关联(有的消息有,有的没):从 toolCalls[].args 派生 ──
// 数据源:AiToolCallInfo.args 已携带工具入参,任务工具的 task_id 就在其中
@@ -676,23 +676,28 @@ const _decoCache = new Map<string, string>()
const DECO_CACHE_LIMIT = 800
function decorateCodeHtml(raw: string): string {
if (!raw || !raw.includes('<pre><code')) return raw
const hit = _decoCache.get(raw)
// F1-1:复制按钮文案走 i18n(v-html 渲染层拿不到 $t,经参数注入);locale 入缓存键,
// 否则切语言后命中旧缓存,按钮文案停留在旧语言。locale 读取同时建立响应式依赖,
// 切语言时调用方(computed/渲染函数)重算拿到新语言装饰结果。
const cacheKey = locale.value + '\u0000' + raw
const hit = _decoCache.get(cacheKey)
if (hit !== undefined) {
// LRU:命中即置为最近使用(delete+set 移到 Map 末尾),淘汰时优先保留热项
_decoCache.delete(raw)
_decoCache.set(raw, hit)
_decoCache.delete(cacheKey)
_decoCache.set(cacheKey, hit)
return hit
}
// marked 的 code renderer 统一输出 <pre><code class="hljs ...">…</code></pre>
//(含缩进码块),代码内容已 HTML 转义,正则非贪婪到首个闭合标签不会越界。
// R4 语言角标:code class 形如 hljs language-ts,捕获 language-xxx 输出到 .md-code-block
// 的 data-lang,CSS ::after 显示;无语言(裸块/auto 猜测无 language- 类)不显角标。
const copyLabel = t('aiChat.copyCode')
const out = raw.replace(
/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g,
(_m: string, attrs: string, body: string) => {
const lang = /language-([a-z0-9+#.-]+)/i.exec(attrs)?.[1] ?? ''
const langAttr = lang ? ` data-lang="${lang}"` : ''
return `<div class="md-code-block"${langAttr}><button type="button" class="md-code-copy">复制</button><pre><code${attrs}>${body}</code></pre></div>`
return `<div class="md-code-block"${langAttr}><button type="button" class="md-code-copy">${copyLabel}</button><pre><code${attrs}>${body}</code></pre></div>`
},
)
// 有界 LRU 淘汰:超限删最久未用项(Map 首项)后插入,替代原整体 clear
@@ -700,7 +705,7 @@ function decorateCodeHtml(raw: string): string {
const oldest = _decoCache.keys().next().value
if (oldest !== undefined) _decoCache.delete(oldest)
}
_decoCache.set(raw, out)
_decoCache.set(cacheKey, out)
return out
}
/** renderMd 包装:渲染 + 代码块装饰(双层 memo:renderMd 的 _mdCache + 本缓存,未变即零成本) */
@@ -744,7 +749,7 @@ async function onMdCodeCopyClick(e: MouseEvent): Promise<void> {
}
// 成功态短暂反馈(直接改按钮 DOM;下次 v-html 重渲染自然还原,无需状态机)
const prev = btn.textContent
btn.textContent = ok ? '已复制' : '复制失败'
btn.textContent = ok ? t('aiChat.copied') : t('aiChat.copyFailed')
btn.classList.add(ok ? 'is-ok' : 'is-err')
setTimeout(() => {
btn.textContent = prev
@@ -924,13 +929,12 @@ function collapsedPreviewHtml(msg: AiMessage): string {
// R3:折叠条带量 —— 折叠态按钮显示「已折叠 N 字符 / 约 M tokens,点击展开」。
// 折叠量按 M1 的标定估算(400px 视口 ≈ 阈值字符量,见 LONG_MSG_THRESHOLD 注),
// 即 content.length - 阈值;token 为粗估(中英混合 1 token ≈ 2.5 字符),只供量级感知。
// 文案硬编码中文:i18n locale 文件本批锁定(他代理并行追加,不新增键),
// 展开态仍复用既有 aiChat.collapse 键。
// 文案走 i18n(F1-1 补键);展开态仍复用既有 aiChat.collapse 键。
const CHAR_PER_TOKEN_EST = 2.5
function collapsedLongLabel(msg: AiMessage): string {
const folded = Math.max(0, (msg.content?.length ?? 0) - LONG_MSG_THRESHOLD)
const tokens = Math.max(1, Math.round(folded / CHAR_PER_TOKEN_EST))
return `已折叠 ${folded} 字符 / 约 ${tokens} tokens,点击展开`
return t('aiChat.foldedSummary', { chars: folded, tokens })
}
/**
@@ -1226,7 +1230,13 @@ const renderItems = computed<RenderItem[]>(() => {
bucket = []
if (msgs.length === 0) return
const lastStoreId = store.state.messages[store.state.messages.length - 1]?.id
const executing = isViewingGenerating.value && msgs[msgs.length - 1].id === lastStoreId
// F1-3(10-17 走查):执行中判定补充「桶内存在 running/pending_approval 工具」。
// 仅靠「桶末=store 末条」时,生成中注入补充消息会在 store 末尾 push 本地 user 气泡,
// 判定瞬间失配 → 执行桶被误判为完成态,降级成默认折叠 trace 卡,正在跑/待审批的
// 工具卡跌进折叠体不可见(下一轮 assistant push 才自愈)。桶内工具仍活跃即说明该轮
// 未收敛,保持 step 形态内联可见;工具全部收敛后回到末条比对,历史桶不受影响(BUG-261001)。
const hasLiveTool = msgs.some(m => (m.toolCalls ?? []).some(tc => tc.status === 'running' || tc.status === 'pending_approval'))
const executing = isViewingGenerating.value && (msgs[msgs.length - 1].id === lastStoreId || hasLiveTool)
// 961③: 轮号=当前发起计数(与轨迹卡是否产出解耦,单消息桶不吞号)
const round = currentRound
const built = buildBucket(msgs, round, executing, lastTs)
@@ -1566,7 +1576,7 @@ watch(() => store.state.activeConversationId, (newId, oldId) => {
onBeforeUnmount(() => {
cancelPendingRaf()
document.removeEventListener('click', closeTokenPopoverOnOutsideClick)
document.removeEventListener('keydown', closeAgentTraceOnEsc)
document.removeEventListener('keydown', closeOverlaysOnEsc)
})
// token 详情面板:点击外部关闭(token 区内的 @click.stop 已阻冒泡,故文档级点击必为外部)
@@ -1575,11 +1585,14 @@ function closeTokenPopoverOnOutsideClick(): void {
}
document.addEventListener('click', closeTokenPopoverOnOutsideClick)
// 子代理轨迹浮层 Esc 关闭(与遮罩点击/X 同为关闭出口;仅浮层开着时消费,不干扰其他快捷键)
function closeAgentTraceOnEsc(e: KeyboardEvent): void {
if (e.key === 'Escape' && agentPeekVisible.value) closeAgentTrace()
// 浮层 Esc 关闭:子代理轨迹浮层 + token 详情 popover(F6-3:后者原先仅外点关闭,
// 键盘用户开了面板只能点别处)。两者同时开着时逐层关(先浮层后 popover),Esc 不冒泡旁路。
function closeOverlaysOnEsc(e: KeyboardEvent): void {
if (e.key !== 'Escape') return
if (agentPeekVisible.value) closeAgentTrace()
else if (tokenPopoverMsgId.value !== null) tokenPopoverMsgId.value = null
}
document.addEventListener('keydown', closeAgentTraceOnEsc)
document.addEventListener('keydown', closeOverlaysOnEsc)
// Markdown 预热(父原 loadMarkdown 在 onMounted 调,子组件同样幂等——useMarkdown 单例,
// 多次调用安全,确保子组件挂载即预热)。
@@ -2141,7 +2154,7 @@ defineExpose({
background: var(--df-bg-card);
color: var(--df-text-dim);
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
box-shadow: var(--df-shadow-sm);
z-index: 15;
transition: color 0.15s var(--df-ease), border-color 0.15s var(--df-ease);
}
@@ -2161,7 +2174,7 @@ defineExpose({
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--df-warning, #e8a33d);
background: var(--df-warning);
animation: ai-btb-pulse 2s ease-in-out infinite;
}
@keyframes ai-btb-pulse {
@@ -2172,14 +2185,8 @@ defineExpose({
/* -- Empty State 样式已随 EmptyState.vue 提取 -- */
/* -- Message Layout -- */
.ai-msg-user {
position: relative;
display: flex;
gap: 8px;
align-items: flex-start;
flex-direction: row-reverse; /* 头像在右:DOM 仍 avatar 在先,视觉反转成【气泡】【头像】靠右 */
margin-top: -3px; /* user 与上方消息间距略收(全局 gap 14 → 视觉 11),更紧凑 */
}
/* 用户消息行 .ai-msg-user 随 MessageItem.vue 抽离(自带 scoped,本组件 scoped 不渗透其内部),
原重复定义删除(2026-10-18 整洁波,死样式验证:模板零引用+scoped 隔离永不命中) */
.ai-msg-ai {
position: relative;
display: flex;
@@ -2197,7 +2204,8 @@ defineExpose({
gap: 8px;
}
/* -- Avatar -- */
/* -- Avatar -- workbench .av 范式:24px 圆角方块渐变(用户 warning→danger 系 / AI info→accent 系),
字色随主题底色反转保证渐变上对比;内部字符 9px 700(当前为 svg 图标,字号字重为字符兜底) */
.ai-msg-avatar {
width: 24px;
height: 24px;
@@ -2206,41 +2214,44 @@ defineExpose({
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 9px;
font-weight: 700;
}
.ai-msg-avatar--user {
background: var(--df-info-bg);
color: var(--df-info);
}
/* .ai-msg-avatar--user 随 MessageItem.vue 抽离(自带 scoped) */
.ai-msg-avatar--ai {
background: var(--df-accent-bg);
color: var(--df-accent);
background: linear-gradient(135deg, color-mix(in srgb, var(--df-info) 70%, var(--df-accent)), var(--df-accent));
color: var(--df-bg);
}
/* -- Bubble -- */
/* -- Bubble -- workbench 聊天范式:用户=bg-card 底+0.5px 边框+9px 圆角;AI=无气泡(透明底直接排版)。
9px 圆角与 9px 13px 内距均为 u-agent workbench 移植值,勿规整 */
.ai-msg-bubble {
position: relative;
font-size: 13px;
line-height: 1.6;
max-width: var(--df-msg-max-width);
padding: 8px 12px;
border-radius: var(--df-radius-lg);
padding: 9px 13px;
border-radius: 9px;
overflow-wrap: anywhere;
}
.ai-msg-bubble--user {
background: var(--df-accent);
color: #fff;
border-bottom-right-radius: var(--df-radius-sm);
}
/* 用户气泡 .ai-msg-bubble--user 随 MessageItem.vue 抽离(自带 scoped,原「同范式兜底」
实为 scoped 隔离永不命中的死规则,2026-10-18 整洁波删除) */
.ai-msg-bubble--ai {
background: var(--df-bg-card);
background: transparent;
border: none;
padding: 0;
color: var(--df-text);
border: 0.5px solid var(--df-border);
border-bottom-left-radius: var(--df-radius-sm);
}
/* 错误气泡对齐 workbench .blocked-card 范式:30% 红边框+6% 红底+12px 字;
AI 无气泡后本卡自含 padding/边框/圆角(排在 --ai 后覆盖) */
.ai-msg-bubble--error {
background: var(--df-danger-bg);
padding: 10px 13px;
border: 1px solid color-mix(in srgb, var(--df-danger) 30%, transparent);
border-radius: 9px;
background: color-mix(in srgb, var(--df-danger) 6%, transparent);
color: var(--df-danger);
border-color: rgba(240,101,101,0.3);
font-size: 12px;
margin-top: 6px;
}
/* 修正 的 display:block 方案——display:block 让 table 失去表格
布局上下文,tr/td 脱离 table 上下文塌成块级堆叠丢列对齐(用户截图反馈测试报告四列表格错乱)。
@@ -2255,8 +2266,8 @@ defineExpose({
overflow-x: auto;
}
/* M1:超长 assistant 文本折叠——max-height 截断 + 底部渐变遮罩 + 展开按钮。
背景 var(--df-bg-card) 对齐 .ai-msg-bubble--ai(L876):渐变淡出到气泡背景,
视觉上内容「沉入」气泡而非被硬切。overflow:hidden 覆盖 .ai-md 的 overflow-x:auto。 */
AI 气泡已透明(workbench 范式):渐变淡出到页面背景 --df-bg,视觉上内容「沉入」页面
而非被硬切。overflow:hidden 覆盖 .ai-md 的 overflow-x:auto。 */
.ai-msg-bubble--collapsed {
max-height: 400px;
overflow: hidden;
@@ -2269,7 +2280,7 @@ defineExpose({
left: 0;
right: 0;
height: 48px;
background: linear-gradient(transparent, var(--df-bg-card));
background: linear-gradient(transparent, var(--df-bg));
pointer-events: none; /* 让点击穿透到下方展开按钮 */
}
/* 展开按钮:气泡底部居中,置于渐变遮罩之上 */
@@ -2293,10 +2304,12 @@ defineExpose({
background: var(--df-bg-card-hover);
}
/* ── JSON 工具结果折叠防御(对齐 miniapp .msg-toolresult 视觉:灰 #b0b0b0 / 11px /
/* ── JSON 工具结果折叠防御(对齐 miniapp .msg-toolresult 视觉:弱化灰字 / 11px /
等宽 / max-height 80px 裁剪):assistant content 为纯 JSON 工具结果时弱化为折叠块,
不渲染 markdown 大气泡刷屏。默认 max-height 80px + overflow-y:auto 可滚动,
点击整块切换 --expanded(移除高度限制)展开/收起完整内容。 ── */
点击整块切换 --expanded(移除高度限制)展开/收起完整内容。
F1-2:灰字由硬编码 #b0b0b0 改 --df-text-dim——硬编码值在浅色主题白底上
对比度约 2.6:1 不可读;token 双主题各有值,弱化语义不变。 ── */
.ai-msg-json {
max-height: 80px;
overflow-y: auto;
@@ -2307,7 +2320,7 @@ defineExpose({
font-family: var(--df-font-mono);
font-size: 11px;
line-height: 1.5;
color: #b0b0b0;
color: var(--df-text-dim);
cursor: pointer;
user-select: none;
}
@@ -2325,27 +2338,8 @@ defineExpose({
border-color: var(--df-accent);
}
/* ── Input Augmentation: 用户气泡内 mention chip(圆角徽标,复用 ChatInput .ai-skill-chip-name 风格) ──
用户气泡背景 = --df-accent(主题强调色,如深色蓝/紫),chip 用半透明白底+深字 保证对比度;
按 kind 区分颜色变体(project/task/idea/skill),与 @ mention 浮层 .ai-mention-item-type--* 视觉呼应。 */
.ai-msg-chip {
display: inline-block;
padding: 1px 6px;
margin: 0 1px;
border-radius: var(--df-radius);
font-size: 0.9em;
font-weight: 600;
line-height: 1.4;
background: rgba(255, 255, 255, 0.22);
color: #fff;
vertical-align: baseline;
white-space: nowrap;
}
/* kind 颜色变体(用更亮的底色区分,但保持白字可读) */
.ai-msg-chip--project { background: rgba(255, 255, 255, 0.32); }
.ai-msg-chip--task { background: rgba(255, 255, 255, 0.26); }
.ai-msg-chip--idea { background: rgba(255, 255, 255, 0.22); border: 1px solid rgba(255, 255, 255, 0.35); }
.ai-msg-chip--skill { background: rgba(0, 0, 0, 0.18); }
/* ── mention chip(.ai-msg-chip 族)随 MessageItem.vue 抽离(自带 scoped,含 --skill 变体;
此处原 kind 变体因 scoped 隔离永不命中,2026-10-18 整洁波删除) ── */
/* ── UX 密度改造:居中时间分隔线(相邻消息间隔 ≥30 分钟或跨天;
per-block 相对时间漂移无价值已移除,精确时刻在消息容器 title) ── */
@@ -2368,10 +2362,13 @@ defineExpose({
background: color-mix(in srgb, var(--df-text-dim) 22%, transparent);
}
/* ── 最终回答卡:左 2px 品牌色强调边条 + 背景微抬升(与轨迹内思考 pill 明确分层) ── */
/* ── 最终回答卡:左 2px 品牌色强调边条 + 背景微抬升(与轨迹内思考 pill 明确分层);
AI 无气泡后本卡自含 padding 保持卡感(普通 AI 消息仍无气泡) ── */
.ai-msg-bubble--answer {
border-left: 2px solid var(--df-accent);
background: color-mix(in srgb, var(--df-accent) 4%, var(--df-bg-card));
border-radius: var(--df-radius);
background: color-mix(in srgb, var(--df-accent) 4%, transparent);
padding: 8px 12px;
}
/* ── 轮次导航条(仅多轮长会话 ≥3 张轨迹卡出现,sticky 置顶,只做跳转) ── */
@@ -2415,8 +2412,8 @@ defineExpose({
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
width: var(--df-h-icon-btn);
height: var(--df-h-icon-btn);
border: none;
border-radius: var(--df-radius-sm);
background: rgba(0, 0, 0, 0.35);
@@ -2426,14 +2423,13 @@ defineExpose({
transition: opacity 0.2s;
flex-shrink: 0;
}
.ai-msg-user:hover .ai-copy-btn,
.ai-msg-ai:hover .ai-copy-btn--ai {
opacity: 1;
}
.ai-copy-btn:hover {
background: rgba(0, 0, 0, 0.55);
}
/* 图片复制按钮排在文本复制按钮左侧(right 让位 22px 按钮 + 4px 间距) */
/* 图片复制按钮排在文本复制按钮左侧(right 让位 26px 按钮 + 4px 间距) */
.ai-copy-btn--image {
right: 30px;
}
@@ -2443,6 +2439,23 @@ defineExpose({
.ai-md :deep(.md-code-block) {
position: relative;
}
/* workbench .code 范式:比 --df-bg 更深一档的底 + 边框 + md 圆角 + mono 11px,
覆盖全局 ai-md.css 的 .ai-md pre(现有 hljs github-dark 语法高亮色不动)。
浅色主题降为 8% 黑:45% 全量在浅底上会压垮正文/高亮对比,违背双主题成立铁律。 */
.ai-md :deep(pre) {
background: color-mix(in srgb, var(--df-bg), #000 45%);
border: 1px solid var(--df-border);
border-radius: var(--df-radius-md);
font-family: var(--df-font-mono);
font-size: 11px;
}
.ai-md :deep(pre code) {
font-size: 11px;
line-height: 1.6;
}
[data-theme='light'] .ai-md :deep(pre) {
background: color-mix(in srgb, var(--df-bg), #000 8%);
}
.ai-md :deep(.md-code-copy) {
position: absolute;
top: 6px;
@@ -2468,8 +2481,8 @@ defineExpose({
}
.ai-md :deep(.md-code-copy.is-ok) {
opacity: 1;
color: var(--df-success, #4caf50);
border-color: var(--df-success, #4caf50);
color: var(--df-success);
border-color: var(--df-success);
}
.ai-md :deep(.md-code-copy.is-err) {
opacity: 1;
@@ -2519,7 +2532,7 @@ defineExpose({
/* Fatal 错误(不可重试)的提示文案——替代隐藏的「重试」按钮,
说明为何无重试入口,引导走「去设置」。 */
.ai-msg-not-retryable {
font-size: 10.5px;
font-size: 10px;
color: var(--df-text-dim);
font-style: italic;
align-self: center;
@@ -2528,12 +2541,12 @@ defineExpose({
display: inline-flex;
align-items: center;
gap: 3px;
padding: 2px 7px;
padding: 2px 6px;
border: none;
border-radius: var(--df-radius-sm);
background: transparent;
color: var(--df-text-dim);
font-size: 10.5px;
font-size: 10px;
line-height: 1.4;
cursor: pointer;
transition: background 0.15s, color 0.15s;
@@ -2565,11 +2578,11 @@ defineExpose({
margin-top: 4px;
padding: 3px 8px;
border-radius: var(--df-radius-sm);
background: color-mix(in srgb, var(--df-warning, #ffc107) 10%, transparent);
background: color-mix(in srgb, var(--df-warning) 10%, transparent);
width: fit-content;
}
.ai-msg-truncated-text {
font-size: 10.5px;
font-size: 10px;
line-height: 1.4;
color: var(--df-text-dim);
}
@@ -2600,7 +2613,7 @@ defineExpose({
padding-top: 6px;
border-top: 0.5px solid var(--df-border);
font-family: var(--df-font-mono);
font-size: 9px;
font-size: 10px;
color: var(--df-text-dim);
opacity: 0.7;
}
@@ -2618,8 +2631,9 @@ defineExpose({
cursor: pointer;
opacity: 1;
}
/* workbench .round-meta 范式:元数据 hover 变 --df-info */
.ai-token-usage-trigger:hover {
color: var(--df-text);
color: var(--df-info);
opacity: 1;
}
.ai-token-sep {
@@ -2627,7 +2641,7 @@ defineExpose({
}
/* G4.3:估算值标注(估算非真实,弱化 dim + warning 色调,不喧宾夺主) */
.ai-token-est {
color: var(--df-warning, #f0c75e);
color: var(--df-warning);
opacity: 0.85;
}
/* 详情面板:复用 TopBar popout 风格(absolute / right 0 / bg-card / border / shadow) */
@@ -2638,7 +2652,7 @@ defineExpose({
margin-top: 4px;
padding: 8px 10px;
min-width: 200px;
background: var(--df-bg-card, var(--df-bg));
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@@ -2678,23 +2692,7 @@ defineExpose({
font-weight: 500;
}
/* 用户气泡内的图片(多模态消息渲染) */
.ai-msg-images {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 6px;
}
.ai-msg-image {
max-width: 200px;
max-height: 200px;
width: auto;
height: auto;
border-radius: var(--df-radius);
object-fit: contain;
display: block;
background: var(--df-bg);
}
/* 用户气泡内图片(.ai-msg-images/.ai-msg-image)随 MessageItem.vue 抽离(自带 scoped) */
/* ═══ 压缩摘要 system 块(compressed 原文已展开,摘要置顶提示发给 AI 的是它) ═══
用户诉求:压缩只后端省 token,界面不藏消息。原文 compressed 照常 user/ai 气泡展开,
@@ -2773,7 +2771,7 @@ defineExpose({
.ai-trace-head {
display: flex;
align-items: center;
gap: 7px;
gap: 8px;
padding: 6px 10px;
font-size: 11px;
cursor: pointer;
@@ -2806,6 +2804,11 @@ defineExpose({
font-size: 10px;
color: var(--df-text-dim);
}
/* workbench .round-meta 范式:轮次/时间/token 小元数据 hover 变 --df-info(折叠交互不动) */
.ai-trace-head:hover .ai-trace-agg,
.ai-trace-head:hover .ai-trace-tokens {
color: var(--df-info);
}
/* 任务关联 chip(仅含任务工具调用的轮次渲染):accent 弱化底 + hover 提亮,点击跳任务 */
.ai-trace-task-chip {
flex-shrink: 0;
@@ -2826,7 +2829,8 @@ defineExpose({
}
/* ── 轨迹展开态 rail 面板:左轨竖线贯通,思考 pill 与工具卡图标锚定其上(git-graph 式轮次结构)。
轨 x=22px 对齐工具卡图标中心(卡内 padding 10px + 24px 图标位一半),pill 灯泡图标同 x。 ── */
轨 x=22px 对齐工具卡图标中心(卡内 padding 10px + 24px 图标位一半),pill 灯泡图标同 x。
workbench .tr 范式:1px 连线用 --df-border。 ── */
.ai-trace-body {
position: relative;
padding: 2px 10px 8px 0;
@@ -2838,7 +2842,7 @@ defineExpose({
top: 0;
bottom: 0;
width: 1px;
background: color-mix(in srgb, var(--df-text-dim) 22%, transparent);
background: var(--df-border);
}
.ai-trace-round {
position: relative;
@@ -2852,10 +2856,10 @@ defineExpose({
position: relative;
display: flex;
align-items: center;
gap: 7px;
gap: 6px;
width: 100%;
min-height: 24px;
padding: 3px 8px 3px 38px;
padding: 3px 8px 3px var(--df-icon-gutter);
border: none;
border-radius: var(--df-radius-sm);
background: transparent;
@@ -2872,16 +2876,16 @@ defineExpose({
}
.ai-think-ic {
position: absolute;
left: 10px;
left: 11px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
border-radius: var(--df-radius-sm);
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--df-accent-bg);
background: color-mix(in srgb, var(--df-accent) 15%, transparent);
color: var(--df-accent);
flex-shrink: 0;
}
@@ -2896,14 +2900,14 @@ defineExpose({
flex: 0 0 auto;
white-space: nowrap;
font-family: var(--df-font-mono);
font-size: 9.5px;
font-size: 10px;
color: var(--df-text-dim);
opacity: 0.75;
margin-left: auto;
}
/* 展开的完整思考段落:保持既有卡片样式(气泡卡同底),挂在 pill 下方缩进对齐正文 */
.ai-think-full {
margin: 2px 0 6px 38px;
margin: 2px 0 6px var(--df-icon-gutter);
padding: 8px 12px;
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
@@ -2916,30 +2920,14 @@ defineExpose({
}
/* 执行中态轨迹卡(骨架与完成态完全同构,仅色/态微差):accent 底 + 脉冲点替代 chevron,
step→trace 切换时卡片高度/内边距一致,只 patch 内部文字 → 无跳变 */
step→trace 切换时卡片高度/内边距一致,只 patch 内部文字 → 无跳变。
(.ai-trace-toggle--running/.ai-step-line/.ai-step-text 旧单行摘要骨架已废:step 卡
改用 trace-card 同构骨架,模板零引用,2026-10-18 整洁波删除) */
.ai-trace-card--running {
border-style: solid;
border-color: color-mix(in srgb, var(--df-accent) 28%, transparent);
background: color-mix(in srgb, var(--df-accent) 7%, transparent);
}
.ai-trace-toggle--running {
color: var(--df-text-dim);
opacity: 0.9;
}
/* 执行中实时步骤摘要(已完成步骤收进一行,accent 呼吸点对齐状态栏 .ai-agentic-progress) */
.ai-step-line {
display: flex;
align-items: center;
gap: 6px;
max-width: var(--df-msg-max-width);
padding: 4px 10px;
border-radius: var(--df-radius);
background: color-mix(in srgb, var(--df-accent) 7%, transparent);
font-size: 11px;
color: var(--df-text-secondary);
user-select: none;
}
.ai-step-dot {
width: 6px;
height: 6px;
@@ -2948,13 +2936,6 @@ defineExpose({
flex-shrink: 0;
animation: ai-step-pulse 1.2s ease-in-out infinite;
}
.ai-step-text {
font-family: var(--df-font-mono);
font-size: 10.5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@keyframes ai-step-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
@@ -2969,7 +2950,7 @@ defineExpose({
max-width: var(--df-msg-max-width);
padding: 4px 10px;
border-radius: var(--df-radius);
background: color-mix(in srgb, var(--df-warning, #f0c75e) 8%, transparent);
background: color-mix(in srgb, var(--df-warning) 8%, transparent);
font-size: 11px;
color: var(--df-text-secondary);
user-select: none;
@@ -2978,13 +2959,13 @@ defineExpose({
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--df-warning, #f0c75e);
background: var(--df-warning);
flex-shrink: 0;
animation: ai-step-pulse 1.2s ease-in-out infinite;
}
.ai-retry-text {
font-family: var(--df-font-mono);
font-size: 10.5px;
font-size: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -3018,10 +2999,10 @@ defineExpose({
flex-shrink: 0;
}
.ai-agent-fin--warn {
background: color-mix(in srgb, var(--df-warning, #f0c75e) 8%, transparent);
background: color-mix(in srgb, var(--df-warning) 8%, transparent);
}
.ai-agent-fin--warn .ai-agent-fin-dot {
background: var(--df-warning, #f0c75e);
background: var(--df-warning);
}
.ai-agent-fin--err {
background: color-mix(in srgb, var(--df-danger) 10%, transparent);
@@ -3044,7 +3025,7 @@ defineExpose({
flex-shrink: 0;
}
.ai-agent-fin--warn .ai-agent-fin-status {
color: var(--df-warning, #f0c75e);
color: var(--df-warning);
}
.ai-agent-fin--err .ai-agent-fin-status {
color: var(--df-danger);
@@ -3057,7 +3038,8 @@ defineExpose({
.ai-agent-fin-meta {
flex-shrink: 0;
font-family: var(--df-font-mono);
font-size: 10.5px;
font-size: 10px;
color: var(--df-text-dim);
}
/* 被拒工具 chip(P2-1):琥珀警示层与 truncated 同色系;限宽防长清单挤掉摘要行 */
.ai-agent-fin-rejected {
@@ -3068,9 +3050,9 @@ defineExpose({
white-space: nowrap;
padding: 0 6px;
border-radius: 999px;
font-size: 10.5px;
background: color-mix(in srgb, var(--df-warning, #f0c75e) 16%, transparent);
color: var(--df-warning, #f0c75e);
font-size: 10px;
background: color-mix(in srgb, var(--df-warning) 16%, transparent);
color: var(--df-warning);
}
/* 无轨迹(启动失败)卡:禁点视觉,hover 不提亮(cursor 维持默认) */
.ai-agent-fin--notrace {
@@ -3080,7 +3062,7 @@ defineExpose({
.ai-agent-fin--notrace:hover {
background: color-mix(in srgb, var(--df-text-secondary) 6%, transparent);
}
/* peek 拉取中的加载微标(挂在具体卡右端) */
/* peek 拉取中的加载微标(挂在具体卡右端);旋转动画消费 global.css @keyframes df-spin */
.ai-agent-fin-spin {
flex-shrink: 0;
width: 10px;
@@ -3088,10 +3070,7 @@ defineExpose({
border: 1.5px solid color-mix(in srgb, var(--df-text-secondary) 35%, transparent);
border-top-color: var(--df-text-secondary);
border-radius: 50%;
animation: ai-agent-fin-spin 0.8s linear infinite;
}
@keyframes ai-agent-fin-spin {
to { transform: rotate(360deg); }
animation: df-spin 0.8s linear infinite;
}
/* ═══ S3 子代理轨迹浮层(只读,关即弃) ═══ */
@@ -3112,7 +3091,7 @@ defineExpose({
background: var(--df-bg-raised);
border: 0.5px solid var(--df-border-strong);
border-radius: var(--df-radius-lg);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
box-shadow: var(--df-shadow-md);
overflow: hidden;
}
.ai-agent-peek-head {
@@ -3124,7 +3103,7 @@ defineExpose({
flex-shrink: 0;
}
.ai-agent-peek-title {
font-size: 12.5px;
font-size: 12px;
font-weight: 600;
color: var(--df-text);
}
@@ -3169,7 +3148,7 @@ defineExpose({
align-items: center;
flex-wrap: wrap;
gap: 6px;
font-size: 10.5px;
font-size: 10px;
font-weight: 600;
color: var(--df-text-secondary);
margin-bottom: 4px;
@@ -3193,7 +3172,7 @@ defineExpose({
.ai-agent-peek-msg--tool .ai-agent-peek-text,
.ai-agent-peek-msg--system .ai-agent-peek-text {
color: var(--df-text-secondary);
font-size: 11.5px;
font-size: 11px;
}
/* 浮层头身份标识行:置于标题下方,窄容器内单行省略 */
.ai-agent-peek-head-text {
@@ -3214,7 +3193,7 @@ defineExpose({
.ai-agent-peek-agent {
flex-shrink: 0;
font-family: var(--df-font-mono);
font-size: 10.5px;
font-size: 10px;
color: var(--df-info);
background: var(--df-info-bg);
border-radius: var(--df-radius-sm);