Skip to content

Commit 64d976c

Browse files
committed
Add video-specific stories
1 parent 9e0036d commit 64d976c

2 files changed

Lines changed: 302 additions & 12 deletions

File tree

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/**
2+
* Video-specific stories for the demo recording pipeline.
3+
* These stories are designed for Playwright to drive — they simulate
4+
* typing, sending, and streaming responses via store manipulation.
5+
*/
6+
7+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8+
import type { Meta, StoryObj } from "@storybook/react";
9+
import { fn } from "storybook/test";
10+
import { useEffect, useRef, useCallback } from "react";
11+
12+
import type { ModelInfo } from "@/components/ModelSelector/ModelSelector";
13+
import { ConfigProvider } from "@/config/ConfigProvider";
14+
import { PreferencesProvider } from "@/preferences/PreferencesProvider";
15+
import { ToastProvider } from "@/components/Toast/Toast";
16+
import { TooltipProvider } from "@/components/Tooltip/Tooltip";
17+
import { useConversationStore } from "@/stores/conversationStore";
18+
import { useStreamingStore } from "@/stores/streamingStore";
19+
import { useChatUIStore } from "@/stores/chatUIStore";
20+
21+
import { ChatView } from "./ChatView";
22+
23+
const queryClient = new QueryClient({
24+
defaultOptions: { queries: { retry: false } },
25+
});
26+
27+
const mockModels: ModelInfo[] = [
28+
{
29+
id: "anthropic/claude-4.6-opus",
30+
owned_by: "anthropic",
31+
context_length: 200000,
32+
pricing: { prompt: "15", completion: "75" },
33+
},
34+
{
35+
id: "google/gemini-3.1-pro",
36+
owned_by: "google",
37+
context_length: 1000000,
38+
pricing: { prompt: "1.25", completion: "5" },
39+
},
40+
{
41+
id: "openai/gpt-5.4",
42+
owned_by: "openai",
43+
context_length: 128000,
44+
pricing: { prompt: "5", completion: "15" },
45+
},
46+
];
47+
48+
const selectedModels = mockModels.map((m) => m.id);
49+
50+
// Per-model streaming responses (shown character-by-character)
51+
const streamedResponses: Record<string, string> = {
52+
"anthropic/claude-4.6-opus": `Great question! Here's a simple explanation:
53+
54+
## What is Quantum Computing?
55+
56+
Regular computers use **bits** — tiny switches that are either 0 or 1. Quantum computers use **qubits**, which can be 0, 1, or *both at once* (called **superposition**).
57+
58+
Think of solving a maze: a regular computer tries one path at a time, while a quantum computer explores many paths simultaneously.
59+
60+
### Real-world applications
61+
- **Cryptography** — breaking and creating unbreakable codes
62+
- **Drug discovery** — simulating molecular interactions
63+
- **Optimization** — solving logistics and scheduling at scale
64+
65+
The catch? Qubits need near **absolute zero** (-273°C) and total isolation. Early days, but progress is rapid.`,
66+
67+
"google/gemini-3.1-pro": `## Quantum Computing, Simply
68+
69+
Classical bits are always 0 or 1. Quantum **qubits** use two key properties:
70+
71+
**Superposition** — A qubit exists as 0 and 1 simultaneously until measured, like a coin spinning in the air.
72+
73+
**Entanglement** — Linked qubits share state instantly, regardless of distance.
74+
75+
Together, these let quantum computers evaluate vast numbers of possibilities at once. Problems that take classical computers thousands of years might take a quantum computer minutes.
76+
77+
Key players like Google and IBM are racing toward practical quantum advantage — we're closer than you think.`,
78+
79+
"openai/gpt-5.4": `**Classical computers** are like reading a book one page at a time. **Quantum computers** read all pages at once.
80+
81+
The magic: **qubits** exploit superposition (multiple states at once) and entanglement (instant correlations between particles).
82+
83+
### What can they do?
84+
- Break and build encryption
85+
- Simulate molecules for drug design
86+
- Optimize supply chains
87+
- Accelerate machine learning
88+
89+
### Reality check
90+
Today's quantum computers have ~1,000 noisy qubits. We need millions of stable ones for most applications. But breakthroughs are happening every year.`,
91+
};
92+
93+
const modelUsage: Record<
94+
string,
95+
{
96+
inputTokens: number;
97+
outputTokens: number;
98+
totalTokens: number;
99+
cost: number;
100+
firstTokenMs: number;
101+
totalDurationMs: number;
102+
tokensPerSecond: number;
103+
}
104+
> = {
105+
"anthropic/claude-4.6-opus": {
106+
inputTokens: 45,
107+
outputTokens: 210,
108+
totalTokens: 255,
109+
cost: 0.0164,
110+
firstTokenMs: 450,
111+
totalDurationMs: 4800,
112+
tokensPerSecond: 43.8,
113+
},
114+
"google/gemini-3.1-pro": {
115+
inputTokens: 45,
116+
outputTokens: 195,
117+
totalTokens: 240,
118+
cost: 0.001,
119+
firstTokenMs: 320,
120+
totalDurationMs: 4200,
121+
tokensPerSecond: 46.4,
122+
},
123+
"openai/gpt-5.4": {
124+
inputTokens: 45,
125+
outputTokens: 175,
126+
totalTokens: 220,
127+
cost: 0.0029,
128+
firstTokenMs: 380,
129+
totalDurationMs: 3900,
130+
tokensPerSecond: 44.9,
131+
},
132+
};
133+
134+
/**
135+
* Video story: empty chat → user types → sends → streaming response appears.
136+
* Playwright drives the typing and clicking; the story simulates the streaming response
137+
* after the send button is clicked.
138+
*/
139+
function VideoSendMessageStory({ onSendMessage }: { onSendMessage: (content: string) => void }) {
140+
const { setMessages, setSelectedModels, addUserMessage } = useConversationStore();
141+
const { setDisabledModels, setActionConfig, setSystemPrompt, setAllModelSettings } =
142+
useChatUIStore();
143+
// Read isStreaming from the streaming store (single boolean subscription)
144+
const isStreaming = useStreamingStore((s) => s.isStreaming);
145+
const sentRef = useRef(false);
146+
147+
useEffect(() => {
148+
setMessages([]);
149+
setSelectedModels(selectedModels);
150+
setDisabledModels([]);
151+
setSystemPrompt("");
152+
setAllModelSettings({});
153+
setActionConfig({
154+
showFeedback: true,
155+
showSelectBest: true,
156+
showRegenerate: true,
157+
showCopy: true,
158+
showExpand: true,
159+
});
160+
return () => {
161+
useStreamingStore.getState().clearStreams();
162+
};
163+
// eslint-disable-next-line react-hooks/exhaustive-deps
164+
}, []);
165+
166+
// Intercept send — add user message and start simulated streaming
167+
const handleSend = useCallback(
168+
(content: string) => {
169+
if (sentRef.current) return;
170+
sentRef.current = true;
171+
172+
// Add the user message to the conversation
173+
addUserMessage(content);
174+
175+
// Start streaming all models after a brief "thinking" delay
176+
setTimeout(() => {
177+
const store = useStreamingStore.getState();
178+
store.initStreaming(selectedModels);
179+
180+
// Stagger model starts for realism
181+
const delays: Record<string, number> = {
182+
"anthropic/claude-4.6-opus": 0,
183+
"google/gemini-3.1-pro": 200,
184+
"openai/gpt-5.4": 400,
185+
};
186+
187+
for (const model of selectedModels) {
188+
let charIndex = 0;
189+
const response = streamedResponses[model]!;
190+
setTimeout(() => {
191+
const interval = setInterval(() => {
192+
if (charIndex < response.length) {
193+
const chunkSize = 2 + Math.floor(Math.random() * 3);
194+
const chunk = response.slice(charIndex, charIndex + chunkSize);
195+
useStreamingStore.getState().appendContent(model, chunk);
196+
charIndex += chunkSize;
197+
} else {
198+
clearInterval(interval);
199+
useStreamingStore.getState().completeStream(model, modelUsage[model]!);
200+
}
201+
}, 25);
202+
}, delays[model] ?? 0);
203+
}
204+
}, 500);
205+
206+
onSendMessage(content);
207+
},
208+
[addUserMessage, onSendMessage]
209+
);
210+
211+
return (
212+
<ChatView availableModels={mockModels} onSendMessage={handleSend} isStreaming={isStreaming} />
213+
);
214+
}
215+
216+
const meta: Meta<typeof ChatView> = {
217+
title: "Chat/ChatView",
218+
component: ChatView,
219+
parameters: {
220+
layout: "fullscreen",
221+
a11y: {
222+
config: {
223+
rules: [
224+
{ id: "landmark-banner-is-top-level", enabled: false },
225+
{ id: "landmark-contentinfo-is-top-level", enabled: false },
226+
{ id: "landmark-main-is-top-level", enabled: false },
227+
],
228+
},
229+
},
230+
},
231+
decorators: [
232+
(Story) => (
233+
<QueryClientProvider client={queryClient}>
234+
<ConfigProvider>
235+
<PreferencesProvider>
236+
<ToastProvider>
237+
<TooltipProvider>
238+
<div className="h-screen">
239+
<Story />
240+
</div>
241+
</TooltipProvider>
242+
</ToastProvider>
243+
</PreferencesProvider>
244+
</ConfigProvider>
245+
</QueryClientProvider>
246+
),
247+
],
248+
};
249+
250+
export default meta;
251+
type Story = StoryObj<typeof meta>;
252+
253+
export const VideoSendMessage: Story = {
254+
args: {
255+
onSendMessage: fn(),
256+
},
257+
render: (args) => <VideoSendMessageStory onSendMessage={args.onSendMessage} />,
258+
};

