feat: Add websearch toggle to AI chat input#2971
Conversation
WalkthroughAdds web search support across the assistant stack. Introduces Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 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 |
packages/cozy-search/src/components/Conversations/ConversationBar.jsx
Outdated
Show resolved
Hide resolved
packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx
Outdated
Show resolved
Hide resolved
packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx
Outdated
Show resolved
Hide resolved
|
Cant't wait to test! For curiosity, how does it work on backend? How URL to fetch are built? What is done with results? |
522f777 to
22eadfe
Compare
Add an optional websearch feature to RAG search. When enabled, the search will leverage user document and perform web search to combine sources. This feature is hidden behind a `cozy.assistant.websearch.enabled` flag
22eadfe to
bc1e002
Compare
This uses an external API able to search web on natural language. Based on their index, relevant URL are retrieved. Then, we (openRAG) fetch the URL content to extract some information, and give it as context to the LLM. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/cozy-search/src/components/Conversations/Sources/Sources.jsx (2)
95-103: UseforEachinstead ofmapfor side effects.
sources.map()returns an array that is discarded. Since you're only performing side effects (pushing to arrays),forEachis semantically more appropriate.Suggested fix
- sources.map(source => { + sources.forEach(source => { if (source.sourceType === WEB_SOURCE_TYPE) { urls.push(source) } else if (source.doctype === EMAIL_DOCTYPE) { emails.push(source) } else { fileIds.push(source.id) } })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cozy-search/src/components/Conversations/Sources/Sources.jsx` around lines 95 - 103, The code uses sources.map(...) purely for side effects (pushing into urls, emails, fileIds) which discards the returned array; change the call to sources.forEach(...) to reflect intent and avoid creating an unused array. Update the invocation where sources.map is used (referencing the symbols sources, WEB_SOURCE_TYPE, EMAIL_DOCTYPE, urls, emails, fileIds) so the behavior and pushes remain identical but use forEach instead of map.
53-56: Inconsistent optional chaining onurls.Line 55 uses
urls.lengthwithout optional chaining, while line 81 usesurls?.map. Sinceurlsis always initialized as an empty array inSourcesWithFilesQuery(line 93), the optional chaining on line 81 is unnecessary but harmless. However, the inconsistency is confusing.Suggested fix for consistency
- {urls?.map(url => ( + {urls.map(url => ( <WebSourceItem key={`${messageId}-${url.url}`} source={url} /> ))}Also applies to: 81-83
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cozy-search/src/components/Conversations/Sources/Sources.jsx` around lines 53 - 56, The code inconsistently uses optional chaining for `urls` (e.g., `urls.length` inside the `label` call using `t('assistant.sources', files.length + emails.length + urls.length)`, while elsewhere `urls?.map` is used); since `SourcesWithFilesQuery` always initializes `urls` to an empty array, make the usage consistent by removing optional chaining and using `urls.map` everywhere (or alternatively add `.length` guard to the earlier usage if you prefer to keep the `?.` style), updating all spots referencing `urls` (including where `urls?.map` and the `label` expression are used) to match the chosen non-optional style.packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx (1)
49-56: Consider extracting a shared type for source metadata.The source object type is duplicated in the
ConversationMessageinterface (lines 49-56) and the realtime event handler (lines 235-242). Extracting a shared type would improve maintainability.Suggested refactor
+interface SourceMetadata { + id?: string + doctype?: string + sourceType?: string + url?: string + title?: string + snippet?: string +} + interface ConversationMessage { id: string role: 'user' | 'assistant' content: string - sources?: Array<{ - id?: string - doctype?: string - sourceType?: string - url?: string - title?: string - snippet?: string - }> + sources?: SourceMetadata[] }Then use
SourceMetadata[]in the realtime event handler as well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx` around lines 49 - 56, Extract a shared type for the source object (e.g. SourceMetadata) and replace the inline array type used in ConversationMessage.sources and the realtime event handler's source array with that shared type; specifically, create a new exported type/interface named SourceMetadata describing { id?, doctype?, sourceType?, url?, title?, snippet? } and update the ConversationMessage interface (sources?: SourceMetadata[]) and the realtime event handler usage (the array currently defined at lines around the handler) to reference SourceMetadata so both places use the same definition for maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cozy-search/src/components/Conversations/ConversationComposer.jsx`:
- Around line 53-55: The toggle handler handleToggleWebsearch currently flips
websearch state unconditionally which can rebuild the adapter during an active
stream; modify handleToggleWebsearch to check the isRunning flag and no-op (or
early-return) when isRunning is true so setWebsearchEnabled is not called while
a response is streaming, preventing adapter rebuilds in
CozyAssistantRuntimeProvider.
In `@packages/cozy-search/src/components/Conversations/Sources/Sources.jsx`:
- Around line 81-83: The map over urls in Sources.jsx uses
key={`${messageId}-${url.url}`} which can produce duplicate keys if url.url is
undefined; update the mapping in the component that renders WebSourceItem to use
a stable fallback (e.g., include the map index or a unique id like url.id when
present) so the key becomes deterministic and unique (reference the map callback
that renders WebSourceItem and the key expression to modify). Ensure the chosen
fallback is used only when url.url is missing to avoid changing existing keys
unnecessarily.
In `@packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx`:
- Around line 12-13: The code directly uses the raw source url when rendering
anchors (variables url and displayTitle in WebSourceItem.jsx); validate and
sanitize the URL before using it: create a helper (e.g., isAllowedProtocol or
sanitizeUrl) that parses url with the URL constructor and only allows http: and
https: (reject javascript:, data:, etc.), then use a safeHref variable for the
anchor; if the url is invalid or uses a disallowed protocol, render
non-clickable text (span) or omit the href rather than setting href={url};
update all occurrences where url is used in anchors (the displayTitle/url usage
and the other anchor renderings around lines referenced) to use this validation
helper.
---
Nitpick comments:
In `@packages/cozy-search/src/components/Conversations/Sources/Sources.jsx`:
- Around line 95-103: The code uses sources.map(...) purely for side effects
(pushing into urls, emails, fileIds) which discards the returned array; change
the call to sources.forEach(...) to reflect intent and avoid creating an unused
array. Update the invocation where sources.map is used (referencing the symbols
sources, WEB_SOURCE_TYPE, EMAIL_DOCTYPE, urls, emails, fileIds) so the behavior
and pushes remain identical but use forEach instead of map.
- Around line 53-56: The code inconsistently uses optional chaining for `urls`
(e.g., `urls.length` inside the `label` call using `t('assistant.sources',
files.length + emails.length + urls.length)`, while elsewhere `urls?.map` is
used); since `SourcesWithFilesQuery` always initializes `urls` to an empty
array, make the usage consistent by removing optional chaining and using
`urls.map` everywhere (or alternatively add `.length` guard to the earlier usage
if you prefer to keep the `?.` style), updating all spots referencing `urls`
(including where `urls?.map` and the `label` expression are used) to match the
chosen non-optional style.
In `@packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx`:
- Around line 49-56: Extract a shared type for the source object (e.g.
SourceMetadata) and replace the inline array type used in
ConversationMessage.sources and the realtime event handler's source array with
that shared type; specifically, create a new exported type/interface named
SourceMetadata describing { id?, doctype?, sourceType?, url?, title?, snippet? }
and update the ConversationMessage interface (sources?: SourceMetadata[]) and
the realtime event handler usage (the array currently defined at lines around
the handler) to reference SourceMetadata so both places use the same definition
for maintainability.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6509e71e-7d4a-403f-87a1-d2b1f8024c06
📒 Files selected for processing (12)
packages/cozy-search/src/components/AssistantProvider.d.tspackages/cozy-search/src/components/AssistantProvider.jsxpackages/cozy-search/src/components/Conversations/ConversationBar.jsxpackages/cozy-search/src/components/Conversations/ConversationComposer.jsxpackages/cozy-search/src/components/Conversations/Sources/Sources.jsxpackages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsxpackages/cozy-search/src/components/Conversations/WebsearchButton.jsxpackages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsxpackages/cozy-search/src/components/adapters/CozyRealtimeChatAdapter.tspackages/cozy-search/src/components/adapters/StreamBridge.tspackages/cozy-search/src/locales/en.jsonpackages/cozy-search/src/locales/fr.json
packages/cozy-search/src/components/Conversations/ConversationComposer.jsx
Outdated
Show resolved
Hide resolved
packages/cozy-search/src/components/Conversations/Sources/Sources.jsx
Outdated
Show resolved
Hide resolved
packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx
Show resolved
Hide resolved
bc1e002 to
5bd133d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx (1)
49-56: Extract a sharedSourcetype to prevent schema drift.The same source metadata shape is declared twice. A single shared type alias will reduce future mismatch risk between conversation history and realtime event payloads.
♻️ Suggested refactor
+type AssistantSource = { + id?: string + doctype?: string + sourceType?: string + url?: string + title?: string + snippet?: string +} + interface ConversationMessage { id: string role: 'user' | 'assistant' content: string - sources?: Array<{ - id?: string - doctype?: string - sourceType?: string - url?: string - title?: string - snippet?: string - }> + sources?: AssistantSource[] } ... | { _id: string object: 'sources' - content: Array<{ - id?: string - doctype?: string - sourceType?: string - url?: string - title?: string - snippet?: string - }> + content: AssistantSource[] }Also applies to: 235-242
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx` around lines 49 - 56, Extract a shared type alias (e.g., "Source") for the repeated source metadata shape and use it for the two inline declarations of sources; specifically, create a type alias matching the object shape (id, doctype, sourceType, url, title, snippet) and replace occurrences of "sources?: Array<{...}>" with "sources?: Source[]" in CozyAssistantRuntimeProvider (and the other duplicate block in the same file). Keep the alias exported if it must be consumed by other modules, and ensure any existing usages (conversation history or realtime payload typings) are updated to reference the new Source type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cozy-search/src/components/adapters/CozyRealtimeChatAdapter.ts`:
- Around line 88-96: Before calling client.stackClient.fetchJSON in
CozyRealtimeChatAdapter, guard against pre-request cancellation by checking
abortSignal.aborted and returning/throwing early; also pass the abort signal
into the fetchJSON call if the HTTP client supports it (e.g., include { signal:
abortSignal } or equivalent with the existing payload). Specifically, in the
method that uses conversationId, userQuery, assistantId and websearchEnabled,
add an early check for abortSignal.aborted and ensure the fetchJSON invocation
includes the abortSignal so the POST is not sent or can be aborted immediately
when cancellation occurs.
---
Nitpick comments:
In `@packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx`:
- Around line 49-56: Extract a shared type alias (e.g., "Source") for the
repeated source metadata shape and use it for the two inline declarations of
sources; specifically, create a type alias matching the object shape (id,
doctype, sourceType, url, title, snippet) and replace occurrences of "sources?:
Array<{...}>" with "sources?: Source[]" in CozyAssistantRuntimeProvider (and the
other duplicate block in the same file). Keep the alias exported if it must be
consumed by other modules, and ensure any existing usages (conversation history
or realtime payload typings) are updated to reference the new Source type.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 53a60958-d5a0-42bd-90d4-950c2af68f82
📒 Files selected for processing (7)
packages/cozy-search/src/components/Conversations/Sources/Sources.jsxpackages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsxpackages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsxpackages/cozy-search/src/components/adapters/CozyRealtimeChatAdapter.tspackages/cozy-search/src/components/adapters/StreamBridge.tspackages/cozy-search/src/locales/en.jsonpackages/cozy-search/src/locales/fr.json
✅ Files skipped from review due to trivial changes (2)
- packages/cozy-search/src/locales/fr.json
- packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/cozy-search/src/locales/en.json
- packages/cozy-search/src/components/Conversations/Sources/Sources.jsx
- packages/cozy-search/src/components/adapters/StreamBridge.ts
5bd133d to
4ec9f64
Compare
Add an optional websearch feature to RAG search.
When enabled, the search will leverage user document and perform web search to combine sources.
This feature is hidden behind a
cozy.assistant.websearch.enabledflagSee
the sources look like this:
Summary by CodeRabbit
New Features
Bug Fixes / UX
Localization