@assistant-ui/react 0.14.29 → 0.15.1

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 (142) hide show
  1. package/dist/client/ExternalThread.js +4 -3
  2. package/dist/client/ExternalThread.js.map +1 -1
  3. package/dist/client/InMemoryThreadList.js +3 -3
  4. package/dist/client/InMemoryThreadList.js.map +1 -1
  5. package/dist/context/index.d.ts +1 -8
  6. package/dist/context/index.js +1 -8
  7. package/dist/hooks/useToolCallElapsed.js +24 -29
  8. package/dist/hooks/useToolCallElapsed.js.map +1 -1
  9. package/dist/index.d.ts +1 -8
  10. package/dist/index.js +1 -8
  11. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +3 -3
  12. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  13. package/dist/mcp-apps/McpAppRenderer.js +1 -1
  14. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  15. package/dist/model-context/makeAssistantVisible.js +26 -25
  16. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  17. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  18. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  19. package/dist/primitives/attachment/AttachmentRemove.js +3 -3
  20. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  21. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +3 -3
  22. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  23. package/dist/primitives/composer/ComposerAddAttachment.js +3 -3
  24. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  25. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  26. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  27. package/dist/primitives/composer/ComposerInput.js +13 -13
  28. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  29. package/dist/primitives/composer/ComposerQuote.js +1 -1
  30. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  31. package/dist/primitives/composer/ComposerStopDictation.js +3 -3
  32. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  33. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  34. package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
  35. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  36. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  37. package/dist/primitives/message/MessagePartsGrouped.js +67 -89
  38. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  39. package/dist/primitives/message/MessageRoot.js +3 -3
  40. package/dist/primitives/message/MessageRoot.js.map +1 -1
  41. package/dist/primitives/queueItem/QueueItemRemove.js +3 -3
  42. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  43. package/dist/primitives/queueItem/QueueItemSteer.js +3 -3
  44. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  45. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  46. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  47. package/dist/primitives/suggestion/SuggestionTrigger.js +16 -15
  48. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  49. package/dist/unstable/useComposerInput.js +2 -2
  50. package/dist/unstable/useComposerInput.js.map +1 -1
  51. package/dist/unstable/useComposerInputHistory.js +6 -6
  52. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  53. package/dist/unstable/useMentionAdapter.js +1 -1
  54. package/dist/unstable/useMentionAdapter.js.map +1 -1
  55. package/dist/utils/smooth/SmoothContext.js +3 -3
  56. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  57. package/dist/utils/smooth/useSmooth.js +1 -1
  58. package/dist/utils/smooth/useSmooth.js.map +1 -1
  59. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  60. package/package.json +5 -5
  61. package/src/client/ExternalThread.ts +5 -3
  62. package/src/client/InMemoryThreadList.ts +3 -3
  63. package/src/context/index.ts +0 -44
  64. package/src/hooks/useToolCallElapsed.ts +9 -12
  65. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +5 -9
  66. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +1 -1
  67. package/src/mcp-apps/McpAppRenderer.tsx +1 -1
  68. package/src/model-context/makeAssistantVisible.test.tsx +1 -1
  69. package/src/model-context/makeAssistantVisible.tsx +1 -1
  70. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  71. package/src/primitives/attachment/AttachmentRemove.ts +1 -1
  72. package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
  73. package/src/primitives/composer/ComposerAddAttachment.test.tsx +2 -2
  74. package/src/primitives/composer/ComposerAddAttachment.ts +1 -1
  75. package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +2 -2
  76. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  77. package/src/primitives/composer/ComposerInput.test.tsx +7 -7
  78. package/src/primitives/composer/ComposerInput.tsx +13 -13
  79. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  80. package/src/primitives/composer/ComposerStopDictation.ts +1 -1
  81. package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
  82. package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
  83. package/src/primitives/message/MessageRoot.tsx +1 -1
  84. package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
  85. package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
  86. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  87. package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
  88. package/src/tests/DataRenderers.test.tsx +1 -1
  89. package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
  90. package/src/tests/external-thread-attachments.test.tsx +21 -23
  91. package/src/tests/external-thread-branches.test.tsx +10 -10
  92. package/src/tests/external-thread-parity.test.tsx +18 -20
  93. package/src/tests/local-runtime-queue.test.tsx +29 -34
  94. package/src/tests/messagePartSwitchRace.test.tsx +30 -13
  95. package/src/unstable/useComposerInput.test.tsx +2 -2
  96. package/src/unstable/useComposerInput.ts +2 -2
  97. package/src/unstable/useComposerInputHistory.test.tsx +3 -3
  98. package/src/unstable/useComposerInputHistory.ts +6 -6
  99. package/src/unstable/useMentionAdapter.ts +1 -1
  100. package/src/utils/smooth/SmoothContext.tsx +1 -1
  101. package/src/utils/smooth/useSmooth.test.tsx +1 -1
  102. package/src/utils/smooth/useSmooth.ts +1 -1
  103. package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
  104. package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
  105. package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
  106. package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
  107. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
  108. package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
  109. package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
  110. package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
  111. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
  112. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
  113. package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
  114. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
  115. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
  116. package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
  117. package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
  118. package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
  119. package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
  120. package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
  121. package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
  122. package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
  123. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
  124. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
  125. package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
  126. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
  127. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
  128. package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
  129. package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
  130. package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
  131. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
  132. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
  133. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
  134. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
  135. package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
  136. package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
  137. package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
  138. package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
  139. package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
  140. package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
  141. package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
  142. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
