@assistant-ui/react 0.12.19 → 0.12.21

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/client/ExternalThread.d.ts +24 -3
  3. package/dist/client/ExternalThread.d.ts.map +1 -1
  4. package/dist/client/ExternalThread.js +106 -27
  5. package/dist/client/ExternalThread.js.map +1 -1
  6. package/dist/client/InMemoryThreadList.js +23 -30
  7. package/dist/client/InMemoryThreadList.js.map +1 -1
  8. package/dist/client/SingleThreadList.d.ts +12 -0
  9. package/dist/client/SingleThreadList.d.ts.map +1 -0
  10. package/dist/client/SingleThreadList.js +68 -0
  11. package/dist/client/SingleThreadList.js.map +1 -0
  12. package/dist/index.d.ts +8 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +6 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  17. package/dist/primitives/composer/ComposerInput.js +37 -7
  18. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  19. package/dist/primitives/composer/ComposerQueue.d.ts +2 -0
  20. package/dist/primitives/composer/ComposerQueue.d.ts.map +1 -0
  21. package/dist/primitives/composer/ComposerQueue.js +3 -0
  22. package/dist/primitives/composer/ComposerQueue.js.map +1 -0
  23. package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
  24. package/dist/primitives/composer/ComposerSend.js +3 -1
  25. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  26. package/dist/primitives/composer/mention/ComposerMentionBack.d.ts +21 -0
  27. package/dist/primitives/composer/mention/ComposerMentionBack.d.ts.map +1 -0
  28. package/dist/primitives/composer/mention/ComposerMentionBack.js +28 -0
  29. package/dist/primitives/composer/mention/ComposerMentionBack.js.map +1 -0
  30. package/dist/primitives/composer/mention/ComposerMentionCategories.d.ts +42 -0
  31. package/dist/primitives/composer/mention/ComposerMentionCategories.d.ts.map +1 -0
  32. package/dist/primitives/composer/mention/ComposerMentionCategories.js +32 -0
  33. package/dist/primitives/composer/mention/ComposerMentionCategories.js.map +1 -0
  34. package/dist/primitives/composer/mention/ComposerMentionContext.d.ts +23 -0
  35. package/dist/primitives/composer/mention/ComposerMentionContext.d.ts.map +1 -0
  36. package/dist/primitives/composer/mention/ComposerMentionContext.js +66 -0
  37. package/dist/primitives/composer/mention/ComposerMentionContext.js.map +1 -0
  38. package/dist/primitives/composer/mention/ComposerMentionItems.d.ts +46 -0
  39. package/dist/primitives/composer/mention/ComposerMentionItems.d.ts.map +1 -0
  40. package/dist/primitives/composer/mention/ComposerMentionItems.js +30 -0
  41. package/dist/primitives/composer/mention/ComposerMentionItems.js.map +1 -0
  42. package/dist/primitives/composer/mention/ComposerMentionPopover.d.ts +24 -0
  43. package/dist/primitives/composer/mention/ComposerMentionPopover.d.ts.map +1 -0
  44. package/dist/primitives/composer/mention/ComposerMentionPopover.js +28 -0
  45. package/dist/primitives/composer/mention/ComposerMentionPopover.js.map +1 -0
  46. package/dist/primitives/composer/mention/MentionResource.d.ts +39 -0
  47. package/dist/primitives/composer/mention/MentionResource.d.ts.map +1 -0
  48. package/dist/primitives/composer/mention/MentionResource.js +230 -0
  49. package/dist/primitives/composer/mention/MentionResource.js.map +1 -0
  50. package/dist/primitives/composer/mention/detectMentionTrigger.d.ts +2 -0
  51. package/dist/primitives/composer/mention/detectMentionTrigger.d.ts.map +1 -0
  52. package/dist/primitives/composer/mention/detectMentionTrigger.js +26 -0
  53. package/dist/primitives/composer/mention/detectMentionTrigger.js.map +1 -0
  54. package/dist/primitives/composer/mention/index.d.ts +6 -0
  55. package/dist/primitives/composer/mention/index.d.ts.map +1 -0
  56. package/dist/primitives/composer/mention/index.js +6 -0
  57. package/dist/primitives/composer/mention/index.js.map +1 -0
  58. package/dist/primitives/composer.d.ts +10 -0
  59. package/dist/primitives/composer.d.ts.map +1 -1
  60. package/dist/primitives/composer.js +10 -0
  61. package/dist/primitives/composer.js.map +1 -1
  62. package/dist/primitives/queueItem/QueueItemRemove.d.ts +19 -0
  63. package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -0
  64. package/dist/primitives/queueItem/QueueItemRemove.js +21 -0
  65. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -0
  66. package/dist/primitives/queueItem/QueueItemSteer.d.ts +19 -0
  67. package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -0
  68. package/dist/primitives/queueItem/QueueItemSteer.js +21 -0
  69. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -0
  70. package/dist/primitives/queueItem/QueueItemText.d.ts +18 -0
  71. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -0
  72. package/dist/primitives/queueItem/QueueItemText.js +19 -0
  73. package/dist/primitives/queueItem/QueueItemText.js.map +1 -0
  74. package/dist/primitives/queueItem.d.ts +4 -0
  75. package/dist/primitives/queueItem.d.ts.map +1 -0
  76. package/dist/primitives/queueItem.js +4 -0
  77. package/dist/primitives/queueItem.js.map +1 -0
  78. package/dist/unstable/useToolMentionAdapter.d.ts +42 -0
  79. package/dist/unstable/useToolMentionAdapter.d.ts.map +1 -0
  80. package/dist/unstable/useToolMentionAdapter.js +65 -0
  81. package/dist/unstable/useToolMentionAdapter.js.map +1 -0
  82. package/package.json +10 -10
  83. package/src/client/ExternalThread.ts +160 -32
  84. package/src/client/InMemoryThreadList.ts +24 -35
  85. package/src/client/SingleThreadList.ts +95 -0
  86. package/src/index.ts +26 -0
  87. package/src/primitives/composer/ComposerInput.tsx +49 -5
  88. package/src/primitives/composer/ComposerQueue.tsx +3 -0
  89. package/src/primitives/composer/ComposerSend.ts +3 -1
  90. package/src/primitives/composer/mention/ComposerMentionBack.tsx +55 -0
  91. package/src/primitives/composer/mention/ComposerMentionCategories.tsx +104 -0
  92. package/src/primitives/composer/mention/ComposerMentionContext.tsx +141 -0
  93. package/src/primitives/composer/mention/ComposerMentionItems.tsx +104 -0
  94. package/src/primitives/composer/mention/ComposerMentionPopover.tsx +52 -0
  95. package/src/primitives/composer/mention/MentionResource.ts +328 -0
  96. package/src/primitives/composer/mention/detectMentionTrigger.test.ts +78 -0
  97. package/src/primitives/composer/mention/detectMentionTrigger.ts +37 -0
  98. package/src/primitives/composer/mention/index.ts +16 -0
  99. package/src/primitives/composer.ts +10 -0
  100. package/src/primitives/queueItem/QueueItemRemove.ts +37 -0
  101. package/src/primitives/queueItem/QueueItemSteer.ts +37 -0
  102. package/src/primitives/queueItem/QueueItemText.tsx +37 -0
  103. package/src/primitives/queueItem.ts +3 -0
  104. package/src/tests/BaseComposerRuntimeCore.test.ts +3 -1
  105. package/src/unstable/useToolMentionAdapter.ts +114 -0
