Skip to content

feat(agent studio): add prompt suggestions widget#7054

Open
shaejaz wants to merge 47 commits into
masterfrom
feat/chat-page-suggestions-widget
Open

feat(agent studio): add prompt suggestions widget#7054
shaejaz wants to merge 47 commits into
masterfrom
feat/chat-page-suggestions-widget

Conversation

@shaejaz

@shaejaz shaejaz commented May 22, 2026

Copy link
Copy Markdown
Contributor

FX-3845

Adds a PromptSuggestions widget that renders AI-generated prompt "pills" for the page, powered by the Agent Studio /tasks endpoint. Clicking a pill hands the prompt off to the Chat widget on the same index.

Highlights

  • New connector connectPromptSuggestions (instantsearch.js, $$type: 'ais.promptSuggestions') — owns all search-aware logic: fetch on mount, 300ms debounced refetch on query/refinement change, manual refresh(), transformItems/transformHits, and chat hand-off.
  • New generic connectTasks connector — the reusable core underneath. connectPromptSuggestions is now a thin search-aware layer on top of it, so any future task-backed feature can reuse the same request/streaming/output plumbing. Connector-only (no default UI), generic over TOutput, $$type: 'ais.tasks'; params are agentId XOR transport, task, and stream.
  • New lib/tasks/ runtime — the generic Agent Studio tasks-endpoint counterpart to lib/chat/ (completions). Endpoint/credential/payload primitives (resolveEndpoint, buildTaskPayload, fetchTask with optional streaming, createTaskRunner, and Task* types); nothing widget-specific.
  • Context, decided at init by the presence of context:
    • Default (no context): auto-extracts { query, filters, hitsSample } from the current search state and refreshes as results change. transformHits shapes the sample (default: first 5 hits, internal _-prefixed metadata stripped).
    • Explicit context (object or per-fetch function): sent as-is; hit auto-extraction is skipped.
  • Shared UI in instantsearch-ui-components (createPromptSuggestionsComponent): .ais-PromptSuggestions pills + skeleton loading state, disabled while the chat is busy. Overridable via layoutComponent (React) / templates.layout (JS) and onSuggestionClick.
  • Auth: pass agentId (uses the InstantSearch client's app id / key) or a custom transport (api / headers). configurationId selects the Agent Studio task (sent as the task field, default 'prompt_suggestions').
  • Tests: full connector unit suites for connectPromptSuggestions and connectTasks, lib/tasks unit tests, and cross-flavor common tests (common-widgets -t "PromptSuggestions widget common tests"); Vue registered as skipped.

React API

Default (page-driven):

import { PromptSuggestions } from 'react-instantsearch';

<PromptSuggestions agentId="YOUR_AGENT_ID" />

Explicit context (e.g. a product page):

<PromptSuggestions
  agentId="YOUR_AGENT_ID"
  context={{
    productName: product.name,
    category: product.category,
    price: product.price,
  }}
/>

@codacy-production

codacy-production Bot commented May 22, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 437 complexity

Metric Results
Complexity 437

View in Codacy

TIP This summary will be updated as you push new changes.

@shaejaz shaejaz changed the title wip: on page suggestions widget feat(chat): add on page suggestions widget Jul 9, 2026
@shaejaz
shaejaz requested a review from Copilot July 9, 2026 09:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new ChatPageSuggestions widget (prompt “pills” driven by Agent Studio tasks) across InstantSearch.js and React InstantSearch, and extends the SSR pipeline to await and hydrate chat-related async state so suggestions/messages can be embedded in server HTML and reused on hydration.

Changes:

  • Add connectChatPageSuggestions + JS/React widget wrappers and common-widget tests (Vue explicitly unsupported).
  • Add SSR “server-wait promises” + initialChatStates serialization/hydration (Next.js + core SSR utilities).
  • Update shared ChatPromptSuggestions UI component and CSS to support loading skeletons/disabled state.

Reviewed changes

Copilot reviewed 45 out of 46 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/common/widgets/index.ts Exports the new common widget test suite.
tests/common/widgets/chat-page-suggestions/templates.tsx Common template/layout override tests for the widget.
tests/common/widgets/chat-page-suggestions/options.ts Common behavioral/option tests (loading, transform, click, transport, context).
tests/common/widgets/chat-page-suggestions/index.ts Registers the flavored common tests for the widget.
packages/vue-instantsearch/src/tests/common-widgets.test.js Marks the widget unsupported in Vue common tests.
packages/react-instantsearch/src/widgets/index.ts Exposes ChatPageSuggestions from React widgets entrypoint.
packages/react-instantsearch/src/widgets/ChatPageSuggestions.tsx Implements the React widget wrapper around useChatPageSuggestions + UI component.
packages/react-instantsearch/src/widgets/tests/all-widgets.test.tsx Adds widget metadata coverage to the “all widgets” snapshot list.
packages/react-instantsearch/src/widgets/tests/utils/all-widgets.tsx Excludes ChatPageSuggestions from generic render assertions (renders null initially).
packages/react-instantsearch/src/tests/common-widgets.test.tsx Wires the React widget into cross-flavor common widget tests.
packages/react-instantsearch-nextjs/src/InstantSearchNext.tsx Adds initialChatStates support to SSR context hydration.
packages/react-instantsearch-nextjs/src/InitializePromise.ts Awaits widget-registered SSR promises and injects serialized chatStates.
packages/react-instantsearch-nextjs/src/createInsertHTML.tsx Injects serialized initial chat state into HTML for hydration.
packages/react-instantsearch-core/src/server/getServerState.tsx Exposes initialChatStates from server state when present.
packages/react-instantsearch-core/src/lib/useInstantSearchApi.ts Hydrates _initialChatStates onto the InstantSearch instance client-side.
packages/react-instantsearch-core/src/index.ts Exports the new useChatPageSuggestions hook.
packages/react-instantsearch-core/src/connectors/useChatPageSuggestions.ts Adds hook wrapper for connectChatPageSuggestions.
packages/react-instantsearch-core/src/components/InstantSearchSSRProvider.tsx Extends SSR provider contract to include initialChatStates.
packages/instantsearch.js/test/createInstantSearch.ts Updates InstantSearch test mock to include SSR wait/chat state fields.
packages/instantsearch.js/src/widgets/index.ts Exposes chatPageSuggestions widget from JS widgets entrypoint.
packages/instantsearch.js/src/widgets/chat-page-suggestions/chat-page-suggestions.tsx Implements the InstantSearch.js widget wrapper (Preact renderer + templates).
packages/instantsearch.js/src/widgets/tests/index.test.ts Ensures the widget is creatable via the widgets index.
packages/instantsearch.js/src/lib/tasks/types.ts Adds types for the Agent Studio tasks transport/credentials.
packages/instantsearch.js/src/lib/tasks/resolveEndpoint.ts Resolves task endpoint + headers from either transport or credentials.
packages/instantsearch.js/src/lib/tasks/index.ts Exports tasks helpers/types.
packages/instantsearch.js/src/lib/tasks/buildTaskPayload.ts Builds { task, input } payload envelope.
packages/instantsearch.js/src/lib/tasks/buildEndpoint.ts Constructs default Agent Studio tasks endpoint URL.
packages/instantsearch.js/src/lib/server.ts SSR now waits for widget-registered promises before resolving.
packages/instantsearch.js/src/lib/InstantSearch.ts Adds _initialChatStates + server-wait promise registry APIs.
packages/instantsearch.js/src/lib/chat/sendMessageWithContext.ts Extracts send-with-context wrapper into reusable helper.
packages/instantsearch.js/src/lib/chat/openChat.ts Extends chat referer values for new page widgets.
packages/instantsearch.js/src/lib/chat/createAgentTransport.ts Introduces shared agent transport builder for chat connector.
packages/instantsearch.js/src/lib/chat/chat.ts Makes chat persistence safer in SSR/non-storage environments and adds persist option.
packages/instantsearch.js/src/connectors/index.ts Exports connectChatPageSuggestions.
packages/instantsearch.js/src/connectors/chat/connectChat.ts Refactors transport creation + context injection to shared helpers.
packages/instantsearch.js/src/connectors/chat-page-suggestions/connectChatPageSuggestions.ts New connector implementing debounced fetch + SSR snapshot/hydration + chat handoff.
packages/instantsearch.js/src/connectors/chat-page-suggestions/tests/connectChatPageSuggestions-test.ts Unit tests for connector lifecycle, payloads, handoff, SSR behavior.
packages/instantsearch.js/src/tests/common-widgets.test.tsx Wires JS widget into cross-flavor common widget tests.
packages/instantsearch.css/src/components/chat/_chat-suggestions.scss Adds skeleton styling for prompt suggestions.
packages/instantsearch-ui-components/src/components/chat/ChatPromptSuggestions.tsx Adds loading skeleton + disabled support to shared UI component.
examples/react/next-app-router/app/Search.tsx Demonstrates the widget usage in Next App Router example.
examples/react/next-app-router/app/page.tsx Computes absolute base URL for SSR-side fetches in example.
examples/react/next-app-router/app/api/chat-page-suggestions/route.ts Adds a mock endpoint for page suggestions example.
examples/react/getting-started/vite.config.mjs Adds a Vite dev-server mock endpoint for page suggestions.
examples/react/getting-started/src/App.tsx Demonstrates the widget usage in the “getting-started” React example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/instantsearch.js/src/lib/chat/createAgentTransport.ts Outdated
Comment thread packages/instantsearch.js/src/connectors/chat/connectChat.ts Outdated
Comment thread examples/react/next-app-router/app/api/chat-page-suggestions/route.ts Outdated
Comment thread examples/react/getting-started/vite.config.mjs Outdated
Comment thread examples/react/next-app-router/app/Search.tsx Outdated
Comment thread packages/instantsearch.js/src/lib/chat/createAgentTransport.ts Outdated
shaejaz and others added 5 commits July 9, 2026 11:05
The example wiring isn't needed for now; reverts examples/ back to master.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shaejaz
shaejaz requested a review from Copilot July 9, 2026 10:39
@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown
More templates

algoliasearch-helper

npm i https://pkg.pr.new/algolia/instantsearch/algoliasearch-helper@7054

instantsearch-ui-components

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch-ui-components@7054

instantsearch.css

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.css@7054

instantsearch.js

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.js@7054

react-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch@7054

react-instantsearch-core

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-core@7054

react-instantsearch-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-nextjs@7054

react-instantsearch-router-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-router-nextjs@7054

vue-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/vue-instantsearch@7054

commit: 8bd3025

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 42 out of 42 changed files in this pull request and generated 4 comments.

Comment thread packages/react-instantsearch-nextjs/src/InstantSearchNext.tsx Outdated
Comment thread packages/instantsearch.js/src/lib/server.ts Outdated
Comment thread packages/instantsearch.js/src/lib/InstantSearch.ts Outdated
@shaejaz
shaejaz requested a review from Haroenv July 15, 2026 09:23
Comment thread packages/instantsearch-ui-components/src/components/chat/OnPageSuggestions.tsx Outdated
Comment thread packages/instantsearch.js/src/lib/ai-lite/parse-partial-json.ts
Comment thread packages/instantsearch.js/src/lib/chat/chat.ts Outdated
Comment thread packages/instantsearch.js/src/lib/chat/openChat.ts Outdated
Comment thread packages/instantsearch.js/src/lib/chat/sendMessageWithContext.ts Outdated
shaejaz and others added 5 commits July 16, 2026 16:56
# Conflicts:
#	packages/instantsearch.js/src/lib/server.ts
#	packages/react-instantsearch-nextjs/src/InitializePromise.ts
#	packages/react-instantsearch-nextjs/src/__tests__/InitializePromise.test.tsx
The SSR guard for `sessionStorage` in the Chat state is moved to its own
PR (fix/chat-ssr-sessionstorage-guard) since it touches internal lib/chat
files and reviews more clearly on its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shaejaz
shaejaz requested a review from Haroenv July 17, 2026 11:35
@Haroenv Haroenv changed the title feat(chat): add on page suggestions widget feat(agent studio): add prompt suggestions widget Jul 17, 2026
const DEFAULT_TRANSFORM_HITS: PromptSuggestionsTransformHits = (hits) =>
hits.slice(0, 5).map(stripInternalHitMetadata);

function buildFilters(results: SearchResults): string[][] | undefined {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

have we tested if this logic is worth it? wonder if it's better to send the whole search results without transforming the parameters and doing any transform later backend?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe the evaluations stated that the results are sent as hits plus the query/filters/categories.
So replicated that here without sending everything.

@shaejaz
shaejaz requested a review from Haroenv July 20, 2026 09:35
Comment on lines +9 to +11
| 'prompt-suggestions'
| 'ai-mode'
| 'prompt-suggestions';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

twice the same one? one of the two should be the autocomplete prompt suggestions, the other should be the standalone one. We should be able to distinguish

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've gone with prompt-suggestions for the widget and prompt-suggestions-autocomplete for autocomplete as it makes a bit more sense to me.

This does cause the previous prompt-suggestions associated with autocomplete to be mixed going forward. Only if we are saving this information currently. Wdyt?

Comment thread packages/react-instantsearch/src/widgets/PromptSuggestions.tsx Outdated
Comment thread packages/instantsearch.js/src/lib/tasks/endpoint.ts Outdated
@shaejaz
shaejaz requested a review from Haroenv July 20, 2026 11:39
Comment thread packages/instantsearch.js/src/lib/chat/openChat.ts Outdated
@shaejaz
shaejaz requested a review from Haroenv July 20, 2026 13:34
Comment thread packages/instantsearch.js/src/connectors/tasks/connectTasks.ts Outdated
Comment thread packages/instantsearch.js/src/connectors/tasks/connectTasks.ts Outdated
@shaejaz
shaejaz requested a review from anjola-adeuyi July 21, 2026 14:48

@Haroenv Haroenv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

couple questions about edge cases

return suggestions.filter(
(s: unknown): s is string => typeof s === 'string' && s.trim().length > 0
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why would there be an empty suggestion?

Comment on lines +407 to +414
const tasksWidget = connectTasks(
handleInnerRender,
noop
)({
...(transport ? { transport } : { agentId }),
task: configurationId ?? 'prompt-suggestions',
stream: true,
} as TasksConnectorParams);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so we create a widget, but don't explicitly render it, this seems fragile. What about returning both widgets in an array and using render state to access its items? otherwise it seems like it could cause issues later (maybe we don't call the tasks widget in all the hooks for example)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that was how it was done initially, but i thought since the task connector is more an internal structure, maybe we don't want our renderstate to have this information.

noop
)({
...(transport ? { transport } : { agentId }),
task: configurationId ?? 'prompt-suggestions',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we need a fallback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

unless the "first created prompt suggestion" always has that id, cc @FabienMotte, it would be possible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

so basically have configurationId be a required prop?

const entries = Object.entries(pageContext)
.map(([key, value]): [string, string] => [
key,
typeof value === 'string' ? value : JSON.stringify(value),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should the chat context be shaped separately here? a valid task input can put hitsSample over chat’s 1 KB per-value limit, so clicking a rendered suggestion can fail with 422

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes this is already the case. this should be rechecked once the 10kb limit is merged on the backend.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks, that helps. if this PR depends on the 10 KB backend rollout, what keeps the default five-hit hitsSample within that limit?

? (prompt: string) => onSuggestionClickOverride(prompt, { sendToChat })
: onSuggestionClick;

if (templates?.layout) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should layout and header use prepareTemplateProps and TemplateComponent like other JS widgets? layout(data, { html }) currently receives only data, so the documented template throws


const chatRenderState = getChatRenderState(renderOptions);

const isChatBusy = chatRenderState

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should chat status transitions schedule a full InstantSearch render? after chat moves from submitted to ready, the shared render state can stay at submitted, leaving these pills disabled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i believe this already happens, yes. will double check

renderOptions: RenderOptions
) => {
if (disposed || !tasksState) return;
refetchPending = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should this invalidate the active task before returning? when search state A changes to a no-hits state B, A can still resolve and render stale pills into B

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it won't render since there's now a check for this var there.

not sure about the invalidate active task, having cancellation support for that request seems a bit more complicated, perhaps in a separate PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

refetchPending is set to false before this return, so the guard in handleInnerRender still accepts A’s late result. could we invalidate A here without cancelling the request?

processStream(
chunkStream,
(chunk) => {
if (!chunk || chunk.type !== 'data-task-output') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should this reject when the stream emits an error event? the backend can send partial output before a terminal error but [DONE] currently resolves the last partial as success

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah we don't really have error states for the pills atm.

I can add that check here, but not sure how to display that to a user. For now a blank state should be okay i think

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a blank state is fine, no error UI is needed. could the error event reject the task and the prompt connector clear any streamed partial so a failed stream can’t leave partial pills visible?

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.

5 participants