@athenaintel/react 0.23.1 → 0.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +45 -2
  2. package/dist/chat/AthenaChat.cjs +132 -39
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.d.ts +37 -4
  5. package/dist/chat/AthenaChat.js +133 -40
  6. package/dist/chat/AthenaChat.js.map +1 -1
  7. package/dist/chat/athena-composer-controls.js +1 -1
  8. package/dist/chat/statewire-client-tools.cjs +20 -0
  9. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  10. package/dist/chat/statewire-client-tools.d.ts +27 -0
  11. package/dist/chat/statewire-client-tools.js +20 -0
  12. package/dist/chat/statewire-client-tools.js.map +1 -1
  13. package/dist/chat/use-athena-run-live.cjs.map +1 -1
  14. package/dist/chat/use-athena-run-live.d.ts +7 -0
  15. package/dist/chat/use-athena-run-live.js.map +1 -1
  16. package/dist/chat-ui/composer/tiptap-composer.cjs +16 -1
  17. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  18. package/dist/chat-ui/composer/tiptap-composer.js +16 -1
  19. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  20. package/dist/composer/composer-handoff.cjs +121 -0
  21. package/dist/composer/composer-handoff.cjs.map +1 -0
  22. package/dist/composer/composer-handoff.d.ts +89 -0
  23. package/dist/composer/composer-handoff.js +121 -0
  24. package/dist/composer/composer-handoff.js.map +1 -0
  25. package/dist/composer/index.d.ts +2 -0
  26. package/dist/composer/useAppendToComposer.cjs +19 -7
  27. package/dist/composer/useAppendToComposer.cjs.map +1 -1
  28. package/dist/composer/useAppendToComposer.d.ts +22 -4
  29. package/dist/composer/useAppendToComposer.js +20 -8
  30. package/dist/composer/useAppendToComposer.js.map +1 -1
  31. package/dist/composer/useSendMessage.cjs +26 -8
  32. package/dist/composer/useSendMessage.cjs.map +1 -1
  33. package/dist/composer/useSendMessage.d.ts +8 -0
  34. package/dist/composer/useSendMessage.js +27 -9
  35. package/dist/composer/useSendMessage.js.map +1 -1
  36. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  37. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  38. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  39. package/dist/diagnostics/errors.cjs +2 -0
  40. package/dist/diagnostics/errors.cjs.map +1 -1
  41. package/dist/diagnostics/errors.d.ts +2 -0
  42. package/dist/diagnostics/errors.js +2 -0
  43. package/dist/diagnostics/errors.js.map +1 -1
  44. package/dist/dictation/DictationStrip.cjs +60 -0
  45. package/dist/dictation/DictationStrip.cjs.map +1 -0
  46. package/dist/dictation/DictationStrip.d.ts +25 -0
  47. package/dist/dictation/DictationStrip.js +60 -0
  48. package/dist/dictation/DictationStrip.js.map +1 -0
  49. package/dist/dictation/audio-level-history.cjs +68 -0
  50. package/dist/dictation/audio-level-history.cjs.map +1 -0
  51. package/dist/dictation/audio-level-history.d.ts +27 -0
  52. package/dist/dictation/audio-level-history.js +68 -0
  53. package/dist/dictation/audio-level-history.js.map +1 -0
  54. package/dist/dictation/dictation-controls.cjs +60 -0
  55. package/dist/dictation/dictation-controls.cjs.map +1 -0
  56. package/dist/dictation/dictation-controls.d.ts +15 -0
  57. package/dist/dictation/dictation-controls.js +60 -0
  58. package/dist/dictation/dictation-controls.js.map +1 -0
  59. package/dist/dictation/dictation-engine.cjs +128 -0
  60. package/dist/dictation/dictation-engine.cjs.map +1 -0
  61. package/dist/dictation/dictation-engine.d.ts +87 -0
  62. package/dist/dictation/dictation-engine.js +128 -0
  63. package/dist/dictation/dictation-engine.js.map +1 -0
  64. package/dist/dictation/dictation-state.cjs +68 -0
  65. package/dist/dictation/dictation-state.cjs.map +1 -0
  66. package/dist/dictation/dictation-state.d.ts +58 -0
  67. package/dist/dictation/dictation-state.js +68 -0
  68. package/dist/dictation/dictation-state.js.map +1 -0
  69. package/dist/dictation/use-dictation.cjs +204 -0
  70. package/dist/dictation/use-dictation.cjs.map +1 -0
  71. package/dist/dictation/use-dictation.d.ts +47 -0
  72. package/dist/dictation/use-dictation.js +204 -0
  73. package/dist/dictation/use-dictation.js.map +1 -0
  74. package/dist/index.cjs +13 -0
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.ts +11 -0
  77. package/dist/index.js +13 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/packages/react/package.json.cjs +1 -1
  80. package/dist/packages/react/package.json.js +1 -1
  81. package/dist/provider/AthenaProvider.cjs +42 -3
  82. package/dist/provider/AthenaProvider.cjs.map +1 -1
  83. package/dist/provider/AthenaProvider.d.ts +1 -1
  84. package/dist/provider/AthenaProvider.js +43 -4
  85. package/dist/provider/AthenaProvider.js.map +1 -1
  86. package/dist/styles.css +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react$1 = require("react");
