diff --git a/doc/prd/workbench.md b/doc/prd/workbench.md index ebdf9384..e9474937 100644 --- a/doc/prd/workbench.md +++ b/doc/prd/workbench.md @@ -321,7 +321,7 @@ - `WB-COMPOSER-003-AC1` 智能模式发送后,主区先可见用户消息与 assistant 占位消息。 - `WB-COMPOSER-003-AC2` AI 运行成功启动后,assistant 过程流通过事件逐步追加更新,而不是一次性静态替换。 - `WB-COMPOSER-003-AC3` assistant 在 Brain 思考阶段于过程流最下方展示“让俺想想”并带有微光流动视觉反馈。运行未结束且尚无最终回答时预留同高占位,避免文案显隐导致消息区上挤抖动。当思考请求发起时展示文案;当收到输出、思考结束、出现“正在重试”过程组、已有可见流式 Thought / 最终回答、或 run 已结束(完成 / 失败 / 取消)时,必须隐藏“让俺想想”。 - - `WB-COMPOSER-003-AC4` assistant turn 中 brain 最终输出始终位于过程流下方,并按 markdown 渲染;最终回答在内容分片到达后直接流式渲染,不展示独立收尾等待层;金额等普通文本中的单个 `$` 不作为公式边界,数学公式使用 `$$...$$` 语法;无序和有序列表分别显示圆点和序号;内部文件链接打开右侧文件预览,网页链接打开右侧内置浏览器;若当前伙伴已有浏览器 Tab 正停留在同一 URL,则激活该 Tab,不新开。 + - `WB-COMPOSER-003-AC4` assistant turn 中 brain 最终输出始终位于过程流下方,并按 markdown 渲染;最终回答在内容分片到达后直接流式渲染,不展示独立收尾等待层;金额等普通文本中的单个 `$` 不作为公式边界,数学公式使用 `$$...$$` 语法;无序和有序列表分别显示圆点和序号;链接图标和链接文字与周围正文沿基线对齐;内部文件链接打开右侧文件预览,网页链接打开右侧内置浏览器;若当前伙伴已有浏览器 Tab 正停留在同一 URL,则激活该 Tab,不新开。 - `WB-COMPOSER-003-AC5` 决策层(Thought)只展示面向用户的短摘要,不得展示模型原始长篇推理;决策层出现时,当前“让俺想想”必须立即隐藏;已落地的 Thought 使用浅灰色(#787774)文本呈现思考状态;未超出单行时不展示折叠图标,超出单行时默认以单行省略号截断展示且不常驻图标,鼠标悬浮时显示展开图标,点击展开后常驻浅灰卡片背景并展示收起图标,展开内容受最大高度限制(超出纵向滚动),流式阶段平铺展开。 - `WB-COMPOSER-003-AC6` 过程流使用用户可理解的文案,不直接暴露内部 tool 名或底层事件名;两次 Thought 之间的工具调用(含 `learn_skill`)合并为一个 Activity 面板,展开后按时间平铺自然语言说明;压缩与重试单独成行。Activity header 汇总规则见 `WB-COMPOSER-009-AC5`;等待审批时的 header 与审后工具行图标见 `WB-BLOCKER-001-AC6` / `WB-BLOCKER-001-AC7`。 - `WB-COMPOSER-003-AC7` AI 输出中切到 Settings 或触发界面重新挂载后,回到 Workbench 能通过伙伴消息记录投影恢复过程流、partial answer、最终回答或等待审批状态。 @@ -479,7 +479,7 @@ #### WB-SCOPE-002 `@` 插入与删除规则 -- 系统行为:候选可通过键盘或鼠标插入为内联 token;目录、文件和网页资料都可作为合法 token 插入;输入区支持多个已插入 token 共存;token 使用无边框的紧凑图标加名称样式展示。目录、网页、PDF、DOCX、PPT/PPTX、Excel 使用各自的专属样式;Markdown、TXT 和其他文件统一使用普通文件样式。文字与图标保持同色。按 `Backspace` 可移除参考资料;发送后,用户消息中保留本轮参考资料 token 回显,样式与输入区 token 保持一致。 +- 系统行为:候选可通过键盘或鼠标插入为内联 token;目录、文件和网页资料都可作为合法 token 插入;输入区支持多个已插入 token 共存;token 使用无边框的紧凑图标加名称样式展示。目录、网页、PDF、DOCX、PPT/PPTX、Excel 使用各自的专属样式;Markdown、TXT 和其他文件统一使用普通文件样式。文字与图标保持同色,token 名称基线与相邻正文对齐。按 `Backspace` 可移除参考资料;发送后,用户消息中保留本轮参考资料 token 回显,样式与输入区 token 保持一致。 - 验收: - `WB-SCOPE-002-AC1` 目录、文件或网页资料插入成功后,参考资料以 token 形式保留在输入流中,并进入后续发送参数。 - `WB-SCOPE-002-AC2` 输入区支持多个已插入 token 共存,而不是后插入覆盖前一个范围。 diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css index 04afc65b..07450f39 100644 --- a/src/renderer/src/assets/base.css +++ b/src/renderer/src/assets/base.css @@ -1739,12 +1739,7 @@ mark { } .markdown-body .markdown-reference-link { - display: inline-flex; - align-items: baseline; - gap: 0.35rem; - max-width: 100%; - min-width: 0; - vertical-align: middle; + vertical-align: baseline; color: var(--notion-accent-blue); text-decoration: none; text-underline-offset: 3px; @@ -1753,18 +1748,28 @@ mark { .markdown-body .markdown-reference-icon { display: inline-flex; - align-items: baseline; + align-items: center; justify-content: center; flex-shrink: 0; + width: 0.95em; + height: 0.95em; + margin-right: 0.35rem; color: var(--notion-accent-blue); line-height: 1; + vertical-align: -0.12em; +} + +.markdown-body .markdown-reference-icon svg { + display: block; + width: 0.9em; + height: 0.9em; } .markdown-body .markdown-normal-link { color: var(--notion-accent-blue); text-decoration: none; text-underline-offset: 3px; - vertical-align: middle; + vertical-align: baseline; word-break: break-word; overflow-wrap: anywhere; } diff --git a/src/renderer/src/components/workbench/WorkbenchComposer.tsx b/src/renderer/src/components/workbench/WorkbenchComposer.tsx index 4111dd4f..5f2b4d5e 100644 --- a/src/renderer/src/components/workbench/WorkbenchComposer.tsx +++ b/src/renderer/src/components/workbench/WorkbenchComposer.tsx @@ -990,7 +990,7 @@ const WorkbenchComposer: React.FC = ({ aria-multiline="true" tabIndex={0} spellCheck={false} - className="block min-h-[32px] w-full whitespace-pre-wrap break-words bg-transparent text-[15px] leading-6 text-[#37352F] outline-none [&_[data-role='scope-token']]:align-middle" + className="block min-h-[32px] w-full whitespace-pre-wrap break-words bg-transparent text-[15px] leading-6 text-[#37352F] outline-none [&_[data-role='scope-token']]:align-baseline" data-testid={workbenchSelectors.input} onInput={handleEditorInput} onKeyDown={handleEditorKeyDown} diff --git a/src/renderer/src/components/workbench/composer/ReferenceIcon.tsx b/src/renderer/src/components/workbench/composer/ReferenceIcon.tsx index 3f091417..845ab2d5 100644 --- a/src/renderer/src/components/workbench/composer/ReferenceIcon.tsx +++ b/src/renderer/src/components/workbench/composer/ReferenceIcon.tsx @@ -10,13 +10,13 @@ type ReferenceIconProps = { const iconSizeClassName = { sm: 'h-[18px] w-[18px]', md: 'h-[21px] w-[21px]', - inline: 'h-[1.22em] w-[1.22em]' + inline: 'h-[0.9em] w-[0.9em]' } as const const containerSizeClassName = { sm: 'h-[22px] w-[22px]', md: 'h-8 w-8', - inline: 'h-[1.34em] w-[1.34em]' + inline: 'relative top-[0.1em] h-[1em] w-[1em]' } as const const DocumentGlyph: React.FC<{ diff --git a/src/renderer/src/components/workbench/composer/ReferenceInlineToken.tsx b/src/renderer/src/components/workbench/composer/ReferenceInlineToken.tsx index a5411ed8..ac92647f 100644 --- a/src/renderer/src/components/workbench/composer/ReferenceInlineToken.tsx +++ b/src/renderer/src/components/workbench/composer/ReferenceInlineToken.tsx @@ -18,7 +18,7 @@ export const ReferenceInlineToken: React.FC = ({ return ( diff --git a/src/renderer/src/components/workbench/composer/utils.ts b/src/renderer/src/components/workbench/composer/utils.ts index ac7c9ba4..66ff8431 100644 --- a/src/renderer/src/components/workbench/composer/utils.ts +++ b/src/renderer/src/components/workbench/composer/utils.ts @@ -133,7 +133,7 @@ export const readComposerNodesFromEditor = ( const appendReferenceIcon = (container: HTMLElement, scope: SelectedReference): void => { const visualSpec = getReferenceVisualSpec(scope) const icon = document.createElement('span') - icon.className = `inline-flex h-[1.34em] w-[1.34em] shrink-0 items-center justify-center ${visualSpec.iconClassName}` + icon.className = `relative top-[0.1em] inline-flex h-[1em] w-[1em] shrink-0 items-center justify-center ${visualSpec.iconClassName}` icon.dataset.referenceIconKind = visualSpec.kind const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') @@ -142,7 +142,7 @@ const appendReferenceIcon = (container: HTMLElement, scope: SelectedReference): visualSpec.kind === 'directory' || visualSpec.kind === 'web' ? '0 0 24 24' : '0 0 18 18' ) svg.setAttribute('aria-hidden', 'true') - svg.setAttribute('class', `h-[1.22em] w-[1.22em] ${visualSpec.iconClassName}`) + svg.setAttribute('class', `h-[0.9em] w-[0.9em] ${visualSpec.iconClassName}`) if (visualSpec.kind === 'directory' || visualSpec.kind === 'web') { svg.setAttribute('fill', 'none') @@ -215,7 +215,7 @@ const appendReferenceIcon = (container: HTMLElement, scope: SelectedReference): export const createScopeTokenElement = (scope: SelectedReference): HTMLElement => { const visualSpec = getReferenceVisualSpec(scope) const token = document.createElement('span') - token.className = `inline-flex max-w-full items-center gap-[2px] align-[-0.08em] text-[0.9em] font-medium leading-[inherit] ${visualSpec.labelClassName}` + token.className = `inline-flex max-w-full items-baseline gap-[2px] align-baseline text-[0.9em] font-medium leading-[inherit] ${visualSpec.labelClassName}` token.contentEditable = 'false' token.dataset.role = 'scope-token' token.dataset.scopeKey = getScopeKey(scope)