This is my current best attempt to allow iterating on the user experience of a UI that loads streaming responses from an LLM and may have some interesting intermediate states while the response trickles in.
import { streamFlow } from 'genkit/beta/client';
// [...]
const flow = streamFlow<FlowOutputType>({
url: '/api/my-flow',
input: flowInput,
});
Plugin: https://storybook.js.org/addons/msw-storybook-addon
import {http} from 'msw';
import {
createGenkitStreamingResponse,
resetDefaultTokenDelay,
setDefaultTokenDelay,
} from './mock-genkit.ts';
export const Example: Story = {
args: {
// [General arguments...]
// Make the delay between tokens configurable to explore the exact UX.
tokenDelay: 100,
},
beforeEach: (context) => {
// It's tough to pass args via the msw storybook plugin, so this just uses
// global state that gets reset afterwards.
setDefaultTokenDelay(context.args.tokenDelay ?? 0);
return () => {
resetDefaultTokenDelay();
};
},
parameters: {
msw: {
handlers: {
chat: http.post('/api/my-flow', async ({request}) => {
const jsonBody = await request.json();
const {
data: flowInput,
} = jsonBody as {data: FlowInputType};
const flowOutput: FlowOutputType = getMockResponse(flowInput);
return createGenkitStreamingResponse(flowOutput);
}),
},
},
},
};
Uses manual setup because the storybook plugin for fetch-mock
doesn't support Storybook v9 yet as of today.
import fetchMock from 'fetch-mock';
import {createGenkitStreamingResponse} from './mock-genkit.ts';
export const Example: Story = {
args: {
// [General arguments...]
// Make the delay between tokens configurable to explore the exact UX.
tokenDelay: 100,
},
beforeEach: (context) => {
const tokenDelay = context.args.tokenDelay ?? 0;
// fetchMock.config.allowRelativeUrls = true;
fetchMock.mockGlobal();
fetchMock.post(`${location.origin}/api/my-flow`, async ({options}) => {
if (typeof options.body !== 'string') return 500;
const {
data: flowInput
} = JSON.parse(options.body) as {data: FlowInputType};
const flowOutput: FlowOutputType = getMockResponse(flowInput);
return createGenkitStreamingResponse(flowOutput, tokenDelay);
});
return () => {
fetchMock.hardReset();
};
},
};