@@ -1 +1 @@
1
- {"version":3,"file":"SuggestionTrigger.js","names":["c","_c","ActionButtonElement","ActionButtonProps","createActionButton","useCallback","useAuiState","useAui","useSuggestionTrigger","t0","$","send","clearComposer","t1","undefined","aui","disabled","_temp","prompt","_temp2","resolvedSend","t2","isRunning","thread","getState","append","content","type","text","runConfig","composer","setText","currentText","trim","callback","SuggestionPrimitiveTrigger","Element","Props","s","isDisabled","s_0","suggestion"],"sources":["../../../src/primitives/suggestion/SuggestionTrigger.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type ActionButtonElement,\n type ActionButtonProps,\n createActionButton,\n} from \"../../utils/createActionButton\";\nimport { useCallback } from \"react\";\nimport { useAuiState, useAui } from \"@assistant-ui/store\";\n\nconst useSuggestionTrigger = ({\n send,\n clearComposer = true,\n}: {\n /**\n * When true, automatically sends the message.\n * When false, replaces or appends the composer text with the suggestion - depending on the value of `clearComposer`.\n */\n send?: boolean | undefined;\n\n /**\n * Whether to clear the composer after sending.\n * When send is set to false, determines if composer text is replaced with suggestion (true, default),\n * or if it's appended to the composer text (false).\n *\n * @default true\n */\n clearComposer?: boolean | undefined;\n}) => {\n const aui = useAui();\n const disabled = useAuiState((s) => s.thread.isDisabled);\n const prompt = useAuiState((s) => s.suggestion.prompt);\n\n const resolvedSend = send ?? false;\n\n const callback = useCallback(() => {\n const isRunning = aui.thread().getState().isRunning;\n\n if (resolvedSend && !isRunning) {\n aui.thread().append({\n content: [{ type: \"text\", text: prompt }],\n runConfig: aui.composer().getState().runConfig,\n });\n if (clearComposer) {\n aui.composer().setText(\"\");\n }\n } else {\n if (clearComposer) {\n aui.composer().setText(prompt);\n } else {\n const currentText = aui.composer().getState().text;\n aui\n .composer()\n .setText(currentText.trim() ? `${currentText} ${prompt}` : prompt);\n }\n }\n }, [aui, resolvedSend, clearComposer, prompt]);\n\n if (disabled) return null;\n return callback;\n};\n\nexport namespace SuggestionPrimitiveTrigger {\n export type Element = ActionButtonElement;\n export type Props = ActionButtonProps<typeof useSuggestionTrigger>;\n}\n\n/**\n * A button that triggers the suggestion action (send or insert into composer).\n *\n * @example\n * ```tsx\n * <SuggestionPrimitive.Trigger send>\n * Click me\n * </SuggestionPrimitive.Trigger>\n * ```\n */\nexport const SuggestionPrimitiveTrigger = createActionButton(\n \"SuggestionPrimitive.Trigger\",\n useSuggestionTrigger,\n [\"send\", \"clearComposer\"],\n);\n"],"mappings":";;;;;;AAUA,MAAMQ,wBAAuBC,OAAA;CAAA,MAAAC,IAAAT,EAAA,CAAA;CAAC,MAAA,EAAAU,MAAAC,eAAAC,OAAAJ;CAE5B,MAAAG,gBAAAC,OAAAC,KAAAA,IAAA,OAAAD;CAiBA,MAAAE,MAAYR,OAAO;CACnB,MAAAS,WAAiBV,YAAYW,KAA0B;CACvD,MAAAC,SAAeZ,YAAYa,MAA0B;CAErD,MAAAC,eAAqBT,QAAA;CAAc,IAAAU;CAAA,IAAAX,EAAA,OAAAK,OAAAL,EAAA,OAAAE,iBAAAF,EAAA,OAAAQ,UAAAR,EAAA,OAAAU,cAAA;EAENC,WAAA;GAC3B,MAAAC,YAAkBP,IAAGQ,OAAQ,CAAC,CAAAC,SAAU,CAAC,CAAAF;GAEzC,IAAIF,gBAAA,CAAiBE,WAAS;IAC5BP,IAAGQ,OAAQ,CAAC,CAAAE,OAAQ;KAAAC,SACT,CAAC;MAAAC,MAAQ;MAAMC,MAAQV;KAAO,CAAC;KAACW,WAC9Bd,IAAGe,SAAU,CAAC,CAAAN,SAAU,CAAC,CAAAK;IACtC,CAAC;IACD,IAAIjB,eACFG,IAAGe,SAAU,CAAC,CAAAC,QAAS,EAAE;GAC1B,OAED,IAAInB,eACFG,IAAGe,SAAU,CAAC,CAAAC,QAASb,MAAM;QAAC;IAE9B,MAAAc,cAAoBjB,IAAGe,SAAU,CAAC,CAAAN,SAAU,CAAC,CAAAI;IAC7Cb,IAAGe,SACS,CAAC,CAAAC,QACFC,YAAWC,KAA4C,IAAvD,GAAwBD,YAAW,GAAId,WAAvCA,MAAwD;GAAC;EAEvE;EACFR,EAAA,KAAAK;EAAAL,EAAA,KAAAE;EAAAF,EAAA,KAAAQ;EAAAR,EAAA,KAAAU;EAAAV,EAAA,KAAAW;CAAA,OAAAA,KAAAX,EAAA;CArBD,MAAAwB,WAAiBb;CAuBjB,IAAIL,UAAQ,OAAS;CAAK,OACnBkB;AAAQ;;;;;;;;;;;AAkBjB,MAAaC,6BAA6B/B,mBACxC,+BACAI,sBACA,CAAC,QAAQ,eAAe,CAC1B;AAvE6B,SAAAS,MAAAqB,GAAA;CAAA,OAoBSA,EAACf,OAAOgB;AAAW;AApB5B,SAAApB,OAAAqB,KAAA;CAAA,OAqBOF,IAACG,WAAWvB;AAAO"}
1
+ {"version":3,"file":"SuggestionTrigger.js","names":["c","_c","ActionButtonElement","ActionButtonProps","createActionButton","useCallback","useAuiState","useAui","useSuggestionTrigger","t0","$","send","clearComposer","t1","undefined","aui","disabled","_temp","prompt","_temp2","resolvedSend","t2","composer","thread","isRunning","getState","append","content","type","text","runConfig","setText","currentText","trim","callback","SuggestionPrimitiveTrigger","Element","Props","s","isDisabled","s_0","suggestion"],"sources":["../../../src/primitives/suggestion/SuggestionTrigger.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type ActionButtonElement,\n type ActionButtonProps,\n createActionButton,\n} from \"../../utils/createActionButton\";\nimport { useCallback } from \"react\";\nimport { useAuiState, useAui } from \"@assistant-ui/store\";\n\nconst useSuggestionTrigger = ({\n send,\n clearComposer = true,\n}: {\n /**\n * When true, automatically sends the message.\n * When false, replaces or appends the composer text with the suggestion - depending on the value of `clearComposer`.\n */\n send?: boolean | undefined;\n\n /**\n * Whether to clear the composer after sending.\n * When send is set to false, determines if composer text is replaced with suggestion (true, default),\n * or if it's appended to the composer text (false).\n *\n * @default true\n */\n clearComposer?: boolean | undefined;\n}) => {\n const aui = useAui();\n const disabled = useAuiState((s) => s.thread.isDisabled);\n const prompt = useAuiState((s) => s.suggestion.prompt);\n\n const resolvedSend = send ?? false;\n\n const callback = useCallback(() => {\n const isRunning = aui.thread.getState().isRunning;\n\n if (resolvedSend && !isRunning) {\n aui.thread.append({\n content: [{ type: \"text\", text: prompt }],\n runConfig: aui.composer.getState().runConfig,\n });\n if (clearComposer) {\n aui.composer.setText(\"\");\n }\n } else {\n if (clearComposer) {\n aui.composer.setText(prompt);\n } else {\n const currentText = aui.composer.getState().text;\n aui.composer.setText(\n currentText.trim() ? `${currentText} ${prompt}` : prompt,\n );\n }\n }\n }, [aui, resolvedSend, clearComposer, prompt]);\n\n if (disabled) return null;\n return callback;\n};\n\nexport namespace SuggestionPrimitiveTrigger {\n export type Element = ActionButtonElement;\n export type Props = ActionButtonProps<typeof useSuggestionTrigger>;\n}\n\n/**\n * A button that triggers the suggestion action (send or insert into composer).\n *\n * @example\n * ```tsx\n * <SuggestionPrimitive.Trigger send>\n * Click me\n * </SuggestionPrimitive.Trigger>\n * ```\n */\nexport const SuggestionPrimitiveTrigger = createActionButton(\n \"SuggestionPrimitive.Trigger\",\n useSuggestionTrigger,\n [\"send\", \"clearComposer\"],\n);\n"],"mappings":";;;;;;AAUA,MAAMQ,wBAAuBC,OAAA;CAAA,MAAAC,IAAAT,EAAA,CAAA;CAAC,MAAA,EAAAU,MAAAC,eAAAC,OAAAJ;CAE5B,MAAAG,gBAAAC,OAAAC,KAAAA,IAAA,OAAAD;CAiBA,MAAAE,MAAYR,OAAO;CACnB,MAAAS,WAAiBV,YAAYW,KAA0B;CACvD,MAAAC,SAAeZ,YAAYa,MAA0B;CAErD,MAAAC,eAAqBT,QAAA;CAAc,IAAAU;CAAA,IAAAX,EAAA,OAAAK,IAAAO,YAAAZ,EAAA,OAAAK,IAAAQ,UAAAb,EAAA,OAAAE,iBAAAF,EAAA,OAAAQ,UAAAR,EAAA,OAAAU,cAAA;EAENC,WAAA;GAC3B,MAAAG,YAAkBT,IAAGQ,OAAOE,SAAU,CAAC,CAAAD;GAEvC,IAAIJ,gBAAA,CAAiBI,WAAS;IAC5BT,IAAGQ,OAAOG,OAAQ;KAAAC,SACP,CAAC;MAAAC,MAAQ;MAAMC,MAAQX;KAAO,CAAC;KAACY,WAC9Bf,IAAGO,SAASG,SAAU,CAAC,CAAAK;IACpC,CAAC;IACD,IAAIlB,eACFG,IAAGO,SAASS,QAAS,EAAE;GACxB,OAED,IAAInB,eACFG,IAAGO,SAASS,QAASb,MAAM;QAAC;IAE5B,MAAAc,cAAoBjB,IAAGO,SAASG,SAAU,CAAC,CAAAI;IAC3Cd,IAAGO,SAASS,QACVC,YAAWC,KAA4C,IAAvD,GAAwBD,YAAW,GAAId,WAAvCA,MACF;GAAC;EAEJ;EACFR,EAAA,KAAAK,IAAAO;EAAAZ,EAAA,KAAAK,IAAAQ;EAAAb,EAAA,KAAAE;EAAAF,EAAA,KAAAQ;EAAAR,EAAA,KAAAU;EAAAV,EAAA,KAAAW;CAAA,OAAAA,KAAAX,EAAA;CArBD,MAAAwB,WAAiBb;CAuBjB,IAAIL,UAAQ,OAAS;CAAK,OACnBkB;AAAQ;;;;;;;;;;;AAkBjB,MAAaC,6BAA6B/B,mBACxC,+BACAI,sBACA,CAAC,QAAQ,eAAe,CAC1B;AAvE6B,SAAAS,MAAAqB,GAAA;CAAA,OAoBSA,EAACf,OAAOgB;AAAW;AApB5B,SAAApB,OAAAqB,KAAA;CAAA,OAqBOF,IAACG,WAAWvB;AAAO"}
@@ -36,9 +36,9 @@ function unstable_useComposerInput(options) {
36
36
  const value = useComposerInputValue();
37
37
  const isDisabled = useComposerInputDisabled(options?.disabled);
38
38
  const setText = useCallback((text) => {
39
- if (!aui.composer().getState().isEditing) return;
39
+ if (!aui.composer.getState().isEditing) return;
40
40
  flushTapSync(() => {
41
- aui.composer().setText(text);
41
+ aui.composer.setText(text);
42
42
  });
43
43
  }, [aui]);
44
44
  const { send: rawSend, disabled: sendDisabled } = useComposerSend();
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInput.js","names":["useCallback","useAui","flushTapSync","useComposerSend","ComposerSendOptions","TriggerPopoverAriaProps","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","Unstable_UseComposerInputOptions","disabled","Unstable_ComposerInput","value","setText","text","send","options","isDisabled","canSend","unstable_useComposerInput","aui","composer","getState","isEditing","rawSend","sendDisabled","sendOptions","Unstable_TriggerPopoverAriaProps","unstable_useTriggerPopoverAriaProps"],"sources":["../../src/unstable/useComposerInput.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerSend } from \"@assistant-ui/core/react\";\nimport type { ComposerSendOptions } from \"@assistant-ui/core/store\";\nimport {\n type TriggerPopoverAriaProps,\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"../primitives/composer/useComposerInputState\";\n\nexport type Unstable_UseComposerInputOptions = {\n /**\n * Disables the input in addition to the composer's own disabled sources\n * (thread disabled, active dictation). When disabled, `isDisabled` is `true`\n * and `canSend` is `false`.\n */\n disabled?: boolean | undefined;\n};\n\nexport type Unstable_ComposerInput = {\n /** Current composer text, or `\"\"` when the composer is not editing. */\n value: string;\n /**\n * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:\n * a no-op unless the composer is editing, committed via `flushTapSync` so the\n * controlled value stays in sync within the same tick.\n */\n setText(text: string): void;\n /**\n * Sends the current message when `canSend` is `true`; otherwise a no-op.\n * Accepts the same options as the composer send action.\n */\n send(options?: ComposerSendOptions): void;\n /**\n * Whether the input is disabled, combining the `disabled` option with the\n * composer's own disabled sources (thread disabled, active dictation).\n */\n isDisabled: boolean;\n /**\n * Whether a send is currently available. Matches `ComposerPrimitive.Send`\n * gating (non-empty editing composer, not running without queue support) and\n * is additionally `false` while `isDisabled`.\n */\n canSend: boolean;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Headless bridge to the composer's text value and send action, for building a\n * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,\n * not a second input: it does not own keyboard behavior, autosize, IME or\n * contentEditable sync, paste/drop attachments, focus management, or rich-text\n * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for\n * trigger-popover combobox semantics.\n *\n * @example\n * ```tsx\n * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();\n * <textarea\n * value={value}\n * disabled={isDisabled}\n * onChange={(e) => setText(e.target.value)}\n * onKeyDown={(e) => {\n * if (e.key === \"Enter\" && !e.shiftKey && canSend) {\n * e.preventDefault();\n * send();\n * }\n * }}\n * />\n * ```\n */\nexport function unstable_useComposerInput(\n options?: Unstable_UseComposerInputOptions,\n): Unstable_ComposerInput {\n const aui = useAui();\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(options?.disabled);\n\n const setText = useCallback(\n (text: string) => {\n if (!aui.composer().getState().isEditing) return;\n flushTapSync(() => {\n aui.composer().setText(text);\n });\n },\n [aui],\n );\n\n const { send: rawSend, disabled: sendDisabled } = useComposerSend();\n const canSend = !sendDisabled && !isDisabled;\n const send = useCallback(\n (sendOptions?: ComposerSendOptions) => {\n if (!canSend) return;\n rawSend(sendOptions);\n },\n [canSend, rawSend],\n );\n\n return { value, setText, send, isDisabled, canSend };\n}\n\nexport type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * ARIA combobox attributes for the focused element (typically the composer\n * input) describing the open trigger popover, per the WAI-ARIA editable\n * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or\n * when no popover is open. Spread these last so they take precedence over any\n * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.\n *\n * @example\n * ```tsx\n * const aria = unstable_useTriggerPopoverAriaProps();\n * <textarea {...aria} />\n * ```\n */\nexport function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps {\n return useTriggerPopoverAriaProps();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgBmB,0BACdH,SACwB;CACxB,MAAMI,MAAMnB,OAAO;CACnB,MAAMW,QAAQL,sBAAsB;CACpC,MAAMU,aAAaX,yBAAyBU,SAASN,QAAQ;CAE7D,MAAMG,UAAUb,aACbc,SAAiB;EAChB,IAAI,CAACM,IAAIC,SAAS,CAAC,CAACC,SAAS,CAAC,CAACC,WAAW;EAC1CrB,mBAAmB;GACjBkB,IAAIC,SAAS,CAAC,CAACR,QAAQC,IAAI;EAC7B,CAAC;CACH,GACA,CAACM,GAAG,CACN;CAEA,MAAM,EAAEL,MAAMS,SAASd,UAAUe,iBAAiBtB,gBAAgB;CAClE,MAAMe,UAAU,CAACO,gBAAgB,CAACR;CASlC,OAAO;EAAEL;EAAOC;EAASE,MARZf,aACV0B,gBAAsC;GACrC,IAAI,CAACR,SAAS;GACdM,QAAQE,WAAW;EACrB,GACA,CAACR,SAASM,OAAO,CAGMT;EAAME;EAAYC;CAAQ;AACrD;;;;;;;;;;;;;;;;AAmBA,SAAgBU,sCAAwE;CACtF,OAAOpB,2BAA2B;AACpC"}
1
+ {"version":3,"file":"useComposerInput.js","names":["useCallback","useAui","flushTapSync","useComposerSend","ComposerSendOptions","TriggerPopoverAriaProps","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","Unstable_UseComposerInputOptions","disabled","Unstable_ComposerInput","value","setText","text","send","options","isDisabled","canSend","unstable_useComposerInput","aui","composer","getState","isEditing","rawSend","sendDisabled","sendOptions","Unstable_TriggerPopoverAriaProps","unstable_useTriggerPopoverAriaProps"],"sources":["../../src/unstable/useComposerInput.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerSend } from \"@assistant-ui/core/react\";\nimport type { ComposerSendOptions } from \"@assistant-ui/core/store\";\nimport {\n type TriggerPopoverAriaProps,\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"../primitives/composer/useComposerInputState\";\n\nexport type Unstable_UseComposerInputOptions = {\n /**\n * Disables the input in addition to the composer's own disabled sources\n * (thread disabled, active dictation). When disabled, `isDisabled` is `true`\n * and `canSend` is `false`.\n */\n disabled?: boolean | undefined;\n};\n\nexport type Unstable_ComposerInput = {\n /** Current composer text, or `\"\"` when the composer is not editing. */\n value: string;\n /**\n * Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:\n * a no-op unless the composer is editing, committed via `flushTapSync` so the\n * controlled value stays in sync within the same tick.\n */\n setText(text: string): void;\n /**\n * Sends the current message when `canSend` is `true`; otherwise a no-op.\n * Accepts the same options as the composer send action.\n */\n send(options?: ComposerSendOptions): void;\n /**\n * Whether the input is disabled, combining the `disabled` option with the\n * composer's own disabled sources (thread disabled, active dictation).\n */\n isDisabled: boolean;\n /**\n * Whether a send is currently available. Matches `ComposerPrimitive.Send`\n * gating (non-empty editing composer, not running without queue support) and\n * is additionally `false` while `isDisabled`.\n */\n canSend: boolean;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Headless bridge to the composer's text value and send action, for building a\n * custom composer input without `ComposerPrimitive.Input`. It is a thin bridge,\n * not a second input: it does not own keyboard behavior, autosize, IME or\n * contentEditable sync, paste/drop attachments, focus management, or rich-text\n * state. Spread `unstable_useTriggerPopoverAriaProps()` onto your element for\n * trigger-popover combobox semantics.\n *\n * @example\n * ```tsx\n * const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();\n * <textarea\n * value={value}\n * disabled={isDisabled}\n * onChange={(e) => setText(e.target.value)}\n * onKeyDown={(e) => {\n * if (e.key === \"Enter\" && !e.shiftKey && canSend) {\n * e.preventDefault();\n * send();\n * }\n * }}\n * />\n * ```\n */\nexport function unstable_useComposerInput(\n options?: Unstable_UseComposerInputOptions,\n): Unstable_ComposerInput {\n const aui = useAui();\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(options?.disabled);\n\n const setText = useCallback(\n (text: string) => {\n if (!aui.composer.getState().isEditing) return;\n flushTapSync(() => {\n aui.composer.setText(text);\n });\n },\n [aui],\n );\n\n const { send: rawSend, disabled: sendDisabled } = useComposerSend();\n const canSend = !sendDisabled && !isDisabled;\n const send = useCallback(\n (sendOptions?: ComposerSendOptions) => {\n if (!canSend) return;\n rawSend(sendOptions);\n },\n [canSend, rawSend],\n );\n\n return { value, setText, send, isDisabled, canSend };\n}\n\nexport type Unstable_TriggerPopoverAriaProps = TriggerPopoverAriaProps;\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * ARIA combobox attributes for the focused element (typically the composer\n * input) describing the open trigger popover, per the WAI-ARIA editable\n * combobox pattern. Returns an empty object outside a `TriggerPopoverRoot` or\n * when no popover is open. Spread these last so they take precedence over any\n * matching ARIA props you set yourself, mirroring `ComposerPrimitive.Input`.\n *\n * @example\n * ```tsx\n * const aria = unstable_useTriggerPopoverAriaProps();\n * <textarea {...aria} />\n * ```\n */\nexport function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps {\n return useTriggerPopoverAriaProps();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgBmB,0BACdH,SACwB;CACxB,MAAMI,MAAMnB,OAAO;CACnB,MAAMW,QAAQL,sBAAsB;CACpC,MAAMU,aAAaX,yBAAyBU,SAASN,QAAQ;CAE7D,MAAMG,UAAUb,aACbc,SAAiB;EAChB,IAAI,CAACM,IAAIC,SAASC,SAAS,CAAC,CAACC,WAAW;EACxCrB,mBAAmB;GACjBkB,IAAIC,SAASR,QAAQC,IAAI;EAC3B,CAAC;CACH,GACA,CAACM,GAAG,CACN;CAEA,MAAM,EAAEL,MAAMS,SAASd,UAAUe,iBAAiBtB,gBAAgB;CAClE,MAAMe,UAAU,CAACO,gBAAgB,CAACR;CASlC,OAAO;EAAEL;EAAOC;EAASE,MARZf,aACV0B,gBAAsC;GACrC,IAAI,CAACR,SAAS;GACdM,QAAQE,WAAW;EACrB,GACA,CAACR,SAASM,OAAO,CAGMT;EAAME;EAAYC;CAAQ;AACrD;;;;;;;;;;;;;;;;AAmBA,SAAgBU,sCAAwE;CACtF,OAAOpB,2BAA2B;AACpC"}
@@ -44,7 +44,7 @@ function unstable_useComposerInputHistory() {
44
44
  const popoverCtx = useTriggerPopoverRootContextOptional();
45
45
  const browseRef = useRef(null);
46
46
  useEffect(() => {
47
- if (aui.composer().getState().type !== "thread") return void 0;
47
+ if (aui.composer.getState().type !== "thread") return void 0;
48
48
  return aui.on("threadListItem.switchedTo", () => {
49
49
  browseRef.current = null;
50
50
  });
@@ -55,14 +55,14 @@ function unstable_useComposerInputHistory() {
55
55
  if (e.nativeEvent.isComposing) return;
56
56
  if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
57
57
  if (popoverCtx && popoverCtx.getActiveAria() !== null) return;
58
- if (aui.composer().getState().type !== "thread") return;
58
+ if (aui.composer.getState().type !== "thread") return;
59
59
  const textarea = e.currentTarget;
60
60
  const { selectionStart, selectionEnd, value } = textarea;
61
61
  if (selectionStart !== selectionEnd) return;
62
62
  if (browseRef.current && value !== browseRef.current.lastRecalledText) browseRef.current = null;
63
63
  const browse = browseRef.current;
64
64
  const commitText = (text) => {
65
- flushTapSync(() => aui.composer().setText(text));
65
+ flushTapSync(() => aui.composer.setText(text));
66
66
  requestAnimationFrame(() => {
67
67
  textarea.setSelectionRange(text.length, text.length);
68
68
  });
@@ -85,12 +85,12 @@ function unstable_useComposerInputHistory() {
85
85
  if (!isOnFirstLine(value, selectionStart)) return;
86
86
  if (!browse) {
87
87
  if (value.trim() !== "") return;
88
- const history = deriveHistory(aui.thread().getState().messages);
88
+ const history = deriveHistory(aui.thread.getState().messages);
89
89
  if (history.length === 0) return;
90
90
  recall(history, 0, value);
91
91
  return;
92
92
  }
93
- const history = deriveHistory(aui.thread().getState().messages);
93
+ const history = deriveHistory(aui.thread.getState().messages);
94
94
  const next = browse.cursor + 1;
95
95
  if (next >= history.length) {
96
96
  e.preventDefault();
@@ -107,7 +107,7 @@ function unstable_useComposerInputHistory() {
107
107
  commitText(browse.draftSnapshot);
108
108
  return;
109
109
  }
110
- recall(deriveHistory(aui.thread().getState().messages), next, browse.draftSnapshot);
110
+ recall(deriveHistory(aui.thread.getState().messages), next, browse.draftSnapshot);
111
111
  }, [aui, popoverCtx]);
112
112
  return useMemo(() => ({ onKeyDown }), [onKeyDown]);
113
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useComposerInputHistory.js","names":["useCallback","useEffect","useMemo","useRef","KeyboardEvent","KeyboardEventHandler","useAui","flushTapSync","ThreadMessage","getThreadMessageText","useTriggerPopoverRootContextOptional","Unstable_ComposerInputHistory","onKeyDown","HTMLTextAreaElement","BrowseState","cursor","draftSnapshot","lastRecalledText","deriveHistory","messages","entries","i","length","message","role","text","trim","push","isOnFirstLine","value","caret","slice","includes","isOnLastLine","unstable_useComposerInputHistory","aui","popoverCtx","browseRef","composer","getState","type","undefined","on","current","e","defaultPrevented","key","nativeEvent","isComposing","shiftKey","ctrlKey","metaKey","altKey","getActiveAria","textarea","currentTarget","selectionStart","selectionEnd","browse","commitText","setText","requestAnimationFrame","setSelectionRange","preventDefault","recall","history","entry","thread","next"],"sources":["../../src/unstable/useComposerInputHistory.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type KeyboardEvent,\n type KeyboardEventHandler,\n} from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport { getThreadMessageText } from \"@assistant-ui/core/internal\";\nimport { useTriggerPopoverRootContextOptional } from \"../primitives/composer/trigger/TriggerPopoverRootContext\";\n\nexport type Unstable_ComposerInputHistory = {\n /** Keydown handler to spread onto `ComposerPrimitive.Input`. */\n onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;\n};\n\ntype BrowseState = {\n cursor: number;\n draftSnapshot: string;\n lastRecalledText: string;\n};\n\nconst deriveHistory = (messages: readonly ThreadMessage[]): string[] => {\n const entries: string[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]!;\n if (message.role !== \"user\") continue;\n const text = getThreadMessageText(message).trim();\n if (!text) continue;\n if (entries[entries.length - 1] === text) continue;\n entries.push(text);\n }\n return entries;\n};\n\nconst isOnFirstLine = (value: string, caret: number): boolean =>\n !value.slice(0, caret).includes(\"\\n\");\n\nconst isOnLastLine = (value: string, caret: number): boolean =>\n !value.slice(caret).includes(\"\\n\");\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Terminal-style input history for the thread composer: ArrowUp on an\n * empty draft recalls previously sent user messages (newest first),\n * ArrowDown steps back toward the newest and finally restores the draft\n * that was being typed when browsing started.\n *\n * Recall only triggers when the caret is on the first/last line with no\n * selection, so multi-line editing keeps native arrow behavior. The\n * handler yields to an open mention/slash popover, to IME composition,\n * to modifier keys, and to consumer handlers that already called\n * `preventDefault`. It is inert on edit composers.\n *\n * @example\n * ```tsx\n * const history = unstable_useComposerInputHistory();\n * <ComposerPrimitive.Input {...history} />\n * ```\n */\nexport function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory {\n const aui = useAui();\n const popoverCtx = useTriggerPopoverRootContextOptional();\n const browseRef = useRef<BrowseState | null>(null);\n\n useEffect(() => {\n if (aui.composer().getState().type !== \"thread\") return undefined;\n\n return aui.on(\"threadListItem.switchedTo\", () => {\n browseRef.current = null;\n });\n }, [aui]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.defaultPrevented) return;\n if (e.key !== \"ArrowUp\" && e.key !== \"ArrowDown\") return;\n if (e.nativeEvent.isComposing) return;\n if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;\n if (popoverCtx && popoverCtx.getActiveAria() !== null) return;\n if (aui.composer().getState().type !== \"thread\") return;\n\n const textarea = e.currentTarget;\n const { selectionStart, selectionEnd, value } = textarea;\n if (selectionStart !== selectionEnd) return;\n\n if (browseRef.current && value !== browseRef.current.lastRecalledText) {\n browseRef.current = null;\n }\n const browse = browseRef.current;\n\n const commitText = (text: string): void => {\n flushTapSync(() => aui.composer().setText(text));\n // React's controlled-value commit restores the pre-recall caret;\n // reposition after the commit, before paint.\n requestAnimationFrame(() => {\n textarea.setSelectionRange(text.length, text.length);\n });\n e.preventDefault();\n };\n\n const recall = (\n history: readonly string[],\n cursor: number,\n draftSnapshot: string,\n ): void => {\n const entry = history[cursor];\n if (entry === undefined) {\n e.preventDefault();\n return;\n }\n browseRef.current = { cursor, draftSnapshot, lastRecalledText: entry };\n commitText(entry);\n };\n\n if (e.key === \"ArrowUp\") {\n if (!isOnFirstLine(value, selectionStart)) return;\n\n if (!browse) {\n if (value.trim() !== \"\") return;\n const history = deriveHistory(aui.thread().getState().messages);\n if (history.length === 0) return;\n recall(history, 0, value);\n return;\n }\n\n const history = deriveHistory(aui.thread().getState().messages);\n const next = browse.cursor + 1;\n if (next >= history.length) {\n e.preventDefault();\n return;\n }\n recall(history, next, browse.draftSnapshot);\n return;\n }\n\n if (!browse) return;\n if (!isOnLastLine(value, selectionEnd)) return;\n\n const next = browse.cursor - 1;\n if (next < 0) {\n browseRef.current = null;\n commitText(browse.draftSnapshot);\n return;\n }\n\n const history = deriveHistory(aui.thread().getState().messages);\n recall(history, next, browse.draftSnapshot);\n },\n [aui, popoverCtx],\n );\n\n return useMemo(() => ({ onKeyDown }), [onKeyDown]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useComposerInputHistory.js","names":["useCallback","useEffect","useMemo","useRef","KeyboardEvent","KeyboardEventHandler","useAui","flushTapSync","ThreadMessage","getThreadMessageText","useTriggerPopoverRootContextOptional","Unstable_ComposerInputHistory","onKeyDown","HTMLTextAreaElement","BrowseState","cursor","draftSnapshot","lastRecalledText","deriveHistory","messages","entries","i","length","message","role","text","trim","push","isOnFirstLine","value","caret","slice","includes","isOnLastLine","unstable_useComposerInputHistory","aui","popoverCtx","browseRef","composer","getState","type","undefined","on","current","e","defaultPrevented","key","nativeEvent","isComposing","shiftKey","ctrlKey","metaKey","altKey","getActiveAria","textarea","currentTarget","selectionStart","selectionEnd","browse","commitText","setText","requestAnimationFrame","setSelectionRange","preventDefault","recall","history","entry","thread","next"],"sources":["../../src/unstable/useComposerInputHistory.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type KeyboardEvent,\n type KeyboardEventHandler,\n} from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport type { ThreadMessage } from \"@assistant-ui/core\";\nimport { getThreadMessageText } from \"@assistant-ui/core/internal\";\nimport { useTriggerPopoverRootContextOptional } from \"../primitives/composer/trigger/TriggerPopoverRootContext\";\n\nexport type Unstable_ComposerInputHistory = {\n /** Keydown handler to spread onto `ComposerPrimitive.Input`. */\n onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;\n};\n\ntype BrowseState = {\n cursor: number;\n draftSnapshot: string;\n lastRecalledText: string;\n};\n\nconst deriveHistory = (messages: readonly ThreadMessage[]): string[] => {\n const entries: string[] = [];\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i]!;\n if (message.role !== \"user\") continue;\n const text = getThreadMessageText(message).trim();\n if (!text) continue;\n if (entries[entries.length - 1] === text) continue;\n entries.push(text);\n }\n return entries;\n};\n\nconst isOnFirstLine = (value: string, caret: number): boolean =>\n !value.slice(0, caret).includes(\"\\n\");\n\nconst isOnLastLine = (value: string, caret: number): boolean =>\n !value.slice(caret).includes(\"\\n\");\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Terminal-style input history for the thread composer: ArrowUp on an\n * empty draft recalls previously sent user messages (newest first),\n * ArrowDown steps back toward the newest and finally restores the draft\n * that was being typed when browsing started.\n *\n * Recall only triggers when the caret is on the first/last line with no\n * selection, so multi-line editing keeps native arrow behavior. The\n * handler yields to an open mention/slash popover, to IME composition,\n * to modifier keys, and to consumer handlers that already called\n * `preventDefault`. It is inert on edit composers.\n *\n * @example\n * ```tsx\n * const history = unstable_useComposerInputHistory();\n * <ComposerPrimitive.Input {...history} />\n * ```\n */\nexport function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory {\n const aui = useAui();\n const popoverCtx = useTriggerPopoverRootContextOptional();\n const browseRef = useRef<BrowseState | null>(null);\n\n useEffect(() => {\n if (aui.composer.getState().type !== \"thread\") return undefined;\n\n return aui.on(\"threadListItem.switchedTo\", () => {\n browseRef.current = null;\n });\n }, [aui]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.defaultPrevented) return;\n if (e.key !== \"ArrowUp\" && e.key !== \"ArrowDown\") return;\n if (e.nativeEvent.isComposing) return;\n if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;\n if (popoverCtx && popoverCtx.getActiveAria() !== null) return;\n if (aui.composer.getState().type !== \"thread\") return;\n\n const textarea = e.currentTarget;\n const { selectionStart, selectionEnd, value } = textarea;\n if (selectionStart !== selectionEnd) return;\n\n if (browseRef.current && value !== browseRef.current.lastRecalledText) {\n browseRef.current = null;\n }\n const browse = browseRef.current;\n\n const commitText = (text: string): void => {\n flushTapSync(() => aui.composer.setText(text));\n // React's controlled-value commit restores the pre-recall caret;\n // reposition after the commit, before paint.\n requestAnimationFrame(() => {\n textarea.setSelectionRange(text.length, text.length);\n });\n e.preventDefault();\n };\n\n const recall = (\n history: readonly string[],\n cursor: number,\n draftSnapshot: string,\n ): void => {\n const entry = history[cursor];\n if (entry === undefined) {\n e.preventDefault();\n return;\n }\n browseRef.current = { cursor, draftSnapshot, lastRecalledText: entry };\n commitText(entry);\n };\n\n if (e.key === \"ArrowUp\") {\n if (!isOnFirstLine(value, selectionStart)) return;\n\n if (!browse) {\n if (value.trim() !== \"\") return;\n const history = deriveHistory(aui.thread.getState().messages);\n if (history.length === 0) return;\n recall(history, 0, value);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n const next = browse.cursor + 1;\n if (next >= history.length) {\n e.preventDefault();\n return;\n }\n recall(history, next, browse.draftSnapshot);\n return;\n }\n\n if (!browse) return;\n if (!isOnLastLine(value, selectionEnd)) return;\n\n const next = browse.cursor - 1;\n if (next < 0) {\n browseRef.current = null;\n commitText(browse.draftSnapshot);\n return;\n }\n\n const history = deriveHistory(aui.thread.getState().messages);\n recall(history, next, browse.draftSnapshot);\n },\n [aui, popoverCtx],\n );\n\n return useMemo(() => ({ onKeyDown }), [onKeyDown]);\n}\n"],"mappings":"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"}
@@ -29,7 +29,7 @@ function unstable_useMentionAdapter(options) {
29
29
  adapter: useMemo(() => {
30
30
  const getModelContextTools = () => {
31
31
  if (!wantsTools) return [];
32
- const tools = aui.thread().getModelContext().tools;
32
+ const tools = aui.thread.getModelContext().tools;
33
33
  if (!tools) return [];
34
34
  const formatLabel = toolsConfig?.formatLabel;
35
35
  const defaultIcon = toolsConfig?.icon;
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","unstable_useMentionAdapter","options","adapter","directive","aui","includeTools","toolsConfig","undefined","wantsTools","getModelContextTools","ctx","thread","getModelContext","tools","defaultIcon","Object","entries","map","name","tool","toTriggerItem","length","groups","cat","toolCategory","toolItems","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","matchesQuery","flatItems","getFlatPool","seen","Set","i","t","has","m","includes"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /** Wrap tools in a dedicated category (drill-down mode). */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /** Flat mention list. Ignored when `categories` is set. */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const getModelContextTools = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return [];\n const ctx = aui.thread().getModelContext();\n const tools = ctx.tools;\n if (!tools) return [];\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n return Object.entries(tools).map(([name, tool]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description: tool.description ?? undefined,\n icon: defaultIcon,\n }),\n );\n };\n\n // Categorized: drill-down mode\n if (categories && categories.length > 0) {\n const groups = categories.map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n\n let toolCategory: {\n id: string;\n label: string;\n items: Unstable_TriggerItem[];\n } | null = null;\n if (wantsTools) {\n const toolItems = getModelContextTools();\n if (toolItems.length > 0) {\n toolCategory = {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n };\n }\n }\n const allGroups = toolCategory ? [...groups, toolCategory] : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n const getFlatPool = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return flatItems;\n const toolItems = getModelContextTools();\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];\n };\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return getFlatPool().filter((item) => matchesQuery(item, lower));\n },\n };\n }, [aui, items, categories, wantsTools, toolsConfig]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n\nfunction matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {\n if (!lower) return true;\n if (item.id.toLowerCase().includes(lower)) return true;\n if (item.label.toLowerCase().includes(lower)) return true;\n if (item.description?.toLowerCase().includes(lower)) return true;\n return false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useMentionAdapter.js","names":["useMemo","FC","useAui","Unstable_DirectiveFormatter","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","unstable_defaultDirectiveFormatter","ReadonlyJSONObject","Unstable_IconComponent","className","Unstable_Mention","id","type","label","description","icon","metadata","Unstable_MentionCategory","items","Unstable_ModelContextToolsOptions","category","formatLabel","toolName","Unstable_UseMentionAdapterOptions","categories","includeModelContextTools","formatter","onInserted","item","iconMap","Record","fallbackIcon","Unstable_MentionDirective","unstable_useMentionAdapter","options","adapter","directive","aui","includeTools","toolsConfig","undefined","wantsTools","getModelContextTools","ctx","thread","getModelContext","tools","defaultIcon","Object","entries","map","name","tool","toTriggerItem","length","groups","cat","toolCategory","toolItems","allGroups","categoryItems","find","g","search","query","lower","toLowerCase","flatMap","filter","matchesQuery","flatItems","getFlatPool","seen","Set","i","t","has","m","includes"],"sources":["../../src/unstable/useMentionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo, type FC } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type {\n Unstable_DirectiveFormatter,\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport { unstable_defaultDirectiveFormatter } from \"@assistant-ui/core\";\nimport type { ReadonlyJSONObject } from \"assistant-stream/utils\";\n\n/** Icon component shape consumed by `ComposerTriggerPopover`'s `iconMap`. */\nexport type Unstable_IconComponent = FC<{ className?: string }>;\n\nexport type Unstable_Mention = {\n readonly id: string;\n readonly type: string;\n readonly label: string;\n readonly description?: string | undefined;\n /** Shortcut for `metadata.icon`; merged with `metadata` if both are given. */\n readonly icon?: string | undefined;\n readonly metadata?: ReadonlyJSONObject | undefined;\n};\n\nexport type Unstable_MentionCategory = {\n readonly id: string;\n readonly label: string;\n readonly items: readonly Unstable_Mention[];\n};\n\nexport type Unstable_ModelContextToolsOptions = {\n /** Wrap tools in a dedicated category (drill-down mode). */\n readonly category?: { readonly id: string; readonly label: string };\n /** Format tool name for display. */\n readonly formatLabel?: (toolName: string) => string;\n /** Default icon key for each tool. */\n readonly icon?: string;\n};\n\nexport type Unstable_UseMentionAdapterOptions = {\n /** Flat mention list. Ignored when `categories` is set. */\n readonly items?: readonly Unstable_Mention[];\n /** Categorized mentions for drill-down navigation. */\n readonly categories?: readonly Unstable_MentionCategory[];\n /**\n * How tools registered in model context integrate.\n * - `false`: exclude.\n * - `true`: include (default when no `items`/`categories`; as a category\n * if `categories` is set, flat otherwise).\n * - object: explicit config.\n *\n * Omitted → defaults to `true` iff neither `items` nor `categories`.\n */\n readonly includeModelContextTools?:\n | boolean\n | Unstable_ModelContextToolsOptions;\n /** Directive formatter. @default unstable_defaultDirectiveFormatter */\n readonly formatter?: Unstable_DirectiveFormatter;\n /** Fires after an item is inserted into the composer. */\n readonly onInserted?: (item: Unstable_TriggerItem) => void;\n /** Maps `metadata.icon` / `category.id` string keys to React components. */\n readonly iconMap?: Record<string, Unstable_IconComponent>;\n /** Fallback icon when no entry in `iconMap` matches. */\n readonly fallbackIcon?: Unstable_IconComponent;\n};\n\nexport type Unstable_MentionDirective = {\n readonly formatter: Unstable_DirectiveFormatter;\n readonly onInserted?: ((item: Unstable_TriggerItem) => void) | undefined;\n};\n\n/**\n * @deprecated Under active development and might change without notice.\n *\n * Creates a spreadable `{ adapter, directive }` bundle for `@` mentions.\n * Supports tools registered in model context, explicit items, or both —\n * flat or categorized.\n *\n * @example\n * ```tsx\n * const mention = unstable_useMentionAdapter();\n * <ComposerTriggerPopover char=\"@\" {...mention} />\n * ```\n */\nexport function unstable_useMentionAdapter(\n options?: Unstable_UseMentionAdapterOptions,\n): {\n adapter: Unstable_TriggerAdapter;\n directive: Unstable_MentionDirective;\n iconMap?: Record<string, Unstable_IconComponent>;\n fallbackIcon?: Unstable_IconComponent;\n} {\n const aui = useAui();\n\n const items = options?.items;\n const categories = options?.categories;\n const includeTools =\n options?.includeModelContextTools ?? (!items && !categories);\n const toolsConfig =\n typeof includeTools === \"object\" ? includeTools : undefined;\n const wantsTools = includeTools !== false;\n const formatter = options?.formatter;\n const onInserted = options?.onInserted;\n\n const adapter = useMemo<Unstable_TriggerAdapter>(() => {\n const getModelContextTools = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return [];\n const ctx = aui.thread.getModelContext();\n const tools = ctx.tools;\n if (!tools) return [];\n const formatLabel = toolsConfig?.formatLabel;\n const defaultIcon = toolsConfig?.icon;\n return Object.entries(tools).map(([name, tool]) =>\n toTriggerItem({\n id: name,\n type: \"tool\",\n label: formatLabel ? formatLabel(name) : name,\n description: tool.description ?? undefined,\n icon: defaultIcon,\n }),\n );\n };\n\n // Categorized: drill-down mode\n if (categories && categories.length > 0) {\n const groups = categories.map((cat) => ({\n id: cat.id,\n label: cat.label,\n items: cat.items.map(toTriggerItem),\n }));\n\n let toolCategory: {\n id: string;\n label: string;\n items: Unstable_TriggerItem[];\n } | null = null;\n if (wantsTools) {\n const toolItems = getModelContextTools();\n if (toolItems.length > 0) {\n toolCategory = {\n id: toolsConfig?.category?.id ?? \"tools\",\n label: toolsConfig?.category?.label ?? \"Tools\",\n items: toolItems,\n };\n }\n }\n const allGroups = toolCategory ? [...groups, toolCategory] : groups;\n\n return {\n categories: () => allGroups.map(({ id, label }) => ({ id, label })),\n categoryItems: (id) => allGroups.find((g) => g.id === id)?.items ?? [],\n search: (query) => {\n const lower = query.toLowerCase();\n return allGroups\n .flatMap((g) => g.items)\n .filter((item) => matchesQuery(item, lower));\n },\n };\n }\n\n // Flat: items + (optionally) tools, all in one search pool\n const flatItems = (items ?? []).map(toTriggerItem);\n const getFlatPool = (): Unstable_TriggerItem[] => {\n if (!wantsTools) return flatItems;\n const toolItems = getModelContextTools();\n // Dedupe by id — explicit items win.\n const seen = new Set(flatItems.map((i) => i.id));\n return [...flatItems, ...toolItems.filter((t) => !seen.has(t.id))];\n };\n\n return {\n categories: (): readonly Unstable_TriggerCategory[] => [],\n categoryItems: () => [],\n search: (query) => {\n const lower = query.toLowerCase();\n return getFlatPool().filter((item) => matchesQuery(item, lower));\n },\n };\n }, [aui, items, categories, wantsTools, toolsConfig]);\n\n const directive = useMemo<Unstable_MentionDirective>(\n () => ({\n formatter: formatter ?? unstable_defaultDirectiveFormatter,\n ...(onInserted ? { onInserted } : {}),\n }),\n [formatter, onInserted],\n );\n\n return {\n adapter,\n directive,\n ...(options?.iconMap ? { iconMap: options.iconMap } : {}),\n ...(options?.fallbackIcon ? { fallbackIcon: options.fallbackIcon } : {}),\n };\n}\n\nfunction toTriggerItem(m: Unstable_Mention): Unstable_TriggerItem {\n const metadata =\n m.icon !== undefined ? { ...(m.metadata ?? {}), icon: m.icon } : m.metadata;\n return {\n id: m.id,\n type: m.type,\n label: m.label,\n ...(m.description !== undefined ? { description: m.description } : {}),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n}\n\nfunction matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {\n if (!lower) return true;\n if (item.id.toLowerCase().includes(lower)) return true;\n if (item.label.toLowerCase().includes(lower)) return true;\n if (item.description?.toLowerCase().includes(lower)) return true;\n return false;\n}\n"],"mappings":"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"}
@@ -21,9 +21,9 @@ const SmoothContextProvider = (t0) => {
21
21
  const outer = useSmoothContext(t1);
22
22
  const aui = useAui();
23
23
  let t2;
24
- if ($[1] !== aui) {
25
- t2 = () => makeSmoothContext(aui.part().getState().status);
26
- $[1] = aui;
24
+ if ($[1] !== aui.part) {
25
+ t2 = () => makeSmoothContext(aui.part.getState().status);
26
+ $[1] = aui.part;
27
27
  $[2] = t2;
28
28
  } else t2 = $[2];
29
29
  const [context] = useState(t2);
@@ -1 +1 @@
1
- {"version":3,"file":"SmoothContext.js","names":["c","_c","ComponentType","createContext","FC","forwardRef","PropsWithChildren","useContext","useState","ReadonlyStore","create","UseBoundStore","MessagePartStatus","ToolCallMessagePartStatus","useAui","createContextStoreHook","SmoothContextValue","useSmoothStatus","SmoothContext","makeSmoothContext","initialState","SmoothContextProvider","t0","$","children","t1","Symbol","for","optional","outer","useSmoothContext","aui","t2","part","getState","status","context","t3","withSmoothContextProvider","Component","C","Wrapped","props","ref","displayName","options","Error","useSmoothStatusStore"],"sources":["../../../src/utils/smooth/SmoothContext.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ComponentType,\n createContext,\n type FC,\n forwardRef,\n type PropsWithChildren,\n useContext,\n useState,\n} from \"react\";\nimport type { ReadonlyStore } from \"../../context/ReadonlyStore\";\nimport { create, type UseBoundStore } from \"zustand\";\nimport type {\n MessagePartStatus,\n ToolCallMessagePartStatus,\n} from \"@assistant-ui/core\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { createContextStoreHook } from \"../../context/react/utils/createContextStoreHook\";\n\ntype SmoothContextValue = {\n useSmoothStatus: UseBoundStore<\n ReadonlyStore<MessagePartStatus | ToolCallMessagePartStatus>\n >;\n};\n\nconst SmoothContext = createContext<SmoothContextValue | null>(null);\n\nconst makeSmoothContext = (\n initialState: MessagePartStatus | ToolCallMessagePartStatus,\n) => {\n const useSmoothStatus = create(() => initialState);\n return { useSmoothStatus };\n};\n\nexport const SmoothContextProvider: FC<PropsWithChildren> = ({ children }) => {\n const outer = useSmoothContext({ optional: true });\n const aui = useAui();\n\n const [context] = useState(() =>\n makeSmoothContext(aui.part().getState().status),\n );\n\n // do not wrap if there is an outer SmoothContextProvider\n if (outer) return children;\n\n return (\n <SmoothContext.Provider value={context}>{children}</SmoothContext.Provider>\n );\n};\n\nexport const withSmoothContextProvider = <C extends ComponentType<any>>(\n Component: C,\n): C => {\n const Wrapped = forwardRef((props, ref) => {\n return (\n <SmoothContextProvider>\n <Component {...(props as any)} ref={ref} />\n </SmoothContextProvider>\n );\n });\n Wrapped.displayName = Component.displayName;\n return Wrapped as any;\n};\n\nfunction useSmoothContext(options?: {\n optional?: false | undefined;\n}): SmoothContextValue;\nfunction useSmoothContext(options?: {\n optional?: boolean | undefined;\n}): SmoothContextValue | null;\nfunction useSmoothContext(options?: { optional?: boolean | undefined }) {\n const context = useContext(SmoothContext);\n if (!options?.optional && !context)\n throw new Error(\n \"This component must be used within a SmoothContextProvider.\",\n );\n return context;\n}\n\nexport const { useSmoothStatus, useSmoothStatusStore } = createContextStoreHook(\n useSmoothContext,\n \"useSmoothStatus\",\n);\n"],"mappings":";;;;;;;;AA0BA,MAAMkB,gBAAgBf,cAAyC,IAAI;AAEnE,MAAMgB,qBACJC,iBACG;CAEH,OAAO,EAAEH,iBADeP,aAAaU,YAC5BH,EAAgB;AAC3B;AAEA,MAAaI,yBAA+CC,OAAA;CAAA,MAAAC,IAAAtB,EAAA,CAAA;CAAC,MAAA,EAAAuB,aAAAF;CAAY,IAAAG;CAAA,IAAAF,EAAA,OAAAG,OAAAC,IAAA,2BAAA,GAAA;EACxCF,KAAA,EAAAG,UAAY,KAAK;EAACL,EAAA,KAAAE;CAAA,OAAAA,KAAAF,EAAA;CAAjD,MAAAM,QAAcC,iBAAiBL,EAAkB;CACjD,MAAAM,MAAYjB,OAAO;CAAE,IAAAkB;CAAA,IAAAT,EAAA,OAAAQ,KAAA;EAEMC,WACzBb,kBAAkBY,IAAGE,KAAM,CAAC,CAAAC,SAAU,CAAC,CAAAC,MAAO;EAACZ,EAAA,KAAAQ;EAAAR,EAAA,KAAAS;CAAA,OAAAA,KAAAT,EAAA;CADjD,MAAA,CAAAa,WAAkB5B,SAASwB,EAE3B;CAGA,IAAIH,OAAK,OAASL;CAAS,IAAAa;CAAA,IAAAd,EAAA,OAAAC,YAAAD,EAAA,OAAAa,SAAA;EAGzBC,KAAA,oBAAA,cAAA,UAAA;GAA+BD,OAAAA;GAAUZ;EAAS,CAAA;EAAyBD,EAAA,KAAAC;EAAAD,EAAA,KAAAa;EAAAb,EAAA,KAAAc;CAAA,OAAAA,KAAAd,EAAA;CAAA,OAA3Ec;AAA2E;AAI/E,MAAaC,6BACXC,cACM;CACN,MAAME,UAAUpC,YAAWqC,OAAAC,QAAA;EAAA,MAAApB,IAAAtB,EAAA,CAAA;EAGL,MAAAqB,KAAAoB;EAAY,IAAAjB;EAAA,IAAAF,EAAA,OAAAoB,OAAApB,EAAA,OAAAD,IAAA;GAD9BG,KAAA,oBAAC,uBAAD,EAAA,UACE,oBAAC,WAAD;IAAU,GAAMH;IAAoBqB;GAAG,CAAA,EADnB,CAAA;GAEEpB,EAAA,KAAAoB;GAAApB,EAAA,KAAAD;GAAAC,EAAA,KAAAE;EAAA,OAAAA,KAAAF,EAAA;EAAA,OAFxBE;CAEwB,CAE3B;CACDgB,QAAQG,cAAcL,UAAUK;CAChC,OAAOH;AACT;AAQA,SAAAX,iBAAAe,SAAA;CACE,MAAAT,UAAgB7B,WAAWW,aAAa;CACxC,IAAI,CAAC2B,SAAOjB,YAAR,CAAuBQ,SACzB,MAAM,IAAIU,MACR,6DACF;CAAE,OACGV;AAAO;AAGhB,MAAa,EAAEnB,iBAAiB8B,yBAAyBhC,uBACvDe,kBACA,iBACF"}
1
+ {"version":3,"file":"SmoothContext.js","names":["c","_c","ComponentType","createContext","FC","forwardRef","PropsWithChildren","useContext","useState","ReadonlyStore","create","UseBoundStore","MessagePartStatus","ToolCallMessagePartStatus","useAui","createContextStoreHook","SmoothContextValue","useSmoothStatus","SmoothContext","makeSmoothContext","initialState","SmoothContextProvider","t0","$","children","t1","Symbol","for","optional","outer","useSmoothContext","aui","t2","part","getState","status","context","t3","withSmoothContextProvider","Component","C","Wrapped","props","ref","displayName","options","Error","useSmoothStatusStore"],"sources":["../../../src/utils/smooth/SmoothContext.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ComponentType,\n createContext,\n type FC,\n forwardRef,\n type PropsWithChildren,\n useContext,\n useState,\n} from \"react\";\nimport type { ReadonlyStore } from \"../../context/ReadonlyStore\";\nimport { create, type UseBoundStore } from \"zustand\";\nimport type {\n MessagePartStatus,\n ToolCallMessagePartStatus,\n} from \"@assistant-ui/core\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { createContextStoreHook } from \"../../context/react/utils/createContextStoreHook\";\n\ntype SmoothContextValue = {\n useSmoothStatus: UseBoundStore<\n ReadonlyStore<MessagePartStatus | ToolCallMessagePartStatus>\n >;\n};\n\nconst SmoothContext = createContext<SmoothContextValue | null>(null);\n\nconst makeSmoothContext = (\n initialState: MessagePartStatus | ToolCallMessagePartStatus,\n) => {\n const useSmoothStatus = create(() => initialState);\n return { useSmoothStatus };\n};\n\nexport const SmoothContextProvider: FC<PropsWithChildren> = ({ children }) => {\n const outer = useSmoothContext({ optional: true });\n const aui = useAui();\n\n const [context] = useState(() =>\n makeSmoothContext(aui.part.getState().status),\n );\n\n // do not wrap if there is an outer SmoothContextProvider\n if (outer) return children;\n\n return (\n <SmoothContext.Provider value={context}>{children}</SmoothContext.Provider>\n );\n};\n\nexport const withSmoothContextProvider = <C extends ComponentType<any>>(\n Component: C,\n): C => {\n const Wrapped = forwardRef((props, ref) => {\n return (\n <SmoothContextProvider>\n <Component {...(props as any)} ref={ref} />\n </SmoothContextProvider>\n );\n });\n Wrapped.displayName = Component.displayName;\n return Wrapped as any;\n};\n\nfunction useSmoothContext(options?: {\n optional?: false | undefined;\n}): SmoothContextValue;\nfunction useSmoothContext(options?: {\n optional?: boolean | undefined;\n}): SmoothContextValue | null;\nfunction useSmoothContext(options?: { optional?: boolean | undefined }) {\n const context = useContext(SmoothContext);\n if (!options?.optional && !context)\n throw new Error(\n \"This component must be used within a SmoothContextProvider.\",\n );\n return context;\n}\n\nexport const { useSmoothStatus, useSmoothStatusStore } = createContextStoreHook(\n useSmoothContext,\n \"useSmoothStatus\",\n);\n"],"mappings":";;;;;;;;AA0BA,MAAMkB,gBAAgBf,cAAyC,IAAI;AAEnE,MAAMgB,qBACJC,iBACG;CAEH,OAAO,EAAEH,iBADeP,aAAaU,YAC5BH,EAAgB;AAC3B;AAEA,MAAaI,yBAA+CC,OAAA;CAAA,MAAAC,IAAAtB,EAAA,CAAA;CAAC,MAAA,EAAAuB,aAAAF;CAAY,IAAAG;CAAA,IAAAF,EAAA,OAAAG,OAAAC,IAAA,2BAAA,GAAA;EACxCF,KAAA,EAAAG,UAAY,KAAK;EAACL,EAAA,KAAAE;CAAA,OAAAA,KAAAF,EAAA;CAAjD,MAAAM,QAAcC,iBAAiBL,EAAkB;CACjD,MAAAM,MAAYjB,OAAO;CAAE,IAAAkB;CAAA,IAAAT,EAAA,OAAAQ,IAAAE,MAAA;EAEMD,WACzBb,kBAAkBY,IAAGE,KAAKC,SAAU,CAAC,CAAAC,MAAO;EAACZ,EAAA,KAAAQ,IAAAE;EAAAV,EAAA,KAAAS;CAAA,OAAAA,KAAAT,EAAA;CAD/C,MAAA,CAAAa,WAAkB5B,SAASwB,EAE3B;CAGA,IAAIH,OAAK,OAASL;CAAS,IAAAa;CAAA,IAAAd,EAAA,OAAAC,YAAAD,EAAA,OAAAa,SAAA;EAGzBC,KAAA,oBAAA,cAAA,UAAA;GAA+BD,OAAAA;GAAUZ;EAAS,CAAA;EAAyBD,EAAA,KAAAC;EAAAD,EAAA,KAAAa;EAAAb,EAAA,KAAAc;CAAA,OAAAA,KAAAd,EAAA;CAAA,OAA3Ec;AAA2E;AAI/E,MAAaC,6BACXC,cACM;CACN,MAAME,UAAUpC,YAAWqC,OAAAC,QAAA;EAAA,MAAApB,IAAAtB,EAAA,CAAA;EAGL,MAAAqB,KAAAoB;EAAY,IAAAjB;EAAA,IAAAF,EAAA,OAAAoB,OAAApB,EAAA,OAAAD,IAAA;GAD9BG,KAAA,oBAAC,uBAAD,EAAA,UACE,oBAAC,WAAD;IAAU,GAAMH;IAAoBqB;GAAG,CAAA,EADnB,CAAA;GAEEpB,EAAA,KAAAoB;GAAApB,EAAA,KAAAD;GAAAC,EAAA,KAAAE;EAAA,OAAAA,KAAAF,EAAA;EAAA,OAFxBE;CAEwB,CAE3B;CACDgB,QAAQG,cAAcL,UAAUK;CAChC,OAAOH;AACT;AAQA,SAAAX,iBAAAe,SAAA;CACE,MAAAT,UAAgB7B,WAAWW,aAAa;CACxC,IAAI,CAAC2B,SAAOjB,YAAR,CAAuBQ,SACzB,MAAM,IAAIU,MACR,6DACF;CAAE,OACGV;AAAO;AAGhB,MAAa,EAAEnB,iBAAiB8B,yBAAyBhC,uBACvDe,kBACA,iBACF"}
@@ -90,7 +90,7 @@ const useSmooth = (state, smooth = false) => {
90
90
  const minCommitMs = positiveOr(options?.minCommitMs, 0);
91
91
  const [displayedText, setDisplayedText] = useState(state.status.type === "running" ? "" : text);
92
92
  const aui = useAui();
93
- const part = useAuiState(() => aui.part());
93
+ const part = useAuiState(() => aui.part);
94
94
  const [prevPart, setPrevPart] = useState(part);
95
95
  if (part !== prevPart || !text.startsWith(displayedText)) {
96
96
  setPrevPart(part);
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.js","names":["useEffect","useMemo","useRef","useState","useAui","useAuiState","MessagePartStatus","ReasoningMessagePart","TextMessagePart","MessagePartState","useCallbackRef","useMediaQuery","useSmoothStatusStore","writableStore","SmoothOptions","drainMs","maxCharIntervalMs","maxCharsPerFrame","minCommitMs","DEFAULT_DRAIN_MS","DEFAULT_MAX_CHAR_INTERVAL_MS","TextStreamAnimator","animationFrameId","lastUpdateTime","Date","now","lastCommitTime","targetText","Infinity","currentText","setText","newText","constructor","start","animate","stop","cancelAnimationFrame","currentTime","deltaTime","timeToConsume","remainingChars","length","baseTimePerChar","Math","min","frameLimit","charsToAdd","requestAnimationFrame","slice","isComplete","SMOOTH_STATUS","Object","freeze","type","positiveOr","value","fallback","undefined","useSmooth","state","smooth","text","reduceMotion","options","enabled","displayedText","setDisplayedText","status","aui","part","prevPart","setPrevPart","startsWith","smoothStatusStore","optional","target","setState","animatorRef","animatorPartRef","partChanged","current"],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useMediaQuery } from \"../hooks/useMediaQuery\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n /**\n * Minimum time in milliseconds between committed updates. The reveal keeps\n * advancing every frame, but the visible text (and the downstream re-render\n * and markdown re-parse it triggers) is committed at most once per interval.\n * The final frame always commits. `0` commits every frame.\n * @default 0\n */\n minCommitMs?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n public lastCommitTime: number = 0;\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n public minCommitMs: number = 0;\n\n public currentText: string;\n private setText: (newText: string) => void;\n\n constructor(currentText: string, setText: (newText: string) => void) {\n this.currentText = currentText;\n this.setText = setText;\n }\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n\n const isComplete = charsToAdd === remainingChars;\n if (isComplete || currentTime - this.lastCommitTime >= this.minCommitMs) {\n this.lastCommitTime = currentTime;\n this.setText(this.currentText);\n }\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * The reveal auto-disables under `prefers-reduced-motion: reduce`,\n * committing the full text immediately; this takes precedence over an\n * explicit `smooth={true}`.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const reduceMotion = useMediaQuery(\"(prefers-reduced-motion: reduce)\");\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null && !reduceMotion;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n const minCommitMs = positiveOr(options?.minCommitMs, 0);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part());\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n animatorRef.minCommitMs = minCommitMs;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame, minCommitMs]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.lastCommitTime = 0;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSmooth.js","names":["useEffect","useMemo","useRef","useState","useAui","useAuiState","MessagePartStatus","ReasoningMessagePart","TextMessagePart","MessagePartState","useCallbackRef","useMediaQuery","useSmoothStatusStore","writableStore","SmoothOptions","drainMs","maxCharIntervalMs","maxCharsPerFrame","minCommitMs","DEFAULT_DRAIN_MS","DEFAULT_MAX_CHAR_INTERVAL_MS","TextStreamAnimator","animationFrameId","lastUpdateTime","Date","now","lastCommitTime","targetText","Infinity","currentText","setText","newText","constructor","start","animate","stop","cancelAnimationFrame","currentTime","deltaTime","timeToConsume","remainingChars","length","baseTimePerChar","Math","min","frameLimit","charsToAdd","requestAnimationFrame","slice","isComplete","SMOOTH_STATUS","Object","freeze","type","positiveOr","value","fallback","undefined","useSmooth","state","smooth","text","reduceMotion","options","enabled","displayedText","setDisplayedText","status","aui","part","prevPart","setPrevPart","startsWith","smoothStatusStore","optional","target","setState","animatorRef","animatorPartRef","partChanged","current"],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useMediaQuery } from \"../hooks/useMediaQuery\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n /**\n * Minimum time in milliseconds between committed updates. The reveal keeps\n * advancing every frame, but the visible text (and the downstream re-render\n * and markdown re-parse it triggers) is committed at most once per interval.\n * The final frame always commits. `0` commits every frame.\n * @default 0\n */\n minCommitMs?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n public lastCommitTime: number = 0;\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n public minCommitMs: number = 0;\n\n public currentText: string;\n private setText: (newText: string) => void;\n\n constructor(currentText: string, setText: (newText: string) => void) {\n this.currentText = currentText;\n this.setText = setText;\n }\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n\n const isComplete = charsToAdd === remainingChars;\n if (isComplete || currentTime - this.lastCommitTime >= this.minCommitMs) {\n this.lastCommitTime = currentTime;\n this.setText(this.currentText);\n }\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * The reveal auto-disables under `prefers-reduced-motion: reduce`,\n * committing the full text immediately; this takes precedence over an\n * explicit `smooth={true}`.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const reduceMotion = useMediaQuery(\"(prefers-reduced-motion: reduce)\");\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null && !reduceMotion;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n const minCommitMs = positiveOr(options?.minCommitMs, 0);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part);\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n animatorRef.minCommitMs = minCommitMs;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame, minCommitMs]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.lastCommitTime = 0;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  //#region src/utils/useToolArgsFieldStatus.d.ts
2
2
  declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
3
+ type: string;
4
+ } | {
3
5
  readonly type: "running";
4
6
  } | {
5
7
  readonly type: "complete";
@@ -7,8 +9,6 @@ declare const useToolArgsFieldStatus: (fieldPath: (string | number)[]) => {
7
9
  readonly type: "incomplete";
8
10
  readonly reason: "cancelled" | "length" | "content-filter" | "other" | "error";
9
11
  readonly error?: unknown;
10
- } | {
11
- type: string;
12
12
  };
13
13
  //#endregion
14
14
  export { useToolArgsFieldStatus };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.29",
3
+ "version": "0.15.1",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,9 +55,9 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.2.23",
59
- "@assistant-ui/store": "^0.2.22",
60
- "@assistant-ui/tap": "^0.9.6",
58
+ "@assistant-ui/core": "^0.3.1",
59
+ "@assistant-ui/store": "^0.3.1",
60
+ "@assistant-ui/tap": "^0.9.7",
61
61
  "@radix-ui/primitive": "^1.1.7",
62
62
  "@radix-ui/react-collection": "^1.1.15",
63
63
  "@radix-ui/react-compose-refs": "^1.1.5",
@@ -67,7 +67,7 @@
67
67
  "@radix-ui/react-use-controllable-state": "^1.2.6",
68
68
  "@radix-ui/react-use-escape-keydown": "^1.1.5",
69
69
  "assistant-cloud": "^0.1.37",
70
- "assistant-stream": "^0.3.28",
70
+ "assistant-stream": "^0.3.30",
71
71
  "nanoid": "^6.0.0",
72
72
  "radix-ui": "^1.6.7",
73
73
  "react-textarea-autosize": "^8.5.9",
@@ -959,10 +959,12 @@ attachTransformScopes(useExternalThread, (scopes, parent) => {
959
959
  if (!scopes.threads && parent.threads.source === null) {
960
960
  const threadElement = scopes.thread as ClientElement<"thread">;
961
961
  scopes.threads = SingleThreadList({ thread: threadElement });
962
+ // scopes mount in key order; re-declare thread after the threads source it resolves from
963
+ delete scopes.thread;
962
964
  scopes.thread = Derived({
963
965
  source: "threads",
964
966
  query: { type: "main" },
965
- get: (aui) => aui.threads().thread("main"),
967
+ get: (aui) => aui.threads.thread("main"),
966
968
  });
967
969
  }
968
970
 
@@ -970,14 +972,14 @@ attachTransformScopes(useExternalThread, (scopes, parent) => {
970
972
  scopes.threadListItem = Derived({
971
973
  source: "threads",
972
974
  query: { type: "main" },
973
- get: (aui) => aui.threads().item("main"),
975
+ get: (aui) => aui.threads.item("main"),
974
976
  });
975
977
  }
976
978
 
977
979
  scopes.composer ??= Derived({
978
980
  source: "thread",
979
981
  query: {},
980
- get: (aui) => aui.thread().composer(),
982
+ get: (aui) => aui.thread.composer(),
981
983
  });
982
984
 
983
985
  if (!scopes.modelContext && parent.modelContext.source === null) {
@@ -192,17 +192,17 @@ attachTransformScopes(useInMemoryThreadList, (scopes, parent) => {
192
192
  scopes.thread ??= Derived({
193
193
  source: "threads",
194
194
  query: { type: "main" },
195
- get: (aui) => aui.threads().thread("main"),
195
+ get: (aui) => aui.threads.thread("main"),
196
196
  });
197
197
  scopes.threadListItem ??= Derived({
198
198
  source: "threads",
199
199
  query: { type: "main" },
200
- get: (aui) => aui.threads().item("main"),
200
+ get: (aui) => aui.threads.item("main"),
201
201
  });
202
202
  scopes.composer ??= Derived({
203
203
  source: "thread",
204
204
  query: {},
205
- get: (aui) => aui.threads().thread("main").composer(),
205
+ get: (aui) => aui.threads.thread("main").composer(),
206
206
  });
207
207
 
208
208
  if (!scopes.modelContext && parent.modelContext.source === null) {
@@ -21,47 +21,3 @@ export {
21
21
  useThreadViewport,
22
22
  useThreadViewportStore,
23
23
  } from "./react/ThreadViewportContext";
24
-
25
- export {
26
- useAssistantRuntime,
27
- useThreadList,
28
- } from "../legacy-runtime/hooks/AssistantContext";
29
-
30
- export {
31
- useAttachmentRuntime,
32
- useAttachment,
33
- useThreadComposerAttachmentRuntime,
34
- useThreadComposerAttachment,
35
- useEditComposerAttachmentRuntime,
36
- useEditComposerAttachment,
37
- useMessageAttachment,
38
- useMessageAttachmentRuntime,
39
- } from "../legacy-runtime/hooks/AttachmentContext";
40
-
41
- export {
42
- useComposerRuntime,
43
- useComposer,
44
- } from "../legacy-runtime/hooks/ComposerContext";
45
-
46
- export {
47
- useMessageRuntime,
48
- useEditComposer,
49
- useMessage,
50
- } from "../legacy-runtime/hooks/MessageContext";
51
-
52
- export {
53
- useMessagePartRuntime,
54
- useMessagePart,
55
- } from "../legacy-runtime/hooks/MessagePartContext";
56
-
57
- export {
58
- useThreadRuntime,
59
- useThread,
60
- useThreadComposer,
61
- useThreadModelContext,
62
- } from "../legacy-runtime/hooks/ThreadContext";
63
-
64
- export {
65
- useThreadListItemRuntime,
66
- useThreadListItem,
67
- } from "../legacy-runtime/hooks/ThreadListItemContext";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect, useState } from "react";
4
- import { useAui, useAuiState } from "@assistant-ui/store";
4
+ import { useAuiState } from "@assistant-ui/store";
5
5
 
6
6
  /**
7
7
  * Hook that returns the elapsed wall-clock time of the current tool call in
@@ -22,17 +22,14 @@ import { useAui, useAuiState } from "@assistant-ui/store";
22
22
  * ```
23
23
  */
24
24
  export const useToolCallElapsed = (): number | undefined => {
25
- const aui = useAui();
26
- const hasPart = aui.part.source !== null;
27
- const timing = useAuiState((s) =>
28
- hasPart && s.part.type === "tool-call" ? s.part.timing : undefined,
29
- );
30
- const partRunning = useAuiState(
31
- (s) =>
32
- hasPart &&
33
- s.part.type === "tool-call" &&
34
- s.part.status.type === "running",
35
- );
25
+ const timing = useAuiState((s) => {
26
+ const part = s.optional.part;
27
+ return part?.type === "tool-call" ? part.timing : undefined;
28
+ });
29
+ const partRunning = useAuiState((s) => {
30
+ const part = s.optional.part;
31
+ return part?.type === "tool-call" && part.status.type === "running";
32
+ });
36
33
  const running =
37
34
  timing !== undefined && timing.completedAt === undefined && partRunning;
38
35
  const [now, setNow] = useState(() => Date.now());
@@ -121,7 +121,7 @@ describe("useAssistantTransportRuntime", () => {
121
121
  const { aui, sendCommand } = mountRuntime({ onError });
122
122
  await waitFor(() =>
123
123
  expect(
124
- (aui().thread().getState().extras as { sendCommand?: unknown })
124
+ (aui().thread.getState().extras as { sendCommand?: unknown })
125
125
  ?.sendCommand,
126
126
  ).toBeTypeOf("function"),
127
127
  );
@@ -139,9 +139,7 @@ describe("useAssistantTransportRuntime", () => {
139
139
 
140
140
  act(() => fetchMock.servers[0]!.close());
141
141
 
142
- await waitFor(() =>
143
- expect(aui().thread().getState().isRunning).toBe(false),
144
- );
142
+ await waitFor(() => expect(aui().thread.getState().isRunning).toBe(false));
145
143
  await act(async () => {});
146
144
  expect(onError).not.toHaveBeenCalled();
147
145
  expect(fetchMock.requests).toHaveLength(1);
@@ -154,7 +152,7 @@ describe("useAssistantTransportRuntime", () => {
154
152
  });
155
153
  await waitFor(() =>
156
154
  expect(
157
- (aui().thread().getState().extras as { sendCommand?: unknown })
155
+ (aui().thread.getState().extras as { sendCommand?: unknown })
158
156
  ?.sendCommand,
159
157
  ).toBeTypeOf("function"),
160
158
  );
@@ -165,7 +163,7 @@ describe("useAssistantTransportRuntime", () => {
165
163
  // Both land while the first run is active and coalesce into one follow-up.
166
164
  act(() => {
167
165
  sendCommand(createMessageCommand("b"));
168
- aui().thread().resumeRun({ parentId: null });
166
+ aui().thread.resumeRun({ parentId: null });
169
167
  });
170
168
 
171
169
  act(() => fetchMock.servers[0]!.close());
@@ -182,8 +180,6 @@ describe("useAssistantTransportRuntime", () => {
182
180
  ]);
183
181
 
184
182
  act(() => fetchMock.servers[2]!.close());
185
- await waitFor(() =>
186
- expect(aui().thread().getState().isRunning).toBe(false),
187
- );
183
+ await waitFor(() => expect(aui().thread.getState().isRunning).toBe(false));
188
184
  });
189
185
  });
@@ -96,7 +96,7 @@ export const useAssistantTransportSendCommand = () => {
96
96
  const aui = useAui();
97
97
 
98
98
  return (command: AssistantTransportCommand) => {
99
- const extras = aui.thread().getState().extras;
99
+ const extras = aui.thread.getState().extras;
100
100
  const transportExtras = asAssistantTransportExtras(extras);
101
101
  transportExtras.sendCommand(command);
102
102
  };