@convex-dev/persistent-text-streaming 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/client/index.d.ts +6 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/component/lib.js +11 -11
- package/dist/component/lib.js.map +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +69 -85
- package/dist/react/index.js.map +1 -1
- package/package.json +27 -28
- package/src/client/index.ts +17 -11
- package/src/component/lib.ts +11 -11
- package/src/react/index.ts +74 -91
package/README.md
CHANGED
|
@@ -125,6 +125,32 @@ http.route({
|
|
|
125
125
|
method: "POST",
|
|
126
126
|
handler: streamChat,
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
// Handle CORS preflight requests so browsers will allow the POST above when
|
|
130
|
+
// your app is served from a different origin than your Convex deployment.
|
|
131
|
+
http.route({
|
|
132
|
+
path: "/chat-stream",
|
|
133
|
+
method: "OPTIONS",
|
|
134
|
+
handler: httpAction(async (_, request) => {
|
|
135
|
+
const headers = request.headers;
|
|
136
|
+
if (
|
|
137
|
+
headers.get("Origin") !== null &&
|
|
138
|
+
headers.get("Access-Control-Request-Method") !== null &&
|
|
139
|
+
headers.get("Access-Control-Request-Headers") !== null
|
|
140
|
+
) {
|
|
141
|
+
return new Response(null, {
|
|
142
|
+
headers: new Headers({
|
|
143
|
+
"Access-Control-Allow-Origin": "*",
|
|
144
|
+
"Access-Control-Allow-Methods": "POST",
|
|
145
|
+
"Access-Control-Allow-Headers": "Content-Type, Digest, Authorization",
|
|
146
|
+
"Access-Control-Max-Age": "86400",
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
return new Response();
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
128
154
|
```
|
|
129
155
|
|
|
130
156
|
Finally, in your app, you can now create chats and them subscribe to them via
|
package/dist/client/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export declare class PersistentTextStreaming {
|
|
|
33
33
|
* });
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
|
-
createStream(ctx:
|
|
36
|
+
createStream(ctx: MutationCtx | ActionCtx): Promise<StreamId>;
|
|
37
37
|
/**
|
|
38
38
|
* Get the body of a stream. This will return the full text of the stream
|
|
39
39
|
* and the status of the stream.
|
|
@@ -47,7 +47,7 @@ export declare class PersistentTextStreaming {
|
|
|
47
47
|
* const { text, status } = await streaming.getStreamBody(ctx, streamId);
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
getStreamBody(ctx:
|
|
50
|
+
getStreamBody(ctx: QueryCtx | MutationCtx | ActionCtx, streamId: StreamId): Promise<StreamBody>;
|
|
51
51
|
/**
|
|
52
52
|
* Inside an HTTP action, this will stream data back to the client while
|
|
53
53
|
* also persisting the final stream in the database.
|
|
@@ -77,16 +77,13 @@ export declare class PersistentTextStreaming {
|
|
|
77
77
|
* @param ctx - A convex context capable of running mutations.
|
|
78
78
|
* @param streamId - The ID of the stream to delete.
|
|
79
79
|
*/
|
|
80
|
-
deleteStream(ctx:
|
|
80
|
+
deleteStream(ctx: MutationCtx | ActionCtx, streamId: StreamId): Promise<void>;
|
|
81
81
|
private addChunk;
|
|
82
82
|
private setStreamStatus;
|
|
83
83
|
}
|
|
84
|
-
type
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
type RunMutationCtx = {
|
|
88
|
-
runMutation: GenericMutationCtx<GenericDataModel>["runMutation"];
|
|
89
|
-
};
|
|
84
|
+
type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
|
|
85
|
+
type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runQuery" | "runMutation">;
|
|
86
|
+
type ActionCtx = Pick<GenericActionCtx<GenericDataModel>, "runQuery" | "runMutation" | "runAction">;
|
|
90
87
|
export type OpaqueIds<T> = T extends GenericId<infer _T> | string ? string : T extends (infer U)[] ? OpaqueIds<U>[] : T extends ArrayBuffer ? ArrayBuffer : T extends object ? {
|
|
91
88
|
[K in keyof T]: OpaqueIds<T[K]>;
|
|
92
89
|
} : T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAK,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,YAAY,EAAE,IAAI,CAAA;CAAE,CAAC;AACvD,eAAO,MAAM,iBAAiB,qDAAa,CAAC;AAC5C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAC5D,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,CACvE,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,aAAa,KACzB,OAAO,CAAC,IAAI,CAAC,CAAC;AAQnB,qBAAa,uBAAuB;IAEzB,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM;gBADhB,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,EAC7B,OAAO,CAAC,EAAE,MAAM,YAAA;IAGzB;;;;;;;;;;;;;;;;OAgBG;IAEG,YAAY,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAkB,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,gCAAgC,CAAC;AAIrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;AAc5C,+CAA+C;AAC/C,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE;IACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF,2EAA2E;AAC3E,MAAM,OAAO,uBAAuB;IAEzB;IACA;IAFT,YACS,SAA6B,EAC7B,OAAgB;QADhB,cAAS,GAAT,SAAS,CAAoB;QAC7B,YAAO,GAAP,OAAO,CAAS;IACtB,CAAC;IAEJ;;;;;;;;;;;;;;;;OAgBG;IAEH,KAAK,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
package/dist/component/lib.js
CHANGED
|
@@ -23,12 +23,12 @@ export const addChunk = mutation({
|
|
|
23
23
|
final: v.boolean(),
|
|
24
24
|
},
|
|
25
25
|
handler: async (ctx, args) => {
|
|
26
|
-
const stream = await ctx.db.get(args.streamId);
|
|
26
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
27
27
|
if (!stream) {
|
|
28
28
|
throw new Error("Stream not found");
|
|
29
29
|
}
|
|
30
30
|
if (stream.status === "pending") {
|
|
31
|
-
await ctx.db.patch(args.streamId, {
|
|
31
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
32
32
|
status: "streaming",
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ export const addChunk = mutation({
|
|
|
40
40
|
text: args.text,
|
|
41
41
|
});
|
|
42
42
|
if (args.final) {
|
|
43
|
-
await ctx.db.patch(args.streamId, {
|
|
43
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
44
44
|
status: "done",
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -54,7 +54,7 @@ export const setStreamStatus = mutation({
|
|
|
54
54
|
status: v.union(v.literal("pending"), v.literal("streaming"), v.literal("done"), v.literal("error"), v.literal("timeout")),
|
|
55
55
|
},
|
|
56
56
|
handler: async (ctx, args) => {
|
|
57
|
-
const stream = await ctx.db.get(args.streamId);
|
|
57
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
58
58
|
if (!stream) {
|
|
59
59
|
throw new Error("Stream not found");
|
|
60
60
|
}
|
|
@@ -62,7 +62,7 @@ export const setStreamStatus = mutation({
|
|
|
62
62
|
console.log("Stream is already finalized; ignoring status change", stream);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
await ctx.db.patch(args.streamId, {
|
|
65
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
66
66
|
status: args.status,
|
|
67
67
|
});
|
|
68
68
|
},
|
|
@@ -74,7 +74,7 @@ export const getStreamStatus = query({
|
|
|
74
74
|
},
|
|
75
75
|
returns: streamStatusValidator,
|
|
76
76
|
handler: async (ctx, args) => {
|
|
77
|
-
const stream = await ctx.db.get(args.streamId);
|
|
77
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
78
78
|
return stream?.status ?? "error";
|
|
79
79
|
},
|
|
80
80
|
});
|
|
@@ -89,7 +89,7 @@ export const getStreamText = query({
|
|
|
89
89
|
status: streamStatusValidator,
|
|
90
90
|
}),
|
|
91
91
|
handler: async (ctx, args) => {
|
|
92
|
-
const stream = await ctx.db.get(args.streamId);
|
|
92
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
93
93
|
if (!stream) {
|
|
94
94
|
throw new Error("Stream not found");
|
|
95
95
|
}
|
|
@@ -118,11 +118,11 @@ export const deleteStream = mutation({
|
|
|
118
118
|
},
|
|
119
119
|
returns: v.null(),
|
|
120
120
|
handler: async (ctx, args) => {
|
|
121
|
-
const stream = await ctx.db.get(args.streamId);
|
|
121
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
122
122
|
if (!stream) {
|
|
123
123
|
throw new Error(`Stream ${args.streamId} not found`);
|
|
124
124
|
}
|
|
125
|
-
await ctx.db.delete(args.streamId);
|
|
125
|
+
await ctx.db.delete("streams", args.streamId);
|
|
126
126
|
await ctx.scheduler.runAfter(0, internal.lib._deleteChunksPage, {
|
|
127
127
|
streamId: args.streamId,
|
|
128
128
|
cursor: null,
|
|
@@ -141,7 +141,7 @@ export const _deleteChunksPage = internalMutation({
|
|
|
141
141
|
.query("chunks")
|
|
142
142
|
.withIndex("byStream", (q) => q.eq("streamId", args.streamId))
|
|
143
143
|
.paginate({ cursor: args.cursor, numItems: DELETE_BATCH_SIZE });
|
|
144
|
-
await Promise.all(result.page.map((chunk) => ctx.db.delete(chunk._id)));
|
|
144
|
+
await Promise.all(result.page.map((chunk) => ctx.db.delete("chunks", chunk._id)));
|
|
145
145
|
if (!result.isDone) {
|
|
146
146
|
await ctx.scheduler.runAfter(0, internal.lib._deleteChunksPage, {
|
|
147
147
|
streamId: args.streamId,
|
|
@@ -167,7 +167,7 @@ export const cleanupExpiredStreams = internalMutation({
|
|
|
167
167
|
for (const stream of [...pendingStreams, ...streamingStreams]) {
|
|
168
168
|
if (now - stream._creationTime > EXPIRATION_TIME) {
|
|
169
169
|
console.log("Cleaning up expired stream", stream._id);
|
|
170
|
-
await ctx.db.patch(stream._id, {
|
|
170
|
+
await ctx.db.patch("streams", stream._id, {
|
|
171
171
|
status: "timeout",
|
|
172
172
|
});
|
|
173
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,MAAM,EAAE,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE5D,wCAAwC;AACxC,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;IACnC,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;YAC9C,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC,CAAC;AAEH,2BAA2B;AAC3B,4CAA4C;AAC5C,8DAA8D;AAC9D,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;IAC/B,IAAI,EAAE;QACJ,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC;QACzB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;KACnB;IACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/component/lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,iBAAiB,EAAE,iBAAiB,CAClC,OAAO,EACP,QAAQ,EACR;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpB,UAAU,CACX,EACD,SAAS,EAAE,GAAG,EACd,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,CAAC,EAAE;IAEL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,iBAAiB,EAAE,iBAAiB,CAClC,OAAO,EACP,QAAQ,EACR;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpB,UAAU,CACX,EACD,SAAS,EAAE,GAAG,EACd,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,CAAC,EAAE;IAEL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,cAuHF"}
|
package/dist/react/index.js
CHANGED
|
@@ -19,9 +19,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
19
19
|
* @returns The body and status of the stream.
|
|
20
20
|
*/
|
|
21
21
|
export function useStream(getPersistentBody, streamUrl, driven, streamId, opts) {
|
|
22
|
+
const [streamBody, setStreamBody] = useState("");
|
|
22
23
|
const [streamEnded, setStreamEnded] = useState(null);
|
|
23
|
-
//
|
|
24
|
-
|
|
24
|
+
// Track the active streamId to handle multiple streams and serve as a
|
|
25
|
+
// Strict Mode guard (prevents double-firing when the same streamId is seen).
|
|
26
|
+
const activeStreamRef = useRef(undefined);
|
|
25
27
|
const usePersistence = useMemo(() => {
|
|
26
28
|
// Something is wrong with the stream, so we need to use the database value.
|
|
27
29
|
if (streamEnded === false) {
|
|
@@ -34,43 +36,75 @@ export function useStream(getPersistentBody, streamUrl, driven, streamId, opts)
|
|
|
34
36
|
// Otherwise, we'll try to drive the stream and use the HTTP response.
|
|
35
37
|
return false;
|
|
36
38
|
}, [driven, streamEnded]);
|
|
37
|
-
// console.log("usePersistence", usePersistence);
|
|
38
39
|
const persistentBody = useQuery(getPersistentBody, usePersistence && streamId ? { streamId } : "skip");
|
|
39
|
-
const [streamBody, setStreamBody] = useState("");
|
|
40
40
|
useEffect(() => {
|
|
41
|
-
if (driven
|
|
42
|
-
|
|
43
|
-
void (async () => {
|
|
44
|
-
const success = await startStreaming(streamUrl, streamId, (text) => {
|
|
45
|
-
setStreamBody((prev) => prev + text);
|
|
46
|
-
}, {
|
|
47
|
-
...opts?.headers,
|
|
48
|
-
...(opts?.authToken
|
|
49
|
-
? { Authorization: `Bearer ${opts.authToken}` }
|
|
50
|
-
: {}),
|
|
51
|
-
});
|
|
52
|
-
setStreamEnded(success);
|
|
53
|
-
})();
|
|
54
|
-
// If we get remounted, we don't want to start a new stream.
|
|
55
|
-
return () => {
|
|
56
|
-
streamStarted.current = true;
|
|
57
|
-
};
|
|
41
|
+
if (!driven || !streamId) {
|
|
42
|
+
return;
|
|
58
43
|
}
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
streamId
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
// Strict Mode guard: don't restart streaming for the same streamId
|
|
45
|
+
if (streamId === activeStreamRef.current) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// New stream: reset state and track the new streamId
|
|
49
|
+
activeStreamRef.current = streamId;
|
|
50
|
+
setStreamBody("");
|
|
51
|
+
setStreamEnded(null);
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
void (async () => {
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(streamUrl, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({ streamId }),
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
...opts?.headers,
|
|
61
|
+
...(opts?.authToken
|
|
62
|
+
? { Authorization: `Bearer ${opts.authToken}` }
|
|
63
|
+
: {}),
|
|
64
|
+
},
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
if (response.status === 205) {
|
|
68
|
+
console.error("Stream already finished", response);
|
|
69
|
+
setStreamEnded(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
console.error("Failed to reach streaming endpoint", response);
|
|
74
|
+
setStreamEnded(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!response.body) {
|
|
78
|
+
console.error("No body in response", response);
|
|
79
|
+
setStreamEnded(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const reader = response.body.getReader();
|
|
83
|
+
const decoder = new TextDecoder();
|
|
84
|
+
for (;;) {
|
|
85
|
+
const { done, value } = await reader.read();
|
|
86
|
+
const text = decoder.decode(value, { stream: !done });
|
|
87
|
+
if (text) {
|
|
88
|
+
setStreamBody((prev) => prev + text);
|
|
89
|
+
}
|
|
90
|
+
if (done) {
|
|
91
|
+
setStreamEnded(true);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
if (!controller.signal.aborted) {
|
|
98
|
+
console.error("Error reading stream", e);
|
|
99
|
+
setStreamEnded(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
return () => {
|
|
104
|
+
controller.abort();
|
|
105
|
+
};
|
|
106
|
+
}, [driven, streamId, streamUrl, opts?.authToken, opts?.headers]);
|
|
68
107
|
const body = useMemo(() => {
|
|
69
|
-
// console.log(
|
|
70
|
-
// "body info p vs. s",
|
|
71
|
-
// persistentBody?.text?.length ?? 0,
|
|
72
|
-
// streamBody.length
|
|
73
|
-
//);
|
|
74
108
|
if (persistentBody) {
|
|
75
109
|
return persistentBody;
|
|
76
110
|
}
|
|
@@ -88,54 +122,4 @@ export function useStream(getPersistentBody, streamUrl, driven, streamId, opts)
|
|
|
88
122
|
}, [persistentBody, streamBody, streamEnded]);
|
|
89
123
|
return body;
|
|
90
124
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Internal helper for starting a stream.
|
|
93
|
-
*
|
|
94
|
-
* @param url - The URL of the http action that will kick off the stream
|
|
95
|
-
* generation and stream the result back to the client using the component's
|
|
96
|
-
* `stream` method.
|
|
97
|
-
* @param streamId - The ID of the stream.
|
|
98
|
-
* @param onUpdate - A function that updates the stream body.
|
|
99
|
-
* @returns A promise that resolves to a boolean indicating whether the stream
|
|
100
|
-
* was started successfully. It can fail if the http action is not found, or
|
|
101
|
-
* CORS fails, or an exception is raised, or the stream is already running
|
|
102
|
-
* or finished, etc.
|
|
103
|
-
*/
|
|
104
|
-
async function startStreaming(url, streamId, onUpdate, headers) {
|
|
105
|
-
const response = await fetch(url, {
|
|
106
|
-
method: "POST",
|
|
107
|
-
body: JSON.stringify({
|
|
108
|
-
streamId: streamId,
|
|
109
|
-
}),
|
|
110
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
111
|
-
});
|
|
112
|
-
// Adapted from https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
|
113
|
-
if (response.status === 205) {
|
|
114
|
-
console.error("Stream already finished", response);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (!response.ok) {
|
|
118
|
-
console.error("Failed to reach streaming endpoint", response);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
if (!response.body) {
|
|
122
|
-
console.error("No body in response", response);
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const reader = response.body.getReader();
|
|
126
|
-
while (true) {
|
|
127
|
-
try {
|
|
128
|
-
const { done, value } = await reader.read();
|
|
129
|
-
if (done) {
|
|
130
|
-
onUpdate(new TextDecoder().decode(value));
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
onUpdate(new TextDecoder().decode(value));
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
console.error("Error reading stream", e);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
125
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAIb,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG7D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,iBAKC,EACD,SAAc,EACd,MAAe,EACf,QAA8B,EAC9B,IAKC;IAED,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"email": "support@convex.dev",
|
|
8
8
|
"url": "https://github.com/get-convex/persistent-text-streaming/issues"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.3",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"convex",
|
|
@@ -15,22 +15,20 @@
|
|
|
15
15
|
],
|
|
16
16
|
"type": "module",
|
|
17
17
|
"scripts": {
|
|
18
|
-
"dev": "
|
|
19
|
-
"dev:backend": "convex dev --typecheck-components",
|
|
18
|
+
"dev": "convex dev --start 'npm run dev:build'",
|
|
20
19
|
"dev:frontend": "cd example && vite --clearScreen false",
|
|
21
20
|
"dev:build": "chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial",
|
|
22
|
-
"predev": "convex init && npm run build",
|
|
21
|
+
"predev": "convex init && npm run build:codegen",
|
|
23
22
|
"build": "tsc --project ./tsconfig.build.json",
|
|
24
23
|
"build:codegen": "npx convex codegen --component-dir ./src/component && npm run build",
|
|
25
24
|
"build:clean": "rm -rf dist *.tsbuildinfo && npm run build:codegen",
|
|
26
25
|
"typecheck": "tsc --noEmit && tsc -p example && tsc -p example/convex",
|
|
27
26
|
"lint": "eslint .",
|
|
28
|
-
"all": "run-p -r 'dev:*' 'test:watch'",
|
|
29
27
|
"test": "vitest run --typecheck",
|
|
30
28
|
"test:watch": "vitest --typecheck --clearScreen false",
|
|
31
29
|
"test:debug": "vitest --inspect-brk --no-file-parallelism",
|
|
32
30
|
"test:coverage": "vitest run --coverage --coverage.reporter=text",
|
|
33
|
-
"preversion": "npm ci && npm run build:clean && run
|
|
31
|
+
"preversion": "npm ci && npm run build:clean && npm run test && npm run lint && npm run typecheck",
|
|
34
32
|
"alpha": "npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags",
|
|
35
33
|
"release": "npm version patch && npm publish && git push --follow-tags",
|
|
36
34
|
"version": "(npm whoami || npm login) && vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md"
|
|
@@ -68,34 +66,35 @@
|
|
|
68
66
|
"react-dom": "~18.3.1 || ^19.0.0"
|
|
69
67
|
},
|
|
70
68
|
"devDependencies": {
|
|
69
|
+
"@convex-dev/eslint-plugin": "^2.0.0",
|
|
71
70
|
"@edge-runtime/vm": "5.0.0",
|
|
72
|
-
"@eslint/eslintrc": "3.3.
|
|
73
|
-
"@eslint/js": "9.
|
|
74
|
-
"@tailwindcss/vite": "4.
|
|
75
|
-
"@types/node": "20.19.
|
|
76
|
-
"@types/react": "19.2.
|
|
77
|
-
"@types/react-dom": "19.2.
|
|
78
|
-
"@vitejs/plugin-react": "
|
|
71
|
+
"@eslint/eslintrc": "3.3.5",
|
|
72
|
+
"@eslint/js": "9.39.4",
|
|
73
|
+
"@tailwindcss/vite": "4.2.2",
|
|
74
|
+
"@types/node": "20.19.39",
|
|
75
|
+
"@types/react": "19.2.14",
|
|
76
|
+
"@types/react-dom": "19.2.3",
|
|
77
|
+
"@vitejs/plugin-react": "6.0.1",
|
|
79
78
|
"chokidar-cli": "3.0.0",
|
|
80
79
|
"clsx": "2.1.1",
|
|
81
|
-
"convex": "1.
|
|
82
|
-
"convex-test": "0.0.
|
|
83
|
-
"eslint": "9.39.
|
|
80
|
+
"convex": "1.35.1",
|
|
81
|
+
"convex-test": "0.0.48",
|
|
82
|
+
"eslint": "9.39.4",
|
|
84
83
|
"eslint-plugin-react-hooks": "7.0.1",
|
|
85
|
-
"eslint-plugin-react-refresh": "0.
|
|
86
|
-
"globals": "
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"prettier": "3.
|
|
90
|
-
"react": "19.2.
|
|
91
|
-
"react-dom": "19.2.
|
|
84
|
+
"eslint-plugin-react-refresh": "0.5.2",
|
|
85
|
+
"globals": "17.5.0",
|
|
86
|
+
"openai": "6.34.0",
|
|
87
|
+
"pkg-pr-new": "0.0.66",
|
|
88
|
+
"prettier": "3.8.3",
|
|
89
|
+
"react": "19.2.5",
|
|
90
|
+
"react-dom": "19.2.5",
|
|
92
91
|
"react-markdown": "10.1.0",
|
|
93
|
-
"tailwind-merge": "3.
|
|
94
|
-
"tailwindcss": "4.
|
|
92
|
+
"tailwind-merge": "3.5.0",
|
|
93
|
+
"tailwindcss": "4.2.2",
|
|
95
94
|
"typescript": "5.9.3",
|
|
96
|
-
"typescript-eslint": "8.
|
|
97
|
-
"vite": "
|
|
98
|
-
"vitest": "
|
|
95
|
+
"typescript-eslint": "8.58.2",
|
|
96
|
+
"vite": "8.0.16",
|
|
97
|
+
"vitest": "4.1.4"
|
|
99
98
|
},
|
|
100
99
|
"types": "./dist/client/index.d.ts",
|
|
101
100
|
"module": "./dist/client/index.js",
|
package/src/client/index.ts
CHANGED
|
@@ -55,7 +55,7 @@ export class PersistentTextStreaming {
|
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
-
async createStream(ctx:
|
|
58
|
+
async createStream(ctx: MutationCtx | ActionCtx): Promise<StreamId> {
|
|
59
59
|
const id = await ctx.runMutation(this.component.lib.createStream);
|
|
60
60
|
return id as StreamId;
|
|
61
61
|
}
|
|
@@ -74,7 +74,7 @@ export class PersistentTextStreaming {
|
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
76
|
async getStreamBody(
|
|
77
|
-
ctx:
|
|
77
|
+
ctx: QueryCtx | MutationCtx | ActionCtx,
|
|
78
78
|
streamId: StreamId,
|
|
79
79
|
): Promise<StreamBody> {
|
|
80
80
|
const { text, status } = await ctx.runQuery(
|
|
@@ -180,7 +180,10 @@ export class PersistentTextStreaming {
|
|
|
180
180
|
* @param ctx - A convex context capable of running mutations.
|
|
181
181
|
* @param streamId - The ID of the stream to delete.
|
|
182
182
|
*/
|
|
183
|
-
async deleteStream(
|
|
183
|
+
async deleteStream(
|
|
184
|
+
ctx: MutationCtx | ActionCtx,
|
|
185
|
+
streamId: StreamId,
|
|
186
|
+
): Promise<void> {
|
|
184
187
|
await ctx.runMutation(this.component.lib.deleteStream, {
|
|
185
188
|
streamId,
|
|
186
189
|
});
|
|
@@ -188,7 +191,7 @@ export class PersistentTextStreaming {
|
|
|
188
191
|
|
|
189
192
|
// Internal helper -- add a chunk to the stream.
|
|
190
193
|
private async addChunk(
|
|
191
|
-
ctx:
|
|
194
|
+
ctx: MutationCtx | ActionCtx,
|
|
192
195
|
streamId: StreamId,
|
|
193
196
|
text: string,
|
|
194
197
|
final: boolean,
|
|
@@ -202,7 +205,7 @@ export class PersistentTextStreaming {
|
|
|
202
205
|
|
|
203
206
|
// Internal helper -- set the status of a stream.
|
|
204
207
|
private async setStreamStatus(
|
|
205
|
-
ctx:
|
|
208
|
+
ctx: MutationCtx | ActionCtx,
|
|
206
209
|
streamId: StreamId,
|
|
207
210
|
status: StreamStatus,
|
|
208
211
|
) {
|
|
@@ -215,12 +218,15 @@ export class PersistentTextStreaming {
|
|
|
215
218
|
|
|
216
219
|
/* Type utils follow */
|
|
217
220
|
|
|
218
|
-
type
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
|
|
222
|
+
type MutationCtx = Pick<
|
|
223
|
+
GenericMutationCtx<GenericDataModel>,
|
|
224
|
+
"runQuery" | "runMutation"
|
|
225
|
+
>;
|
|
226
|
+
type ActionCtx = Pick<
|
|
227
|
+
GenericActionCtx<GenericDataModel>,
|
|
228
|
+
"runQuery" | "runMutation" | "runAction"
|
|
229
|
+
>;
|
|
224
230
|
|
|
225
231
|
export type OpaqueIds<T> = T extends GenericId<infer _T> | string
|
|
226
232
|
? string
|
package/src/component/lib.ts
CHANGED
|
@@ -25,12 +25,12 @@ export const addChunk = mutation({
|
|
|
25
25
|
final: v.boolean(),
|
|
26
26
|
},
|
|
27
27
|
handler: async (ctx, args) => {
|
|
28
|
-
const stream = await ctx.db.get(args.streamId);
|
|
28
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
29
29
|
if (!stream) {
|
|
30
30
|
throw new Error("Stream not found");
|
|
31
31
|
}
|
|
32
32
|
if (stream.status === "pending") {
|
|
33
|
-
await ctx.db.patch(args.streamId, {
|
|
33
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
34
34
|
status: "streaming",
|
|
35
35
|
});
|
|
36
36
|
} else if (stream.status !== "streaming") {
|
|
@@ -41,7 +41,7 @@ export const addChunk = mutation({
|
|
|
41
41
|
text: args.text,
|
|
42
42
|
});
|
|
43
43
|
if (args.final) {
|
|
44
|
-
await ctx.db.patch(args.streamId, {
|
|
44
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
45
45
|
status: "done",
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -62,7 +62,7 @@ export const setStreamStatus = mutation({
|
|
|
62
62
|
),
|
|
63
63
|
},
|
|
64
64
|
handler: async (ctx, args) => {
|
|
65
|
-
const stream = await ctx.db.get(args.streamId);
|
|
65
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
66
66
|
if (!stream) {
|
|
67
67
|
throw new Error("Stream not found");
|
|
68
68
|
}
|
|
@@ -73,7 +73,7 @@ export const setStreamStatus = mutation({
|
|
|
73
73
|
);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
await ctx.db.patch(args.streamId, {
|
|
76
|
+
await ctx.db.patch("streams", args.streamId, {
|
|
77
77
|
status: args.status,
|
|
78
78
|
});
|
|
79
79
|
},
|
|
@@ -86,7 +86,7 @@ export const getStreamStatus = query({
|
|
|
86
86
|
},
|
|
87
87
|
returns: streamStatusValidator,
|
|
88
88
|
handler: async (ctx, args) => {
|
|
89
|
-
const stream = await ctx.db.get(args.streamId);
|
|
89
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
90
90
|
return stream?.status ?? "error";
|
|
91
91
|
},
|
|
92
92
|
});
|
|
@@ -102,7 +102,7 @@ export const getStreamText = query({
|
|
|
102
102
|
status: streamStatusValidator,
|
|
103
103
|
}),
|
|
104
104
|
handler: async (ctx, args) => {
|
|
105
|
-
const stream = await ctx.db.get(args.streamId);
|
|
105
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
106
106
|
if (!stream) {
|
|
107
107
|
throw new Error("Stream not found");
|
|
108
108
|
}
|
|
@@ -133,11 +133,11 @@ export const deleteStream = mutation({
|
|
|
133
133
|
},
|
|
134
134
|
returns: v.null(),
|
|
135
135
|
handler: async (ctx, args) => {
|
|
136
|
-
const stream = await ctx.db.get(args.streamId);
|
|
136
|
+
const stream = await ctx.db.get("streams", args.streamId);
|
|
137
137
|
if (!stream) {
|
|
138
138
|
throw new Error(`Stream ${args.streamId} not found`);
|
|
139
139
|
}
|
|
140
|
-
await ctx.db.delete(args.streamId);
|
|
140
|
+
await ctx.db.delete("streams", args.streamId);
|
|
141
141
|
await ctx.scheduler.runAfter(0, internal.lib._deleteChunksPage, {
|
|
142
142
|
streamId: args.streamId,
|
|
143
143
|
cursor: null,
|
|
@@ -158,7 +158,7 @@ export const _deleteChunksPage = internalMutation({
|
|
|
158
158
|
.withIndex("byStream", (q) => q.eq("streamId", args.streamId))
|
|
159
159
|
.paginate({ cursor: args.cursor, numItems: DELETE_BATCH_SIZE });
|
|
160
160
|
|
|
161
|
-
await Promise.all(result.page.map((chunk) => ctx.db.delete(chunk._id)));
|
|
161
|
+
await Promise.all(result.page.map((chunk) => ctx.db.delete("chunks", chunk._id)));
|
|
162
162
|
|
|
163
163
|
if (!result.isDone) {
|
|
164
164
|
await ctx.scheduler.runAfter(0, internal.lib._deleteChunksPage, {
|
|
@@ -187,7 +187,7 @@ export const cleanupExpiredStreams = internalMutation({
|
|
|
187
187
|
for (const stream of [...pendingStreams, ...streamingStreams]) {
|
|
188
188
|
if (now - stream._creationTime > EXPIRATION_TIME) {
|
|
189
189
|
console.log("Cleaning up expired stream", stream._id);
|
|
190
|
-
await ctx.db.patch(stream._id, {
|
|
190
|
+
await ctx.db.patch("streams", stream._id, {
|
|
191
191
|
status: "timeout",
|
|
192
192
|
});
|
|
193
193
|
}
|
package/src/react/index.ts
CHANGED
|
@@ -41,10 +41,12 @@ export function useStream(
|
|
|
41
41
|
headers?: Record<string, string>;
|
|
42
42
|
},
|
|
43
43
|
) {
|
|
44
|
-
const [
|
|
44
|
+
const [streamBody, setStreamBody] = useState<string>("");
|
|
45
|
+
const [streamEnded, setStreamEnded] = useState<boolean | null>(null);
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
|
|
47
|
+
// Track the active streamId to handle multiple streams and serve as a
|
|
48
|
+
// Strict Mode guard (prevents double-firing when the same streamId is seen).
|
|
49
|
+
const activeStreamRef = useRef<StreamId | undefined>(undefined);
|
|
48
50
|
|
|
49
51
|
const usePersistence = useMemo(() => {
|
|
50
52
|
// Something is wrong with the stream, so we need to use the database value.
|
|
@@ -58,53 +60,88 @@ export function useStream(
|
|
|
58
60
|
// Otherwise, we'll try to drive the stream and use the HTTP response.
|
|
59
61
|
return false;
|
|
60
62
|
}, [driven, streamEnded]);
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
const persistentBody = useQuery(
|
|
63
65
|
getPersistentBody,
|
|
64
66
|
usePersistence && streamId ? { streamId } : "skip",
|
|
65
67
|
);
|
|
66
|
-
const [streamBody, setStreamBody] = useState<string>("");
|
|
67
68
|
|
|
68
69
|
useEffect(() => {
|
|
69
|
-
if (driven
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
if (!driven || !streamId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Strict Mode guard: don't restart streaming for the same streamId
|
|
75
|
+
if (streamId === activeStreamRef.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// New stream: reset state and track the new streamId
|
|
80
|
+
activeStreamRef.current = streamId;
|
|
81
|
+
setStreamBody("");
|
|
82
|
+
setStreamEnded(null);
|
|
83
|
+
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
|
|
86
|
+
void (async () => {
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(streamUrl, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify({ streamId }),
|
|
91
|
+
headers: {
|
|
92
|
+
"Content-Type": "application/json",
|
|
79
93
|
...opts?.headers,
|
|
80
94
|
...(opts?.authToken
|
|
81
95
|
? { Authorization: `Bearer ${opts.authToken}` }
|
|
82
96
|
: {}),
|
|
83
97
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (response.status === 205) {
|
|
102
|
+
console.error("Stream already finished", response);
|
|
103
|
+
setStreamEnded(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.error("Failed to reach streaming endpoint", response);
|
|
108
|
+
setStreamEnded(false);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!response.body) {
|
|
112
|
+
console.error("No body in response", response);
|
|
113
|
+
setStreamEnded(false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const reader = response.body.getReader();
|
|
118
|
+
const decoder = new TextDecoder();
|
|
119
|
+
|
|
120
|
+
for (;;) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
const text = decoder.decode(value, { stream: !done });
|
|
123
|
+
if (text) {
|
|
124
|
+
setStreamBody((prev) => prev + text);
|
|
125
|
+
}
|
|
126
|
+
if (done) {
|
|
127
|
+
setStreamEnded(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
if (!controller.signal.aborted) {
|
|
133
|
+
console.error("Error reading stream", e);
|
|
134
|
+
setStreamEnded(false);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
controller.abort();
|
|
141
|
+
};
|
|
142
|
+
}, [driven, streamId, streamUrl, opts?.authToken, opts?.headers]);
|
|
101
143
|
|
|
102
144
|
const body = useMemo<StreamBody>(() => {
|
|
103
|
-
// console.log(
|
|
104
|
-
// "body info p vs. s",
|
|
105
|
-
// persistentBody?.text?.length ?? 0,
|
|
106
|
-
// streamBody.length
|
|
107
|
-
//);
|
|
108
145
|
if (persistentBody) {
|
|
109
146
|
return persistentBody;
|
|
110
147
|
}
|
|
@@ -123,57 +160,3 @@ export function useStream(
|
|
|
123
160
|
return body;
|
|
124
161
|
}
|
|
125
162
|
|
|
126
|
-
/**
|
|
127
|
-
* Internal helper for starting a stream.
|
|
128
|
-
*
|
|
129
|
-
* @param url - The URL of the http action that will kick off the stream
|
|
130
|
-
* generation and stream the result back to the client using the component's
|
|
131
|
-
* `stream` method.
|
|
132
|
-
* @param streamId - The ID of the stream.
|
|
133
|
-
* @param onUpdate - A function that updates the stream body.
|
|
134
|
-
* @returns A promise that resolves to a boolean indicating whether the stream
|
|
135
|
-
* was started successfully. It can fail if the http action is not found, or
|
|
136
|
-
* CORS fails, or an exception is raised, or the stream is already running
|
|
137
|
-
* or finished, etc.
|
|
138
|
-
*/
|
|
139
|
-
async function startStreaming(
|
|
140
|
-
url: URL,
|
|
141
|
-
streamId: StreamId,
|
|
142
|
-
onUpdate: (text: string) => void,
|
|
143
|
-
headers: Record<string, string>,
|
|
144
|
-
) {
|
|
145
|
-
const response = await fetch(url, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
body: JSON.stringify({
|
|
148
|
-
streamId: streamId,
|
|
149
|
-
}),
|
|
150
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
151
|
-
});
|
|
152
|
-
// Adapted from https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
|
153
|
-
if (response.status === 205) {
|
|
154
|
-
console.error("Stream already finished", response);
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
if (!response.ok) {
|
|
158
|
-
console.error("Failed to reach streaming endpoint", response);
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
if (!response.body) {
|
|
162
|
-
console.error("No body in response", response);
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
const reader = response.body.getReader();
|
|
166
|
-
while (true) {
|
|
167
|
-
try {
|
|
168
|
-
const { done, value } = await reader.read();
|
|
169
|
-
if (done) {
|
|
170
|
-
onUpdate(new TextDecoder().decode(value));
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
onUpdate(new TextDecoder().decode(value));
|
|
174
|
-
} catch (e) {
|
|
175
|
-
console.error("Error reading stream", e);
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|