4
+ const react = require("@assistant-ui/react");
5
+ const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
6
+ const errors = require("../diagnostics/errors.cjs");
7
+ const statewireThreadList = require("../threads/statewire-thread-list.cjs");
8
+ function createComposerHandoffStore() {
9
+ return { pending: [], focusOnNextMirror: false, focusEnd: null };
10
+ }
11
+ const ComposerHandoffContext = react$1.createContext(null);
12
+ function useComposerHandoffStore() {
13
+ return react$1.useContext(ComposerHandoffContext);
14
+ }
15
+ function useComposerReady() {
16
+ const list = statewireThreadList.useAthenaStatewireThreadList();
17
+ const threadLoading = react.useAuiState((s) => {
18
+ var _a;
19
+ return ((_a = s.optional.thread) == null ? void 0 : _a.isLoading) ?? false;
20
+ });
21
+ return !((list == null ? void 0 : list.isLoading) ?? false) && !threadLoading;
22
+ }
23
+ function mergeComposerDraft({
24
+ current,
25
+ write
26
+ }) {
27
+ if (write.replace || !current) return write.text;
28
+ return `${current}
29
+ ${write.text}`;
30
+ }
31
+ function applyComposerWrite({
32
+ target,
33
+ store,
34
+ write
35
+ }) {
36
+ var _a;
37
+ const current = target.composer.getState().text ?? "";
38
+ const next = mergeComposerDraft({ current, write });
39
+ target.composer.setText(next);
40
+ if (write.focus && store) {
41
+ if (next === current) (_a = store.focusEnd) == null ? void 0 : _a.call(store);
42
+ else store.focusOnNextMirror = true;
43
+ }
44
+ athenaDiagnostics.athenaDiagnostics.emit("composer.handoff", {
45
+ level: "debug",
46
+ data: { phase: "applied", send: write.send, focus: write.focus, chars: next.length }
47
+ });
48
+ if (write.send) {
49
+ return Promise.resolve(target.composer.send({ steer: false })).then(() => void 0);
50
+ }
51
+ return void 0;
52
+ }
53
+ function queueComposerWrite({
54
+ store,
55
+ write
56
+ }) {
57
+ const last = store.pending[store.pending.length - 1];
58
+ const foldable = last !== void 0 && !last.send && !write.send && !write.replace && last.threadId === write.threadId && last.resolve === void 0 && write.resolve === void 0;
59
+ if (foldable) {
60
+ last.text = mergeComposerDraft({ current: last.text, write });
61
+ last.focus = last.focus || write.focus;
62
+ } else {
63
+ store.pending.push(write);
64
+ }
65
+ athenaDiagnostics.athenaDiagnostics.emit("composer.handoff", {
66
+ level: "debug",
67
+ data: { phase: "deferred", send: write.send, focus: write.focus, queued: store.pending.length }
68
+ });
69
+ }
70
+ function dropWrite({ write, reason }) {
71
+ var _a;
72
+ const error = new errors.AthenaSdkError({
73
+ code: "composer_write_dropped",
74
+ message: `A composer ${write.send ? "send" : "draft"} queued before the chat was ready was dropped: ${reason}.`,
75
+ hint: "Make the write again from the chat that is active now.",
76
+ context: { threadId: write.threadId, send: write.send }
77
+ });
78
+ athenaDiagnostics.athenaDiagnostics.emit("composer.handoff", {
79
+ level: "warn",
80
+ error,
81
+ data: { phase: "dropped", send: write.send, reason, threadId: write.threadId }
82
+ });
83
+ (_a = write.reject) == null ? void 0 : _a.call(write, error);
84
+ }
85
+ function drainComposerHandoff({
86
+ store,
87
+ target,
88
+ threadId
89
+ }) {
90
+ var _a;
91
+ const writes = store.pending;
92
+ if (writes.length === 0) return;
93
+ store.pending = [];
94
+ for (const write of writes) {
95
+ if (write.threadId !== null && threadId !== null && write.threadId !== threadId) {
96
+ dropWrite({ write, reason: `it was made for thread ${write.threadId}, thread ${threadId} is active` });
97
+ continue;
98
+ }
99
+ const outcome = applyComposerWrite({ target, store, write });
100
+ (_a = write.resolve) == null ? void 0 : _a.call(write, outcome);
101
+ }
102
+ }
103
+ function clearComposerHandoff({
104
+ store,
105
+ reason
106
+ }) {
107
+ const writes = store.pending;
108
+ if (writes.length === 0) return;
109
+ store.pending = [];
110
+ for (const write of writes) dropWrite({ write, reason });
111
+ }
112
+ exports.ComposerHandoffContext = ComposerHandoffContext;
113
+ exports.applyComposerWrite = applyComposerWrite;
114
+ exports.clearComposerHandoff = clearComposerHandoff;
115
+ exports.createComposerHandoffStore = createComposerHandoffStore;
116
+ exports.drainComposerHandoff = drainComposerHandoff;
117
+ exports.mergeComposerDraft = mergeComposerDraft;
118
+ exports.queueComposerWrite = queueComposerWrite;
119
+ exports.useComposerHandoffStore = useComposerHandoffStore;
120
+ exports.useComposerReady = useComposerReady;
121
+ //# sourceMappingURL=composer-handoff.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer-handoff.cjs","sources":["../../src/composer/composer-handoff.ts"],"sourcesContent":["/**\n * Composer handoff: writes to the chat composer from host UI that lives\n * outside `<AthenaChat>` (a sidebar, an agenda card, a workflow button).\n *\n * Two problems it solves for `useAppendToComposer` / `useSendMessage`:\n *\n * 1. A write before the chat is ready is lost. While the thread list is still\n * resolving the active thread, or the thread's history is still loading,\n * the composer the hooks write to belongs to a mount that is about to be\n * replaced (thread-list mode keys the statewire subtree by thread), and\n * the draft goes with it. The store below lives ABOVE that mount, so a\n * write made too early waits here and the ready mount drains it.\n * 2. A prefilled draft should put the caret after the text so the user can\n * keep typing or dictating. The Tiptap editor mirrors runtime text on a\n * later tick; the `focusOnNextMirror` flag asks it to focus at the end\n * right after that mirror lands, instead of the host guessing a delay.\n *\n * Every queued write is bound to the statewire thread it was made for and\n * drains only into that thread's mount; a thread switch, an identity change\n * or a provider unmount drops what waits with a `composer_write_dropped`\n * error, never into another user's or another thread's composer.\n */\nimport { createContext, useContext } from 'react';\nimport { useAuiState } from '@assistant-ui/react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\n\n/** The slice of the assistant-ui client the handoff writes to. */\nexport interface ComposerTarget {\n composer: {\n getState: () => { text: string };\n setText: (text: string) => void;\n send: (options?: { steer?: boolean }) => unknown;\n };\n}\n\nexport interface ComposerWrite {\n text: string;\n /** Replace the draft instead of appending on a new line. */\n replace: boolean;\n /** Submit right after writing. */\n send: boolean;\n /** Put the caret at the end of the draft once the editor shows it. */\n focus: boolean;\n /** The statewire thread the write was made for; it drains only into that thread's mount. */\n threadId: string | null;\n /** Settled once the write has been applied (and, for `send`, with the send's outcome). */\n resolve?: (outcome: Promise<void> | void) => void;\n /** Settled with `composer_write_dropped` when the write can no longer land. */\n reject?: (error: AthenaSdkError) => void;\n}\n\nexport interface ComposerHandoffStore {\n /** Writes waiting for a ready composer, oldest first. Adjacent plain appends fold together. */\n pending: ComposerWrite[];\n /** Set by a `focus` write; the editor consumes it after mirroring the draft. */\n focusOnNextMirror: boolean;\n /** Registered by the mounted editor: place the caret at the end of the draft. */\n focusEnd: (() => void) | null;\n}\n\nexport function createComposerHandoffStore(): ComposerHandoffStore {\n return { pending: [], focusOnNextMirror: false, focusEnd: null };\n}\n\nexport const ComposerHandoffContext = createContext<ComposerHandoffStore | null>(null);\n\n/** The provider's handoff store, or null outside `AthenaProvider`. */\nexport function useComposerHandoffStore(): ComposerHandoffStore | null {\n return useContext(ComposerHandoffContext);\n}\n\n/**\n * Whether a composer write lands now: in thread-list mode the list has\n * resolved which thread is active, and that thread's history has loaded.\n * Until then the composer the hooks write to is the one of a mount that is\n * about to be replaced, and the draft goes with it.\n *\n * Deliberately not the socket status: a never-started chat sits in\n * `standby` until its first send and must still take a prefill.\n */\nexport function useComposerReady(): boolean {\n const list = useAthenaStatewireThreadList();\n const threadLoading = useAuiState((s) => s.optional.thread?.isLoading ?? false);\n return !(list?.isLoading ?? false) && !threadLoading;\n}\n\n/** The draft after `write`: `replace` (or an empty draft) takes the text as is. */\nexport function mergeComposerDraft({\n current,\n write,\n}: {\n current: string;\n write: Pick<ComposerWrite, 'text' | 'replace'>;\n}): string {\n if (write.replace || !current) return write.text;\n return `${current}\\n${write.text}`;\n}\n\n/**\n * Write into the live composer. Returns the send's outcome for a `send`\n * write so callers can await it.\n */\nexport function applyComposerWrite({\n target,\n store,\n write,\n}: {\n target: ComposerTarget;\n store: ComposerHandoffStore | null;\n write: ComposerWrite;\n}): Promise<void> | void {\n const current = target.composer.getState().text ?? '';\n const next = mergeComposerDraft({ current, write });\n target.composer.setText(next);\n if (write.focus && store) {\n // An unchanged draft never re-mirrors, so focus at once; otherwise the\n // editor focuses itself right after it has shown the new text.\n if (next === current) store.focusEnd?.();\n else store.focusOnNextMirror = true;\n }\n athenaDiagnostics.emit('composer.handoff', {\n level: 'debug',\n data: { phase: 'applied', send: write.send, focus: write.focus, chars: next.length },\n });\n if (write.send) {\n return Promise.resolve(target.composer.send({ steer: false })).then(() => undefined);\n }\n return undefined;\n}\n\n/**\n * Hold a write until the composer is ready. Writes keep their order; a plain\n * append folds into a directly preceding plain append for the same thread\n * (one draft, not two), while a replace or a send is kept as its own step so\n * no message is lost and every send settles on its own.\n */\nexport function queueComposerWrite({\n store,\n write,\n}: {\n store: ComposerHandoffStore;\n write: ComposerWrite;\n}): void {\n const last = store.pending[store.pending.length - 1];\n const foldable =\n last !== undefined &&\n !last.send &&\n !write.send &&\n !write.replace &&\n last.threadId === write.threadId &&\n last.resolve === undefined &&\n write.resolve === undefined;\n if (foldable) {\n last.text = mergeComposerDraft({ current: last.text, write });\n last.focus = last.focus || write.focus;\n } else {\n store.pending.push(write);\n }\n athenaDiagnostics.emit('composer.handoff', {\n level: 'debug',\n data: { phase: 'deferred', send: write.send, focus: write.focus, queued: store.pending.length },\n });\n}\n\nfunction dropWrite({ write, reason }: { write: ComposerWrite; reason: string }): void {\n const error = new AthenaSdkError({\n code: 'composer_write_dropped',\n message: `A composer ${write.send ? 'send' : 'draft'} queued before the chat was ready was dropped: ${reason}.`,\n hint: 'Make the write again from the chat that is active now.',\n context: { threadId: write.threadId, send: write.send },\n });\n athenaDiagnostics.emit('composer.handoff', {\n level: 'warn',\n error,\n data: { phase: 'dropped', send: write.send, reason, threadId: write.threadId },\n });\n write.reject?.(error);\n}\n\n/**\n * Deliver the pending writes, in order, into a now-ready composer. A write\n * made for another thread is dropped (rejected), never applied here.\n */\nexport function drainComposerHandoff({\n store,\n target,\n threadId,\n}: {\n store: ComposerHandoffStore;\n target: ComposerTarget;\n /** The statewire thread of the mount that is draining. */\n threadId: string | null;\n}): void {\n const writes = store.pending;\n if (writes.length === 0) return;\n store.pending = [];\n for (const write of writes) {\n if (write.threadId !== null && threadId !== null && write.threadId !== threadId) {\n dropWrite({ write, reason: `it was made for thread ${write.threadId}, thread ${threadId} is active` });\n continue;\n }\n const outcome = applyComposerWrite({ target, store, write });\n write.resolve?.(outcome);\n }\n}\n\n/** Drop every pending write (identity changed, provider unmounted). */\nexport function clearComposerHandoff({\n store,\n reason,\n}: {\n store: ComposerHandoffStore;\n reason: string;\n}): void {\n const writes = store.pending;\n if (writes.length === 0) return;\n store.pending = [];\n for (const write of writes) dropWrite({ write, reason });\n}\n"],"names":["createContext","useContext","useAthenaStatewireThreadList","useAuiState","athenaDiagnostics","AthenaSdkError"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { AthenaSdkError } from '../diagnostics/errors';
2
+ /** The slice of the assistant-ui client the handoff writes to. */
3
+ export interface ComposerTarget {
4
+ composer: {
5
+ getState: () => {
6
+ text: string;
7
+ };
8
+ setText: (text: string) => void;
9
+ send: (options?: {
10
+ steer?: boolean;
11
+ }) => unknown;
12
+ };
13
+ }
14
+ export interface ComposerWrite {
15
+ text: string;
16
+ /** Replace the draft instead of appending on a new line. */
17
+ replace: boolean;
18
+ /** Submit right after writing. */
19
+ send: boolean;
20
+ /** Put the caret at the end of the draft once the editor shows it. */
21
+ focus: boolean;
22
+ /** The statewire thread the write was made for; it drains only into that thread's mount. */
23
+ threadId: string | null;
24
+ /** Settled once the write has been applied (and, for `send`, with the send's outcome). */
25
+ resolve?: (outcome: Promise<void> | void) => void;
26
+ /** Settled with `composer_write_dropped` when the write can no longer land. */
27
+ reject?: (error: AthenaSdkError) => void;
28
+ }
29
+ export interface ComposerHandoffStore {
30
+ /** Writes waiting for a ready composer, oldest first. Adjacent plain appends fold together. */
31
+ pending: ComposerWrite[];
32
+ /** Set by a `focus` write; the editor consumes it after mirroring the draft. */
33
+ focusOnNextMirror: boolean;
34
+ /** Registered by the mounted editor: place the caret at the end of the draft. */
35
+ focusEnd: (() => void) | null;
36
+ }
37
+ export declare function createComposerHandoffStore(): ComposerHandoffStore;
38
+ export declare const ComposerHandoffContext: import("react").Context<ComposerHandoffStore | null>;
39
+ /** The provider's handoff store, or null outside `AthenaProvider`. */
40
+ export declare function useComposerHandoffStore(): ComposerHandoffStore | null;
41
+ /**
42
+ * Whether a composer write lands now: in thread-list mode the list has
43
+ * resolved which thread is active, and that thread's history has loaded.
44
+ * Until then the composer the hooks write to is the one of a mount that is
45
+ * about to be replaced, and the draft goes with it.
46
+ *
47
+ * Deliberately not the socket status: a never-started chat sits in
48
+ * `standby` until its first send and must still take a prefill.
49
+ */
50
+ export declare function useComposerReady(): boolean;
51
+ /** The draft after `write`: `replace` (or an empty draft) takes the text as is. */
52
+ export declare function mergeComposerDraft({ current, write, }: {
53
+ current: string;
54
+ write: Pick<ComposerWrite, 'text' | 'replace'>;
55
+ }): string;
56
+ /**
57
+ * Write into the live composer. Returns the send's outcome for a `send`
58
+ * write so callers can await it.
59
+ */
60
+ export declare function applyComposerWrite({ target, store, write, }: {
61
+ target: ComposerTarget;
62
+ store: ComposerHandoffStore | null;
63
+ write: ComposerWrite;
64
+ }): Promise<void> | void;
65
+ /**
66
+ * Hold a write until the composer is ready. Writes keep their order; a plain
67
+ * append folds into a directly preceding plain append for the same thread
68
+ * (one draft, not two), while a replace or a send is kept as its own step so
69
+ * no message is lost and every send settles on its own.
70
+ */
71
+ export declare function queueComposerWrite({ store, write, }: {
72
+ store: ComposerHandoffStore;
73
+ write: ComposerWrite;
74
+ }): void;
75
+ /**
76
+ * Deliver the pending writes, in order, into a now-ready composer. A write
77
+ * made for another thread is dropped (rejected), never applied here.
78
+ */
79
+ export declare function drainComposerHandoff({ store, target, threadId, }: {
80
+ store: ComposerHandoffStore;
81
+ target: ComposerTarget;
82
+ /** The statewire thread of the mount that is draining. */
83
+ threadId: string | null;
84
+ }): void;
85
+ /** Drop every pending write (identity changed, provider unmounted). */
86
+ export declare function clearComposerHandoff({ store, reason, }: {
87
+ store: ComposerHandoffStore;
88
+ reason: string;
89
+ }): void;
@@ -0,0 +1,121 @@
1
+ import { createContext, useContext } from "react";
2
+ import { useAuiState } from "@assistant-ui/react";
3
+ import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
4
+ import { AthenaSdkError } from "../diagnostics/errors.js";
5
+ import { useAthenaStatewireThreadList } from "../threads/statewire-thread-list.js";
6
+ function createComposerHandoffStore() {
7
+ return { pending: [], focusOnNextMirror: false, focusEnd: null };
8
+ }
9
+ const ComposerHandoffContext = createContext(null);
10
+ function useComposerHandoffStore() {
11
+ return useContext(ComposerHandoffContext);
12
+ }
13
+ function useComposerReady() {
14
+ const list = useAthenaStatewireThreadList();
15
+ const threadLoading = useAuiState((s) => {
16
+ var _a;
17
+ return ((_a = s.optional.thread) == null ? void 0 : _a.isLoading) ?? false;
18
+ });
19
+ return !((list == null ? void 0 : list.isLoading) ?? false) && !threadLoading;
20
+ }
21
+ function mergeComposerDraft({
22
+ current,
23
+ write
24
+ }) {
25
+ if (write.replace || !current) return write.text;
26
+ return `${current}
27
+ ${write.text}`;
28
+ }
29
+ function applyComposerWrite({
30
+ target,
31
+ store,
32
+ write
33
+ }) {
34
+ var _a;
35
+ const current = target.composer.getState().text ?? "";
36
+ const next = mergeComposerDraft({ current, write });
37
+ target.composer.setText(next);
38
+ if (write.focus && store) {
39
+ if (next === current) (_a = store.focusEnd) == null ? void 0 : _a.call(store);
40
+ else store.focusOnNextMirror = true;
41
+ }
42
+ athenaDiagnostics.emit("composer.handoff", {
43
+ level: "debug",
44
+ data: { phase: "applied", send: write.send, focus: write.focus, chars: next.length }
45
+ });
46
+ if (write.send) {
47
+ return Promise.resolve(target.composer.send({ steer: false })).then(() => void 0);
48
+ }
49
+ return void 0;
50
+ }
51
+ function queueComposerWrite({
52
+ store,
53
+ write
54
+ }) {
55
+ const last = store.pending[store.pending.length - 1];
56
+ const foldable = last !== void 0 && !last.send && !write.send && !write.replace && last.threadId === write.threadId && last.resolve === void 0 && write.resolve === void 0;
57
+ if (foldable) {
58
+ last.text = mergeComposerDraft({ current: last.text, write });
59
+ last.focus = last.focus || write.focus;
60
+ } else {
61
+ store.pending.push(write);
62
+ }
63
+ athenaDiagnostics.emit("composer.handoff", {
64
+ level: "debug",
65
+ data: { phase: "deferred", send: write.send, focus: write.focus, queued: store.pending.length }
66
+ });
67
+ }
68
+ function dropWrite({ write, reason }) {
69
+ var _a;
70
+ const error = new AthenaSdkError({
71
+ code: "composer_write_dropped",
72
+ message: `A composer ${write.send ? "send" : "draft"} queued before the chat was ready was dropped: ${reason}.`,
73
+ hint: "Make the write again from the chat that is active now.",
74
+ context: { threadId: write.threadId, send: write.send }
75
+ });
76
+ athenaDiagnostics.emit("composer.handoff", {
77
+ level: "warn",
78
+ error,
79
+ data: { phase: "dropped", send: write.send, reason, threadId: write.threadId }
80
+ });
81
+ (_a = write.reject) == null ? void 0 : _a.call(write, error);
82
+ }
83
+ function drainComposerHandoff({
84
+ store,
85
+ target,
86
+ threadId
87
+ }) {
88
+ var _a;
89
+ const writes = store.pending;
90
+ if (writes.length === 0) return;
91
+ store.pending = [];
92
+ for (const write of writes) {
93
+ if (write.threadId !== null && threadId !== null && write.threadId !== threadId) {
94
+ dropWrite({ write, reason: `it was made for thread ${write.threadId}, thread ${threadId} is active` });
95
+ continue;
96
+ }
97
+ const outcome = applyComposerWrite({ target, store, write });
98
+ (_a = write.resolve) == null ? void 0 : _a.call(write, outcome);
99
+ }
100
+ }
101
+ function clearComposerHandoff({
102
+ store,
103
+ reason
104
+ }) {
105
+ const writes = store.pending;
106
+ if (writes.length === 0) return;
107
+ store.pending = [];
108
+ for (const write of writes) dropWrite({ write, reason });
109
+ }
110
+ export {
111
+ ComposerHandoffContext,
112
+ applyComposerWrite,
113
+ clearComposerHandoff,
114
+ createComposerHandoffStore,
115
+ drainComposerHandoff,
116
+ mergeComposerDraft,
117
+ queueComposerWrite,
118
+ useComposerHandoffStore,
119
+ useComposerReady
120
+ };
121
+ //# sourceMappingURL=composer-handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer-handoff.js","sources":["../../src/composer/composer-handoff.ts"],"sourcesContent":["/**\n * Composer handoff: writes to the chat composer from host UI that lives\n * outside `<AthenaChat>` (a sidebar, an agenda card, a workflow button).\n *\n * Two problems it solves for `useAppendToComposer` / `useSendMessage`:\n *\n * 1. A write before the chat is ready is lost. While the thread list is still\n * resolving the active thread, or the thread's history is still loading,\n * the composer the hooks write to belongs to a mount that is about to be\n * replaced (thread-list mode keys the statewire subtree by thread), and\n * the draft goes with it. The store below lives ABOVE that mount, so a\n * write made too early waits here and the ready mount drains it.\n * 2. A prefilled draft should put the caret after the text so the user can\n * keep typing or dictating. The Tiptap editor mirrors runtime text on a\n * later tick; the `focusOnNextMirror` flag asks it to focus at the end\n * right after that mirror lands, instead of the host guessing a delay.\n *\n * Every queued write is bound to the statewire thread it was made for and\n * drains only into that thread's mount; a thread switch, an identity change\n * or a provider unmount drops what waits with a `composer_write_dropped`\n * error, never into another user's or another thread's composer.\n */\nimport { createContext, useContext } from 'react';\nimport { useAuiState } from '@assistant-ui/react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaStatewireThreadList } from '../threads/statewire-thread-list';\n\n/** The slice of the assistant-ui client the handoff writes to. */\nexport interface ComposerTarget {\n composer: {\n getState: () => { text: string };\n setText: (text: string) => void;\n send: (options?: { steer?: boolean }) => unknown;\n };\n}\n\nexport interface ComposerWrite {\n text: string;\n /** Replace the draft instead of appending on a new line. */\n replace: boolean;\n /** Submit right after writing. */\n send: boolean;\n /** Put the caret at the end of the draft once the editor shows it. */\n focus: boolean;\n /** The statewire thread the write was made for; it drains only into that thread's mount. */\n threadId: string | null;\n /** Settled once the write has been applied (and, for `send`, with the send's outcome). */\n resolve?: (outcome: Promise<void> | void) => void;\n /** Settled with `composer_write_dropped` when the write can no longer land. */\n reject?: (error: AthenaSdkError) => void;\n}\n\nexport interface ComposerHandoffStore {\n /** Writes waiting for a ready composer, oldest first. Adjacent plain appends fold together. */\n pending: ComposerWrite[];\n /** Set by a `focus` write; the editor consumes it after mirroring the draft. */\n focusOnNextMirror: boolean;\n /** Registered by the mounted editor: place the caret at the end of the draft. */\n focusEnd: (() => void) | null;\n}\n\nexport function createComposerHandoffStore(): ComposerHandoffStore {\n return { pending: [], focusOnNextMirror: false, focusEnd: null };\n}\n\nexport const ComposerHandoffContext = createContext<ComposerHandoffStore | null>(null);\n\n/** The provider's handoff store, or null outside `AthenaProvider`. */\nexport function useComposerHandoffStore(): ComposerHandoffStore | null {\n return useContext(ComposerHandoffContext);\n}\n\n/**\n * Whether a composer write lands now: in thread-list mode the list has\n * resolved which thread is active, and that thread's history has loaded.\n * Until then the composer the hooks write to is the one of a mount that is\n * about to be replaced, and the draft goes with it.\n *\n * Deliberately not the socket status: a never-started chat sits in\n * `standby` until its first send and must still take a prefill.\n */\nexport function useComposerReady(): boolean {\n const list = useAthenaStatewireThreadList();\n const threadLoading = useAuiState((s) => s.optional.thread?.isLoading ?? false);\n return !(list?.isLoading ?? false) && !threadLoading;\n}\n\n/** The draft after `write`: `replace` (or an empty draft) takes the text as is. */\nexport function mergeComposerDraft({\n current,\n write,\n}: {\n current: string;\n write: Pick<ComposerWrite, 'text' | 'replace'>;\n}): string {\n if (write.replace || !current) return write.text;\n return `${current}\\n${write.text}`;\n}\n\n/**\n * Write into the live composer. Returns the send's outcome for a `send`\n * write so callers can await it.\n */\nexport function applyComposerWrite({\n target,\n store,\n write,\n}: {\n target: ComposerTarget;\n store: ComposerHandoffStore | null;\n write: ComposerWrite;\n}): Promise<void> | void {\n const current = target.composer.getState().text ?? '';\n const next = mergeComposerDraft({ current, write });\n target.composer.setText(next);\n if (write.focus && store) {\n // An unchanged draft never re-mirrors, so focus at once; otherwise the\n // editor focuses itself right after it has shown the new text.\n if (next === current) store.focusEnd?.();\n else store.focusOnNextMirror = true;\n }\n athenaDiagnostics.emit('composer.handoff', {\n level: 'debug',\n data: { phase: 'applied', send: write.send, focus: write.focus, chars: next.length },\n });\n if (write.send) {\n return Promise.resolve(target.composer.send({ steer: false })).then(() => undefined);\n }\n return undefined;\n}\n\n/**\n * Hold a write until the composer is ready. Writes keep their order; a plain\n * append folds into a directly preceding plain append for the same thread\n * (one draft, not two), while a replace or a send is kept as its own step so\n * no message is lost and every send settles on its own.\n */\nexport function queueComposerWrite({\n store,\n write,\n}: {\n store: ComposerHandoffStore;\n write: ComposerWrite;\n}): void {\n const last = store.pending[store.pending.length - 1];\n const foldable =\n last !== undefined &&\n !last.send &&\n !write.send &&\n !write.replace &&\n last.threadId === write.threadId &&\n last.resolve === undefined &&\n write.resolve === undefined;\n if (foldable) {\n last.text = mergeComposerDraft({ current: last.text, write });\n last.focus = last.focus || write.focus;\n } else {\n store.pending.push(write);\n }\n athenaDiagnostics.emit('composer.handoff', {\n level: 'debug',\n data: { phase: 'deferred', send: write.send, focus: write.focus, queued: store.pending.length },\n });\n}\n\nfunction dropWrite({ write, reason }: { write: ComposerWrite; reason: string }): void {\n const error = new AthenaSdkError({\n code: 'composer_write_dropped',\n message: `A composer ${write.send ? 'send' : 'draft'} queued before the chat was ready was dropped: ${reason}.`,\n hint: 'Make the write again from the chat that is active now.',\n context: { threadId: write.threadId, send: write.send },\n });\n athenaDiagnostics.emit('composer.handoff', {\n level: 'warn',\n error,\n data: { phase: 'dropped', send: write.send, reason, threadId: write.threadId },\n });\n write.reject?.(error);\n}\n\n/**\n * Deliver the pending writes, in order, into a now-ready composer. A write\n * made for another thread is dropped (rejected), never applied here.\n */\nexport function drainComposerHandoff({\n store,\n target,\n threadId,\n}: {\n store: ComposerHandoffStore;\n target: ComposerTarget;\n /** The statewire thread of the mount that is draining. */\n threadId: string | null;\n}): void {\n const writes = store.pending;\n if (writes.length === 0) return;\n store.pending = [];\n for (const write of writes) {\n if (write.threadId !== null && threadId !== null && write.threadId !== threadId) {\n dropWrite({ write, reason: `it was made for thread ${write.threadId}, thread ${threadId} is active` });\n continue;\n }\n const outcome = applyComposerWrite({ target, store, write });\n write.resolve?.(outcome);\n }\n}\n\n/** Drop every pending write (identity changed, provider unmounted). */\nexport function clearComposerHandoff({\n store,\n reason,\n}: {\n store: ComposerHandoffStore;\n reason: string;\n}): void {\n const writes = store.pending;\n if (writes.length === 0) return;\n store.pending = [];\n for (const write of writes) dropWrite({ write, reason });\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  export { useAppendToComposer } from './useAppendToComposer';
2
+ export type { AppendToComposerOptions } from './useAppendToComposer';
2
3
  export { useSendMessage } from './useSendMessage';
