@assistant-ui/core 0.3.12 → 0.3.13
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 +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.js +5 -1
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +5 -1
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +25 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +64 -11
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +14 -0
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +31 -0
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +1 -6
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -4
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts +15 -0
- package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
- package/dist/runtime/queue/message-queue.js +23 -10
- package/dist/runtime/queue/message-queue.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +7 -0
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +3 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +49 -13
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +2 -1
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/store/clients/external-thread.d.ts +7 -0
- package/dist/store/clients/external-thread.d.ts.map +1 -1
- package/dist/store/clients/external-thread.js +460 -369
- package/dist/store/clients/external-thread.js.map +1 -1
- package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +5 -2
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/clients/single-thread-list.d.ts.map +1 -1
- package/dist/store/clients/single-thread-list.js +31 -27
- package/dist/store/clients/single-thread-list.js.map +1 -1
- package/dist/store/clients/suggestions.d.ts +3 -1
- package/dist/store/clients/suggestions.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +65 -37
- package/dist/store/clients/suggestions.js.map +1 -1
- package/dist/store/index.d.ts +2 -2
- package/dist/store/index.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +64 -50
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +98 -82
- package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
- package/dist/store/scope-registration.d.ts +2 -2
- package/dist/store/scopes/suggestions.d.ts +6 -1
- package/dist/store/scopes/suggestions.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +17 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/types/error.d.ts +15 -1
- package/dist/types/error.d.ts.map +1 -1
- package/dist/types/error.js +18 -1
- package/dist/types/error.js.map +1 -1
- package/dist/types/message.d.ts +2 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +1 -0
- package/src/react/client/interactable-model-context.ts +6 -1
- package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +114 -0
- package/src/runtime/api/thread-runtime.ts +8 -1
- package/src/runtime/base/base-composer-runtime-core.ts +100 -8
- package/src/runtime/base/base-thread-runtime-core.ts +36 -0
- package/src/runtime/base/default-edit-composer-runtime-core.ts +1 -15
- package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -11
- package/src/runtime/queue/external-thread-queue-adapter.ts +17 -0
- package/src/runtime/queue/message-queue.ts +26 -9
- package/src/runtimes/external-store/external-store-adapter.ts +7 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +100 -26
- package/src/runtimes/local/local-thread-runtime-core.ts +4 -1
- package/src/store/clients/external-thread.ts +119 -16
- package/src/store/clients/runtime-adapter.ts +5 -2
- package/src/store/clients/single-thread-list.ts +2 -1
- package/src/store/clients/suggestions.ts +39 -13
- package/src/store/runtime-clients/thread-list-item-runtime-client.ts +13 -28
- package/src/store/runtime-clients/thread-runtime-client.ts +5 -0
- package/src/store/scopes/suggestions.ts +6 -0
- package/src/store/scopes/thread.ts +17 -0
- package/src/tests/append-interactable-snapshots.test.ts +350 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +126 -0
- package/src/tests/base-composer-runtime-core.test.ts +108 -1
- package/src/tests/default-edit-composer-runtime-core.test.ts +0 -68
- package/src/tests/external-store-thread-runtime-core-adapter.test.ts +300 -0
- package/src/tests/external-thread-attachments.test.tsx +214 -0
- package/src/tests/external-thread-parity.test.tsx +54 -0
- package/src/tests/external-thread-refetch.test.tsx +117 -0
- package/src/tests/external-thread-suggestions.test.tsx +59 -0
- package/src/tests/message-queue.test.ts +26 -0
- package/src/tests/thread-message-like.test.ts +39 -0
- package/src/tests/thread-switch-events.test.tsx +136 -0
- package/src/types/error.ts +24 -0
- package/src/types/message.ts +2 -0
- package/src/tests/default-thread-composer-runtime-core.test.ts +0 -82
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
RespondToToolApprovalOptions,
|
|
28
28
|
ResumeToolCallOptions,
|
|
29
29
|
SpeechState,
|
|
30
|
+
ThreadSuggestion,
|
|
30
31
|
} from "../../runtime/interfaces/thread-runtime-core";
|
|
31
32
|
import type {
|
|
32
33
|
ExternalThreadQueueAdapter,
|
|
@@ -45,13 +46,14 @@ import { getThreadMessageText } from "../../utils/text";
|
|
|
45
46
|
import { resolveToolApprovalResponse } from "../../runtime/utils/resolveToolApprovalResponse";
|
|
46
47
|
import { toMessagePartStatus } from "../../utils/normalizePartStatus";
|
|
47
48
|
import { ModelContext } from "./model-context-client";
|
|
48
|
-
import {
|
|
49
|
+
import { ThreadSuggestions } from "./suggestions";
|
|
49
50
|
import { Tools } from "../../react/client/Tools";
|
|
50
51
|
import { DataRenderers } from "../../react/client/DataRenderers";
|
|
51
52
|
import { SingleThreadList } from "./single-thread-list";
|
|
52
53
|
|
|
53
54
|
const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = [];
|
|
54
55
|
const EMPTY_BRANCH_IDS: readonly string[] = [];
|
|
56
|
+
const EMPTY_SUGGESTIONS: readonly ThreadSuggestion[] = [];
|
|
55
57
|
|
|
56
58
|
export type ExternalThreadMessage = ThreadMessage & {
|
|
57
59
|
id: string;
|
|
@@ -93,6 +95,13 @@ export type ExternalThreadProps = {
|
|
|
93
95
|
onStartRun?: () => void;
|
|
94
96
|
onCancel?: () => void;
|
|
95
97
|
onResume?: (() => void) | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Handler for re-fetching this thread's state in place, driving
|
|
100
|
+
* `threads.reloadMainThread()`. Unrelated to `onReload`, which re-generates
|
|
101
|
+
* an assistant message. Presence enables the `refetchThread` capability;
|
|
102
|
+
* rejections propagate to the caller.
|
|
103
|
+
*/
|
|
104
|
+
onRefetchThread?: (() => Promise<void>) | undefined;
|
|
96
105
|
onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
|
|
97
106
|
/** Callback for resuming a tool call that is waiting for human input. */
|
|
98
107
|
onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
|
|
@@ -401,6 +410,7 @@ const AttachmentResource = resource(useAttachmentResource);
|
|
|
401
410
|
type ComposerClientResourceProps = {
|
|
402
411
|
type: "thread" | "edit";
|
|
403
412
|
canCancel: boolean;
|
|
413
|
+
isRunning?: boolean;
|
|
404
414
|
isSendDisabled?: boolean;
|
|
405
415
|
onCancel?: () => void;
|
|
406
416
|
onBeginEdit?: () => void;
|
|
@@ -410,6 +420,53 @@ type ComposerClientResourceProps = {
|
|
|
410
420
|
attachmentAdapter?: AttachmentAdapter | undefined;
|
|
411
421
|
};
|
|
412
422
|
|
|
423
|
+
type AttachmentAddOperation = {
|
|
424
|
+
cancelled: boolean;
|
|
425
|
+
attachmentIds: Set<string>;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
class AttachmentAddOperations {
|
|
429
|
+
private readonly operations = new Set<AttachmentAddOperation>();
|
|
430
|
+
|
|
431
|
+
start() {
|
|
432
|
+
const operation: AttachmentAddOperation = {
|
|
433
|
+
cancelled: false,
|
|
434
|
+
attachmentIds: new Set(),
|
|
435
|
+
};
|
|
436
|
+
this.operations.add(operation);
|
|
437
|
+
return operation;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
accept(operation: AttachmentAddOperation, attachmentId: string) {
|
|
441
|
+
if (operation.cancelled) return false;
|
|
442
|
+
operation.attachmentIds.add(attachmentId);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
finish(operation: AttachmentAddOperation) {
|
|
447
|
+
this.operations.delete(operation);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
isCancelled(operation: AttachmentAddOperation) {
|
|
451
|
+
return operation.cancelled;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
cancel(attachmentId: string) {
|
|
455
|
+
for (const operation of [...this.operations]) {
|
|
456
|
+
if (!operation.attachmentIds.has(attachmentId)) continue;
|
|
457
|
+
operation.cancelled = true;
|
|
458
|
+
this.operations.delete(operation);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
cancelAll() {
|
|
463
|
+
for (const operation of this.operations) {
|
|
464
|
+
operation.cancelled = true;
|
|
465
|
+
}
|
|
466
|
+
this.operations.clear();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
413
470
|
const useQueueItemClient = ({
|
|
414
471
|
item,
|
|
415
472
|
onMove,
|
|
@@ -431,11 +488,11 @@ const QueueItemClient = resource(useQueueItemClient);
|
|
|
431
488
|
|
|
432
489
|
const drainAdapterAdd = async (
|
|
433
490
|
result: ReturnType<AttachmentAdapter["add"]>,
|
|
434
|
-
upsert: (attachment: Attachment) =>
|
|
491
|
+
upsert: (attachment: Attachment) => boolean,
|
|
435
492
|
) => {
|
|
436
493
|
if (Symbol.asyncIterator in result) {
|
|
437
494
|
for await (const attachment of result) {
|
|
438
|
-
upsert(attachment);
|
|
495
|
+
if (!upsert(attachment)) break;
|
|
439
496
|
}
|
|
440
497
|
} else {
|
|
441
498
|
upsert(await result);
|
|
@@ -460,6 +517,7 @@ const useLiveState = <T>(initial: T) => {
|
|
|
460
517
|
const useComposerClientResource = ({
|
|
461
518
|
type,
|
|
462
519
|
canCancel,
|
|
520
|
+
isRunning = false,
|
|
463
521
|
isSendDisabled = false,
|
|
464
522
|
onCancel,
|
|
465
523
|
onBeginEdit,
|
|
@@ -484,6 +542,10 @@ const useComposerClientResource = ({
|
|
|
484
542
|
const [quote, setQuote, quoteRef] = useLiveState<
|
|
485
543
|
{ readonly text: string; readonly messageId: string } | undefined
|
|
486
544
|
>(undefined);
|
|
545
|
+
const attachmentAddOperations = useMemo(
|
|
546
|
+
() => new AttachmentAddOperations(),
|
|
547
|
+
[],
|
|
548
|
+
);
|
|
487
549
|
|
|
488
550
|
const updateFromMessage = () => {
|
|
489
551
|
if (!message) return;
|
|
@@ -503,6 +565,7 @@ const useComposerClientResource = ({
|
|
|
503
565
|
AttachmentResource({
|
|
504
566
|
attachment,
|
|
505
567
|
onRemove: async () => {
|
|
568
|
+
attachmentAddOperations.cancel(attachment.id);
|
|
506
569
|
await attachmentAdapter?.remove(attachment);
|
|
507
570
|
setAttachments((prev) =>
|
|
508
571
|
prev.filter((a) => a.id !== attachment.id),
|
|
@@ -607,10 +670,22 @@ const useComposerClientResource = ({
|
|
|
607
670
|
);
|
|
608
671
|
}
|
|
609
672
|
if (!isCreateAttachment(fileOrAttachment) && attachmentAdapter) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
673
|
+
const operation = attachmentAddOperations.start();
|
|
674
|
+
try {
|
|
675
|
+
await drainAdapterAdd(
|
|
676
|
+
attachmentAdapter.add({ file: fileOrAttachment }),
|
|
677
|
+
(attachment) => {
|
|
678
|
+
if (!attachmentAddOperations.accept(operation, attachment.id))
|
|
679
|
+
return false;
|
|
680
|
+
upsertAttachment(attachment);
|
|
681
|
+
return true;
|
|
682
|
+
},
|
|
683
|
+
);
|
|
684
|
+
attachmentAddOperations.finish(operation);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
attachmentAddOperations.finish(operation);
|
|
687
|
+
if (!attachmentAddOperations.isCancelled(operation)) throw error;
|
|
688
|
+
}
|
|
614
689
|
} else if (!isCreateAttachment(fileOrAttachment)) {
|
|
615
690
|
const newAttachment: Attachment = {
|
|
616
691
|
id: Math.random().toString(36).substring(7),
|
|
@@ -635,6 +710,7 @@ const useComposerClientResource = ({
|
|
|
635
710
|
}
|
|
636
711
|
},
|
|
637
712
|
clearAttachments: async () => {
|
|
713
|
+
attachmentAddOperations.cancelAll();
|
|
638
714
|
const removed = attachmentsRef.current;
|
|
639
715
|
setAttachments([]);
|
|
640
716
|
await removePendingAttachments(removed);
|
|
@@ -646,6 +722,7 @@ const useComposerClientResource = ({
|
|
|
646
722
|
return attachmentClients.get(selector);
|
|
647
723
|
},
|
|
648
724
|
reset: async () => {
|
|
725
|
+
attachmentAddOperations.cancelAll();
|
|
649
726
|
const removed = attachmentsRef.current;
|
|
650
727
|
setText("");
|
|
651
728
|
setRole("user");
|
|
@@ -657,18 +734,21 @@ const useComposerClientResource = ({
|
|
|
657
734
|
send: (opts?: ComposerSendOptions) => {
|
|
658
735
|
const currentQuote = quoteRef.current;
|
|
659
736
|
const currentText = textRef.current;
|
|
737
|
+
const currentRole = roleRef.current;
|
|
738
|
+
const currentRunConfig = runConfigRef.current;
|
|
660
739
|
const currentAttachments = attachmentsRef.current;
|
|
661
740
|
const isEmpty = !currentText.trim() && !currentAttachments.length;
|
|
662
741
|
if (!isEditingRef.current) throw new Error("Composer is not available");
|
|
663
742
|
if (isEmpty || isSendDisabled) return;
|
|
664
743
|
|
|
744
|
+
attachmentAddOperations.cancelAll();
|
|
665
745
|
setText("");
|
|
666
746
|
setAttachments([]);
|
|
667
747
|
setQuote(undefined);
|
|
668
748
|
|
|
669
749
|
const dispatch = (sendAttachments: readonly Attachment[]) => {
|
|
670
750
|
const composedMessage: AppendMessage = {
|
|
671
|
-
role:
|
|
751
|
+
role: currentRole,
|
|
672
752
|
content: currentText
|
|
673
753
|
? [{ type: "text" as const, text: currentText }]
|
|
674
754
|
: [],
|
|
@@ -676,7 +756,7 @@ const useComposerClientResource = ({
|
|
|
676
756
|
createdAt: new Date(),
|
|
677
757
|
parentId: null,
|
|
678
758
|
sourceId: null,
|
|
679
|
-
runConfig:
|
|
759
|
+
runConfig: currentRunConfig,
|
|
680
760
|
startRun: opts?.startRun,
|
|
681
761
|
metadata: {
|
|
682
762
|
custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
|
|
@@ -684,7 +764,7 @@ const useComposerClientResource = ({
|
|
|
684
764
|
};
|
|
685
765
|
// edit sends carry a sourceId contract; only thread sends queue
|
|
686
766
|
if (queue && type === "thread") {
|
|
687
|
-
if (opts?.steer ??
|
|
767
|
+
if (opts?.steer ?? isRunning) queue.steer(composedMessage);
|
|
688
768
|
else queue.enqueue(composedMessage);
|
|
689
769
|
} else {
|
|
690
770
|
onSend?.(composedMessage);
|
|
@@ -829,6 +909,7 @@ const useExternalThread = ({
|
|
|
829
909
|
onStartRun,
|
|
830
910
|
onCancel,
|
|
831
911
|
onResume,
|
|
912
|
+
onRefetchThread,
|
|
832
913
|
onAddToolResult,
|
|
833
914
|
onResumeToolCall,
|
|
834
915
|
onLoadExternalState,
|
|
@@ -942,7 +1023,15 @@ const useExternalThread = ({
|
|
|
942
1023
|
);
|
|
943
1024
|
|
|
944
1025
|
const handleCancelRun = () => {
|
|
945
|
-
|
|
1026
|
+
// Nothing is aborted without a handler, so pausing the queue would hold
|
|
1027
|
+
// the pending items against a run that keeps going.
|
|
1028
|
+
if (!onCancel) return;
|
|
1029
|
+
|
|
1030
|
+
// Before the run is aborted, so the settle it produces keeps the pending
|
|
1031
|
+
// items instead of dispatching the next one at the moment the user
|
|
1032
|
+
// stopped.
|
|
1033
|
+
queue?.__internal_notifyCancelled?.();
|
|
1034
|
+
onCancel();
|
|
946
1035
|
};
|
|
947
1036
|
|
|
948
1037
|
const handleSendNew = (message: AppendMessage) => {
|
|
@@ -952,6 +1041,8 @@ const useExternalThread = ({
|
|
|
952
1041
|
};
|
|
953
1042
|
|
|
954
1043
|
const headId = messages.at(-1)?.id ?? null;
|
|
1044
|
+
const hasCancel = !!onCancel;
|
|
1045
|
+
const hasRefetchThread = !!onRefetchThread;
|
|
955
1046
|
const composerQueue = useMemo(
|
|
956
1047
|
(): ExternalThreadQueueAdapter | undefined =>
|
|
957
1048
|
queue && {
|
|
@@ -967,7 +1058,8 @@ const useExternalThread = ({
|
|
|
967
1058
|
const composerClient = useClientResource(
|
|
968
1059
|
ComposerClientResource({
|
|
969
1060
|
type: "thread",
|
|
970
|
-
canCancel: isRunning,
|
|
1061
|
+
canCancel: isRunning && hasCancel,
|
|
1062
|
+
isRunning,
|
|
971
1063
|
isSendDisabled,
|
|
972
1064
|
onCancel: handleCancelRun,
|
|
973
1065
|
onSend: handleSendNew,
|
|
@@ -975,6 +1067,9 @@ const useExternalThread = ({
|
|
|
975
1067
|
attachmentAdapter,
|
|
976
1068
|
}),
|
|
977
1069
|
);
|
|
1070
|
+
const suggestionsClient = useClientResource(
|
|
1071
|
+
ThreadSuggestions(EMPTY_SUGGESTIONS),
|
|
1072
|
+
);
|
|
978
1073
|
|
|
979
1074
|
const hasQueue = !!queue;
|
|
980
1075
|
const hasBranches = !!branches;
|
|
@@ -998,8 +1093,8 @@ const useExternalThread = ({
|
|
|
998
1093
|
edit: hasEdit,
|
|
999
1094
|
delete: false,
|
|
1000
1095
|
reload: hasReload,
|
|
1001
|
-
refetchThread:
|
|
1002
|
-
cancel:
|
|
1096
|
+
refetchThread: hasRefetchThread,
|
|
1097
|
+
cancel: hasCancel,
|
|
1003
1098
|
speech: hasSpeech,
|
|
1004
1099
|
attachments: hasAttachments,
|
|
1005
1100
|
feedback: hasFeedback,
|
|
@@ -1012,7 +1107,7 @@ const useExternalThread = ({
|
|
|
1012
1107
|
},
|
|
1013
1108
|
messages: messageStates,
|
|
1014
1109
|
state: threadState ?? {},
|
|
1015
|
-
suggestions:
|
|
1110
|
+
suggestions: EMPTY_SUGGESTIONS,
|
|
1016
1111
|
extras,
|
|
1017
1112
|
speech,
|
|
1018
1113
|
voice: undefined,
|
|
@@ -1028,6 +1123,8 @@ const useExternalThread = ({
|
|
|
1028
1123
|
hasBranches,
|
|
1029
1124
|
hasEdit,
|
|
1030
1125
|
hasReload,
|
|
1126
|
+
hasCancel,
|
|
1127
|
+
hasRefetchThread,
|
|
1031
1128
|
hasAttachments,
|
|
1032
1129
|
hasFeedback,
|
|
1033
1130
|
hasSpeech,
|
|
@@ -1039,6 +1136,7 @@ const useExternalThread = ({
|
|
|
1039
1136
|
return {
|
|
1040
1137
|
getState: () => state,
|
|
1041
1138
|
composer: () => composerClient.methods,
|
|
1139
|
+
suggestions: () => suggestionsClient.methods,
|
|
1042
1140
|
append: (message) => {
|
|
1043
1141
|
const appendMessage: AppendMessage =
|
|
1044
1142
|
typeof message === "string"
|
|
@@ -1081,6 +1179,7 @@ const useExternalThread = ({
|
|
|
1081
1179
|
onResume();
|
|
1082
1180
|
},
|
|
1083
1181
|
cancelRun: handleCancelRun,
|
|
1182
|
+
...(onRefetchThread && { unstable_refetchThread: onRefetchThread }),
|
|
1084
1183
|
importExternalState: (state: unknown) => {
|
|
1085
1184
|
if (!onLoadExternalState)
|
|
1086
1185
|
throw new Error(
|
|
@@ -1147,6 +1246,10 @@ attachTransformScopes(useExternalThread, (scopes, parent) => {
|
|
|
1147
1246
|
scopes.dataRenderers = DataRenderers();
|
|
1148
1247
|
}
|
|
1149
1248
|
if (!scopes.suggestions && parent.suggestions.source === null) {
|
|
1150
|
-
scopes.suggestions =
|
|
1249
|
+
scopes.suggestions = Derived({
|
|
1250
|
+
source: "thread",
|
|
1251
|
+
query: {},
|
|
1252
|
+
get: (aui) => aui.thread.suggestions(),
|
|
1253
|
+
});
|
|
1151
1254
|
}
|
|
1152
1255
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AssistantClient, ScopesConfig } from "@assistant-ui/store";
|
|
2
2
|
import { Derived } from "@assistant-ui/store/client";
|
|
3
3
|
import { ModelContext } from "./model-context-client";
|
|
4
|
-
import { Suggestions } from "./suggestions";
|
|
5
4
|
|
|
6
5
|
export const baseRuntimeAdapterTransformScopes = (
|
|
7
6
|
scopes: ScopesConfig,
|
|
@@ -27,6 +26,10 @@ export const baseRuntimeAdapterTransformScopes = (
|
|
|
27
26
|
scopes.modelContext = ModelContext();
|
|
28
27
|
}
|
|
29
28
|
if (!scopes.suggestions && parent.suggestions.source === null) {
|
|
30
|
-
scopes.suggestions =
|
|
29
|
+
scopes.suggestions = Derived({
|
|
30
|
+
source: "thread",
|
|
31
|
+
query: {},
|
|
32
|
+
get: (aui) => aui.thread.suggestions(),
|
|
33
|
+
});
|
|
31
34
|
}
|
|
32
35
|
};
|
|
@@ -79,7 +79,8 @@ const useSingleThreadList = ({
|
|
|
79
79
|
},
|
|
80
80
|
getLoadThreadsPromise: () => RESOLVED_PROMISE,
|
|
81
81
|
reload: () => RESOLVED_PROMISE,
|
|
82
|
-
reloadMainThread: () =>
|
|
82
|
+
reloadMainThread: () =>
|
|
83
|
+
threadClient.methods.unstable_refetchThread?.() ?? RESOLVED_PROMISE,
|
|
83
84
|
loadMore: () => RESOLVED_PROMISE,
|
|
84
85
|
item: (selector) => {
|
|
85
86
|
if (
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
2
|
import { resource, withKey } from "@assistant-ui/tap";
|
|
3
3
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
4
4
|
import { useClientLookup } from "@assistant-ui/store/client";
|
|
5
5
|
import type { SuggestionsState } from "../scopes/suggestions";
|
|
6
6
|
import type { SuggestionState } from "../scopes/suggestion";
|
|
7
|
+
import type { ThreadSuggestion } from "../../runtime/interfaces/thread-runtime-core";
|
|
7
8
|
|
|
8
9
|
export type SuggestionConfig =
|
|
9
10
|
| string
|
|
@@ -19,7 +20,24 @@ const useSuggestionClient = (
|
|
|
19
20
|
|
|
20
21
|
const SuggestionClient = resource(useSuggestionClient);
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
+
const useSuggestionsClient = (
|
|
24
|
+
state: SuggestionsState,
|
|
25
|
+
): ClientOutput<"suggestions"> => {
|
|
26
|
+
const suggestionClients = useClientLookup(
|
|
27
|
+
state.suggestions.map((suggestion, index) =>
|
|
28
|
+
withKey(index, SuggestionClient(suggestion), [suggestion]),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
getState: () => state,
|
|
34
|
+
suggestion: ({ index }: { index: number }) => {
|
|
35
|
+
return suggestionClients.get({ index });
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const useStaticSuggestions = (
|
|
23
41
|
suggestions?: SuggestionConfig[],
|
|
24
42
|
): ClientOutput<"suggestions"> => {
|
|
25
43
|
const [state] = useState<SuggestionsState>(() => {
|
|
@@ -43,18 +61,26 @@ const useSuggestionsResource = (
|
|
|
43
61
|
};
|
|
44
62
|
});
|
|
45
63
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
return useSuggestionsClient(state);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Suggestions = resource(useStaticSuggestions);
|
|
68
|
+
|
|
69
|
+
const useThreadSuggestions = (
|
|
70
|
+
suggestions: readonly ThreadSuggestion[],
|
|
71
|
+
): ClientOutput<"suggestions"> => {
|
|
72
|
+
const state = useMemo<SuggestionsState>(
|
|
73
|
+
() => ({
|
|
74
|
+
suggestions: suggestions.map((s) => ({
|
|
75
|
+
title: s.prompt,
|
|
76
|
+
label: "",
|
|
77
|
+
prompt: s.prompt,
|
|
78
|
+
})),
|
|
79
|
+
}),
|
|
80
|
+
[suggestions],
|
|
50
81
|
);
|
|
51
82
|
|
|
52
|
-
return
|
|
53
|
-
getState: () => state,
|
|
54
|
-
suggestion: ({ index }: { index: number }) => {
|
|
55
|
-
return suggestionClients.get({ index });
|
|
56
|
-
},
|
|
57
|
-
};
|
|
83
|
+
return useSuggestionsClient(state);
|
|
58
84
|
};
|
|
59
85
|
|
|
60
|
-
export const
|
|
86
|
+
export const ThreadSuggestions = resource(useThreadSuggestions);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useEffect, useMemo } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
3
2
|
import { resource } from "@assistant-ui/tap";
|
|
4
3
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
5
4
|
import { useAssistantEmit } from "@assistant-ui/store/client";
|
|
6
|
-
import type {
|
|
7
|
-
ThreadListItemEventType,
|
|
8
|
-
ThreadListItemRuntime,
|
|
9
|
-
} from "../../runtime/api/thread-list-item-runtime";
|
|
5
|
+
import type { ThreadListItemRuntime } from "../../runtime/api/thread-list-item-runtime";
|
|
10
6
|
import { useSubscribable } from "./useSubscribable";
|
|
11
7
|
|
|
12
8
|
const useThreadListItemClient = ({
|
|
@@ -28,29 +24,18 @@ const useThreadListItemClient = ({
|
|
|
28
24
|
}, [runtimeState, mainThreadIsRunning]);
|
|
29
25
|
const emit = useAssistantEmit();
|
|
30
26
|
|
|
31
|
-
//
|
|
27
|
+
// Emitted after the flush that rebinds the derived scopes; the runtime's own
|
|
28
|
+
// synchronous notification would be delivered against the pre-switch binding.
|
|
29
|
+
const { isMain, id: threadId } = runtimeState;
|
|
30
|
+
const selectionRef = useRef({ isMain, threadId });
|
|
32
31
|
useEffect(() => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
for (const event of threadListItemEvents) {
|
|
42
|
-
const unsubscribe = runtime.unstable_on(event, () => {
|
|
43
|
-
emit(`threadListItem.${event}`, {
|
|
44
|
-
threadId: runtime.getState()!.id,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
unsubscribers.push(unsubscribe);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
for (const unsub of unsubscribers) unsub();
|
|
52
|
-
};
|
|
53
|
-
}, [runtime, emit]);
|
|
32
|
+
const previous = selectionRef.current;
|
|
33
|
+
if (previous.isMain === isMain && previous.threadId === threadId) return;
|
|
34
|
+
selectionRef.current = { isMain, threadId };
|
|
35
|
+
emit(isMain ? "threadListItem.switchedTo" : "threadListItem.switchedAway", {
|
|
36
|
+
threadId,
|
|
37
|
+
});
|
|
38
|
+
}, [isMain, threadId, emit]);
|
|
54
39
|
|
|
55
40
|
return {
|
|
56
41
|
getState: () => state,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@assistant-ui/store/client";
|
|
13
13
|
import { ComposerClient } from "./composer-runtime-client";
|
|
14
14
|
import { MessageClient } from "./message-runtime-client";
|
|
15
|
+
import { ThreadSuggestions } from "../clients/suggestions";
|
|
15
16
|
import { useSubscribable } from "./useSubscribable";
|
|
16
17
|
import type { ThreadState } from "../scopes/thread";
|
|
17
18
|
|
|
@@ -78,6 +79,9 @@ const useThreadClient = ({
|
|
|
78
79
|
threadIdRef,
|
|
79
80
|
}),
|
|
80
81
|
);
|
|
82
|
+
const suggestions = useClientResource(
|
|
83
|
+
ThreadSuggestions(runtimeState.suggestions),
|
|
84
|
+
);
|
|
81
85
|
const messages = useClientLookup(
|
|
82
86
|
runtimeState.messages.map((m) =>
|
|
83
87
|
withKey(m.id, MessageClientById({ runtime, id: m.id, threadIdRef }), [
|
|
@@ -109,6 +113,7 @@ const useThreadClient = ({
|
|
|
109
113
|
return {
|
|
110
114
|
getState: () => state,
|
|
111
115
|
composer: () => composer.methods,
|
|
116
|
+
suggestions: () => suggestions.methods,
|
|
112
117
|
append: runtime.append,
|
|
113
118
|
deleteMessage: runtime.deleteMessage,
|
|
114
119
|
startRun: runtime.startRun,
|
|
@@ -15,6 +15,12 @@ export type SuggestionsMethods = {
|
|
|
15
15
|
suggestion(query: { index: number }): SuggestionMethods;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export type SuggestionsMeta = {
|
|
19
|
+
source: "thread";
|
|
20
|
+
query: Record<string, never>;
|
|
21
|
+
};
|
|
22
|
+
|
|
18
23
|
export type SuggestionsClientSchema = {
|
|
19
24
|
methods: SuggestionsMethods;
|
|
25
|
+
meta: SuggestionsMeta;
|
|
20
26
|
};
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
import type { ModelContext } from "../../model-context/types";
|
|
18
18
|
import type { MessageMethods, MessageState } from "./message";
|
|
19
19
|
import type { ComposerMethods, ComposerState } from "./composer";
|
|
20
|
+
import type { SuggestionsMethods } from "./suggestions";
|
|
20
21
|
|
|
21
22
|
export type ThreadState = {
|
|
22
23
|
/**
|
|
@@ -71,6 +72,10 @@ export type ThreadMethods = {
|
|
|
71
72
|
* The thread composer runtime.
|
|
72
73
|
*/
|
|
73
74
|
composer(): ComposerMethods;
|
|
75
|
+
/**
|
|
76
|
+
* The suggestions shown for this thread.
|
|
77
|
+
*/
|
|
78
|
+
suggestions(): SuggestionsMethods;
|
|
74
79
|
/**
|
|
75
80
|
* Append a new message to the thread.
|
|
76
81
|
*
|
|
@@ -100,6 +105,18 @@ export type ThreadMethods = {
|
|
|
100
105
|
*/
|
|
101
106
|
resumeRun(config: CreateResumeRunConfig): void;
|
|
102
107
|
cancelRun(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Re-fetch this thread's state from its backing store, in place: the tap
|
|
110
|
+
* thread's refetch hook, which `threads.reloadMainThread()` prefers and
|
|
111
|
+
* whose rejection it propagates. `capabilities.refetchThread` is the
|
|
112
|
+
* portable feature-detection signal; a legacy-bridged thread reports it
|
|
113
|
+
* there while routing the refetch through its runtime, not this method.
|
|
114
|
+
* The method-shorthand optionality is load-bearing: an explicit
|
|
115
|
+
* `| undefined` stops `ThreadMethods` satisfying `ClientMethods` and
|
|
116
|
+
* collapses the client schema, which only a workspace-level app typecheck
|
|
117
|
+
* surfaces.
|
|
118
|
+
*/
|
|
119
|
+
unstable_refetchThread?(): Promise<void>;
|
|
103
120
|
getModelContext(): ModelContext;
|
|
104
121
|
export(): ExportedMessageRepository;
|
|
105
122
|
import(repository: ExportedMessageRepository): void;
|