@assistant-ui/core 0.3.1 → 0.3.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.
- package/dist/index.d.ts +4 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +6 -7
- package/dist/internal.js +5 -6
- package/dist/react/AssistantProvider.d.ts +1 -1
- package/dist/react/AssistantProvider.d.ts.map +1 -1
- package/dist/react/AssistantProvider.js +35 -9
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
- package/dist/react/AssistantRuntimeProvider.js +1 -1
- package/dist/react/RuntimeAdapter.js +2 -2
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +17 -33
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/DataRenderers.js +1 -1
- package/dist/react/client/Interactables.js +1 -1
- package/dist/react/client/Tools.js +2 -2
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +2 -1
- package/dist/react/interactables-legacy/Interactables.js +1 -1
- package/dist/react/interactables-legacy/useAssistantInteractable.js +1 -1
- package/dist/react/interactables-legacy/useInteractableState.js +2 -2
- package/dist/react/model-context/useAssistantContext.js +1 -1
- package/dist/react/model-context/useAssistantDataUI.js +2 -2
- package/dist/react/model-context/useAssistantInstructions.js +2 -2
- package/dist/react/model-context/useAssistantTool.js +2 -2
- package/dist/react/model-context/useAssistantToolUI.js +2 -2
- package/dist/react/model-context/useAuiToolOverrides.js +1 -1
- package/dist/react/model-context/useInlineRender.js +1 -1
- package/dist/react/model-context/useInteractable.js +1 -1
- package/dist/react/model-context/useInteractableState.js +2 -2
- package/dist/react/model-context/useInteractableVersions.js +2 -2
- package/dist/react/model-context/useToolArgsStatus.js +2 -2
- package/dist/react/primitive-hooks/useActionBarCopy.js +52 -25
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarEdit.js +2 -2
- package/dist/react/primitive-hooks/useActionBarFeedback.js +2 -2
- package/dist/react/primitive-hooks/useActionBarReload.js +2 -2
- package/dist/react/primitive-hooks/useActionBarSpeak.js +2 -2
- package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +2 -2
- package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -2
- package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -2
- package/dist/react/primitive-hooks/useComposerAddAttachment.js +2 -2
- package/dist/react/primitive-hooks/useComposerCancel.js +2 -2
- package/dist/react/primitive-hooks/useComposerDictate.js +2 -2
- package/dist/react/primitive-hooks/useComposerSend.js +2 -2
- package/dist/react/primitive-hooks/useEditComposerCancel.js +2 -2
- package/dist/react/primitive-hooks/useEditComposerSend.js +2 -2
- package/dist/react/primitive-hooks/useMessageBranching.js +2 -2
- package/dist/react/primitive-hooks/useMessageReload.js +2 -2
- package/dist/react/primitive-hooks/useSuggestionTrigger.js +2 -2
- package/dist/react/primitive-hooks/useThreadListItemArchive.js +2 -2
- package/dist/react/primitive-hooks/useThreadListItemDelete.js +2 -2
- package/dist/react/primitive-hooks/useThreadListItemTrigger.js +2 -2
- package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +2 -2
- package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -2
- package/dist/react/primitive-hooks/useThreadListNew.js +2 -2
- package/dist/react/primitive-hooks/useThreadMessageIds.js +1 -1
- package/dist/react/primitive-hooks/useVoice.js +2 -2
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +2 -2
- package/dist/react/primitives/composer/ComposerAttachments.js +2 -2
- package/dist/react/primitives/composer/ComposerIf.js +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
- package/dist/react/primitives/message/MessageAttachments.js +2 -2
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +3 -3
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +2 -2
- package/dist/react/primitives/message/MessageQuote.js +2 -2
- package/dist/react/primitives/part/PartMessages.js +2 -2
- package/dist/react/primitives/thread/ThreadMessages.js +2 -2
- package/dist/react/primitives/thread/ThreadSuggestions.js +2 -2
- package/dist/react/primitives/threadList/ThreadListItems.js +2 -2
- package/dist/react/primitives/threadListItem/ThreadListItemTitle.js +1 -1
- package/dist/react/providers/AttachmentByIndexProvider.js +1 -1
- package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +1 -1
- package/dist/react/providers/MessageByIdProvider.js +1 -1
- package/dist/react/providers/MessageByIndexProvider.js +1 -1
- package/dist/react/providers/PartByIndexProvider.js +1 -1
- package/dist/react/providers/QueueItemByIndexProvider.js +1 -1
- package/dist/react/providers/ReadonlyThreadProvider.js +2 -2
- package/dist/react/providers/SuggestionByIndexProvider.js +1 -1
- package/dist/react/providers/TextMessagePartProvider.js +2 -2
- package/dist/react/providers/ThreadListItemByIndexProvider.js +1 -1
- package/dist/react/providers/ThreadListItemRuntimeProvider.js +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +14 -13
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +22 -12
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/RuntimeAdapterProvider.js +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +9 -23
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.d.ts +24 -2
- package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +24 -5
- package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +1 -1
- package/dist/react/runtimes/useExternalStoreRuntime.js +1 -1
- package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +7 -13
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/utils/useJSONEqual.js +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +5 -1
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/internal.d.ts +4 -4
- package/dist/runtime/internal.js +4 -4
- package/dist/runtime/utils/thread-message-like.js +2 -2
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +5 -4
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/chain-of-thought-client.js +30 -24
- package/dist/store/clients/chain-of-thought-client.js.map +1 -1
- package/dist/store/clients/model-context-client.js +1 -1
- package/dist/store/clients/no-op-composer-client.js +1 -1
- package/dist/store/clients/suggestions.js +2 -2
- package/dist/store/clients/thread-message-client.d.ts.map +1 -1
- package/dist/store/clients/thread-message-client.js +103 -91
- package/dist/store/clients/thread-message-client.js.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +2 -2
- package/dist/store/runtime-clients/message-runtime-client.js +2 -2
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
- package/dist/store/runtime-clients/thread-list-runtime-client.js +2 -2
- package/dist/store/runtime-clients/thread-runtime-client.js +2 -2
- package/dist/store/scopes/thread.d.ts +1 -1
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/message.d.ts +11 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.js +2 -2
- package/dist/utils/data-url.js.map +1 -1
- package/dist/utils/getGroupStatus.d.ts +9 -0
- package/dist/utils/getGroupStatus.d.ts.map +1 -0
- package/dist/utils/getGroupStatus.js +15 -0
- package/dist/utils/getGroupStatus.js.map +1 -0
- package/dist/utils/normalizePartStatus.d.ts +8 -0
- package/dist/utils/normalizePartStatus.d.ts.map +1 -0
- package/dist/utils/normalizePartStatus.js +39 -0
- package/dist/utils/normalizePartStatus.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/internal.ts +1 -7
- package/src/react/AssistantProvider.tsx +28 -15
- package/src/react/AssistantRuntimeProvider.test.tsx +101 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +28 -7
- package/src/react/index.ts +4 -0
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +109 -14
- package/src/react/primitive-hooks/useActionBarCopy.ts +27 -2
- package/src/react/primitives/message/MessageGroupedParts.tsx +4 -4
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +16 -13
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +53 -10
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +7 -5
- package/src/react/runtimes/cloud/auiV0.ts +51 -12
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +9 -15
- package/src/runtime/api/message-runtime.test.ts +140 -0
- package/src/runtime/api/message-runtime.ts +10 -5
- package/src/runtime/utils/thread-message-like.ts +2 -2
- package/src/runtimes/remote-thread-list/types.ts +5 -4
- package/src/store/clients/chain-of-thought-client.ts +2 -7
- package/src/store/clients/thread-message-client.test.ts +84 -0
- package/src/store/clients/thread-message-client.ts +14 -4
- package/src/store/scopes/thread.ts +1 -1
- package/src/tests/auiV0Encode.test.ts +93 -0
- package/src/tests/remote-thread-list-reactive-threadId.test.ts +0 -92
- package/src/tests/thread-message-like.test.ts +15 -0
- package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +293 -0
- package/src/types/index.ts +1 -0
- package/src/types/message.ts +19 -0
- package/src/utils/data-url.test.ts +26 -2
- package/src/utils/data-url.ts +2 -2
- package/src/utils/getGroupStatus.test.ts +36 -0
- package/src/utils/getGroupStatus.ts +31 -0
- package/src/utils/normalizePartStatus.test.ts +59 -0
- package/src/utils/normalizePartStatus.ts +55 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MessagePartStatus, ToolCallMessagePartStatus } from "../types/message.js";
|
|
2
|
+
//#region src/utils/getGroupStatus.d.ts
|
|
3
|
+
type PartWithStatus = {
|
|
4
|
+
readonly status: MessagePartStatus | ToolCallMessagePartStatus;
|
|
5
|
+
};
|
|
6
|
+
declare const getGroupStatus: (parts: readonly (PartWithStatus | undefined)[], indices?: readonly number[]) => MessagePartStatus | ToolCallMessagePartStatus;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { getGroupStatus };
|
|
9
|
+
//# sourceMappingURL=getGroupStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getGroupStatus.d.ts","names":[],"sources":["../../src/utils/getGroupStatus.ts"],"mappings":";;KAMK;WACM,QAAQ,oBAAoB;;cAG1B,iBACX,iBAAiB,+BACjB,gCACC,oBAAoB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { COMPLETE_STATUS, RUNNING_STATUS } from "./normalizePartStatus.js";
|
|
2
|
+
//#region src/utils/getGroupStatus.ts
|
|
3
|
+
const getGroupStatus = (parts, indices) => {
|
|
4
|
+
if (indices) {
|
|
5
|
+
for (const index of indices) if (parts[index]?.status.type === "running") return RUNNING_STATUS;
|
|
6
|
+
const lastIndex = indices.at(-1);
|
|
7
|
+
return lastIndex === void 0 ? COMPLETE_STATUS : parts[lastIndex]?.status ?? COMPLETE_STATUS;
|
|
8
|
+
}
|
|
9
|
+
for (const part of parts) if (part?.status.type === "running") return RUNNING_STATUS;
|
|
10
|
+
return parts.at(-1)?.status ?? COMPLETE_STATUS;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { getGroupStatus };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=getGroupStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getGroupStatus.js","names":["MessagePartStatus","ToolCallMessagePartStatus","COMPLETE_STATUS","RUNNING_STATUS","PartWithStatus","status","getGroupStatus","parts","indices","index","type","lastIndex","at","undefined","part"],"sources":["../../src/utils/getGroupStatus.ts"],"sourcesContent":["import type {\n MessagePartStatus,\n ToolCallMessagePartStatus,\n} from \"../types/message\";\nimport { COMPLETE_STATUS, RUNNING_STATUS } from \"./normalizePartStatus\";\n\ntype PartWithStatus = {\n readonly status: MessagePartStatus | ToolCallMessagePartStatus;\n};\n\nexport const getGroupStatus = (\n parts: readonly (PartWithStatus | undefined)[],\n indices?: readonly number[],\n): MessagePartStatus | ToolCallMessagePartStatus => {\n if (indices) {\n for (const index of indices) {\n if (parts[index]?.status.type === \"running\") return RUNNING_STATUS;\n }\n\n const lastIndex = indices.at(-1);\n return lastIndex === undefined\n ? COMPLETE_STATUS\n : (parts[lastIndex]?.status ?? COMPLETE_STATUS);\n }\n\n for (const part of parts) {\n if (part?.status.type === \"running\") return RUNNING_STATUS;\n }\n\n return parts.at(-1)?.status ?? COMPLETE_STATUS;\n};\n"],"mappings":";;AAUA,MAAaM,kBACXC,OACAC,YACkD;CAClD,IAAIA,SAAS;EACX,KAAK,MAAMC,SAASD,SAClB,IAAID,MAAME,MAAM,EAAEJ,OAAOK,SAAS,WAAW,OAAOP;EAGtD,MAAMQ,YAAYH,QAAQI,GAAG,EAAE;EAC/B,OAAOD,cAAcE,KAAAA,IACjBX,kBACCK,MAAMI,UAAU,EAAEN,UAAUH;CACnC;CAEA,KAAK,MAAMY,QAAQP,OACjB,IAAIO,MAAMT,OAAOK,SAAS,WAAW,OAAOP;CAG9C,OAAOI,MAAMK,GAAG,EAAE,CAAC,EAAEP,UAAUH;AACjC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MessagePartStatus, ThreadAssistantMessagePart, ThreadUserMessagePart } from "../types/message.js";
|
|
2
|
+
//#region src/utils/normalizePartStatus.d.ts
|
|
3
|
+
declare const COMPLETE_STATUS: MessagePartStatus;
|
|
4
|
+
declare const RUNNING_STATUS: MessagePartStatus;
|
|
5
|
+
declare const normalizePartStatus: (part: ThreadUserMessagePart | ThreadAssistantMessagePart) => MessagePartStatus | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { COMPLETE_STATUS, RUNNING_STATUS, normalizePartStatus };
|
|
8
|
+
//# sourceMappingURL=normalizePartStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizePartStatus.d.ts","names":[],"sources":["../../src/utils/normalizePartStatus.ts"],"mappings":";;cAOa,iBAAiB;cAGjB,gBAAgB;cAoBhB,sBACX,MAAM,wBAAwB,+BAC7B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/utils/normalizePartStatus.ts
|
|
2
|
+
const COMPLETE_STATUS = Object.freeze({ type: "complete" });
|
|
3
|
+
const RUNNING_STATUS = Object.freeze({ type: "running" });
|
|
4
|
+
const INCOMPLETE_STATUSES = Object.freeze({
|
|
5
|
+
cancelled: Object.freeze({
|
|
6
|
+
type: "incomplete",
|
|
7
|
+
reason: "cancelled"
|
|
8
|
+
}),
|
|
9
|
+
length: Object.freeze({
|
|
10
|
+
type: "incomplete",
|
|
11
|
+
reason: "length"
|
|
12
|
+
}),
|
|
13
|
+
"content-filter": Object.freeze({
|
|
14
|
+
type: "incomplete",
|
|
15
|
+
reason: "content-filter"
|
|
16
|
+
}),
|
|
17
|
+
other: Object.freeze({
|
|
18
|
+
type: "incomplete",
|
|
19
|
+
reason: "other"
|
|
20
|
+
}),
|
|
21
|
+
error: Object.freeze({
|
|
22
|
+
type: "incomplete",
|
|
23
|
+
reason: "error"
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
const normalizePartStatus = (part) => {
|
|
27
|
+
const status = part.status;
|
|
28
|
+
if (!status || typeof status !== "object") return void 0;
|
|
29
|
+
const { type } = status;
|
|
30
|
+
if (type === "running") return RUNNING_STATUS;
|
|
31
|
+
if (type === "complete") return COMPLETE_STATUS;
|
|
32
|
+
if (type !== "incomplete") return void 0;
|
|
33
|
+
const { reason } = status;
|
|
34
|
+
return INCOMPLETE_STATUSES[reason === "cancelled" || reason === "length" || reason === "content-filter" || reason === "other" || reason === "error" ? reason : "other"];
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { COMPLETE_STATUS, RUNNING_STATUS, normalizePartStatus };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=normalizePartStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizePartStatus.js","names":["MessagePartStatus","MessagePartStreamStatus","ThreadAssistantMessagePart","ThreadUserMessagePart","COMPLETE_STATUS","Object","freeze","type","RUNNING_STATUS","IncompleteMessagePartStatus","Extract","INCOMPLETE_STATUSES","Readonly","Record","cancelled","reason","length","other","error","normalizePartStatus","part","status","undefined","normalizedReason"],"sources":["../../src/utils/normalizePartStatus.ts"],"sourcesContent":["import type {\n MessagePartStatus,\n MessagePartStreamStatus,\n ThreadAssistantMessagePart,\n ThreadUserMessagePart,\n} from \"../types/message\";\n\nexport const COMPLETE_STATUS: MessagePartStatus = Object.freeze({\n type: \"complete\",\n});\nexport const RUNNING_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\ntype IncompleteMessagePartStatus = Extract<\n MessagePartStreamStatus,\n { readonly type: \"incomplete\" }\n>;\nconst INCOMPLETE_STATUSES: Readonly<\n Record<IncompleteMessagePartStatus[\"reason\"], IncompleteMessagePartStatus>\n> = Object.freeze({\n cancelled: Object.freeze({ type: \"incomplete\", reason: \"cancelled\" }),\n length: Object.freeze({ type: \"incomplete\", reason: \"length\" }),\n \"content-filter\": Object.freeze({\n type: \"incomplete\",\n reason: \"content-filter\",\n }),\n other: Object.freeze({ type: \"incomplete\", reason: \"other\" }),\n error: Object.freeze({ type: \"incomplete\", reason: \"error\" }),\n});\n\nexport const normalizePartStatus = (\n part: ThreadUserMessagePart | ThreadAssistantMessagePart,\n): MessagePartStatus | undefined => {\n const status = (part as { readonly status?: unknown }).status;\n if (!status || typeof status !== \"object\") return undefined;\n\n const { type } = status as { readonly type?: unknown };\n if (type === \"running\") return RUNNING_STATUS;\n if (type === \"complete\") return COMPLETE_STATUS;\n if (type !== \"incomplete\") return undefined;\n\n const { reason } = status as {\n readonly reason?: unknown;\n };\n const normalizedReason: IncompleteMessagePartStatus[\"reason\"] =\n reason === \"cancelled\" ||\n reason === \"length\" ||\n reason === \"content-filter\" ||\n reason === \"other\" ||\n reason === \"error\"\n ? reason\n : \"other\";\n\n return INCOMPLETE_STATUSES[normalizedReason];\n};\n"],"mappings":";AAOA,MAAaI,kBAAqCC,OAAOC,OAAO,EAC9DC,MAAM,WACR,CAAC;AACD,MAAaC,iBAAoCH,OAAOC,OAAO,EAC7DC,MAAM,UACR,CAAC;AAKD,MAAMI,sBAEFN,OAAOC,OAAO;CAChBQ,WAAWT,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAY,CAAC;CACpEC,QAAQX,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAS,CAAC;CAC9D,kBAAkBV,OAAOC,OAAO;EAC9BC,MAAM;EACNQ,QAAQ;CACV,CAAC;CACDE,OAAOZ,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAQ,CAAC;CAC5DG,OAAOb,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAQ,CAAC;AAC9D,CAAC;AAED,MAAaI,uBACXC,SACkC;CAClC,MAAMC,SAAUD,KAAuCC;CACvD,IAAI,CAACA,UAAU,OAAOA,WAAW,UAAU,OAAOC,KAAAA;CAElD,MAAM,EAAEf,SAASc;CACjB,IAAId,SAAS,WAAW,OAAOC;CAC/B,IAAID,SAAS,YAAY,OAAOH;CAChC,IAAIG,SAAS,cAAc,OAAOe,KAAAA;CAElC,MAAM,EAAEP,WAAWM;CAYnB,OAAOV,oBARLI,WAAW,eACXA,WAAW,YACXA,WAAW,oBACXA,WAAW,WACXA,WAAW,UACPA,SACA;AAGR"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Framework-agnostic core runtime for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"sideEffects": false,
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"assistant-stream": "^0.3.
|
|
62
|
+
"assistant-stream": "^0.3.31",
|
|
63
63
|
"nanoid": "^6.0.0"
|
|
64
64
|
},
|
|
65
65
|
"optionalDevDependencies": {
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
"react-dom": "^19.2.8",
|
|
96
96
|
"vitest": "^4.1.10",
|
|
97
97
|
"zustand": "^5.0.14",
|
|
98
|
-
"@assistant-ui/store": "0.3.
|
|
99
|
-
"@assistant-ui/tap": "0.9.
|
|
98
|
+
"@assistant-ui/store": "0.3.2",
|
|
99
|
+
"@assistant-ui/tap": "0.9.9",
|
|
100
100
|
"@assistant-ui/vite": "0.0.11",
|
|
101
101
|
"@assistant-ui/x-buildutils": "0.0.20",
|
|
102
102
|
"assistant-cloud": "0.1.37"
|
package/src/index.ts
CHANGED
package/src/internal.ts
CHANGED
|
@@ -32,6 +32,7 @@ export {
|
|
|
32
32
|
|
|
33
33
|
// Message utilities
|
|
34
34
|
export { getThreadMessageText } from "./utils/text";
|
|
35
|
+
export { toMessagePartStatus } from "./runtime/api/message-runtime";
|
|
35
36
|
export { resolveToolApprovalResponse } from "./runtime/utils/resolveToolApprovalResponse";
|
|
36
37
|
export { consumeSuggestionResult } from "./adapters/suggestion";
|
|
37
38
|
|
|
@@ -58,12 +59,5 @@ export { isJSONValue, isRecord } from "./utils/json/is-json";
|
|
|
58
59
|
// outbound part conversion lives in one place.
|
|
59
60
|
export { httpUrlPattern, parseDataUrl } from "./utils/data-url";
|
|
60
61
|
|
|
61
|
-
// Runtime extras helper for external-store adapters. Internal because the
|
|
62
|
-
// tap-native runtime path replaces the `thread.extras` side-channel it wraps.
|
|
63
|
-
export {
|
|
64
|
-
createRuntimeExtras,
|
|
65
|
-
type RuntimeExtras,
|
|
66
|
-
} from "./react/runtimes/createRuntimeExtras";
|
|
67
|
-
|
|
68
62
|
export * from "./runtime/internal";
|
|
69
63
|
export * from "./runtimes/internal";
|
|
@@ -19,24 +19,37 @@ export type AssistantProviderBaseProps = PropsWithChildren<{
|
|
|
19
19
|
aui?: AssistantClient | null;
|
|
20
20
|
}>;
|
|
21
21
|
|
|
22
|
+
const AssistantProviderInner: FC<
|
|
23
|
+
PropsWithChildren<{ runtime: AssistantRuntime }>
|
|
24
|
+
> = ({ runtime, children }) => {
|
|
25
|
+
// The runtime has a stable identity but mutates in place: its options are
|
|
26
|
+
// pushed in by an unconditional effect inside <RenderComponent />, so that
|
|
27
|
+
// element must be re-created every commit for React to re-render it and
|
|
28
|
+
// re-run the effect. React Compiler caches <RenderComponent /> on the
|
|
29
|
+
// stable RenderComponent type, which silences the effect and stops option
|
|
30
|
+
// changes (e.g. unstable_enableMessageQueue) from reaching the runtime.
|
|
31
|
+
"use no memo";
|
|
32
|
+
const aui = useAui({ threads: RuntimeAdapter(runtime) });
|
|
33
|
+
const RenderComponent = getRenderComponent(runtime);
|
|
34
|
+
return (
|
|
35
|
+
<AuiProvider value={aui}>
|
|
36
|
+
{RenderComponent && <RenderComponent />}
|
|
37
|
+
{children}
|
|
38
|
+
</AuiProvider>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
22
42
|
export const AssistantProviderBase: FC<AssistantProviderBaseProps> = memo(
|
|
23
|
-
({ runtime, aui
|
|
24
|
-
// The runtime has a stable identity but mutates in place: its options are
|
|
25
|
-
// pushed in by an unconditional effect inside <RenderComponent />, so that
|
|
26
|
-
// element must be re-created every commit for React to re-render it and
|
|
27
|
-
// re-run the effect. React Compiler caches <RenderComponent /> on the
|
|
28
|
-
// stable RenderComponent type, which silences the effect and stops option
|
|
29
|
-
// changes (e.g. unstable_enableMessageQueue) from reaching the runtime.
|
|
30
|
-
"use no memo";
|
|
31
|
-
const aui = useAui({ threads: RuntimeAdapter(runtime) }, { parent });
|
|
32
|
-
const RenderComponent = getRenderComponent(runtime);
|
|
43
|
+
({ runtime, aui = null, children }) => {
|
|
33
44
|
const inner = (
|
|
34
|
-
<
|
|
35
|
-
{RenderComponent && <RenderComponent />}
|
|
45
|
+
<AssistantProviderInner runtime={runtime}>
|
|
36
46
|
{children}
|
|
37
|
-
</
|
|
47
|
+
</AssistantProviderInner>
|
|
48
|
+
);
|
|
49
|
+
return aui ? (
|
|
50
|
+
<AuiProvider value={aui}>{inner}</AuiProvider>
|
|
51
|
+
) : (
|
|
52
|
+
<AuiProvider value={null}>{inner}</AuiProvider>
|
|
38
53
|
);
|
|
39
|
-
if (!parent) return inner;
|
|
40
|
-
return <AuiProvider value={parent}>{inner}</AuiProvider>;
|
|
41
54
|
},
|
|
42
55
|
);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { cleanup, render } from "@testing-library/react";
|
|
6
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { resource } from "@assistant-ui/tap";
|
|
8
|
+
import { AuiProvider, useAui, type AssistantClient } from "@assistant-ui/store";
|
|
9
|
+
import type { ChatModelAdapter } from "../runtime/utils/chat-model-adapter";
|
|
10
|
+
import { AssistantRuntimeProvider } from "./AssistantRuntimeProvider";
|
|
11
|
+
import { useLocalRuntime } from "./runtimes/useLocalRuntime";
|
|
12
|
+
|
|
13
|
+
type AnyClient = Record<string, any>;
|
|
14
|
+
|
|
15
|
+
const chatModel: ChatModelAdapter = {
|
|
16
|
+
run: async () => ({ content: [] }),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const makeCounterClient = (log?: string[]) => {
|
|
20
|
+
const useCounterClient = () => {
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
log?.push("parent tap effect");
|
|
23
|
+
}, []);
|
|
24
|
+
return { getState: () => ({ count: 0 }) };
|
|
25
|
+
};
|
|
26
|
+
return resource(useCounterClient);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cleanup();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("AssistantRuntimeProvider aui composition", () => {
|
|
34
|
+
it("isolates from ambient context by default", () => {
|
|
35
|
+
let aui!: AnyClient;
|
|
36
|
+
const Consumer = () => {
|
|
37
|
+
aui = useAui();
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const Ambient = ({ children }: { children: ReactNode }) => {
|
|
42
|
+
const ambient = useAui({
|
|
43
|
+
parentScope: makeCounterClient()(),
|
|
44
|
+
} as unknown as useAui.Props);
|
|
45
|
+
return <AuiProvider value={ambient}>{children}</AuiProvider>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const App = () => {
|
|
49
|
+
const runtime = useLocalRuntime(chatModel);
|
|
50
|
+
return (
|
|
51
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
52
|
+
<Consumer />
|
|
53
|
+
</AssistantRuntimeProvider>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<Ambient>
|
|
59
|
+
<App />
|
|
60
|
+
</Ambient>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(aui.threads).toBeDefined();
|
|
64
|
+
expect(() => aui.parentScope.getState()).toThrow(
|
|
65
|
+
/The current scope does not have a "parentScope" property/,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("inherits a prop-passed parent and mounts its effects", () => {
|
|
70
|
+
const log: string[] = [];
|
|
71
|
+
let aui!: AnyClient;
|
|
72
|
+
const Consumer = () => {
|
|
73
|
+
aui = useAui();
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
log.push("consumer effect");
|
|
76
|
+
}, []);
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const App = ({ parent }: { parent: AssistantClient }) => {
|
|
81
|
+
const runtime = useLocalRuntime(chatModel);
|
|
82
|
+
return (
|
|
83
|
+
<AssistantRuntimeProvider runtime={runtime} aui={parent}>
|
|
84
|
+
<Consumer />
|
|
85
|
+
</AssistantRuntimeProvider>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const Root = () => {
|
|
90
|
+
const parent = useAui({
|
|
91
|
+
parentScope: makeCounterClient(log)(),
|
|
92
|
+
} as unknown as useAui.Props);
|
|
93
|
+
return <App parent={parent} />;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
render(<Root />);
|
|
97
|
+
|
|
98
|
+
expect(aui.parentScope.getState()).toEqual({ count: 0 });
|
|
99
|
+
expect(log).toEqual(["parent tap effect", "consumer effect"]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type AssistantStream, createAssistantStream } from "assistant-stream";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type FC,
|
|
4
|
+
type PropsWithChildren,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
3
10
|
import { useAui } from "@assistant-ui/store";
|
|
4
11
|
import type {
|
|
5
12
|
RemoteThreadInitializeResponse,
|
|
@@ -276,22 +283,26 @@ export const parseStoredMessageRepository = (
|
|
|
276
283
|
|
|
277
284
|
class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
|
|
278
285
|
private storage: AsyncStorageLike;
|
|
279
|
-
private
|
|
286
|
+
private getAui: () => ReturnType<typeof useAui>;
|
|
280
287
|
private prefix: string;
|
|
281
288
|
private mutationQueue: KeyedMutationQueue;
|
|
282
289
|
|
|
283
290
|
constructor(
|
|
284
291
|
storage: AsyncStorageLike,
|
|
285
|
-
|
|
292
|
+
getAui: () => ReturnType<typeof useAui>,
|
|
286
293
|
prefix: string,
|
|
287
294
|
mutationQueue: KeyedMutationQueue,
|
|
288
295
|
) {
|
|
289
296
|
this.storage = storage;
|
|
290
|
-
this.
|
|
297
|
+
this.getAui = getAui;
|
|
291
298
|
this.prefix = prefix;
|
|
292
299
|
this.mutationQueue = mutationQueue;
|
|
293
300
|
}
|
|
294
301
|
|
|
302
|
+
private get aui(): ReturnType<typeof useAui> {
|
|
303
|
+
return this.getAui();
|
|
304
|
+
}
|
|
305
|
+
|
|
295
306
|
private _messagesKey(remoteId: string) {
|
|
296
307
|
return `${this.prefix}messages:${remoteId}`;
|
|
297
308
|
}
|
|
@@ -334,9 +345,19 @@ const createHistoryProvider = (
|
|
|
334
345
|
): FC<PropsWithChildren> => {
|
|
335
346
|
const Provider: FC<PropsWithChildren> = ({ children }) => {
|
|
336
347
|
const aui = useAui();
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
348
|
+
// Not useEffectEvent: history adapter methods run during render (SSR load).
|
|
349
|
+
const auiRef = useRef(aui);
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
auiRef.current = aui;
|
|
352
|
+
});
|
|
353
|
+
const [history] = useState(
|
|
354
|
+
() =>
|
|
355
|
+
new AsyncStorageHistoryAdapter(
|
|
356
|
+
storage,
|
|
357
|
+
() => auiRef.current,
|
|
358
|
+
prefix,
|
|
359
|
+
mutationQueue,
|
|
360
|
+
),
|
|
340
361
|
);
|
|
341
362
|
const adapters = useMemo(() => ({ history }), [history]);
|
|
342
363
|
|
package/src/react/index.ts
CHANGED
|
@@ -226,6 +226,10 @@ export {
|
|
|
226
226
|
type StreamingTimingOptions,
|
|
227
227
|
type StreamingTimingState,
|
|
228
228
|
} from "./runtimes/useStreamingTiming";
|
|
229
|
+
export {
|
|
230
|
+
createRuntimeExtras,
|
|
231
|
+
type RuntimeExtras,
|
|
232
|
+
} from "./runtimes/createRuntimeExtras";
|
|
229
233
|
export { RemoteThreadListHookInstanceManager } from "./runtimes/RemoteThreadListHookInstanceManager";
|
|
230
234
|
export { RemoteThreadListThreadListRuntimeCore } from "./runtimes/RemoteThreadListThreadListRuntimeCore";
|
|
231
235
|
export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @vitest-environment jsdom */
|
|
2
|
+
import { cleanup, renderHook } from "@testing-library/react";
|
|
1
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
4
|
|
|
3
5
|
const mocks = vi.hoisted(() => {
|
|
@@ -23,18 +25,18 @@ const mocks = vi.hoisted(() => {
|
|
|
23
25
|
setIsCopied,
|
|
24
26
|
},
|
|
25
27
|
},
|
|
28
|
+
currentAui: {
|
|
29
|
+
message: {
|
|
30
|
+
getCopyText: () => "Hello",
|
|
31
|
+
setIsCopied,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
26
34
|
};
|
|
27
35
|
});
|
|
28
36
|
|
|
29
|
-
vi.mock("react", async (importOriginal) => ({
|
|
30
|
-
...(await importOriginal<typeof import("react")>()),
|
|
31
|
-
useCallback: ((callback: unknown) =>
|
|
32
|
-
callback) as typeof import("react").useCallback,
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
37
|
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
36
38
|
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
37
|
-
useAui: () => mocks.
|
|
39
|
+
useAui: () => mocks.currentAui,
|
|
38
40
|
useAuiState: ((selector: (state: typeof mocks.state) => unknown) =>
|
|
39
41
|
selector(mocks.state)) as typeof import("@assistant-ui/store").useAuiState,
|
|
40
42
|
}));
|
|
@@ -42,25 +44,28 @@ vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
|
42
44
|
import { useActionBarCopy } from "./useActionBarCopy";
|
|
43
45
|
|
|
44
46
|
afterEach(() => {
|
|
47
|
+
cleanup();
|
|
45
48
|
vi.clearAllMocks();
|
|
49
|
+
vi.useRealTimers();
|
|
50
|
+
mocks.currentAui = mocks.aui;
|
|
46
51
|
});
|
|
47
52
|
|
|
48
53
|
describe("useActionBarCopy", () => {
|
|
49
54
|
it("does not report copy success without a clipboard handler", async () => {
|
|
50
|
-
const {
|
|
55
|
+
const { result } = renderHook(() => useActionBarCopy());
|
|
51
56
|
|
|
52
|
-
copy();
|
|
57
|
+
result.current.copy();
|
|
53
58
|
await Promise.resolve();
|
|
54
59
|
|
|
55
|
-
expect(disabled).toBe(true);
|
|
60
|
+
expect(result.current.disabled).toBe(true);
|
|
56
61
|
expect(mocks.setIsCopied).not.toHaveBeenCalled();
|
|
57
62
|
});
|
|
58
63
|
|
|
59
64
|
it("reports copy success after the clipboard handler resolves", async () => {
|
|
60
65
|
const copyToClipboard = vi.fn();
|
|
61
|
-
const {
|
|
66
|
+
const { result } = renderHook(() => useActionBarCopy({ copyToClipboard }));
|
|
62
67
|
|
|
63
|
-
copy();
|
|
68
|
+
result.current.copy();
|
|
64
69
|
await Promise.resolve();
|
|
65
70
|
|
|
66
71
|
expect(copyToClipboard).toHaveBeenCalledWith("Hello");
|
|
@@ -69,11 +74,101 @@ describe("useActionBarCopy", () => {
|
|
|
69
74
|
|
|
70
75
|
it("does not report copy success when the clipboard handler rejects", async () => {
|
|
71
76
|
const copyToClipboard = vi.fn().mockRejectedValue(new Error("denied"));
|
|
72
|
-
const {
|
|
77
|
+
const { result } = renderHook(() => useActionBarCopy({ copyToClipboard }));
|
|
78
|
+
|
|
79
|
+
result.current.copy();
|
|
80
|
+
await Promise.resolve();
|
|
81
|
+
|
|
82
|
+
expect(mocks.setIsCopied).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("keeps copy success visible for the full duration after copying again", async () => {
|
|
86
|
+
vi.useFakeTimers();
|
|
87
|
+
const copyToClipboard = vi.fn();
|
|
88
|
+
const { result } = renderHook(() =>
|
|
89
|
+
useActionBarCopy({
|
|
90
|
+
copiedDuration: 3000,
|
|
91
|
+
copyToClipboard,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
result.current.copy();
|
|
96
|
+
await Promise.resolve();
|
|
97
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
98
|
+
|
|
99
|
+
result.current.copy();
|
|
100
|
+
await Promise.resolve();
|
|
101
|
+
mocks.setIsCopied.mockClear();
|
|
102
|
+
|
|
103
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
104
|
+
expect(mocks.setIsCopied).not.toHaveBeenCalledWith(false);
|
|
73
105
|
|
|
74
|
-
|
|
106
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
107
|
+
expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("resets copy feedback when unmounted", async () => {
|
|
111
|
+
vi.useFakeTimers();
|
|
112
|
+
const copyToClipboard = vi.fn();
|
|
113
|
+
const { result, unmount } = renderHook(() =>
|
|
114
|
+
useActionBarCopy({ copyToClipboard }),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
result.current.copy();
|
|
118
|
+
await Promise.resolve();
|
|
119
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
120
|
+
|
|
121
|
+
mocks.setIsCopied.mockClear();
|
|
122
|
+
unmount();
|
|
123
|
+
|
|
124
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
125
|
+
expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("ignores clipboard success after unmount", async () => {
|
|
129
|
+
vi.useFakeTimers();
|
|
130
|
+
let resolveCopy: (() => void) | undefined;
|
|
131
|
+
const copyToClipboard = vi.fn(
|
|
132
|
+
() =>
|
|
133
|
+
new Promise<void>((resolve) => {
|
|
134
|
+
resolveCopy = resolve;
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
const { result, unmount } = renderHook(() =>
|
|
138
|
+
useActionBarCopy({ copyToClipboard }),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
result.current.copy();
|
|
142
|
+
unmount();
|
|
143
|
+
resolveCopy?.();
|
|
75
144
|
await Promise.resolve();
|
|
76
145
|
|
|
77
146
|
expect(mocks.setIsCopied).not.toHaveBeenCalled();
|
|
147
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("resets feedback for the previous message scope", async () => {
|
|
151
|
+
vi.useFakeTimers();
|
|
152
|
+
const copyToClipboard = vi.fn();
|
|
153
|
+
const nextSetIsCopied = vi.fn();
|
|
154
|
+
const { result, rerender } = renderHook(() =>
|
|
155
|
+
useActionBarCopy({ copyToClipboard }),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
result.current.copy();
|
|
159
|
+
await Promise.resolve();
|
|
160
|
+
mocks.setIsCopied.mockClear();
|
|
161
|
+
|
|
162
|
+
mocks.currentAui = {
|
|
163
|
+
message: {
|
|
164
|
+
getCopyText: () => "Next message",
|
|
165
|
+
setIsCopied: nextSetIsCopied,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
rerender();
|
|
169
|
+
|
|
170
|
+
expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
|
|
171
|
+
expect(nextSetIsCopied).not.toHaveBeenCalled();
|
|
172
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
78
173
|
});
|
|
79
174
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export type UseActionBarCopyOptions = {
|
|
@@ -21,19 +21,44 @@ export const useActionBarCopy = ({
|
|
|
21
21
|
const isCopied = useAuiState((s) => s.message.isCopied);
|
|
22
22
|
const isEditing = useAuiState((s) => s.composer.isEditing);
|
|
23
23
|
const composerValue = useAuiState((s) => s.composer.text);
|
|
24
|
+
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
25
|
+
undefined,
|
|
26
|
+
);
|
|
27
|
+
const scopeGenerationRef = useRef(0);
|
|
28
|
+
|
|
29
|
+
useEffect(
|
|
30
|
+
() => () => {
|
|
31
|
+
scopeGenerationRef.current += 1;
|
|
32
|
+
if (copiedTimerRef.current === undefined) return;
|
|
33
|
+
|
|
34
|
+
clearTimeout(copiedTimerRef.current);
|
|
35
|
+
copiedTimerRef.current = undefined;
|
|
36
|
+
aui.message.setIsCopied(false);
|
|
37
|
+
},
|
|
38
|
+
[aui],
|
|
39
|
+
);
|
|
24
40
|
|
|
25
41
|
const copy = useCallback(() => {
|
|
26
42
|
if (!copyToClipboard) return;
|
|
27
43
|
|
|
28
44
|
const valueToCopy = isEditing ? composerValue : aui.message.getCopyText();
|
|
29
45
|
if (!valueToCopy) return;
|
|
46
|
+
const scopeGeneration = scopeGenerationRef.current;
|
|
30
47
|
|
|
31
48
|
// The rejection handler swallows clipboard write failures (permission denied,
|
|
32
49
|
// API unavailable) so they don't surface as unhandled promise rejections.
|
|
33
50
|
Promise.resolve(copyToClipboard(valueToCopy)).then(
|
|
34
51
|
() => {
|
|
52
|
+
if (scopeGeneration !== scopeGenerationRef.current) return;
|
|
53
|
+
|
|
54
|
+
if (copiedTimerRef.current !== undefined) {
|
|
55
|
+
clearTimeout(copiedTimerRef.current);
|
|
56
|
+
}
|
|
35
57
|
aui.message.setIsCopied(true);
|
|
36
|
-
setTimeout(() =>
|
|
58
|
+
copiedTimerRef.current = setTimeout(() => {
|
|
59
|
+
copiedTimerRef.current = undefined;
|
|
60
|
+
aui.message.setIsCopied(false);
|
|
61
|
+
}, copiedDuration);
|
|
37
62
|
},
|
|
38
63
|
() => {},
|
|
39
64
|
);
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
MessagePartStatus,
|
|
9
9
|
ToolCallMessagePartStatus,
|
|
10
10
|
} from "../../../types/message";
|
|
11
|
+
import { getGroupStatus } from "../../../utils/getGroupStatus";
|
|
11
12
|
import {
|
|
12
13
|
buildGroupTree,
|
|
13
14
|
GROUPBY_MEMO_KEY,
|
|
@@ -21,7 +22,8 @@ export namespace MessagePrimitiveGroupedParts {
|
|
|
21
22
|
* A coalesced group of adjacent parts. Surfaced through the same
|
|
22
23
|
* `{ part }` channel as a leaf {@link EnrichedPartState} so consumers
|
|
23
24
|
* dispatch on a single `switch (part.type)`. `type` is the group key
|
|
24
|
-
* (always `"group-…"`); `status`
|
|
25
|
+
* (always `"group-…"`); `status` is running when any contained part runs,
|
|
26
|
+
* otherwise it mirrors the last contained part.
|
|
25
27
|
*/
|
|
26
28
|
export type GroupPart<TKey extends `group-${string}` = `group-${string}`> = {
|
|
27
29
|
readonly type: TKey;
|
|
@@ -133,8 +135,6 @@ export namespace MessagePrimitiveGroupedParts {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
const COMPLETE_STATUS: MessagePartStatus = Object.freeze({ type: "complete" });
|
|
137
|
-
|
|
138
138
|
const shouldShowIndicator = (
|
|
139
139
|
mode: MessagePrimitiveGroupedParts.IndicatorMode,
|
|
140
140
|
parts: readonly PartState[],
|
|
@@ -194,7 +194,7 @@ const renderNode = <TKey extends `group-${string}`>(
|
|
|
194
194
|
);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
const status = parts
|
|
197
|
+
const status = getGroupStatus(parts, node.indices);
|
|
198
198
|
const groupPart: MessagePrimitiveGroupedParts.GroupPart<TKey> = {
|
|
199
199
|
type: node.key as TKey,
|
|
200
200
|
status,
|