@convex-dev/agent 0.1.2-alpha.0 → 0.1.2
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/dist/commonjs/client/streaming.d.ts.map +1 -1
- package/dist/commonjs/client/streaming.js +0 -1
- package/dist/commonjs/client/streaming.js.map +1 -1
- package/dist/commonjs/component/schema.d.ts +4 -4
- package/dist/commonjs/component/schema.d.ts.map +1 -1
- package/dist/commonjs/component/schema.js +6 -1
- package/dist/commonjs/component/schema.js.map +1 -1
- package/dist/commonjs/component/streams.js +1 -1
- package/dist/commonjs/component/streams.js.map +1 -1
- package/dist/commonjs/react/index.d.ts +26 -2
- package/dist/commonjs/react/index.d.ts.map +1 -1
- package/dist/commonjs/react/index.js +77 -2
- package/dist/commonjs/react/index.js.map +1 -1
- package/dist/commonjs/react/toUIMessages.d.ts +8 -3
- package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
- package/dist/commonjs/react/toUIMessages.js +16 -12
- package/dist/commonjs/react/toUIMessages.js.map +1 -1
- package/dist/esm/client/streaming.d.ts.map +1 -1
- package/dist/esm/client/streaming.js +0 -1
- package/dist/esm/client/streaming.js.map +1 -1
- package/dist/esm/component/schema.d.ts +4 -4
- package/dist/esm/component/schema.d.ts.map +1 -1
- package/dist/esm/component/schema.js +6 -1
- package/dist/esm/component/schema.js.map +1 -1
- package/dist/esm/component/streams.js +1 -1
- package/dist/esm/component/streams.js.map +1 -1
- package/dist/esm/react/index.d.ts +26 -2
- package/dist/esm/react/index.d.ts.map +1 -1
- package/dist/esm/react/index.js +77 -2
- package/dist/esm/react/index.js.map +1 -1
- package/dist/esm/react/toUIMessages.d.ts +8 -3
- package/dist/esm/react/toUIMessages.d.ts.map +1 -1
- package/dist/esm/react/toUIMessages.js +16 -12
- package/dist/esm/react/toUIMessages.js.map +1 -1
- package/package.json +1 -1
- package/src/client/streaming.ts +0 -1
- package/src/component/schema.ts +6 -1
- package/src/component/streams.ts +2 -2
- package/src/react/index.ts +102 -3
- package/src/react/toUIMessages.ts +25 -17
package/src/react/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
|
|
2
2
|
import type { BetterOmit, ErrorMessage, Expand } from "convex-helpers";
|
|
3
3
|
import {
|
|
4
|
+
insertAtTop,
|
|
4
5
|
type PaginatedQueryArgs,
|
|
5
6
|
usePaginatedQuery,
|
|
6
7
|
type UsePaginatedQueryResult,
|
|
@@ -12,7 +13,7 @@ import type {
|
|
|
12
13
|
PaginationOptions,
|
|
13
14
|
PaginationResult,
|
|
14
15
|
} from "convex/server";
|
|
15
|
-
import { useMemo, useState } from "react";
|
|
16
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
16
17
|
import type { MessageDoc } from "../client";
|
|
17
18
|
import type { SyncStreamsReturnValue } from "../client/types";
|
|
18
19
|
import type {
|
|
@@ -21,10 +22,12 @@ import type {
|
|
|
21
22
|
StreamMessage,
|
|
22
23
|
TextStreamPart,
|
|
23
24
|
} from "../validators";
|
|
24
|
-
import type {
|
|
25
|
+
import type { UIMessage } from "./toUIMessages";
|
|
25
26
|
import { toUIMessages } from "./toUIMessages";
|
|
27
|
+
import { OptimisticLocalStore } from "convex/browser";
|
|
28
|
+
import { api } from "../component/_generated/api";
|
|
26
29
|
|
|
27
|
-
export { toUIMessages, type
|
|
30
|
+
export { toUIMessages, type UIMessage };
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* A hook that fetches messages from a thread.
|
|
@@ -220,6 +223,102 @@ export function useStreamingThreadMessages<
|
|
|
220
223
|
return messages as ThreadMessagesResult<Query>[] | undefined;
|
|
221
224
|
}
|
|
222
225
|
|
|
226
|
+
/**
|
|
227
|
+
* A hook that smoothly displays text as it is streamed.
|
|
228
|
+
*
|
|
229
|
+
* @param text The text to display. Pass in the full text each time.
|
|
230
|
+
* @param charsPerSec The number of characters to display per second.
|
|
231
|
+
* @returns A tuple of the visible text and the state of the smooth text,
|
|
232
|
+
* including the current cursor position and whether it's still streaming.
|
|
233
|
+
* This allows you to decide if it's too far behind and you want to adjust
|
|
234
|
+
* the charsPerSec or just prefer the full text.
|
|
235
|
+
*/
|
|
236
|
+
export function useSmoothText(
|
|
237
|
+
text: string,
|
|
238
|
+
{
|
|
239
|
+
charsPerSec = 512,
|
|
240
|
+
}: {
|
|
241
|
+
/**
|
|
242
|
+
* The number of characters to display per second.
|
|
243
|
+
*/
|
|
244
|
+
charsPerSec?: number;
|
|
245
|
+
} = {}
|
|
246
|
+
): [string, { cursor: number; isStreaming: boolean }] {
|
|
247
|
+
const [visibleText, setVisibleText] = useState(text);
|
|
248
|
+
const smoothState = useRef({ lastUpdated: Date.now(), cursor: text.length });
|
|
249
|
+
|
|
250
|
+
const isStreaming = smoothState.current.cursor < text.length;
|
|
251
|
+
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!isStreaming) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
function update() {
|
|
257
|
+
if (smoothState.current.cursor >= text.length) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
const timeSinceLastUpdate = now - smoothState.current.lastUpdated;
|
|
262
|
+
const chars = Math.floor((timeSinceLastUpdate * charsPerSec) / 1000);
|
|
263
|
+
smoothState.current.cursor = Math.min(
|
|
264
|
+
smoothState.current.cursor + chars,
|
|
265
|
+
text.length
|
|
266
|
+
);
|
|
267
|
+
smoothState.current.lastUpdated = now;
|
|
268
|
+
setVisibleText(text.slice(0, smoothState.current.cursor));
|
|
269
|
+
}
|
|
270
|
+
update();
|
|
271
|
+
const interval = setInterval(() => {
|
|
272
|
+
update();
|
|
273
|
+
}, 1000 / 60);
|
|
274
|
+
return () => clearInterval(interval);
|
|
275
|
+
}, [text, isStreaming, charsPerSec]);
|
|
276
|
+
|
|
277
|
+
return [visibleText, { cursor: smoothState.current.cursor, isStreaming }];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function optimisticallySendMessage(
|
|
281
|
+
query: ThreadQuery<unknown, MessageDoc>
|
|
282
|
+
): (
|
|
283
|
+
store: OptimisticLocalStore,
|
|
284
|
+
args: { threadId: string; prompt: string }
|
|
285
|
+
) => void {
|
|
286
|
+
return (store, args) => {
|
|
287
|
+
const queries = store.getAllQueries(query);
|
|
288
|
+
let maxOrder = 0;
|
|
289
|
+
let maxStepOrder = 0;
|
|
290
|
+
for (const q of queries) {
|
|
291
|
+
if (q.args?.threadId !== args.threadId) continue;
|
|
292
|
+
if (q.args.streamArgs) continue;
|
|
293
|
+
for (const m of q.value?.page ?? []) {
|
|
294
|
+
maxOrder = Math.max(maxOrder, m.order);
|
|
295
|
+
maxStepOrder = Math.max(maxStepOrder, m.stepOrder);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const order = maxOrder + 1;
|
|
299
|
+
const stepOrder = 0;
|
|
300
|
+
insertAtTop({
|
|
301
|
+
paginatedQuery: query,
|
|
302
|
+
argsToMatch: { threadId: args.threadId, streamArgs: undefined },
|
|
303
|
+
item: {
|
|
304
|
+
_creationTime: Date.now(),
|
|
305
|
+
_id: crypto.randomUUID(),
|
|
306
|
+
order,
|
|
307
|
+
stepOrder,
|
|
308
|
+
status: "pending",
|
|
309
|
+
threadId: args.threadId,
|
|
310
|
+
tool: false,
|
|
311
|
+
message: {
|
|
312
|
+
role: "user",
|
|
313
|
+
content: args.prompt,
|
|
314
|
+
},
|
|
315
|
+
text: args.prompt,
|
|
316
|
+
},
|
|
317
|
+
localQueryStore: store,
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
223
322
|
function mergeDeltas(
|
|
224
323
|
threadId: string,
|
|
225
324
|
streamMessages: StreamMessage[],
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { UIMessage } from "ai";
|
|
1
|
+
import { UIMessage as AIUIMessage } from "ai";
|
|
2
2
|
import type { ToolInvocationUIPart } from "@ai-sdk/ui-utils";
|
|
3
3
|
import { MessageDoc } from "../client";
|
|
4
4
|
import { deserializeMessage, toUIFilePart } from "../mapping";
|
|
5
|
+
import { MessageStatus } from "../validators";
|
|
5
6
|
|
|
6
|
-
export type
|
|
7
|
+
export type UIMessage = AIUIMessage & {
|
|
8
|
+
key: string;
|
|
7
9
|
order: number;
|
|
8
10
|
stepOrder: number;
|
|
11
|
+
status: "streaming" | MessageStatus;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
|
-
export function toUIMessages(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
export function toUIMessages(
|
|
15
|
+
messages: (MessageDoc & { streaming?: boolean })[]
|
|
16
|
+
): UIMessage[] {
|
|
17
|
+
const uiMessages: UIMessage[] = [];
|
|
18
|
+
let assistantMessage: UIMessage | undefined;
|
|
14
19
|
for (const message of messages) {
|
|
15
20
|
const coreMessage = message.message && deserializeMessage(message.message);
|
|
16
21
|
const text = message.text ?? "";
|
|
@@ -18,15 +23,20 @@ export function toUIMessages(messages: MessageDoc[]): UIMessageOrdered[] {
|
|
|
18
23
|
const nonStringContent =
|
|
19
24
|
content && typeof content !== "string" ? content : [];
|
|
20
25
|
if (!coreMessage) continue;
|
|
26
|
+
const common = {
|
|
27
|
+
id: message.id ?? message._id,
|
|
28
|
+
createdAt: new Date(message._creationTime),
|
|
29
|
+
order: message.order,
|
|
30
|
+
stepOrder: message.stepOrder,
|
|
31
|
+
status: message.streaming ? ("streaming" as const) : message.status,
|
|
32
|
+
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
|
|
33
|
+
};
|
|
21
34
|
if (coreMessage.role === "system") {
|
|
22
35
|
uiMessages.push({
|
|
23
|
-
|
|
24
|
-
createdAt: new Date(message._creationTime),
|
|
36
|
+
...common,
|
|
25
37
|
role: "system",
|
|
26
38
|
content: text,
|
|
27
39
|
parts: [{ type: "text", text }],
|
|
28
|
-
order: message.order,
|
|
29
|
-
stepOrder: message.stepOrder,
|
|
30
40
|
});
|
|
31
41
|
} else if (coreMessage.role === "user") {
|
|
32
42
|
const parts: UIMessage["parts"] = [];
|
|
@@ -37,13 +47,10 @@ export function toUIMessages(messages: MessageDoc[]): UIMessageOrdered[] {
|
|
|
37
47
|
parts.push(...message.files.map(toUIFilePart));
|
|
38
48
|
}
|
|
39
49
|
uiMessages.push({
|
|
40
|
-
|
|
41
|
-
createdAt: new Date(message._creationTime),
|
|
50
|
+
...common,
|
|
42
51
|
role: "user",
|
|
43
52
|
content: message.text ?? "",
|
|
44
53
|
parts,
|
|
45
|
-
order: message.order,
|
|
46
|
-
stepOrder: message.stepOrder,
|
|
47
54
|
});
|
|
48
55
|
} else {
|
|
49
56
|
if (coreMessage.role === "tool" && !assistantMessage) {
|
|
@@ -55,15 +62,16 @@ export function toUIMessages(messages: MessageDoc[]): UIMessageOrdered[] {
|
|
|
55
62
|
}
|
|
56
63
|
if (!assistantMessage) {
|
|
57
64
|
assistantMessage = {
|
|
58
|
-
|
|
59
|
-
createdAt: new Date(message._creationTime),
|
|
65
|
+
...common,
|
|
60
66
|
role: "assistant",
|
|
61
67
|
content: message.text ?? "",
|
|
62
68
|
parts: [],
|
|
63
|
-
order: message.order,
|
|
64
|
-
stepOrder: message.stepOrder,
|
|
65
69
|
};
|
|
66
70
|
uiMessages.push(assistantMessage);
|
|
71
|
+
} else {
|
|
72
|
+
assistantMessage.status = message.streaming
|
|
73
|
+
? "streaming"
|
|
74
|
+
: message.status;
|
|
67
75
|
}
|
|
68
76
|
// update it to the last message's id
|
|
69
77
|
assistantMessage.id = message.id ?? message._id;
|