3
4
  export type { SendMessageOptions } from './useSendMessage';
5
+ export { useComposerReady } from './composer-handoff';
4
6
  export { useComposerAttachment } from './useComposerAttachment';
5
7
  export { useFileUpload } from './useFileUpload';
6
8
  export type { UploadedAsset, UploadProgress, UseFileUploadReturn } from './useFileUpload';
@@ -2,19 +2,31 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react$1 = require("react");
4
4
  const react = require("@assistant-ui/react");
5
+ const threadIdContext = require("../threads/thread-id-context.cjs");
6
+ const composerHandoff = require("./composer-handoff.cjs");
5
7
  function useAppendToComposer() {
6
8
  const aui = react.useAui();
9
+ const store = composerHandoff.useComposerHandoffStore();
10
+ const threadId = react$1.useContext(threadIdContext.AthenaStatewireThreadIdContext);
11
+ const ready = composerHandoff.useComposerReady();
12
+ const readyRef = react$1.useRef(ready);
13
+ readyRef.current = ready;
7
14
  return react$1.useCallback(
8
15
  (text, opts) => {
9
- if (opts == null ? void 0 : opts.replace) {
10
- aui.composer.setText(text);
11
- } else {
12
- const current = aui.composer.getState().text;
13
- aui.composer.setText(current ? `${current}
14
- ${text}` : text);
16
+ const write = {
17
+ text,
18
+ replace: (opts == null ? void 0 : opts.replace) ?? false,
19
+ send: false,
20
+ focus: (opts == null ? void 0 : opts.focus) ?? false,
21
+ threadId
22
+ };
23
+ if (store && !readyRef.current) {
24
+ composerHandoff.queueComposerWrite({ store, write });
25
+ return;
15
26
  }
27
+ composerHandoff.applyComposerWrite({ target: aui, store, write });
16
28
  },
17
- [aui]
29
+ [aui, store, threadId]
18
30
  );
19
31
  }
20
32
  exports.useAppendToComposer = useAppendToComposer;
@@ -1 +1 @@
1
- {"version":3,"file":"useAppendToComposer.cjs","sources":["../../src/composer/useAppendToComposer.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useAui } from '@assistant-ui/react';\n\n/**\n * Hook to programmatically append text to the composer without sending it.\n *\n * Use `useSendMessage()` for workflow shortcuts or other UI that should\n * immediately submit a prompt.\n *\n * Usage:\n * const append = useAppendToComposer();\n * append(\"some text to add\");\n * append(\"quoted text\", { replace: true }); // replaces existing text\n */\nexport function useAppendToComposer() {\n const aui = useAui();\n\n return useCallback(\n (text: string, opts?: { replace?: boolean }) => {\n if (opts?.replace) {\n aui.composer.setText(text);\n } else {\n const current = aui.composer.getState().text;\n aui.composer.setText(current ? `${current}\\n${text}` : text);\n }\n },\n [aui],\n );\n}\n"],"names":["useAui","useCallback"],"mappings":";;;;AAcO,SAAS,sBAAsB;AACpC,QAAM,MAAMA,MAAAA,OAAA;AAEZ,SAAOC,QAAAA;AAAAA,IACL,CAAC,MAAc,SAAiC;AAC9C,UAAI,6BAAM,SAAS;AACjB,YAAI,SAAS,QAAQ,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM,UAAU,IAAI,SAAS,SAAA,EAAW;AACxC,YAAI,SAAS,QAAQ,UAAU,GAAG,OAAO;AAAA,EAAK,IAAI,KAAK,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAER;;"}
1
+ {"version":3,"file":"useAppendToComposer.cjs","sources":["../../src/composer/useAppendToComposer.ts"],"sourcesContent":["import { useCallback, useContext, useRef } from 'react';\nimport { useAui } from '@assistant-ui/react';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport {\n applyComposerWrite,\n queueComposerWrite,\n useComposerHandoffStore,\n useComposerReady,\n type ComposerWrite,\n} from './composer-handoff';\n\nexport interface AppendToComposerOptions {\n /** Replace the current draft instead of appending on a new line. */\n replace?: boolean;\n /**\n * Focus the composer with the caret after the text once the editor shows\n * it — for a prefilled draft the user is meant to finish by typing or\n * dictating. Defaults to false.\n */\n focus?: boolean;\n}\n\n/**\n * Hook to programmatically append text to the composer without sending it.\n *\n * Safe to call at any time: a write made before the chat is ready (the\n * thread list still resolving the active thread, or the thread's history\n * still loading) waits in the provider and lands once the composer is\n * ready, instead of being lost with the mount it was written into. A\n * never-started chat is ready without a live socket. The write is bound to\n * the thread it was made for: if the user switches threads first, it is\n * dropped (a `composer.handoff` diagnostic says so), never written into\n * another thread.\n *\n * Use `useSendMessage()` for workflow shortcuts or other UI that should\n * immediately submit a prompt.\n *\n * Usage:\n * const append = useAppendToComposer();\n * append(\"some text to add\");\n * append(\"quoted text\", { replace: true }); // replaces existing text\n * append(\"Here is what happened: \", { replace: true, focus: true }); // caret after the text\n */\nexport function useAppendToComposer() {\n const aui = useAui();\n const store = useComposerHandoffStore();\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const ready = useComposerReady();\n // The callback stays identity-stable across readiness changes.\n const readyRef = useRef(ready);\n readyRef.current = ready;\n\n return useCallback(\n (text: string, opts?: AppendToComposerOptions) => {\n const write: ComposerWrite = {\n text,\n replace: opts?.replace ?? false,\n send: false,\n focus: opts?.focus ?? false,\n threadId,\n };\n if (store && !readyRef.current) {\n queueComposerWrite({ store, write });\n return;\n }\n applyComposerWrite({ target: aui, store, write });\n },\n [aui, store, threadId],\n );\n}\n"],"names":["useAui","useComposerHandoffStore","useContext","AthenaStatewireThreadIdContext","useComposerReady","useRef","useCallback","queueComposerWrite","applyComposerWrite"],"mappings":";;;;;;AA2CO,SAAS,sBAAsB;AACpC,QAAM,MAAMA,MAAAA,OAAA;AACZ,QAAM,QAAQC,gBAAAA,wBAAA;AACd,QAAM,WAAWC,QAAAA,WAAWC,8CAA8B;AAC1D,QAAM,QAAQC,gBAAAA,iBAAA;AAEd,QAAM,WAAWC,QAAAA,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,SAAOC,QAAAA;AAAAA,IACL,CAAC,MAAc,SAAmC;AAChD,YAAM,QAAuB;AAAA,QAC3B;AAAA,QACA,UAAS,6BAAM,YAAW;AAAA,QAC1B,MAAM;AAAA,QACN,QAAO,6BAAM,UAAS;AAAA,QACtB;AAAA,MAAA;AAEF,UAAI,SAAS,CAAC,SAAS,SAAS;AAC9BC,2CAAmB,EAAE,OAAO,OAAO;AACnC;AAAA,MACF;AACAC,sBAAAA,mBAAmB,EAAE,QAAQ,KAAK,OAAO,OAAO;AAAA,IAClD;AAAA,IACA,CAAC,KAAK,OAAO,QAAQ;AAAA,EAAA;AAEzB;;"}
@@ -1,14 +1,32 @@
1
+ export interface AppendToComposerOptions {
2
+ /** Replace the current draft instead of appending on a new line. */
3
+ replace?: boolean;
4
+ /**
5
+ * Focus the composer with the caret after the text once the editor shows
6
+ * it — for a prefilled draft the user is meant to finish by typing or
7
+ * dictating. Defaults to false.
8
+ */
9
+ focus?: boolean;
10
+ }
1
11
  /**
2
12
  * Hook to programmatically append text to the composer without sending it.
3
13
  *
14
+ * Safe to call at any time: a write made before the chat is ready (the
15
+ * thread list still resolving the active thread, or the thread's history
16
+ * still loading) waits in the provider and lands once the composer is
17
+ * ready, instead of being lost with the mount it was written into. A
18
+ * never-started chat is ready without a live socket. The write is bound to
19
+ * the thread it was made for: if the user switches threads first, it is
20
+ * dropped (a `composer.handoff` diagnostic says so), never written into
21
+ * another thread.
22
+ *
4
23
  * Use `useSendMessage()` for workflow shortcuts or other UI that should
5
24
  * immediately submit a prompt.
6
25
  *
7
26
  * Usage:
8
27
  * const append = useAppendToComposer();
9
28
  * append("some text to add");
10
- * append("quoted text", { replace: true }); // replaces existing text
29
+ * append("quoted text", { replace: true }); // replaces existing text
30
+ * append("Here is what happened: ", { replace: true, focus: true }); // caret after the text
11
31
  */
12
- export declare function useAppendToComposer(): (text: string, opts?: {
13
- replace?: boolean;
14
- }) => void;
32
+ export declare function useAppendToComposer(): (text: string, opts?: AppendToComposerOptions) => void;
@@ -1,18 +1,30 @@
1
- import { useCallback } from "react";
1
+ import { useContext, useRef, useCallback } from "react";
2
2
  import { useAui } from "@assistant-ui/react";
3
+ import { AthenaStatewireThreadIdContext } from "../threads/thread-id-context.js";
4
+ import { useComposerHandoffStore, useComposerReady, queueComposerWrite, applyComposerWrite } from "./composer-handoff.js";
3
5
  function useAppendToComposer() {
4
6
  const aui = useAui();
7
+ const store = useComposerHandoffStore();
8
+ const threadId = useContext(AthenaStatewireThreadIdContext);
9
+ const ready = useComposerReady();
10
+ const readyRef = useRef(ready);
11
+ readyRef.current = ready;
5
12
  return useCallback(
6
13
  (text, opts) => {
7
- if (opts == null ? void 0 : opts.replace) {
8
- aui.composer.setText(text);
9
- } else {
10
- const current = aui.composer.getState().text;
11
- aui.composer.setText(current ? `${current}
12
- ${text}` : text);
14
+ const write = {
15
+ text,
16
+ replace: (opts == null ? void 0 : opts.replace) ?? false,
17
+ send: false,
18
+ focus: (opts == null ? void 0 : opts.focus) ?? false,
19
+ threadId
20
+ };
21
+ if (store && !readyRef.current) {
22
+ queueComposerWrite({ store, write });
23
+ return;
13
24
  }
25
+ applyComposerWrite({ target: aui, store, write });
14
26
  },
15
- [aui]
27
+ [aui, store, threadId]
16
28
  );
17
29
  }
18
30
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useAppendToComposer.js","sources":["../../src/composer/useAppendToComposer.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useAui } from '@assistant-ui/react';\n\n/**\n * Hook to programmatically append text to the composer without sending it.\n *\n * Use `useSendMessage()` for workflow shortcuts or other UI that should\n * immediately submit a prompt.\n *\n * Usage:\n * const append = useAppendToComposer();\n * append(\"some text to add\");\n * append(\"quoted text\", { replace: true }); // replaces existing text\n */\nexport function useAppendToComposer() {\n const aui = useAui();\n\n return useCallback(\n (text: string, opts?: { replace?: boolean }) => {\n if (opts?.replace) {\n aui.composer.setText(text);\n } else {\n const current = aui.composer.getState().text;\n aui.composer.setText(current ? `${current}\\n${text}` : text);\n }\n },\n [aui],\n );\n}\n"],"names":[],"mappings":";;AAcO,SAAS,sBAAsB;AACpC,QAAM,MAAM,OAAA;AAEZ,SAAO;AAAA,IACL,CAAC,MAAc,SAAiC;AAC9C,UAAI,6BAAM,SAAS;AACjB,YAAI,SAAS,QAAQ,IAAI;AAAA,MAC3B,OAAO;AACL,cAAM,UAAU,IAAI,SAAS,SAAA,EAAW;AACxC,YAAI,SAAS,QAAQ,UAAU,GAAG,OAAO;AAAA,EAAK,IAAI,KAAK,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAER;"}
1
+ {"version":3,"file":"useAppendToComposer.js","sources":["../../src/composer/useAppendToComposer.ts"],"sourcesContent":["import { useCallback, useContext, useRef } from 'react';\nimport { useAui } from '@assistant-ui/react';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport {\n applyComposerWrite,\n queueComposerWrite,\n useComposerHandoffStore,\n useComposerReady,\n type ComposerWrite,\n} from './composer-handoff';\n\nexport interface AppendToComposerOptions {\n /** Replace the current draft instead of appending on a new line. */\n replace?: boolean;\n /**\n * Focus the composer with the caret after the text once the editor shows\n * it — for a prefilled draft the user is meant to finish by typing or\n * dictating. Defaults to false.\n */\n focus?: boolean;\n}\n\n/**\n * Hook to programmatically append text to the composer without sending it.\n *\n * Safe to call at any time: a write made before the chat is ready (the\n * thread list still resolving the active thread, or the thread's history\n * still loading) waits in the provider and lands once the composer is\n * ready, instead of being lost with the mount it was written into. A\n * never-started chat is ready without a live socket. The write is bound to\n * the thread it was made for: if the user switches threads first, it is\n * dropped (a `composer.handoff` diagnostic says so), never written into\n * another thread.\n *\n * Use `useSendMessage()` for workflow shortcuts or other UI that should\n * immediately submit a prompt.\n *\n * Usage:\n * const append = useAppendToComposer();\n * append(\"some text to add\");\n * append(\"quoted text\", { replace: true }); // replaces existing text\n * append(\"Here is what happened: \", { replace: true, focus: true }); // caret after the text\n */\nexport function useAppendToComposer() {\n const aui = useAui();\n const store = useComposerHandoffStore();\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const ready = useComposerReady();\n // The callback stays identity-stable across readiness changes.\n const readyRef = useRef(ready);\n readyRef.current = ready;\n\n return useCallback(\n (text: string, opts?: AppendToComposerOptions) => {\n const write: ComposerWrite = {\n text,\n replace: opts?.replace ?? false,\n send: false,\n focus: opts?.focus ?? false,\n threadId,\n };\n if (store && !readyRef.current) {\n queueComposerWrite({ store, write });\n return;\n }\n applyComposerWrite({ target: aui, store, write });\n },\n [aui, store, threadId],\n );\n}\n"],"names":[],"mappings":";;;;AA2CO,SAAS,sBAAsB;AACpC,QAAM,MAAM,OAAA;AACZ,QAAM,QAAQ,wBAAA;AACd,QAAM,WAAW,WAAW,8BAA8B;AAC1D,QAAM,QAAQ,iBAAA;AAEd,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,SAAO;AAAA,IACL,CAAC,MAAc,SAAmC;AAChD,YAAM,QAAuB;AAAA,QAC3B;AAAA,QACA,UAAS,6BAAM,YAAW;AAAA,QAC1B,MAAM;AAAA,QACN,QAAO,6BAAM,UAAS;AAAA,QACtB;AAAA,MAAA;AAEF,UAAI,SAAS,CAAC,SAAS,SAAS;AAC9B,2BAAmB,EAAE,OAAO,OAAO;AACnC;AAAA,MACF;AACA,yBAAmB,EAAE,QAAQ,KAAK,OAAO,OAAO;AAAA,IAClD;AAAA,IACA,CAAC,KAAK,OAAO,QAAQ;AAAA,EAAA;AAEzB;"}
@@ -2,20 +2,38 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react$1 = require("react");
4
4
  const react = require("@assistant-ui/react");
5
+ const threadIdContext = require("../threads/thread-id-context.cjs");
6
+ const composerHandoff = require("./composer-handoff.cjs");
5
7
  function useSendMessage() {
6
8
  const aui = react.useAui();
9
+ const store = composerHandoff.useComposerHandoffStore();
10
+ const threadId = react$1.useContext(threadIdContext.AthenaStatewireThreadIdContext);
11
+ const ready = composerHandoff.useComposerReady();
12
+ const readyRef = react$1.useRef(ready);
13
+ readyRef.current = ready;
7
14
  return react$1.useCallback(
8
15
  async (text, options) => {
9
16
  if (!text.trim()) return;
10
- const currentText = aui.composer.getState().text;
11
- const shouldReplace = (options == null ? void 0 : options.replace) ?? true;
12
- aui.composer.setText(
13
- shouldReplace || !currentText ? text : `${currentText}
14
- ${text}`
15
- );
16
- await aui.composer.send({ steer: false });
17
+ const write = {
18
+ text,
19
+ replace: (options == null ? void 0 : options.replace) ?? true,
20
+ send: true,
21
+ focus: false,
22
+ threadId
23
+ };
24
+ if (store && !readyRef.current) {
25
+ await new Promise((resolve, reject) => {
26
+ write.resolve = (outcome) => {
27
+ Promise.resolve(outcome).then(() => resolve(), reject);
28
+ };
29
+ write.reject = reject;
30
+ composerHandoff.queueComposerWrite({ store, write });
31
+ });
32
+ return;
33
+ }
34
+ await composerHandoff.applyComposerWrite({ target: aui, store, write });
17
35
  },
18
- [aui]
36
+ [aui, store, threadId]
19
37
  );
20
38
  }
21
39
  exports.useSendMessage = useSendMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"useSendMessage.cjs","sources":["../../src/composer/useSendMessage.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useAui } from '@assistant-ui/react';\n\nexport interface SendMessageOptions {\n /**\n * Replace the current composer text before sending.\n * Defaults to `true`.\n */\n replace?: boolean;\n}\n\n/**\n * Hook to programmatically send a user message from anywhere inside AthenaProvider.\n *\n * This is intended for custom workflow buttons, sidebar shortcuts, or other UI\n * that lives outside the AthenaChat thread tree. Unlike `aui.thread().append()`,\n * this uses the provider-scoped composer runtime and does not depend on\n * ThreadPrimitive context.\n *\n * Usage:\n * const sendMessage = useSendMessage();\n * await sendMessage(\"Run the quarterly workflow\");\n * await sendMessage(\"Add this follow-up\", { replace: false });\n */\nexport function useSendMessage() {\n const aui = useAui();\n\n return useCallback(\n async (text: string, options?: SendMessageOptions) => {\n if (!text.trim()) return;\n\n const currentText = aui.composer.getState().text;\n const shouldReplace = options?.replace ?? true;\n\n aui.composer.setText(\n shouldReplace || !currentText ? text : `${currentText}\\n${text}`,\n );\n\n await aui.composer.send({ steer: false });\n },\n [aui],\n );\n}\n"],"names":["useAui","useCallback"],"mappings":";;;;AAwBO,SAAS,iBAAiB;AAC/B,QAAM,MAAMA,MAAAA,OAAA;AAEZ,SAAOC,QAAAA;AAAAA,IACL,OAAO,MAAc,YAAiC;AACpD,UAAI,CAAC,KAAK,OAAQ;AAElB,YAAM,cAAc,IAAI,SAAS,SAAA,EAAW;AAC5C,YAAM,iBAAgB,mCAAS,YAAW;AAE1C,UAAI,SAAS;AAAA,QACX,iBAAiB,CAAC,cAAc,OAAO,GAAG,WAAW;AAAA,EAAK,IAAI;AAAA,MAAA;AAGhE,YAAM,IAAI,SAAS,KAAK,EAAE,OAAO,OAAO;AAAA,IAC1C;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAER;;"}
