fix(desktop): 修复会话内 Ctrl/Cmd+F 搜索无关或隐藏内容 - #1140
Conversation
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 新增会话搜索状态、本地与数据库命中合并及历史消息定位;数据库候选池截断仍会造成结果不完整。 |
| apps/desktop/src/renderer/components/chat/sessionSearchHighlight.ts | 新增基于可见 DOM 文本的词级 Range 收集,并规范化空白和块边界。 |
| apps/desktop/src/shared/conversationSearch.ts | 新增共享可见文本规范化,但图片 alt 文本仍会进入计数而无法在当前 DOM 高亮路径中生成 Range。 |
| apps/desktop/src/main/localDb/conversationSearch.pure.ts | 数据库搜索改为完整短语匹配、可见文本过滤并返回实际出现次数。 |
| apps/desktop/src/renderer/components/chat/MessageStream.tsx | 将搜索命中映射为 CSS Custom Highlight,并在聚焦时展开可折叠正文。 |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Q[会话内查询] --> L[已加载消息可见文本]
Q --> D[本地数据库历史搜索]
L --> M[合并词级命中]
D --> M
M --> J[加载并定位消息]
J --> R[从渲染正文创建 Range]
R --> H[普通命中与当前命中高亮]
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/shared/conversationSearch.ts:183-187
**图片替代文本无法高亮**
当消息包含 `` 且查询只出现在 `diagram` 中时,计数路径会保留图片替代文本,但 DOM Range 收集器会跳过承载图片的 `button` 后代,导致搜索栏计入并导航到该结果,却无法显示对应的词级高亮。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (9): Last reviewed commit: "fix(desktop): keep collapsed search hits..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
本 PR 在 Desktop 会话页接管 Ctrl/Cmd+F,将“整页搜索”收敛为仅对当前会话可见消息文本的局部搜索,并实现词级高亮与按真实出现次数统计/导航,同时用 DB 搜索补齐未加载的历史消息命中。
Changes:
- 会话页接管 Ctrl/Cmd+F 并新增局部搜索条,维护命中列表与前后导航(含 DB 补齐未加载消息)。
- 新增基于 CSS Highlights API 的词级高亮(区分当前命中与其他命中),并补齐主题语义色 token。
- 搜索/预览侧新增“可见文本”提取与 occurrenceCount 字段,并补充回归测试。
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/desktop/src/shared/conversationSearch.ts | 扩展搜索请求/命中结构,并新增可见 Markdown 文本提取函数供前后端复用 |
| apps/desktop/src/renderer/themes/colors.ts | 新增当前命中高亮的语义色 token(Light/Dark) |
| apps/desktop/src/renderer/themes/tests/cindyDecisionData.ts | 主题 token 测试数据补齐新增颜色 ID |
| apps/desktop/src/renderer/styles/globals.css | 添加 ::highlight 规则,定义会话内搜索命中/当前命中的样式 |
| apps/desktop/src/renderer/features/cc-agent/SessionSearchBar.tsx | 新增会话内搜索条组件(输入、计数、导航、关闭) |
| apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 接管快捷键、聚焦/跳转命中、合并本地与 DB 搜索结果并驱动消息流高亮 |
| apps/desktop/src/renderer/components/find-in-page/findInPageOwnership.ts | 增加 claim 订阅机制,支持全局 FindInPage 在被接管时主动关闭/隐藏 |
| apps/desktop/src/renderer/components/find-in-page/FindInPageBar.tsx | 在被 claim 时隐藏并在 claim 变化时自动关闭,避免与会话内搜索冲突 |
| apps/desktop/src/renderer/components/chat/sessionSearchHighlight.ts | 新增 DOM Range 构建逻辑,用于词级高亮而不改动 React DOM 树 |
| apps/desktop/src/renderer/components/chat/MessageStream.tsx | 集成 CSS.highlights + Highlight,对聚焦消息进行词级高亮与滚动定位 |
| apps/desktop/src/renderer/components/chat/tests/sessionSearchHighlight.test.ts | 增加可见文本 Range 构建的单测覆盖 |
| apps/desktop/src/main/localDb/ipc/search.ts | IPC 层透传 messagesOnly 参数到本地搜索实现 |
| apps/desktop/src/main/localDb/conversationSearch.ts | 本地搜索结果补齐 occurrenceCount,并支持按 messagesOnly 跳过 title 匹配 |
| apps/desktop/src/main/localDb/conversationSearch.pure.ts | 预览归一化补齐 occurrenceCount,并在可见文本提取中剥离不可见 Markdown/HTML 源信息 |
| apps/desktop/src/main/localDb/tests/conversationSearch.pure.test.ts | 补齐 occurrenceCount 断言,并新增“排除 Markdown 链接 destination”回归用例 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 950d064fb8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
MagicLizi
left a comment
There was a problem hiding this comment.
格式门
以下格式问题需修复后才能继续审查流程:
- Description 缺段落:风险 — PR 描述中缺少「风险」段落,请按 PR 模板补齐(说明可能的副作用或回归范围)
请修复后 push 新 commit。
|
命中 UI 路径(apps/desktop/src/renderer/components/chat/MessageStream.tsx / apps/desktop/src/renderer/components/find-in-page/FindInPageBar.tsx / apps/desktop/src/renderer/features/cc-agent/SessionSearchBar.tsx 等)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md 设计规范。 |
|
本 PR 触发了产品签字门(命中 UI 路径:聊天区搜索相关组件与样式),需要放行名单成员 review 并 Approve 后才能合并。 讨论 issue:#1156 放行方式:在本 PR 上提交 Approve;需要修改请 Request Changes,改完后重新 Approve 即放行。 |
950d064 to
e64b91a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e64b91a41f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (4)
apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx:676
- remoteHits 展开时用
Math.max(1, hit.occurrenceCount)会把 occurrenceCount=0 的命中强行计为 1 个结果,导致数量/导航与“可见文本出现次数”语义不一致,也可能把仅向量命中的不可见结果带进会话内搜索。建议只在 occurrenceCount>0 时纳入,并按 occurrenceCount 精确展开。
const remoteHits = response.results.flatMap((result) =>
result.contentHits
.filter((hit) => !seen.has(hit.messageClientId))
.flatMap((hit) =>
Array.from({ length: Math.max(1, hit.occurrenceCount) }, (_, occurrenceIndex) => ({
messageId: hit.messageId,
messageClientId: hit.messageClientId,
occurrenceIndex,
})),
),
apps/desktop/src/shared/conversationSearch.ts:128
visibleMarkdownTextForSearch会对整个 Markdown 源文本直接做 HTML tag / link destination 的剥离:这会误伤 fenced/inline code 中本应“可见”的内容(例如代码块里的<div>会被当作 tag 删除、代码块里的[a](b)会被当作链接去掉目标),从而导致会话内搜索漏匹配,occurrenceIndex 也可能与 DOM 高亮不一致。建议在处理时跳过 code block / inline code 片段,只对非代码片段做清理。
export function visibleMarkdownTextForSearch(source: string): string {
let text = source.replace(/<!--[\s\S]*?-->/g, '');
text = text.replace(/<[^>]+>/g, '');
text = stripMarkdownLinkDestinations(text);
return text;
apps/desktop/src/renderer/components/chat/sessionSearchHighlight.ts:42
findSessionSearchRanges在每次命中时都会用segments.find(...)线性扫描起止 segment,整体复杂度接近 O(segments * matches)。在较长消息或高频重算(例如流式输出/窗口变动导致多次 effect 重跑)时容易引入卡顿。由于 offset 单调递增,可以用双指针推进来把查找摊到 O(segments + matches)。
const normalizedText = text.toLocaleLowerCase();
const ranges: Range[] = [];
let offset = normalizedText.indexOf(normalizedQuery);
while (offset >= 0) {
const end = offset + normalizedQuery.length;
const startSegment = segments.find((segment) => segment.start <= offset && offset < segment.end);
const endSegment = segments.find((segment) => segment.start < end && end <= segment.end);
if (startSegment && endSegment) {
const range = document.createRange();
range.setStart(startSegment.node, offset - startSegment.start);
range.setEnd(endSegment.node, end - endSegment.start);
ranges.push(range);
}
offset = normalizedText.indexOf(normalizedQuery, end);
}
apps/desktop/src/renderer/components/chat/MessageStream.tsx:2506
- 这里的
scrollIntoView会在该 effect 每次重跑时触发;而依赖里包含visibleRenderItems,根据本文件注释其会在流式输出等场景频繁变化(甚至每 token),可能导致搜索态持续“抢滚动”/抖动。建议仅在命中不在视口内时才滚动到 active match,避免无意义的重复滚动。
highlights.set(matchKey, new Highlight(...ranges));
const activeRange = ranges[Math.min(focusMessageOccurrenceIndex ?? 0, ranges.length - 1)];
highlights.set(activeKey, new Highlight(activeRange));
activeRange.startContainer.parentElement?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
已根据 review 意见更新(commit af898df):\n\n- 将数据库历史消息匹配与当前已加载消息统一为大小写不敏感的完整输入短语匹配,避免多词查询按 token 部分命中。\n- 将 CSS Highlight 的 Range 搜索范围限制到各消息的正文容器,排除消息外层的标签、操作控件和特殊卡片标记,保持 occurrenceIndex 对齐。\n- 增加 CSS Highlights API 能力检测;不支持时跳过词级高亮但保留搜索导航。\n- 将 Range 的 segment 定位改为单调指针扫描,避免每次命中线性查找。\n- 保留 Markdown 行内代码和 fenced code 的可见文本,再过滤 HTML 注释、HTML source tag 和链接 destination。\n- 补充了完整短语计数、代码可见文本和正文边界回归测试。\n\n按产品决定,本次没有调整现有候选池截断策略。\n\n验证:Desktop typecheck、定向测试(18/18)、完整 unit workspace 门禁均通过。UI 手工验证截图仍需产品审核按仓库 UI 证据要求查看。 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af898df08f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Suppressed comments (2)
apps/desktop/src/shared/conversationSearch.ts:134
- visibleMarkdownTextForSearch 目前会直接用 /<[^>]+>/g 删除所有尖括号内容:这会把 Markdown autolink(例如
<https://example.com>/<foo@bar.com>)这种“渲染后可见”的文本也删掉,导致会话内搜索漏匹配;另外 stripMarkdownLinkDestinations 只处理](的行内链接,未处理 reference-style 链接([label][id])和引用定义([id]: https://...),这些 destination 在渲染后不可见但仍会进入搜索文本/preview,和 PR 目标(排除不可见链接目标、避免隐藏 token 被计入)不一致。建议:将<...>中的 URL/email 保留为可见文本;同时在 stripMarkdownLinkDestinations 之前剔除 reference-style 引用定义并把[label][id]归一为label。
text = text.replace(/<!--[\s\S]*?-->/g, '');
text = text.replace(/<[^>]+>/g, '');
text = stripMarkdownLinkDestinations(text);
apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx:680
- 远端结果合并回来时,这里无条件把 active 索引重置为 0(setSessionSearchActive(hits.length > 0 ? 0 : -1))。如果用户在远端请求完成前已经用 Enter/按钮导航到了其它命中,远端返回会把当前选中跳回第一个命中,属于可复现的行为回跳。可以用函数式 setState:仅在“当前还没有 active(-1)且合并后有命中”时才置 0,否则保留用户当前 active。
setSessionSearchHits(hits);
setSessionSearchActive(hits.length > 0 ? 0 : -1);
if (hits[0] && localHits.length === 0) focusSessionSearchHit(hits[0]);
MagicLizi
left a comment
There was a problem hiding this comment.
格式门打回
PR description 缺少必填段落:风险
请补全 PR 描述中的「风险」部分(按照 PR 模板要求,feat/fix 类型须包含:这次改了什么、怎么验证的、风险)。
补充后 push 新 commit,下轮 auto-review 会自动重新处理。
|
签字门已放行(product / arch,由 MagicLizi 确认),PR 恢复正常推进。 |
|
@xushixd 👋 这个 PR 还有 7 条 review conversation 没 resolve(apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx / apps/desktop/src/renderer/components/chat/sessionSearchHighlight.ts / apps/desktop/src/shared/conversationSearch.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
|
@xushixd 👋 这个 PR 还有 2 条 review conversation 没 resolve(apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
Replace Chromium whole-page find in conversation views with occurrence-level message search and visible-text highlighting. Exclude hidden Markdown targets, thinking, and tool-process content from matches. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Use exact phrase matching for persisted history, scope DOM ranges to message bodies, preserve visible code text, and guard unsupported highlight APIs. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
Signed-off-by: xushi <10560530+qingshuizhiren@users.noreply.github.com>
9512550 to
be104f8
Compare
| const labelStart = source[cursor] === '!' && source[cursor + 1] === '[' | ||
| ? cursor + 1 | ||
| : source[cursor] === '[' | ||
| ? cursor | ||
| : -1; |
There was a problem hiding this comment.
当消息包含  且查询只出现在 diagram 中时,计数路径会保留图片替代文本,但 DOM Range 收集器会跳过承载图片的 button 后代,导致搜索栏计入并导航到该结果,却无法显示对应的词级高亮。
Context Used: 使用和PR描述相同的语言进行评论 (source)
Knowledge Base Used: Desktop Renderer Chat UI
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/shared/conversationSearch.ts
Line: 183-187
Comment:
**图片替代文本无法高亮**
当消息包含 `` 且查询只出现在 `diagram` 中时,计数路径会保留图片替代文本,但 DOM Range 收集器会跳过承载图片的 `button` 后代,导致搜索栏计入并导航到该结果,却无法显示对应的词级高亮。
**Context Used:** 使用和PR描述相同的语言进行评论 ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))
**Knowledge Base Used:** [Desktop Renderer Chat UI](https://app.greptile.com/xindong/-/custom-context/knowledge-base/makecindy/cindy/-/docs/desktop-renderer-chat-ui.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.
Suppressed comments (2)
apps/desktop/src/shared/conversationSearch.ts:212
- stripMarkdownLinkDestinations 会把 Markdown 图片语法
的 alt 文本当作“可见文本”保留下来;但在当前 MarkdownRenderer 里图片渲染为<img alt=...>,alt 不会作为可见文本出现在消息气泡中。这样会导致数据库侧搜索命中图片 alt,但渲染侧高亮/跳转时找不到对应可见匹配。建议在处理图片语法时跳过 label 输出。
output += source.slice(labelStart + 1, labelEnd);
cursor = destinationEnd;
apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx:800
- 远端补全的 contentHits 目前只用 hiddenGhostMessageClientIds(仅来自已加载消息)来排除 ghost 自绘卡片对应的 assistant 消息。MessageStream 打开会话时会批量 ensureSessionCards,使 ghostCardSnapshot.byCallId 可能已包含“未加载历史消息”的 turn card 条目;这些消息在 UI 上同样会被自绘卡片替换/隐藏,但这里不会被排除,导致会话内搜索可能跳到一个实际不可见/不可匹配的命中。建议在远端命中过滤时也用 ghostCardSnapshot.byCallId 判定并排除 entry.status === 'ready' 的 messageClientId。
(hit) =>
!seen.has(hit.messageClientId) &&
!hiddenGhostMessageClientIds.has(hit.messageClientId) &&
hit.occurrenceCount > 0,
)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be104f8de5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const activeKey = 'cindy-session-search-active'; | ||
| highlights.delete(matchKey); | ||
| highlights.delete(activeKey); | ||
| if (!searchQuery || !focusMessageClientId) return; |
There was a problem hiding this comment.
Keep non-search jumps highlighted
When focusMessageClientId is set by existing entry points such as conversation-search result jumps or message deep links, the parent does not pass searchQuery (it is only set while the new in-session bar is open). This early return now leaves those jumps with only a scroll, because the previous message-level background highlight was removed from the wrapper, so users can land in a long message stream without any indication of the target. Please preserve a message-level highlight for focus requests that are not tied to the session search query.
Useful? React with 👍 / 👎.
| void makerChatStore | ||
| .loadAroundMessageClientId(sessionId, hit.messageClientId, { radius: 60 }) | ||
| .then((message) => { | ||
| if (message) requestFocusMessage(message.clientId, hit.occurrenceIndex); | ||
| }) |
There was a problem hiding this comment.
Ignore stale unloaded search focuses
When a search hit is not currently loaded, this starts an async loadAroundMessageClientId, but the callback is not tied to the current query, active hit, or open state. If the user changes the query, closes the bar, or navigates to another hit before the load resolves, the stale promise can still call requestFocusMessage and jump the viewport back to an old result while the UI indicates a different active hit. Please gate the callback with the current search request/active hit or cancel it during cleanup.
Useful? React with 👍 / 👎.
|
@xushixd 👋 这个 PR 现在在等维护者确认,确认之前流程不会合并它 —— 不是卡住了,也不是在等你再改一版(你推的改动流程都读到了,判的就是最新一版代码)。
这条是流程自动发的状态提醒(同一版代码只发一次),不用回复。 |
|
@xushixd 👋 这个 PR 还有 7 条 review conversation 没 resolve(apps/desktop/src/renderer/components/chat/sessionSearchHighlight.ts / apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx / apps/desktop/src/renderer/components/chat/UserMessage.tsx / apps/desktop/src/shared/conversationSearch.ts / apps/desktop/src/renderer/components/chat/MessageStream.tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
|
@xushixd 👋 这个 PR 目前与 请在本地 merge 最新的 |
这次改了什么
摘要
修复 Desktop 会话页面中 Ctrl/Cmd+F 使用 Chromium 整页搜索的问题。
此前搜索会把模型名称、侧栏、搜索框自身,以及默认隐藏的思考和工具过程等无关内容计入结果。现在搜索范围仅限当前会话中的可见消息文本,并按照关键词实际出现次数统计和导航,只高亮匹配到的词语。
变更类型
feat新功能fix缺陷修复refactor/perf重构或性能优化docs/test/chore文档、测试或工程维护范围
UI 变化
Desktop 会话页新增局部搜索条,并将原来的整条消息高亮改为词级高亮。当前匹配与其他匹配使用不同强度的语义颜色。
docs/design-rules/DESIGN.md§2 Color Palette & Roles:搜索匹配颜色使用语义色,不硬编码主题专属颜色。docs/design-rules/DESIGN.md§4 Component Stylings / Inputs & Forms:搜索条沿用现有输入框和按钮样式。docs/design-rules/DESIGN.md§10 Theme System & Token Reference:新增语义 token,并同时实现 Light 与 Dark 模式。docs/design-rules/DESIGN.md§14.2 Focus Management:通过快捷键打开搜索条后,自动聚焦并选中查询输入。怎么验证的
自动验证
手工验证
在 macOS 上启动独立的 Desktop 开发版实例进行实际操作验证:
风险
风险分类
影响与回滚
提交前检查
git commit -s,见 DCO)