Skip to content

Commit 5292594

Browse files
committed
experiment_throttle is now just throttle
1 parent 23b511e commit 5292594

File tree

3 files changed

+137
-9
lines changed

3 files changed

+137
-9
lines changed

packages/react-hooks/src/hooks/useRealtime.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import { createThrottledQueue } from "../utils/throttle.js";
1515
export type UseRealtimeRunOptions = UseApiClientOptions & {
1616
id?: string;
1717
enabled?: boolean;
18-
experimental_throttleInMs?: number;
18+
/**
19+
* The number of milliseconds to throttle the stream updates.
20+
*
21+
* @default 16
22+
*/
23+
throttleInMs?: number;
1924
};
2025

2126
export type UseRealtimeSingleRunOptions<TTask extends AnyTask = AnyTask> = UseRealtimeRunOptions & {
@@ -283,7 +288,7 @@ export function useRealtimeRunWithStreams<
283288
setError,
284289
abortControllerRef,
285290
typeof options?.stopOnCompletion === "boolean" ? options.stopOnCompletion : true,
286-
options?.experimental_throttleInMs
291+
options?.throttleInMs ?? 16
287292
);
288293
} catch (err) {
289294
// Ignore abort errors as they are expected.
@@ -587,7 +592,12 @@ export type UseRealtimeStreamInstance<TPart> = {
587592
export type UseRealtimeStreamOptions<TPart> = UseApiClientOptions & {
588593
id?: string;
589594
enabled?: boolean;
590-
experimental_throttleInMs?: number;
595+
/**
596+
* The number of milliseconds to throttle the stream updates.
597+
*
598+
* @default 16
599+
*/
600+
throttleInMs?: number;
591601
/**
592602
* The number of seconds to wait for new data to be available,
593603
* If no data arrives within the timeout, the stream will be closed.
@@ -704,7 +714,7 @@ export function useRealtimeStream<TPart>(
704714
abortControllerRef,
705715
options?.timeoutInSeconds,
706716
options?.startIndex,
707-
options?.experimental_throttleInMs
717+
options?.throttleInMs ?? 16
708718
);
709719
} catch (err) {
710720
// Ignore abort errors as they are expected.

references/realtime-streams/src/app/layout.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ export default function RootLayout({
2424
}>) {
2525
return (
2626
<html lang="en">
27-
<body
28-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29-
>
30-
{children}
31-
</body>
27+
<head>
28+
<script crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js" />
29+
</head>
30+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
3231
</html>
3332
);
3433
}

references/realtime-streams/src/components/ai-chat.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,125 @@ import type { UIMessage, UIMessageChunk } from "ai";
55
import { Streamdown } from "streamdown";
66

77
export function AIChat({ accessToken, runId }: { accessToken: string; runId: string }) {
8+
return (
9+
<div className="space-y-8">
10+
<AIChatStats accessToken={accessToken} runId={runId} />
11+
<AIChatFull accessToken={accessToken} runId={runId} />
12+
</div>
13+
);
14+
}
15+
16+
function AIChatStats({ accessToken, runId }: { accessToken: string; runId: string }) {
17+
const { parts, error } = useRealtimeStream<UIMessageChunk>(runId, "chat", {
18+
accessToken,
19+
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
20+
timeoutInSeconds: 600,
21+
});
22+
23+
if (error) return <div className="text-red-600 font-semibold">Error: {error.message}</div>;
24+
25+
if (!parts || parts.length === 0) {
26+
return (
27+
<div className="p-4 rounded-lg bg-blue-50 border-l-4 border-blue-500">
28+
<div className="text-sm font-semibold text-blue-900 mb-2">📊 Stream Statistics</div>
29+
<div className="text-xs text-blue-700">Waiting for stream data...</div>
30+
</div>
31+
);
32+
}
33+
34+
// Calculate statistics
35+
const stats = {
36+
totalChunks: parts.length,
37+
textStartChunks: 0,
38+
textDeltaChunks: 0,
39+
textEndChunks: 0,
40+
errorChunks: 0,
41+
otherChunks: 0,
42+
totalCharacters: 0,
43+
averageChunkSize: 0,
44+
};
45+
46+
const chunkTimings: number[] = [];
47+
let firstChunkTime: number | null = null;
48+
let lastChunkTime: number | null = null;
49+
50+
for (const chunk of parts) {
51+
switch (chunk.type) {
52+
case "text-start":
53+
stats.textStartChunks++;
54+
break;
55+
case "text-delta":
56+
stats.textDeltaChunks++;
57+
stats.totalCharacters += chunk.delta.length;
58+
break;
59+
case "text-end":
60+
stats.textEndChunks++;
61+
break;
62+
case "error":
63+
stats.errorChunks++;
64+
break;
65+
default:
66+
stats.otherChunks++;
67+
}
68+
}
69+
70+
stats.averageChunkSize =
71+
stats.textDeltaChunks > 0 ? Math.round(stats.totalCharacters / stats.textDeltaChunks) : 0;
72+
73+
const isStreaming = stats.textEndChunks === 0;
74+
75+
return (
76+
<div className="p-4 rounded-lg bg-blue-50 border-l-4 border-blue-500">
77+
<div className="text-sm font-semibold text-blue-900 mb-3">
78+
📊 Stream Statistics {isStreaming && <span className="text-blue-600">(live)</span>}
79+
</div>
80+
81+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
82+
<div>
83+
<div className="text-blue-600 font-semibold">Total Chunks</div>
84+
<div className="text-blue-900 text-lg font-bold">{stats.totalChunks}</div>
85+
</div>
86+
<div>
87+
<div className="text-blue-600 font-semibold">Text Deltas</div>
88+
<div className="text-blue-900 text-lg font-bold">{stats.textDeltaChunks}</div>
89+
</div>
90+
<div>
91+
<div className="text-blue-600 font-semibold">Total Characters</div>
92+
<div className="text-blue-900 text-lg font-bold">{stats.totalCharacters}</div>
93+
</div>
94+
<div>
95+
<div className="text-blue-600 font-semibold">Avg Chunk Size</div>
96+
<div className="text-blue-900 text-lg font-bold">{stats.averageChunkSize} chars</div>
97+
</div>
98+
</div>
99+
100+
<div className="mt-3 pt-3 border-t border-blue-200 grid grid-cols-2 md:grid-cols-5 gap-2 text-xs">
101+
<div>
102+
<span className="text-blue-600">text-start:</span>{" "}
103+
<span className="text-blue-900 font-semibold">{stats.textStartChunks}</span>
104+
</div>
105+
<div>
106+
<span className="text-blue-600">text-delta:</span>{" "}
107+
<span className="text-blue-900 font-semibold">{stats.textDeltaChunks}</span>
108+
</div>
109+
<div>
110+
<span className="text-blue-600">text-end:</span>{" "}
111+
<span className="text-blue-900 font-semibold">{stats.textEndChunks}</span>
112+
</div>
113+
<div>
114+
<span className="text-blue-600">errors:</span>{" "}
115+
<span className="text-blue-900 font-semibold">{stats.errorChunks}</span>
116+
</div>
117+
<div>
118+
<span className="text-blue-600">other:</span>{" "}
119+
<span className="text-blue-900 font-semibold">{stats.otherChunks}</span>
120+
</div>
121+
</div>
122+
</div>
123+
);
124+
}
125+
126+
function AIChatFull({ accessToken, runId }: { accessToken: string; runId: string }) {
8127
const { parts, error } = useRealtimeStream<UIMessageChunk>(runId, "chat", {
9128
accessToken,
10129
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,

0 commit comments

Comments
 (0)