Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
38a296a
init
shaejaz May 21, 2026
8d1e0b4
fix types
shaejaz May 21, 2026
fd8f9dd
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz May 22, 2026
599683b
fix duplicate requests
shaejaz May 22, 2026
b867e28
rename current page suggestions
shaejaz May 26, 2026
aee97fd
add suggestions pills component
shaejaz May 26, 2026
01349ec
standalone suggestions
shaejaz May 26, 2026
b33b848
fix ssr
shaejaz May 26, 2026
6881fd8
remove ChatPageSummary widget (split to feat/chat-page-summary-widget)
shaejaz May 26, 2026
5479d0f
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz May 26, 2026
fe120b1
fix tests
shaejaz May 27, 2026
a9a6004
update api
shaejaz May 28, 2026
2e0f439
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz Jul 7, 2026
3b48a55
update endpoint, remove standalone naming
shaejaz Jul 9, 2026
71db731
add tests
shaejaz Jul 9, 2026
2fd7918
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz Jul 9, 2026
922aa54
chore(examples): revert example changes for chat-page-suggestions
shaejaz Jul 9, 2026
953baf4
add task name test
shaejaz Jul 9, 2026
aae135c
address comments
shaejaz Jul 9, 2026
5970f2b
fix bundlesize
shaejaz Jul 9, 2026
9f59a30
fix nextjs and loading states
shaejaz Jul 9, 2026
738eacb
address comments and fix test
shaejaz Jul 9, 2026
6bfbd52
add header
shaejaz Jul 9, 2026
30271bd
add streaming support
shaejaz Jul 12, 2026
82c47a1
rename widget
shaejaz Jul 12, 2026
ff9ce8a
move functionality to connector
shaejaz Jul 13, 2026
42fdb5b
add comments
shaejaz Jul 13, 2026
82241ca
add filters support
shaejaz Jul 13, 2026
0f37cfa
address comments
shaejaz Jul 14, 2026
1f6ab0f
update bundlesize
shaejaz Jul 14, 2026
b588873
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz Jul 14, 2026
1b0b390
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz Jul 16, 2026
f360a6f
address comments
shaejaz Jul 16, 2026
bd11185
Merge branch 'master' into feat/chat-page-suggestions-widget
shaejaz Jul 17, 2026
bf118b4
chore(chat): drop sessionStorage SSR guard (split to separate PR)
shaejaz Jul 17, 2026
88fdcc1
remove next and ssr changes
shaejaz Jul 17, 2026
99ae0b5
rename to prompt suggestions
shaejaz Jul 17, 2026
87c12b1
add back chat prompt suggestions
shaejaz Jul 17, 2026
9ee79ae
move back chat transport
shaejaz Jul 17, 2026
c52f220
rename connector
shaejaz Jul 17, 2026
ff78870
update bundlesize
shaejaz Jul 20, 2026
5781b1d
address comments
shaejaz Jul 20, 2026
3244084
update refer name
shaejaz Jul 20, 2026
f624a9e
use queryid
shaejaz Jul 21, 2026
8633901
address comments
shaejaz Jul 21, 2026
0aa15eb
update default task name
shaejaz Jul 21, 2026
8bd3025
update turn context
shaejaz Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion examples/react/getting-started/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hit } from 'instantsearch.js';
import React from 'react';
import {
Configure,
Expand All @@ -12,13 +11,16 @@ import {
TrendingItems,
Carousel,
Chat,
ChatPageSuggestions,
ChatTrigger,
FilterSuggestions,
CurrentRefinements,
} from 'react-instantsearch';

import { Panel } from './Panel';

import type { Hit } from 'instantsearch.js';

import 'instantsearch.css/themes/satellite.css';

import './App.css';
Expand Down Expand Up @@ -77,6 +79,12 @@ export function App() {
headerComponent={false}
/>
</Panel>
<Panel header="Prompt pills (POC)">
<ChatPageSuggestions
maxSuggestions={4}
transport={{ api: '/api/chat-page-suggestions?delay=3000' }}
/>
</Panel>
<Hits hitComponent={HitComponent} />

<div className="pagination">
Expand Down
77 changes: 76 additions & 1 deletion examples/react/getting-started/vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,83 @@ import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import commonjs from 'vite-plugin-commonjs';

// Mocks the agent-studio response shape `connectChatPageSuggestions` parses
// (`data.parts[1].text` = JSON-stringified string array). Reactive to query
// and first-hit category so refinements visibly drive the pills.
function chatPageSuggestionsMockPlugin() {
return {
name: 'chat-page-suggestions-mock',
configureServer(server) {
server.middlewares.use('/api/chat-page-suggestions', async (req, res, next) => {
if (req.method !== 'POST') {
next();
return;
}
// Tune via `?delay=N` (ms). Default 800ms so the loading skeleton is
// clearly visible on every refetch (initial mount + each refinement
// change).
const url = new URL(req.url ?? '', 'http://localhost');
const delayParam = Number(url.searchParams.get('delay'));
const delayMs =
Number.isFinite(delayParam) && delayParam >= 0 ? delayParam : 800;
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const raw = Buffer.concat(chunks).toString('utf8');
let input = {};
try {
const body = JSON.parse(raw);
const text = body?.messages?.[0]?.parts?.[0]?.text;
if (typeof text === 'string') input = JSON.parse(text);
} catch {
// ignore — fall through with empty input
}
const query = (input.query || '').trim();
const category = input.hitsSample?.[0]?.categories?.[0];
const max = Math.max(1, Math.min(input.maxSuggestions ?? 4, 8));
Comment thread
shaejaz marked this conversation as resolved.
Outdated

const pool = [];
if (query) {
pool.push(
`Compare the top ${query} options`,
`What should I look for in a ${query}?`,
`Recommend a ${query} under $500`,
`Show me bestsellers for "${query}"`
);
}
if (category) {
pool.push(
`What's new in ${category}?`,
`Top-rated ${category} this month`,
`Help me choose a ${category}`
);
}
pool.push(
'Help me find what I need',
'What are people buying right now?',
'Suggest something for a gift'
);
const suggestions = Array.from(new Set(pool)).slice(0, max);

await new Promise((resolve) => setTimeout(resolve, delayMs));

res.setHeader('Content-Type', 'application/json');
res.end(
JSON.stringify({
id: `dummy-${Date.now()}`,
role: 'assistant',
parts: [
{ type: 'reasoning', text: 'mocked' },
{ type: 'text', text: JSON.stringify(suggestions) },
],
})
);
});
},
};
}

export default defineConfig({
plugins: [commonjs(), react()],
plugins: [commonjs(), react(), chatPageSuggestionsMockPlugin()],
build: {
commonjsOptions: {
requireReturnsDefault: 'preferred',
Expand Down
49 changes: 38 additions & 11 deletions examples/react/next-app-router/app/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import Link from 'next/link';
import React from 'react';
import {
Chat,
ChatPageSuggestions,
Hits,
SearchBox,
RefinementList,
DynamicWidgets,
} from 'react-instantsearch';
import { InstantSearchNext } from 'react-instantsearch-nextjs';

Expand All @@ -15,7 +16,14 @@ import { Panel } from '../components/Panel';
import { QueryId } from '../components/QueryId';
import { client } from '../lib/client';

export default function Search() {
const isServer = typeof window === 'undefined';
const PHASE = isServer ? '[SSR]' : '[CSR]';

console.log(
`${PHASE} Search.tsx module evaluated at ${new Date().toISOString()}`
);
Comment thread
shaejaz marked this conversation as resolved.
Outdated

export default function Search({ baseUrl }: { baseUrl: string }) {
return (
<InstantSearchNext
searchClient={client}
Expand All @@ -25,25 +33,44 @@ export default function Search() {
>
<div className="Container">
<div>
<DynamicWidgets fallbackComponent={FallbackComponent} />
<Panel header="brand">
<RefinementList attribute="brand" />
</Panel>
<Panel header="categories">
<RefinementList attribute="categories" />
</Panel>
</div>
<div>
<SearchBox />
<Panel header="Prompt pills (SSR test)">
{/*
Demo wiring:
- `?delay=500` on the dummy endpoint
- `ssrTimeoutMs={1500}` → SSR wins the race and bakes pills into
server HTML (curl the page and grep for one).
- On client refinement changes, the 500ms delay makes the
skeleton visible during the refetch.
Flip `delay` higher than `ssrTimeoutMs` to test the SSR-timeout
path (server HTML has no pills; client renders the skeleton
then the pills after hydration).
*/}
<ChatPageSuggestions
maxSuggestions={4}
ssrTimeoutMs={1500}
transport={{
api: `${baseUrl}/api/chat-page-suggestions?delay=500`,
}}
/>
</Panel>
<Hits hitComponent={Hit} />
</div>
</div>
<QueryId />
<Link href="/layout" id="link">
Other page
</Link>
</InstantSearchNext>
);
}

function FallbackComponent({ attribute }: { attribute: string }) {
return (
<Panel header={attribute}>
<RefinementList attribute={attribute} />
</Panel>
<Chat agentId="eedef238-5468-470d-bc37-f99fa741bd25" feedback={true} />
</InstantSearchNext>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { NextResponse } from 'next/server';

type RequestBody = {
messages?: Array<{
parts?: Array<{ type?: string; text?: string }>;
}>;
};

type AgentInput = {
query?: string;
hitsSample?: Array<{ name?: string; categories?: string[] }>;
context?: Record<string, unknown>;
maxSuggestions?: number;
};

// Returns a mocked agent-studio response in the shape the
// `connectChatPageSuggestions` connector expects:
// { parts: [<anything>, { text: '["prompt one", "prompt two", ...]' }] }
//
// Suggestions are loosely derived from the search query and the first hit's
// category so the demo feels reactive to refinements.
export async function POST(request: Request) {
const url = new URL(request.url);
const delayParam = Number(url.searchParams.get('delay'));
// Tune via `?delay=N` (ms). Default 500ms — long enough to see the client
// skeleton on refinement changes; short enough that SSR (with a generous
// `ssrTimeoutMs`) still wins the race and bakes pills into server HTML.
const delayMs = Number.isFinite(delayParam) && delayParam >= 0 ? delayParam : 500;

const body = (await request.json().catch(() => ({}))) as RequestBody;
const rawText = body.messages?.[0]?.parts?.[0]?.text ?? '{}';
const input: AgentInput =
typeof rawText === 'string'
? safeParse<AgentInput>(rawText) ?? {}
: (rawText as AgentInput);

Comment thread
shaejaz marked this conversation as resolved.
Outdated
const query = (input.query || '').trim();
const firstHit = input.hitsSample?.[0];
const category = firstHit?.categories?.[0];
const max = Math.max(1, Math.min(input.maxSuggestions ?? 4, 8));

const pool: string[] = [];
if (query) {
pool.push(
`Compare the top ${query} options`,
`What should I look for in a ${query}?`,
`Recommend a ${query} under $500`,
`Show me bestsellers for "${query}"`
);
}
if (category) {
pool.push(
`What's new in ${category}?`,
`Top-rated ${category} this month`,
`Help me choose a ${category}`
);
}
pool.push(
'Help me find what I need',
'What are people buying right now?',
'Suggest something for a gift'
);

// Dedupe and trim to the requested length.
const suggestions = Array.from(new Set(pool)).slice(0, max);

await new Promise((resolve) => setTimeout(resolve, delayMs));

return NextResponse.json({
id: `dummy-${Date.now()}`,
role: 'assistant',
parts: [
{ type: 'reasoning', text: 'mocked' },
{ type: 'text', text: JSON.stringify(suggestions) },
],
});
}

function safeParse<T>(text: string): T | null {
try {
return JSON.parse(text) as T;
} catch {
return null;
}
}
12 changes: 10 additions & 2 deletions examples/react/next-app-router/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { headers } from 'next/headers';
import React from 'react';

import { responsesCache } from '../lib/client';
Expand All @@ -6,8 +7,15 @@ import Search from './Search';

export const dynamic = 'force-dynamic';

export default function Page() {
export default async function Page() {
responsesCache.clear();

return <Search />;
// SSR-side fetches (e.g. `<ChatPageSuggestions>`'s transport) need an
// absolute URL because Node fetch can't resolve relative paths.
const h = await headers();
const host = h.get('host') ?? 'localhost:3000';
const proto = h.get('x-forwarded-proto') ?? 'http';
const baseUrl = `${proto}://${host}`;

return <Search baseUrl={baseUrl} />;
}
1 change: 1 addition & 0 deletions examples/react/next-app-router/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

Loading