Repository navigation
feat: Add transcript text search to AI chat - #628
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR implements an end-to-end chat search feature. It adds core search utilities (markdown plaintext conversion, match finding, query normalization), a ChangesChat Search Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ai-core/__tests__/ChatSearch.test.tsx`:
- Around line 66-71: The test mutates a module-scoped variable (latestSearch)
during render in SearchController which causes the lint error; instead stop
reassigning latestSearch during render by making latestSearch a React ref or
state held by the test and have SearchController assign into it inside a
useEffect (or expose the hook result via a callback prop) after mount; update
the test to declare latestSearch as a MutableRef (or provide a setter function)
and in SearchController call useChatSearch() then set ref.current (or call the
setter) inside useEffect so no module-scoped mutation happens during render
(references: latestSearch, SearchController, useChatSearch).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 72eadddd-e1a9-4814-a22e-46143b726e1d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
examples/ai-core/src/components/MainView.tsxexamples/ai/src/components/MainView.tsxpackages/ai-core/__tests__/ChatSearch.test.tsxpackages/ai-core/package.jsonpackages/ai-core/src/components/AnalysisAnswer.tsxpackages/ai-core/src/components/AnalysisResult.tsxpackages/ai-core/src/components/Chat.tsxpackages/ai-core/src/components/ChatSearch.tsxpackages/ai-core/src/components/FlatAgentRenderer.tsx
There was a problem hiding this comment.
Pull request overview
Adds a chat-local transcript search surface to @sqlrooms/ai-core via Chat.Search, enabling query-based navigation through the current session transcript with match highlighting across prompts, assistant text, reasoning, and lightweight tool labels.
Changes:
- Introduces
ChatSearchProvider, search UI controls, match-finding, and highlighting utilities. - Integrates search block registration + highlighting into
AnalysisResult,AnalysisAnswer, and orchestrator tool log labeling. - Updates example AI UIs to display the new search control in the chat header.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new markdown parsing + jsdom-related dependencies. |
| packages/ai-core/src/components/FlatAgentRenderer.tsx | Adds optional search highlighting for orchestrator tool activity labels. |
| packages/ai-core/src/components/ChatSearch.tsx | Implements search state, match indexing, highlighting components, and a rehype plugin for markdown highlighting. |
| packages/ai-core/src/components/Chat.tsx | Wraps Chat.Root with ChatSearchProvider and exposes Chat.Search. |
| packages/ai-core/src/components/AnalysisResult.tsx | Registers searchable transcript blocks and highlights prompt/reasoning/tool labels. |
| packages/ai-core/src/components/AnalysisAnswer.tsx | Adds optional rehype highlighting plugin per message block. |
| packages/ai-core/package.json | Adds dependencies for markdown-to-plain-text and jsdom for tests. |
| packages/ai-core/tests/ChatSearch.test.tsx | Adds unit/component tests for matching, highlighting plugin behavior, navigation, and session reset. |
| examples/ai/src/components/MainView.tsx | Adds Chat.Search to the example header layout. |
| examples/ai-core/src/components/MainView.tsx | Adds Chat.Search to the example header layout. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const normalizedQuery = normalizeChatSearchQuery(query); | ||
| if (!normalizedQuery) return []; | ||
|
|
||
| const matches: ChatSearchMatch[] = []; | ||
| for (const block of blocks) { | ||
| if (!block.text) continue; | ||
|
|
||
| const normalizedText = block.text.toLocaleLowerCase(); | ||
| let start = normalizedText.indexOf(normalizedQuery); | ||
|
|
||
| while (start !== -1) { | ||
| const end = start + normalizedQuery.length; |
| <Markdown | ||
| remarkPlugins={[remarkGfm]} | ||
| rehypePlugins={[rehypeRaw]} | ||
| rehypePlugins={rehypePlugins as never} | ||
| components={ |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ai-core/src/components/AnalysisResult.tsx`:
- Around line 375-385: The JSX for ReasoningBox is malformed and passing an
undeclared prop: remove the className prop from the ReasoningBox invocation
(since ReasoningBox props don’t include className) and fix the tag nesting so
HighlightedChatSearchText is a child of ReasoningBox and ReasoningBox is
properly closed (use <ReasoningBox key=... text={part.text} isRunning={...}>
...</ReasoningBox> with HighlightedChatSearchText as the inner element). Ensure
you keep the existing props (key, text, isRunning) and the
HighlightedChatSearchText props (blockId, text) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 782f1f5c-8112-4f2f-825c-db2ea8644845
📒 Files selected for processing (5)
packages/ai-core/__tests__/ChatSearch.test.tsxpackages/ai-core/package.jsonpackages/ai-core/src/components/AnalysisAnswer.tsxpackages/ai-core/src/components/AnalysisResult.tsxpackages/ai-core/src/components/ChatSearch.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/ai-core/src/components/AnalysisAnswer.tsx
- packages/ai-core/package.json
- packages/ai-core/tests/ChatSearch.test.tsx
- packages/ai-core/src/components/ChatSearch.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ai-core/src/components/AnalysisResult.tsx`:
- Around line 220-254: Generate search blocks only for the same parts that are
actually rendered: before the forEach over uiMessageParts, filter uiMessageParts
using the same predicate the render path uses (exclude any part whose index
appears in the suppressed indices collection used by rendering and exclude parts
with ids that start with "step-"), then map that filtered array through the
existing isTextPart/isReasoningPart/isToolPart/isDynamicToolPart logic (using
getToolName and processAnalysisAnswerContent as before) so searchBlockPrefix and
analysisResult.id remain, ensuring returned blocks match visible highlights.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7e4efbdb-b04f-4e13-93e2-8807a6b77508
📒 Files selected for processing (3)
packages/ai-core/src/components/AnalysisResult.tsxpackages/ai-core/src/components/ChatSearch.tsxpackages/ai-core/src/hooks/useScrollToBottom.ts
✅ Files skipped from review due to trivial changes (1)
- packages/ai-core/src/hooks/useScrollToBottom.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ai-core/src/components/ChatSearch.tsx
…-to-ai # Conflicts: # packages/ai-core/src/components/Chat.tsx
| Item: typeof PromptSuggestions.Item; | ||
| VisibilityToggle: typeof PromptSuggestions.VisibilityToggle; | ||
| }; | ||
| Search: typeof ChatSearch; | ||
| ModelSelector: typeof ModelSelector; |
| reasoning && 'italic', | ||
| )} | ||
| > | ||
| {label} | ||
| {searchBlockId ? ( | ||
| <HighlightedChatSearchText blockId={searchBlockId} text={label} /> | ||
| ) : ( | ||
| label | ||
| )} |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
| useEffect(() => { | ||
| setActiveMatchIndex(0); | ||
| }, [currentSessionId, query]); | ||
|
|
||
| const blocks = useMemo(() => { | ||
| const allBlocks = Object.values(blockGroups).flat(); | ||
| if (!currentSessionId) return allBlocks; |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)
packages/ai-core/src/components/FlatAgentRenderer.tsx:623
searchBlockIdwas added to theOrchestratorLogLineInnerprops/destructuring but is not used anywhere in the component. That means matches for the tool-label blocks registered inAnalysisResultcannot render a corresponding<mark id=...>for highlighting/scrolling, so navigating to those matches won’t scroll to the tool line. Either usesearchBlockIdto wrap the renderedlabelwithHighlightedChatSearchText, or stop registering tool-label blocks until highlighting is implemented.
isSuccess: boolean;
isError: boolean;
toolCall: AgentToolCall;
searchBlockId?: string;
}> = ({toolCallId, isPending, isSuccess, isError, toolCall, searchBlockId}) => {
const showDetails = useShowToolCallDetails();
const {getActivityLabel} = useToolRenderBehavior();
const timing = useStoreWithAi((s) => s.ai.toolTimings[toolCallId]);
packages/ai-core/src/components/Chat.tsx:76
Chat.Searchis exported as part of theChatcompound component, but onlyChat.Rootis wrapped inChatSearchProvider. If a consumer renders<Chat.Search />inside<Chat.LocalAgentRoot>,useChatSearch()will throw at runtime ("Chat.Search must be rendered inside Chat.Root."). Consider also wrappingLocalAgentRootinChatSearchProvider(if search should work there), or adjust the API/error message to make the session-only constraint explicit and prevent accidental misuse.
const Root: FC<RootProps> = ({ children, toolRenderBehavior }) => (
<ToolRenderBehaviorProvider value={toolRenderBehavior ?? EMPTY_BEHAVIOR}>
<SessionChatRuntimeProvider>
<ChatSearchProvider>
<SessionChatManager />
{children}
</ChatSearchProvider>
</SessionChatRuntimeProvider>
</ToolRenderBehaviorProvider>
);
const LocalAgentRoot: FC<LocalAgentChatRootProps> = ({
children,
toolRenderBehavior,
...props
}) => (
<ToolRenderBehaviorProvider value={toolRenderBehavior ?? EMPTY_BEHAVIOR}>
<LocalAgentChatRuntimeProvider {...props}>
{children}
</LocalAgentChatRuntimeProvider>
</ToolRenderBehaviorProvider>
);
| uiMessageParts.forEach((part, index) => { | ||
| if (isTextPart(part)) { | ||
| blocks.push({ | ||
| id: `${searchBlockPrefix}:text:${index}`, | ||
| resultId: analysisResult.id, | ||
| text: markdownToPlainText( | ||
| processAnalysisAnswerContent(part.text).processedContent, | ||
| ), | ||
| }); |
There was a problem hiding this comment.
I added a guard for empty queries
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Summary
Chat.Searchin@sqlrooms/ai-coreTesting
@sqlrooms/ai-coreJest suiteSummary by CodeRabbit
New Features
UI/UX Improvements