@@ -0,0 +1,4 @@
1
+ export { QueueItemPrimitiveText as Text } from "./queueItem/QueueItemText.js";
2
+ export { QueueItemPrimitiveSteer as Steer } from "./queueItem/QueueItemSteer.js";
3
+ export { QueueItemPrimitiveRemove as Remove } from "./queueItem/QueueItemRemove.js";
4
+ //# sourceMappingURL=queueItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queueItem.js","sourceRoot":"","sources":["../../src/primitives/queueItem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,IAAI,IAAI,EAAE,qCAAkC;AAC3E,OAAO,EAAE,uBAAuB,IAAI,KAAK,EAAE,sCAAmC;AAC9E,OAAO,EAAE,wBAAwB,IAAI,MAAM,EAAE,uCAAoC"}
@@ -0,0 +1,42 @@
1
+ import type { Unstable_MentionAdapter, Unstable_MentionItem } from "@assistant-ui/core";
2
+ export type Unstable_ToolMentionAdapterOptions = {
3
+ /**
4
+ * Explicit list of tools to show in the mention picker.
5
+ * If provided, model context tools are NOT included unless
6
+ * `includeModelContextTools` is true.
7
+ */
8
+ tools?: readonly Unstable_MentionItem[] | undefined;
9
+ /**
10
+ * Include tools from the model context (registered via useAssistantTool).
11
+ * Defaults to true when `tools` is not provided, false otherwise.
12
+ */
13
+ includeModelContextTools?: boolean | undefined;
14
+ /**
15
+ * Custom function to format the display label for a tool.
16
+ * Receives the tool name (id) and returns the display label.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * formatLabel: (name) => name.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase())
21
+ * // "get_current_weather" → "Get Current Weather"
22
+ * ```
23
+ */
24
+ formatLabel?: ((toolName: string) => string) | undefined;
25
+ /** Custom label for the tools category. @default "Tools" */
26
+ categoryLabel?: string | undefined;
27
+ };
28
+ /**
29
+ * @deprecated This API is still under active development and might change without notice.
30
+ *
31
+ * Creates a MentionAdapter for tools. When a user types `@`, they see
32
+ * available tools and can mention them to hint the LLM to use a specific tool.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * const mentionAdapter = unstable_useToolMentionAdapter({
37
+ * formatLabel: (name) => name.replaceAll("_", " "),
38
+ * });
39
+ * ```
40
+ */
41
+ export declare function unstable_useToolMentionAdapter(options?: Unstable_ToolMentionAdapterOptions): Unstable_MentionAdapter;
42
+ //# sourceMappingURL=useToolMentionAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToolMentionAdapter.d.ts","sourceRoot":"","sources":["../../src/unstable/useToolMentionAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EAEvB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,kCAAkC,GAAG;IAC/C;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,oBAAoB,EAAE,GAAG,SAAS,CAAC;IAEpD;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE/C;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;IAEzD,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,CAAC,EAAE,kCAAkC,GAC3C,uBAAuB,CA0DzB"}
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ import { useAui } from "@assistant-ui/store";
4
+ /**
5
+ * @deprecated This API is still under active development and might change without notice.
6
+ *
7
+ * Creates a MentionAdapter for tools. When a user types `@`, they see
8
+ * available tools and can mention them to hint the LLM to use a specific tool.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const mentionAdapter = unstable_useToolMentionAdapter({
13
+ * formatLabel: (name) => name.replaceAll("_", " "),
14
+ * });
15
+ * ```
16
+ */
17
+ export function unstable_useToolMentionAdapter(options) {
18
+ const aui = useAui();
19
+ const explicitTools = options?.tools;
20
+ const includeModelContext = options?.includeModelContextTools ?? !explicitTools;
21
+ const formatLabel = options?.formatLabel;
22
+ const categoryLabel = options?.categoryLabel;
23
+ return useMemo(() => {
24
+ const getTools = () => {
25
+ const items = [];
26
+ if (explicitTools) {
27
+ items.push(...explicitTools);
28
+ }
29
+ if (includeModelContext) {
30
+ const context = aui.thread().getModelContext();
31
+ const tools = context.tools;
32
+ if (tools) {
33
+ for (const [name, tool] of Object.entries(tools)) {
34
+ if (!items.some((i) => i.id === name)) {
35
+ items.push({
36
+ id: name,
37
+ type: "tool",
38
+ label: formatLabel ? formatLabel(name) : name,
39
+ description: tool.description ?? undefined,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ }
45
+ return items;
46
+ };
47
+ return {
48
+ categories() {
49
+ return [
50
+ { id: "tools", label: categoryLabel ?? "Tools", icon: undefined },
51
+ ];
52
+ },
53
+ categoryItems(_categoryId) {
54
+ return getTools();
55
+ },
56
+ search(query) {
57
+ const lower = query.toLowerCase();
58
+ return getTools().filter((item) => item.id.toLowerCase().includes(lower) ||
59
+ item.label.toLowerCase().includes(lower) ||
60
+ item.description?.toLowerCase().includes(lower));
61
+ },
62
+ };
63
+ }, [aui, explicitTools, includeModelContext, formatLabel, categoryLabel]);
64
+ }
65
+ //# sourceMappingURL=useToolMentionAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useToolMentionAdapter.js","sourceRoot":"","sources":["../../src/unstable/useToolMentionAdapter.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqC7C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAA4C;IAE5C,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,aAAa,GAAG,OAAO,EAAE,KAAK,CAAC;IACrC,MAAM,mBAAmB,GACvB,OAAO,EAAE,wBAAwB,IAAI,CAAC,aAAa,CAAC;IACtD,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC;IACzC,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,CAAC;IAE7C,OAAO,OAAO,CAA0B,GAAG,EAAE;QAC3C,MAAM,QAAQ,GAAG,GAA2B,EAAE;YAC5C,MAAM,KAAK,GAA2B,EAAE,CAAC;YAEzC,IAAI,aAAa,EAAE,CAAC;gBAClB,KAAK,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC;YAC/B,CAAC;YAED,IAAI,mBAAmB,EAAE,CAAC;gBACxB,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;gBAC5B,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;wBACjD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;4BACtC,KAAK,CAAC,IAAI,CAAC;gCACT,EAAE,EAAE,IAAI;gCACR,IAAI,EAAE,MAAM;gCACZ,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;gCAC7C,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS;6BAC3C,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC,CAAC;QAEF,OAAO;YACL,UAAU;gBACR,OAAO;oBACL,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;iBAClE,CAAC;YACJ,CAAC;YAED,aAAa,CAAC,WAAmB;gBAC/B,OAAO,QAAQ,EAAE,CAAC;YACpB,CAAC;YAED,MAAM,CAAC,KAAa;gBAClB,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;gBAClC,OAAO,QAAQ,EAAE,CAAC,MAAM,CACtB,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACrC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACxC,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClD,CAAC;YACJ,CAAC;SACF,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;AAC5E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.12.19",
3
+ "version": "0.12.21",
4
4
  "description": "TypeScript/React library for AI Chat",
5
5
  "keywords": [
6
6
  "radix-ui",
@@ -49,22 +49,22 @@
49
49
  ],
50
50
  "sideEffects": false,
51
51
  "dependencies": {
52
- "@assistant-ui/core": "^0.1.7",
53
- "@assistant-ui/store": "^0.2.3",
54
- "@assistant-ui/tap": "^0.5.3",
52
+ "@assistant-ui/core": "^0.1.9",
53
+ "@assistant-ui/store": "^0.2.5",
54
+ "@assistant-ui/tap": "^0.5.5",
55
55
  "@radix-ui/primitive": "^1.1.3",
56
56
  "@radix-ui/react-compose-refs": "^1.1.2",
57
57
  "@radix-ui/react-context": "^1.1.3",
58
58
  "@radix-ui/react-primitive": "^2.1.4",
59
59
  "@radix-ui/react-use-callback-ref": "^1.1.1",
60
60
  "@radix-ui/react-use-escape-keydown": "^1.1.1",
61
- "assistant-cloud": "^0.1.22",
62
- "assistant-stream": "^0.3.6",
63
- "nanoid": "^5.1.6",
61
+ "assistant-cloud": "^0.1.23",
62
+ "assistant-stream": "^0.3.8",
63
+ "nanoid": "^5.1.7",
64
64
  "radix-ui": "^1.4.3",
65
65
  "react-textarea-autosize": "^8.5.9",
66
66
  "zod": "^4.3.6",
67
- "zustand": "^5.0.11"
67
+ "zustand": "^5.0.12"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@types/react": "*",
@@ -86,10 +86,10 @@
86
86
  "@types/node": "^25.5.0",
87
87
  "@types/react": "^19.2.14",
88
88
  "@types/react-dom": "^19.2.3",
89
- "jsdom": "^28.1.0",
89
+ "jsdom": "^29.0.1",
90
90
  "react": "^19.2.4",
91
91
  "react-dom": "^19.2.4",
92
- "vitest": "^4.1.0",
92
+ "vitest": "^4.1.1",
93
93
  "@assistant-ui/x-buildutils": "0.0.3"
94
94
  },
95
95
  "publishConfig": {
@@ -6,6 +6,7 @@ import {
6
6
  tapEffectEvent,
7
7
  } from "@assistant-ui/tap";
8
8
  import {
9
+ type ClientElement,
9
10
  type ClientOutput,
10
11
  tapClientLookup,
11
12
  attachTransformScopes,
@@ -14,34 +15,60 @@ import {
14
15
  } from "@assistant-ui/store";
15
16
  import { withKey } from "@assistant-ui/tap";
16
17
  import type {
18
+ AppendMessage,
17
19
  Attachment,
18
20
  CreateAttachment,
19
21
  ThreadAssistantMessagePart,
20
22
  ThreadUserMessagePart,
21
23
  ThreadMessage,
22
24
  } from "@assistant-ui/core";
25
+ import type { QueueItemState } from "@assistant-ui/core/store";
26
+ import type { ComposerSendOptions } from "@assistant-ui/core/store";
23
27
  import { ModelContext, Suggestions } from "@assistant-ui/core/store";
24
28
  import { Tools, DataRenderers } from "@assistant-ui/core/react";
29
+ import { SingleThreadList } from "./SingleThreadList";
30
+
31
+ const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = [];
25
32
 
26
33
  export type ExternalThreadMessage = ThreadMessage & {
27
34
  id: string;
28
35
  };
29
36
 
37
+ export type ExternalThreadQueueAdapter = {
38
+ /** The current queue items. */
39
+ items: readonly QueueItemState[];
40
+ /** Called when a message is submitted via the composer. Receives the steer preference. */
41
+ enqueue: (message: AppendMessage, opts: { steer: boolean }) => void;
42
+ /** Called to promote an existing queue item (cancel current run, run this immediately). */
43
+ steer: (queueItemId: string) => void;
44
+ /** Called to remove an item from the queue. */
45
+ remove: (queueItemId: string) => void;
46
+ /** Called to clear all pending queue items, with the reason for clearing. */
47
+ clear: (reason: "edit" | "reload" | "cancel-run") => void;
48
+ };
49
+
30
50
  export type ExternalThreadProps = {
31
51
  messages: readonly ExternalThreadMessage[];
32
52
  isRunning?: boolean;
33
- onNew?: (message: any) => void;
34
- onEdit?: (message: any) => void;
53
+ /**
54
+ * Callback for new messages (non-queue runtimes).
55
+ * @note Unused when `queue` is provided — new messages are routed through `queue.enqueue` instead.
56
+ */
57
+ onNew?: (message: AppendMessage) => void;
58
+ onEdit?: (message: AppendMessage) => void;
35
59
  onReload?: (parentId: string | null) => void;
36
60
  onStartRun?: () => void;
37
61
  onCancel?: () => void;
62
+ /** Queue adapter for runtimes that support message queuing and steering. */
63
+ queue?: ExternalThreadQueueAdapter;
38
64
  };
39
65
 
40
66
  type MessageClientProps = {
41
67
  message: ExternalThreadMessage;
42
68
  index: number;
43
- onEdit?: (message: any) => void;
69
+ onEdit?: (message: AppendMessage) => void;
44
70
  onReload?: () => void;
71
+ queue?: ExternalThreadQueueAdapter | undefined;
45
72
  };
46
73
 
47
74
  // Message Client - minimal implementation
@@ -51,6 +78,7 @@ const MessageClient = resource(
51
78
  index,
52
79
  onEdit,
53
80
  onReload,
81
+ queue,
54
82
  }: MessageClientProps): ClientOutput<"message"> => {
55
83
  const [isCopied, setIsCopied] = tapState(false);
56
84
  const [isHovering, setIsHovering] = tapState(false);
@@ -86,7 +114,8 @@ const MessageClient = resource(
86
114
  setIsEditing(false);
87
115
  };
88
116
 
89
- const handleSendEdit = (msg: any) => {
117
+ const handleSendEdit = (msg: AppendMessage) => {
118
+ queue?.clear("edit");
90
119
  onEdit?.({
91
120
  ...msg,
92
121
  parentId: message.id,
@@ -104,6 +133,7 @@ const MessageClient = resource(
104
133
  onBeginEdit: handleBeginEdit,
105
134
  onSend: handleSendEdit,
106
135
  message,
136
+ queue,
107
137
  }),
108
138
  );
109
139
 
@@ -214,10 +244,29 @@ type ComposerClientResourceProps = {
214
244
  canCancel: boolean;
215
245
  onCancel: () => void;
216
246
  onBeginEdit?: () => void;
217
- onSend?: (message: any) => void;
247
+ onSend?: (message: AppendMessage) => void;
218
248
  message?: ExternalThreadMessage;
249
+ queue?: ExternalThreadQueueAdapter | undefined;
219
250
  };
220
251
 
252
+ const QueueItemClient = resource(
253
+ ({
254
+ item,
255
+ onSteer,
256
+ onRemove,
257
+ }: {
258
+ item: QueueItemState;
259
+ onSteer: () => void;
260
+ onRemove: () => void;
261
+ }): ClientOutput<"queueItem"> => {
262
+ return {
263
+ getState: () => item,
264
+ steer: onSteer,
265
+ remove: onRemove,
266
+ };
267
+ },
268
+ );
269
+
221
270
  // Composer Client - minimal implementation
222
271
  const ComposerClientResource = resource(
223
272
  ({
@@ -228,6 +277,7 @@ const ComposerClientResource = resource(
228
277
  onBeginEdit,
229
278
  onSend,
230
279
  message,
280
+ queue,
231
281
  }: ComposerClientResourceProps): ClientOutput<"composer"> => {
232
282
  const [text, setText] = tapState("");
233
283
  const [role, setRole] = tapState<"user" | "assistant" | "system">("user");
@@ -276,6 +326,22 @@ const ComposerClientResource = resource(
276
326
  [attachments],
277
327
  );
278
328
 
329
+ const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
330
+ const queueItemClients = tapClientLookup(
331
+ () =>
332
+ queueItems.map((item) =>
333
+ withKey(
334
+ item.id,
335
+ QueueItemClient({
336
+ item,
337
+ onSteer: () => queue?.steer(item.id),
338
+ onRemove: () => queue?.remove(item.id),
339
+ }),
340
+ ),
341
+ ),
342
+ [queueItems],
343
+ );
344
+
279
345
  const state = tapMemo(
280
346
  () => ({
281
347
  text,
@@ -289,6 +355,7 @@ const ComposerClientResource = resource(
289
355
  type,
290
356
  dictation: undefined,
291
357
  quote,
358
+ queue: queueItems,
292
359
  }),
293
360
  [
294
361
  text,
@@ -300,6 +367,7 @@ const ComposerClientResource = resource(
300
367
  type,
301
368
  attachments.length,
302
369
  quote,
370
+ queueItems,
303
371
  ],
304
372
  );
305
373
 
@@ -348,19 +416,25 @@ const ComposerClientResource = resource(
348
416
  setAttachments([]);
349
417
  setQuote(undefined);
350
418
  },
351
- send: () => {
419
+ send: (opts?: ComposerSendOptions) => {
352
420
  const currentQuote = quote;
353
- const message = {
421
+ const composedMessage: AppendMessage = {
354
422
  role,
355
423
  content: text ? [{ type: "text" as const, text }] : [],
356
424
  attachments: attachments as any,
357
425
  createdAt: new Date(),
426
+ parentId: null,
427
+ sourceId: null,
358
428
  runConfig,
359
429
  metadata: {
360
430
  custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
361
431
  },
362
432
  };
363
- onSend?.(message);
433
+ if (queue) {
434
+ queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
435
+ } else {
436
+ onSend?.(composedMessage);
437
+ }
364
438
  setText("");
365
439
  setAttachments([]);
366
440
  setQuote(undefined);
@@ -372,6 +446,9 @@ const ComposerClientResource = resource(
372
446
  startDictation: () => {},
373
447
  stopDictation: () => {},
374
448
  setQuote,
449
+ queueItem: (selector: { index: number }) => {
450
+ return queueItemClients.get(selector);
451
+ },
375
452
  };
376
453
  },
377
454
  );
@@ -386,12 +463,14 @@ export const ExternalThread = resource(
386
463
  onReload,
387
464
  onStartRun,
388
465
  onCancel,
466
+ queue,
389
467
  }: ExternalThreadProps): ClientOutput<"thread"> => {
390
468
  const handleReload = (messageId: string) => {
391
469
  const messageIndex = messages.findIndex((m) => m.id === messageId);
392
470
  if (messageIndex === -1) return;
393
471
 
394
472
  const parentId = messageIndex > 0 ? messages[messageIndex - 1]!.id : null;
473
+ queue?.clear("reload");
395
474
  onReload?.(parentId);
396
475
  };
397
476
 
@@ -402,18 +481,20 @@ export const ExternalThread = resource(
402
481
  message: msg,
403
482
  index,
404
483
  onReload: () => handleReload(msg.id),
484
+ queue,
405
485
  };
406
486
  if (onEdit) props.onEdit = onEdit;
407
487
  return withKey(msg.id, MessageClient(props));
408
488
  }),
409
- [messages, onEdit],
489
+ [messages, onEdit, queue],
410
490
  );
411
491
 
412
492
  const handleCancelRun = () => {
493
+ queue?.clear("cancel-run");
413
494
  onCancel?.();
414
495
  };
415
496
 
416
- const handleSendNew = (message: any) => {
497
+ const handleSendNew = (message: AppendMessage) => {
417
498
  onNew?.(message);
418
499
  };
419
500
 
@@ -424,9 +505,11 @@ export const ExternalThread = resource(
424
505
  canCancel: isRunning,
425
506
  onCancel: handleCancelRun,
426
507
  onSend: handleSendNew,
508
+ queue,
427
509
  }),
428
510
  );
429
511
 
512
+ const hasQueue = !!queue;
430
513
  const state = tapMemo(() => {
431
514
  const messageStates = messageClients.state.map((s, idx, arr) => ({
432
515
  ...s,
@@ -449,6 +532,7 @@ export const ExternalThread = resource(
449
532
  switchBranchDuringRun: false,
450
533
  unstable_copy: false,
451
534
  dictation: false,
535
+ queue: hasQueue,
452
536
  },
453
537
  messages: messageStates,
454
538
  state: {},
@@ -457,13 +541,46 @@ export const ExternalThread = resource(
457
541
  speech: undefined,
458
542
  composer: composerClient.state,
459
543
  };
460
- }, [messages, isRunning, messageClients.state, composerClient.state]);
544
+ }, [
545
+ messages,
546
+ isRunning,
547
+ hasQueue,
548
+ messageClients.state,
549
+ composerClient.state,
550
+ ]);
461
551
 
462
552
  return {
463
553
  getState: () => state,
464
554
  composer: () => composerClient.methods,
465
555
  append: (message) => {
466
- onNew?.(message);
556
+ const appendMessage: AppendMessage =
557
+ typeof message === "string"
558
+ ? {
559
+ createdAt: new Date(),
560
+ parentId: messages.at(-1)?.id ?? null,
561
+ sourceId: null,
562
+ runConfig: {},
563
+ role: "user",
564
+ content: [{ type: "text", text: message }],
565
+ attachments: [],
566
+ metadata: { custom: {} },
567
+ }
568
+ : {
569
+ createdAt: message.createdAt ?? new Date(),
570
+ parentId: message.parentId ?? messages.at(-1)?.id ?? null,
571
+ sourceId: message.sourceId ?? null,
572
+ role: message.role ?? "user",
573
+ content: message.content,
574
+ attachments: message.attachments ?? [],
575
+ metadata: message.metadata ?? { custom: {} },
576
+ runConfig: message.runConfig ?? {},
577
+ startRun: message.startRun,
578
+ };
579
+ if (queue) {
580
+ queue.enqueue(appendMessage, { steer: false });
581
+ } else {
582
+ onNew?.(appendMessage);
583
+ }
467
584
  },
468
585
  startRun: () => {
469
586
  onStartRun?.();
@@ -489,29 +606,40 @@ export const ExternalThread = resource(
489
606
  );
490
607
 
491
608
  attachTransformScopes(ExternalThread, (scopes, parent) => {
492
- const result = {
493
- ...scopes,
494
- composer:
495
- scopes.composer ??
496
- Derived({
497
- source: "thread",
498
- query: {},
499
- get: (aui) => aui.thread().composer(),
500
- }),
501
- };
609
+ if (!scopes.threads && parent.threads.source === null) {
610
+ const threadElement = scopes.thread as ClientElement<"thread">;
611
+ scopes.threads = SingleThreadList({ thread: threadElement });
612
+ scopes.thread = Derived({
613
+ source: "threads",
614
+ query: { type: "main" },
615
+ get: (aui) => aui.threads().thread("main"),
616
+ });
617
+ }
618
+
619
+ if (!scopes.threadListItem && parent.threadListItem.source === null) {
620
+ scopes.threadListItem = Derived({
621
+ source: "threads",
622
+ query: { type: "main" },
623
+ get: (aui) => aui.threads().item("main"),
624
+ });
625
+ }
502
626
 
503
- if (!result.modelContext && parent.modelContext.source === null) {
504
- result.modelContext = ModelContext();
627
+ scopes.composer ??= Derived({
628
+ source: "thread",
629
+ query: {},
630
+ get: (aui) => aui.thread().composer(),
631
+ });
632
+
633
+ if (!scopes.modelContext && parent.modelContext.source === null) {
634
+ scopes.modelContext = ModelContext();
505
635
  }
506
- if (!result.tools && parent.tools.source === null) {
507
- result.tools = Tools({});
636
+ if (!scopes.tools && parent.tools.source === null) {
637
+ scopes.tools = Tools({});
508
638
  }
509
- if (!result.dataRenderers && parent.dataRenderers.source === null) {
510
- result.dataRenderers = DataRenderers();
639
+ if (!scopes.dataRenderers && parent.dataRenderers.source === null) {
640
+ scopes.dataRenderers = DataRenderers();
511
641
  }
512
- if (!result.suggestions && parent.suggestions.source === null) {
513
- result.suggestions = Suggestions();
642
+ if (!scopes.suggestions && parent.suggestions.source === null) {
643
+ scopes.suggestions = Suggestions();
514
644
  }
515
-
516
- return result;
517
645
  });
@@ -168,43 +168,32 @@ export const InMemoryThreadList = resource(
168
168
  );
169
169
 
170
170
  attachTransformScopes(InMemoryThreadList, (scopes, parent) => {
171
- const result = {
172
- ...scopes,
173
- thread:
174
- scopes.thread ??
175
- Derived({
176
- source: "threads",
177
- query: { type: "main" },
178
- get: (aui) => aui.threads().thread("main"),
179
- }),
180
- threadListItem:
181
- scopes.threadListItem ??
182
- Derived({
183
- source: "threads",
184
- query: { type: "main" },
185
- get: (aui) => aui.threads().item("main"),
186
- }),
187
- composer:
188
- scopes.composer ??
189
- Derived({
190
- source: "thread",
191
- query: {},
192
- get: (aui) => aui.threads().thread("main").composer(),
193
- }),
194
- };
195
-
196
- if (!result.modelContext && parent.modelContext.source === null) {
197
- result.modelContext = ModelContext();
171
+ scopes.thread ??= Derived({
172
+ source: "threads",
173
+ query: { type: "main" },
174
+ get: (aui) => aui.threads().thread("main"),
175
+ });
176
+ scopes.threadListItem ??= Derived({
177
+ source: "threads",
178
+ query: { type: "main" },
179
+ get: (aui) => aui.threads().item("main"),
180
+ });
181
+ scopes.composer ??= Derived({
182
+ source: "thread",
183
+ query: {},
184
+ get: (aui) => aui.threads().thread("main").composer(),
185
+ });
186
+
187
+ if (!scopes.modelContext && parent.modelContext.source === null) {
188
+ scopes.modelContext = ModelContext();
198
189
  }
199
- if (!result.tools && parent.tools.source === null) {
200
- result.tools = Tools({});
190
+ if (!scopes.tools && parent.tools.source === null) {
191
+ scopes.tools = Tools({});
201
192
  }
202
- if (!result.dataRenderers && parent.dataRenderers.source === null) {
203
- result.dataRenderers = DataRenderers();
193
+ if (!scopes.dataRenderers && parent.dataRenderers.source === null) {
194
+ scopes.dataRenderers = DataRenderers();
204
195
  }
205
- if (!result.suggestions && parent.suggestions.source === null) {
206
- result.suggestions = Suggestions();
196
+ if (!scopes.suggestions && parent.suggestions.source === null) {
197
+ scopes.suggestions = Suggestions();
207
198
  }
208
-
209
- return result;
210
199
  });