diff --git a/src/App.vue b/src/App.vue index ef86723..95cd342 100644 --- a/src/App.vue +++ b/src/App.vue @@ -31,16 +31,16 @@ -
{{ ln.text }}{{ '\n' }}
+ {{ ln.text }}{{ '\n' }}
{{ ln.text }}{{ '\n' }}
+ {{ ln.text }}{{ '\n' }}
{{ ln.text }}{{ '\n' }}
+ {{ ln.text }}{{ '\n' }}
{{ $t('aiChat.emptyNoProvider') }}
{{ $t('aiChat.emptyNoProviderHint') }}
@@ -46,7 +46,7 @@ const examplePrompts = [{{ $t('aiChat.emptyTitle') }}
{{ $t('aiChat.emptyHint') }}
@@ -73,7 +73,8 @@ const examplePrompts = [ justify-content: center; gap: 8px; } -.ai-empty-icon { margin-bottom: 4px; opacity: 0.4; } +/* wave2:空态图标统一 dim 色(经 currentColor 接管 stroke),引导文字 secondary 层级 */ +.ai-empty-icon { margin-bottom: 4px; opacity: 0.4; color: var(--df-text-dim); } .ai-empty-text { font-size: 14px; font-weight: 500; @@ -81,7 +82,7 @@ const examplePrompts = [ } .ai-empty-sub { font-size: 12px; - color: var(--df-text-dim); + color: var(--df-text-secondary); text-align: center; line-height: 1.5; } @@ -117,7 +118,7 @@ const examplePrompts = [ border-radius: var(--df-radius); background: var(--df-bg-card); color: var(--df-text-secondary); - font-size: 11.5px; + font-size: 11px; line-height: 1.4; text-align: left; cursor: pointer; diff --git a/src/components/ai/HelpRequiredCard.vue b/src/components/ai/HelpRequiredCard.vue index c4b7ef5..8f56ed8 100644 --- a/src/components/ai/HelpRequiredCard.vue +++ b/src/components/ai/HelpRequiredCard.vue @@ -74,30 +74,30 @@ function handleSelectOption(opt: string): void { diff --git a/src/components/ai/MentionPopover.vue b/src/components/ai/MentionPopover.vue index 65929e0..1fdddf8 100644 --- a/src/components/ai/MentionPopover.vue +++ b/src/components/ai/MentionPopover.vue @@ -84,7 +84,7 @@ const emit = defineEmits<{ text-overflow: ellipsis; white-space: nowrap; } -/* 类型标签小色块(项目/任务区分) */ +/* 类型标签小色块(项目/任务区分)。2026-10-18: 原底色引用未定义 token 静默失效,修正为 bg-card-hover */ .ai-mention-item-type { flex-shrink: 0; padding: 1px 5px; @@ -92,7 +92,7 @@ const emit = defineEmits<{ font-size: 9px; font-weight: 600; letter-spacing: 0.3px; - background: var(--df-bg-hover); + background: var(--df-bg-card-hover); color: var(--df-text-dim); } .ai-mention-item-type--project { diff --git a/src/components/ai/MessageItem.vue b/src/components/ai/MessageItem.vue index 93be2c2..14e90f9 100644 --- a/src/components/ai/MessageItem.vue +++ b/src/components/ai/MessageItem.vue @@ -149,29 +149,34 @@ const imageParts = computed(() => msgImageParts(props.msg)) align-items: flex-start; flex-direction: row-reverse; } -/* 头像通用 */ +/* 头像通用 — workbench .av 范式:24px 圆角方块渐变,用户 warning→danger 系, + 字色随主题底色反转保证渐变上对比;内部字符 9px 700(当前为 svg 图标,字号字重为字符兜底) */ .ai-msg-avatar { - width: 22px; - height: 22px; - border-radius: 50%; + width: 24px; + height: 24px; + border-radius: var(--df-radius); display: flex; align-items: center; justify-content: center; flex-shrink: 0; + font-size: 9px; + font-weight: 700; } .ai-msg-avatar--user { - background: var(--df-info-bg, rgba(100,181,246,0.15)); - color: var(--df-info, #64b5f6); + background: linear-gradient(135deg, color-mix(in srgb, var(--df-warning) 70%, var(--df-danger)), var(--df-danger)); + color: var(--df-bg); } -/* 气泡通用 */ +/* 气泡通用 — workbench 聊天范式:用户气泡=bg-card 底+0.5px 边框+9px 圆角。 + 9px 圆角与 9px 13px 内距均为 u-agent workbench 移植值,勿规整 */ .ai-msg-bubble { - border-radius: 10px; - padding: 8px 12px; + border-radius: 9px; + padding: 9px 13px; word-break: break-word; max-width: 80%; } .ai-msg-bubble--user { - background: var(--df-info-bg, rgba(100,181,246,0.1)); + background: var(--df-bg-card); + border: 0.5px solid var(--df-border); color: var(--df-text); } /* chip */ @@ -181,13 +186,13 @@ const imageParts = computed(() => msgImageParts(props.msg)) border-radius: 4px; font-size: 11px; margin: 0 2px; - background: var(--df-accent-bg, rgba(108,99,255,0.1)); - color: var(--df-accent, #6c63ff); + background: var(--df-accent-bg); + color: var(--df-accent); } /* 技能调用徽章(问题3):与 mention chip 同视觉家族,亮一档紫色底 + ⚡ 前缀区分 */ .ai-msg-chip--skill { - background: var(--df-accent-bg, rgba(108,99,255,0.18)); - color: var(--df-accent, #6c63ff); + background: var(--df-accent-bg); + color: var(--df-accent); font-weight: 600; } /* 图片 */ diff --git a/src/components/ai/MessageList.vue b/src/components/ai/MessageList.vue index 638d867..174362c 100644 --- a/src/components/ai/MessageList.vue +++ b/src/components/ai/MessageList.vue @@ -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…
//(含缩进码块),代码内容已 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(
/]*)>([\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 `${body}
`
+ return `${body}
`
},
)
// 有界 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 {
}
// 成功态短暂反馈(直接改按钮 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(() => {
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);
diff --git a/src/components/ai/OutboxDrawer.vue b/src/components/ai/OutboxDrawer.vue
index c896da0..24cd364 100644
--- a/src/components/ai/OutboxDrawer.vue
+++ b/src/components/ai/OutboxDrawer.vue
@@ -73,7 +73,7 @@
// OutboxDrawer:定时/待发消息统一列表(跨会话),支持 改期/立即发送/取消 + 底部新建。
// 挂载点在 ChatInput.vue(⏰ 入口按钮旁,Teleport 自脱离输入区布局);
// 后端 ai_outbox_* 命令未接线时 invoke 静默降级(useAiOutbox 内),抽屉空态可用。
-import { ref, computed, watch } from 'vue'
+import { ref, computed, watch, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAiOutbox, type AiOutboxItem } from '../../composables/ai/useAiOutbox'
import { useAiStore } from '../../stores/ai'
@@ -96,6 +96,20 @@ watch(() => props.open, (v) => {
if (v) void refresh()
})
+// F6-2(走查-信息UX 面六):Esc 关抽屉(与遮罩点击/✕ 同出口,键盘可达)。
+// 监听挂 document:AiChat 全局 Esc 仲裁在 window 层,document 先于 window 收到冒泡,
+// 此处消费即收口(stopPropagation),防「关抽屉的同时误关分离窗」一次按键双效果;
+// IME 组词中的 Esc(229/isComposing)归输入法,不关抽屉(仓内三处同款守卫)。
+function onEscClose(e: KeyboardEvent): void {
+ if (!props.open || e.key !== 'Escape') return
+ if (e.isComposing || e.keyCode === 229) return
+ e.preventDefault()
+ e.stopPropagation()
+ emit('close')
+}
+document.addEventListener('keydown', onEscClose)
+onBeforeUnmount(() => document.removeEventListener('keydown', onEscClose))
+
// ── 列表展示 ──
// 排序:活跃在前(sending→pending→failed),终态沉底;同级按 due_at 升序(尽快项 due null 排最前)
@@ -230,7 +244,8 @@ async function onCreate(): Promise {
.outbox-panel {
width: min(420px, 92vw);
height: 100%;
- background: var(--df-bg-card);
+ /* wave2:抽屉底 bg-raised(比 bg-card 抬一级,与遮罩分层) */
+ background: var(--df-bg-raised);
border-left: 0.5px solid var(--df-border);
display: flex;
flex-direction: column;
@@ -259,7 +274,7 @@ async function onCreate(): Promise {
border-radius: var(--df-radius);
}
.outbox-close:hover {
- background: var(--df-bg-raised);
+ background: var(--df-bg-card-hover);
color: var(--df-text);
}
.outbox-list {
@@ -309,8 +324,8 @@ async function onCreate(): Promise {
border-color: color-mix(in srgb, var(--df-warning) 40%, transparent);
}
.outbox-badge--failed {
- color: #e5484d;
- border-color: color-mix(in srgb, #e5484d 40%, transparent);
+ color: var(--df-danger);
+ border-color: color-mix(in srgb, var(--df-danger) 40%, transparent);
}
.outbox-text {
font-size: 12px;
@@ -336,7 +351,7 @@ async function onCreate(): Promise {
transition: background 0.15s, color 0.15s;
}
.outbox-act:hover:not(:disabled) {
- background: var(--df-bg-raised);
+ background: var(--df-bg-card-hover);
color: var(--df-text);
}
.outbox-act:disabled {
@@ -353,12 +368,12 @@ async function onCreate(): Promise {
color: #fff;
}
.outbox-act--danger:hover:not(:disabled) {
- color: #e5484d;
- border-color: color-mix(in srgb, #e5484d 40%, transparent);
+ color: var(--df-danger);
+ border-color: color-mix(in srgb, var(--df-danger) 40%, transparent);
}
.outbox-dt {
border: 0.5px solid var(--df-border);
- background: var(--df-bg-raised);
+ background: var(--df-bg-card);
color: var(--df-text);
font-size: 11px;
padding: 3px 6px;
@@ -382,7 +397,7 @@ async function onCreate(): Promise {
box-sizing: border-box;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius);
- background: var(--df-bg-raised);
+ background: var(--df-bg-card);
color: var(--df-text);
font-size: 12px;
font-family: var(--df-font-sans);
diff --git a/src/components/ai/TaskBoundaryCard.vue b/src/components/ai/TaskBoundaryCard.vue
index d1d5cfc..42e607a 100644
--- a/src/components/ai/TaskBoundaryCard.vue
+++ b/src/components/ai/TaskBoundaryCard.vue
@@ -276,7 +276,8 @@ const echoSummary = computed(() => {
diff --git a/src/components/ai/TaskParkedCard.vue b/src/components/ai/TaskParkedCard.vue
index b4e6ed5..06edb8b 100644
--- a/src/components/ai/TaskParkedCard.vue
+++ b/src/components/ai/TaskParkedCard.vue
@@ -99,15 +99,16 @@ async function handleStopArchive(): Promise {
diff --git a/src/components/ideas/IdeaDetail.vue b/src/components/ideas/IdeaDetail.vue
index f508993..57f1e34 100644
--- a/src/components/ideas/IdeaDetail.vue
+++ b/src/components/ideas/IdeaDetail.vue
@@ -114,7 +114,7 @@