ui/src/pages/admin/OrganizationDetailPage.stories.tsx

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,38 @@ const mockMembers: User[] = [
117117
created_at: daysAgo(110),
118118
updated_at: daysAgo(35),
119119
},
120+
{
121+
id: "usr-004",
122+
external_id: "saml|diana",
123+
name: "Diana Chen",
124+
email: "diana@acme-corp.com",
125+
created_at: daysAgo(80),
126+
updated_at: daysAgo(80),
127+
},
128+
{
129+
id: "usr-005",
130+
external_id: "oidc|eve",
131+
name: "Eve Taylor",
132+
email: "eve@acme-corp.com",
133+
created_at: daysAgo(60),
134+
updated_at: daysAgo(60),
135+
},
136+
{
137+
id: "usr-006",
138+
external_id: "auth0|frank",
139+
name: "Frank Okafor",
140+
email: "frank@acme-corp.com",
141+
created_at: daysAgo(45),
142+
updated_at: daysAgo(15),
143+
},
144+
{
145+
id: "usr-007",
146+
external_id: "oidc|grace",
147+
name: "Grace Kim",
148+
email: "grace@acme-corp.com",
149+
created_at: daysAgo(30),
150+
updated_at: daysAgo(5),
151+
},
120152
];
121153

122154
const mockApiKeys: ApiKey[] = [
@@ -205,20 +237,20 @@ const mockPricing: DbModelPricing[] = [
205237
const mockAllUsers: User[] = [
206238
...mockMembers,
207239
{
208-
id: "usr-004",
209-
external_id: "saml|diana",
210-
name: "Diana Chen",
211-
email: "diana@acme-corp.com",
212-
created_at: daysAgo(80),
213-
updated_at: daysAgo(80),
240+
id: "usr-008",
241+
external_id: "saml|henry",
242+
name: "Henry Patel",
243+
email: "henry@acme-corp.com",
244+
created_at: daysAgo(20),
245+
updated_at: daysAgo(20),
214246
},
215247
{
216-
id: "usr-005",
217-
external_id: "oidc|eve",
218-
name: "Eve Taylor",
219-
email: "eve@acme-corp.com",
220-
created_at: daysAgo(60),
221-
updated_at: daysAgo(60),
248+
id: "usr-009",
249+
external_id: "auth0|iris",
250+
name: "Iris Müller",
251+
email: "iris@acme-corp.com",
252+
created_at: daysAgo(10),
253+
updated_at: daysAgo(10),
222254
},
223255
];
224256

0 commit comments

Comments
 (0)