@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.
Files changed (40) hide show
  1. package/dist/commonjs/client/streaming.d.ts.map +1 -1
  2. package/dist/commonjs/client/streaming.js +0 -1
  3. package/dist/commonjs/client/streaming.js.map +1 -1
  4. package/dist/commonjs/component/schema.d.ts +4 -4
  5. package/dist/commonjs/component/schema.d.ts.map +1 -1
  6. package/dist/commonjs/component/schema.js +6 -1
  7. package/dist/commonjs/component/schema.js.map +1 -1
  8. package/dist/commonjs/component/streams.js +1 -1
  9. package/dist/commonjs/component/streams.js.map +1 -1
  10. package/dist/commonjs/react/index.d.ts +26 -2
  11. package/dist/commonjs/react/index.d.ts.map +1 -1
  12. package/dist/commonjs/react/index.js +77 -2
  13. package/dist/commonjs/react/index.js.map +1 -1
  14. package/dist/commonjs/react/toUIMessages.d.ts +8 -3
  15. package/dist/commonjs/react/toUIMessages.d.ts.map +1 -1
  16. package/dist/commonjs/react/toUIMessages.js +16 -12
  17. package/dist/commonjs/react/toUIMessages.js.map +1 -1
  18. package/dist/esm/client/streaming.d.ts.map +1 -1
  19. package/dist/esm/client/streaming.js +0 -1
  20. package/dist/esm/client/streaming.js.map +1 -1
  21. package/dist/esm/component/schema.d.ts +4 -4
  22. package/dist/esm/component/schema.d.ts.map +1 -1
  23. package/dist/esm/component/schema.js +6 -1
  24. package/dist/esm/component/schema.js.map +1 -1
  25. package/dist/esm/component/streams.js +1 -1
  26. package/dist/esm/component/streams.js.map +1 -1
  27. package/dist/esm/react/index.d.ts +26 -2
  28. package/dist/esm/react/index.d.ts.map +1 -1
  29. package/dist/esm/react/index.js +77 -2
  30. package/dist/esm/react/index.js.map +1 -1
  31. package/dist/esm/react/toUIMessages.d.ts +8 -3
  32. package/dist/esm/react/toUIMessages.d.ts.map +1 -1
  33. package/dist/esm/react/toUIMessages.js +16 -12
  34. package/dist/esm/react/toUIMessages.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/client/streaming.ts +0 -1
  37. package/src/component/schema.ts +6 -1
  38. package/src/component/streams.ts +2 -2
  39. package/src/react/index.ts +102 -3
  40. package/src/react/toUIMessages.ts +25 -17
@@ -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 { UIMessageOrdered } from "./toUIMessages";
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 UIMessageOrdered };
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 UIMessageOrdered = UIMessage & {
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(messages: MessageDoc[]): UIMessageOrdered[] {
12
- const uiMessages: UIMessageOrdered[] = [];
13
- let assistantMessage: UIMessageOrdered | undefined;
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
- id: message.id ?? message._id,
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
- id: message.id ?? message._id,
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
- id: message.id ?? message._id,
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;