1
+ {"version":3,"file":"useSendMessage.cjs","sources":["../../src/composer/useSendMessage.ts"],"sourcesContent":["import { useCallback, useContext, useRef } from 'react';\nimport { useAui } from '@assistant-ui/react';\nimport { AthenaStatewireThreadIdContext } from '../threads/thread-id-context';\nimport {\n applyComposerWrite,\n queueComposerWrite,\n useComposerHandoffStore,\n useComposerReady,\n type ComposerWrite,\n} from './composer-handoff';\n\nexport interface SendMessageOptions {\n /**\n * Replace the current composer text before sending.\n * Defaults to `true`.\n */\n replace?: boolean;\n}\n\n/**\n * Hook to programmatically send a user message from anywhere inside AthenaProvider.\n *\n * This is intended for custom workflow buttons, sidebar shortcuts, or other UI\n * that lives outside the AthenaChat thread tree. Unlike `aui.thread().append()`,\n * this uses the provider-scoped composer runtime and does not depend on\n * ThreadPrimitive context.\n *\n * Safe to call at any time: a send made before the chat is ready (the thread\n * list still resolving the active thread, or the thread's history still\n * loading) waits in the provider, in order with any other early sends, and\n * goes out once the composer is ready. The returned promise settles when\n * that send has been dispatched, and rejects with an `AthenaSdkError`\n * (`composer_write_dropped`) if it can no longer land — the user switched\n * threads, the identity changed, or the provider unmounted first.\n *\n * Usage:\n * const sendMessage = useSendMessage();\n * await sendMessage(\"Run the quarterly workflow\");\n * await sendMessage(\"Add this follow-up\", { replace: false });\n */\nexport function useSendMessage() {\n const aui = useAui();\n const store = useComposerHandoffStore();\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const ready = useComposerReady();\n const readyRef = useRef(ready);\n readyRef.current = ready;\n\n return useCallback(\n async (text: string, options?: SendMessageOptions) => {\n if (!text.trim()) return;\n\n const write: ComposerWrite = {\n text,\n replace: options?.replace ?? true,\n send: true,\n focus: false,\n threadId,\n };\n if (store && !readyRef.current) {\n await new Promise<void>((resolve, reject) => {\n write.resolve = (outcome) => {\n Promise.resolve(outcome).then(() => resolve(), reject);\n };\n write.reject = reject;\n queueComposerWrite({ store, write });\n });\n return;\n }\n await applyComposerWrite({ target: aui, store, write });\n },\n [aui, store, threadId],\n );\n}\n"],"names":["useAui","useComposerHandoffStore","useContext","AthenaStatewireThreadIdContext","useComposerReady","useRef","useCallback","queueComposerWrite","applyComposerWrite"],"mappings":";;;;;;AAwCO,SAAS,iBAAiB;AAC/B,QAAM,MAAMA,MAAAA,OAAA;AACZ,QAAM,QAAQC,gBAAAA,wBAAA;AACd,QAAM,WAAWC,QAAAA,WAAWC,8CAA8B;AAC1D,QAAM,QAAQC,gBAAAA,iBAAA;AACd,QAAM,WAAWC,QAAAA,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,SAAOC,QAAAA;AAAAA,IACL,OAAO,MAAc,YAAiC;AACpD,UAAI,CAAC,KAAK,OAAQ;AAElB,YAAM,QAAuB;AAAA,QAC3B;AAAA,QACA,UAAS,mCAAS,YAAW;AAAA,QAC7B,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,MAAA;AAEF,UAAI,SAAS,CAAC,SAAS,SAAS;AAC9B,cAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,gBAAM,UAAU,CAAC,YAAY;AAC3B,oBAAQ,QAAQ,OAAO,EAAE,KAAK,MAAM,QAAA,GAAW,MAAM;AAAA,UACvD;AACA,gBAAM,SAAS;AACfC,6CAAmB,EAAE,OAAO,OAAO;AAAA,QACrC,CAAC;AACD;AAAA,MACF;AACA,YAAMC,gBAAAA,mBAAmB,EAAE,QAAQ,KAAK,OAAO,OAAO;AAAA,IACxD;AAAA,IACA,CAAC,KAAK,OAAO,QAAQ;AAAA,EAAA;AAEzB;;"}
@@ -13,6 +13,14 @@ export interface SendMessageOptions {
13
13
  * this uses the provider-scoped composer runtime and does not depend on
14
14
  * ThreadPrimitive context.
15
15
  *
16
+ * Safe to call at any time: a send made before the chat is ready (the thread
17
+ * list still resolving the active thread, or the thread's history still
18
+ * loading) waits in the provider, in order with any other early sends, and
19
+ * goes out once the composer is ready. The returned promise settles when
20
+ * that send has been dispatched, and rejects with an `AthenaSdkError`
21
+ * (`composer_write_dropped`) if it can no longer land — the user switched
22
+ * threads, the identity changed, or the provider unmounted first.
23
+ *
16
24
  * Usage:
17
25
  * const sendMessage = useSendMessage();
18
26
  * await sendMessage("Run the quarterly workflow");