Skip to content

feat: Add websearch toggle to AI chat input#2971

Open
paultranvan wants to merge 2 commits intomasterfrom
feat/assistant-websearch
Open

feat: Add websearch toggle to AI chat input#2971
paultranvan wants to merge 2 commits intomasterfrom
feat/assistant-websearch

Conversation

@paultranvan
Copy link
Copy Markdown
Contributor

@paultranvan paultranvan commented Mar 24, 2026

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

See image

the sources look like this:

image

Summary by CodeRabbit

  • New Features

    • Added a web search toggle to enable/disable web search in conversations
    • Web sources (URLs) are now shown alongside files and emails in message sources
    • New web source list items link to original pages and include title/URL
  • Bug Fixes / UX

    • Improved error message wording during response generation
  • Localization

    • Added "Web search" translation entries (en/fr)

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Walkthrough

Adds web search support across the assistant stack. Introduces websearchEnabled state in AssistantProvider and exposes it via useAssistant(). Adds WebsearchButton UI and integrates it into ConversationBar and ConversationComposer (propagating toggle handlers). Extends Sources handling to include web sources and adds WebSourceItem. Expands stream/source types and payloads to carry web metadata. CozyRealtimeChatAdapter now accepts websearchEnabled and conditionally sends a websearch flag. Adds English and French localization keys for the websearch label and updates an error message.

Suggested reviewers

  • zatteo
  • rezk2ll
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a websearch toggle feature to the AI chat input, which is the primary feature delivered across the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/assistant-websearch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zatteo
Copy link
Copy Markdown
Member

zatteo commented Mar 26, 2026

Cant't wait to test! For curiosity, how does it work on backend? How URL to fetch are built? What is done with results?

@paultranvan paultranvan force-pushed the feat/assistant-websearch branch 2 times, most recently from 522f777 to 22eadfe Compare April 7, 2026 14:13
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
@paultranvan paultranvan force-pushed the feat/assistant-websearch branch from 22eadfe to bc1e002 Compare April 7, 2026 14:14
@paultranvan paultranvan marked this pull request as ready for review April 7, 2026 14:14
@paultranvan
Copy link
Copy Markdown
Contributor Author

paultranvan commented Apr 7, 2026

Cant't wait to test! For curiosity, how does it work on backend? How URL to fetch are built? What is done with results?

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.
Our main issue currently is this fetching: it adds latency, and can be token-consuming, so we truncate the results to avoid exceeding our context window

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/cozy-search/src/components/Conversations/Sources/Sources.jsx (2)

95-103: Use forEach instead of map for side effects.

sources.map() returns an array that is discarded. Since you're only performing side effects (pushing to arrays), forEach is 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 on urls.

Line 55 uses urls.length without optional chaining, while line 81 uses urls?.map. Since urls is always initialized as an empty array in SourcesWithFilesQuery (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 ConversationMessage interface (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

📥 Commits

Reviewing files that changed from the base of the PR and between f19da18 and bc1e002.

📒 Files selected for processing (12)
  • packages/cozy-search/src/components/AssistantProvider.d.ts
  • packages/cozy-search/src/components/AssistantProvider.jsx
  • packages/cozy-search/src/components/Conversations/ConversationBar.jsx
  • packages/cozy-search/src/components/Conversations/ConversationComposer.jsx
  • packages/cozy-search/src/components/Conversations/Sources/Sources.jsx
  • packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx
  • packages/cozy-search/src/components/Conversations/WebsearchButton.jsx
  • packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx
  • packages/cozy-search/src/components/adapters/CozyRealtimeChatAdapter.ts
  • packages/cozy-search/src/components/adapters/StreamBridge.ts
  • packages/cozy-search/src/locales/en.json
  • packages/cozy-search/src/locales/fr.json

@paultranvan paultranvan force-pushed the feat/assistant-websearch branch from bc1e002 to 5bd133d Compare April 7, 2026 14:49
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx (1)

49-56: Extract a shared Source type 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

📥 Commits

Reviewing files that changed from the base of the PR and between bc1e002 and 5bd133d.

📒 Files selected for processing (7)
  • packages/cozy-search/src/components/Conversations/Sources/Sources.jsx
  • packages/cozy-search/src/components/Conversations/Sources/WebSourceItem.jsx
  • packages/cozy-search/src/components/CozyAssistantRuntimeProvider.tsx
  • packages/cozy-search/src/components/adapters/CozyRealtimeChatAdapter.ts
  • packages/cozy-search/src/components/adapters/StreamBridge.ts
  • packages/cozy-search/src/locales/en.json
  • packages/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

@paultranvan paultranvan force-pushed the feat/assistant-websearch branch from 5bd133d to 4ec9f64 Compare April 8, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants