@assistant-ui/react 0.12.0 → 0.12.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/client/Suggestions.d.ts +11 -0
- package/dist/client/Suggestions.d.ts.map +1 -0
- package/dist/client/Suggestions.js +43 -0
- package/dist/client/Suggestions.js.map +1 -0
- package/dist/client/Tools.d.ts.map +1 -1
- package/dist/client/Tools.js +5 -1
- package/dist/client/Tools.js.map +1 -1
- package/dist/context/providers/SuggestionByIndexProvider.d.ts +6 -0
- package/dist/context/providers/SuggestionByIndexProvider.d.ts.map +1 -0
- package/dist/context/providers/SuggestionByIndexProvider.js +14 -0
- package/dist/context/providers/SuggestionByIndexProvider.js.map +1 -0
- package/dist/context/providers/ThreadListItemProvider.d.ts +3 -2
- package/dist/context/providers/ThreadListItemProvider.d.ts.map +1 -1
- package/dist/context/providers/ThreadListItemProvider.js +3 -6
- package/dist/context/providers/ThreadListItemProvider.js.map +1 -1
- package/dist/context/providers/index.d.ts +2 -1
- package/dist/context/providers/index.d.ts.map +1 -1
- package/dist/context/providers/index.js +2 -1
- package/dist/context/providers/index.js.map +1 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts +5 -0
- package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +2 -4
- package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
- package/dist/legacy-runtime/RuntimeAdapter.js +2 -1
- package/dist/legacy-runtime/RuntimeAdapter.js.map +1 -1
- package/dist/legacy-runtime/client/ThreadListRuntimeClient.js +3 -3
- package/dist/legacy-runtime/client/ThreadListRuntimeClient.js.map +1 -1
- package/dist/legacy-runtime/runtime/RuntimeBindings.d.ts +0 -4
- package/dist/legacy-runtime/runtime/RuntimeBindings.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +3 -3
- package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime/ThreadListRuntime.js +4 -5
- package/dist/legacy-runtime/runtime/ThreadListRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +3 -5
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +10 -12
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +14 -19
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/core/ThreadListRuntimeCore.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/core/ThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadListRuntimeCore.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadListRuntimeCore.js +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadListRuntimeCore.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js +32 -4
- package/dist/legacy-runtime/runtime-cores/external-store/external-message-converter.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/local/LocalThreadListRuntimeCore.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/local/LocalThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/local/LocalThreadListRuntimeCore.js +1 -1
- package/dist/legacy-runtime/runtime-cores/local/LocalThreadListRuntimeCore.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/BaseSubscribable.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/BaseSubscribable.js +3 -0
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/BaseSubscribable.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts +3 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.js +8 -5
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js +2 -2
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/model-context/index.d.ts +1 -0
- package/dist/model-context/index.d.ts.map +1 -1
- package/dist/model-context/index.js +1 -0
- package/dist/model-context/index.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/message/MessageParts.d.ts +11 -0
- package/dist/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.js +18 -2
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.d.ts +18 -0
- package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -0
- package/dist/primitives/suggestion/SuggestionDescription.js +19 -0
- package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -0
- package/dist/primitives/suggestion/SuggestionTitle.d.ts +18 -0
- package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -0
- package/dist/primitives/suggestion/SuggestionTitle.js +19 -0
- package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts +49 -0
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js +45 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -0
- package/dist/primitives/suggestion/index.d.ts +4 -0
- package/dist/primitives/suggestion/index.d.ts.map +1 -0
- package/dist/primitives/suggestion/index.js +4 -0
- package/dist/primitives/suggestion/index.js.map +1 -0
- package/dist/primitives/thread/ThreadSuggestions.d.ts +53 -0
- package/dist/primitives/thread/ThreadSuggestions.d.ts.map +1 -0
- package/dist/primitives/thread/ThreadSuggestions.js +58 -0
- package/dist/primitives/thread/ThreadSuggestions.js.map +1 -0
- package/dist/primitives/thread/index.d.ts +1 -0
- package/dist/primitives/thread/index.d.ts.map +1 -1
- package/dist/primitives/thread/index.js +1 -0
- package/dist/primitives/thread/index.js.map +1 -1
- package/dist/types/scopes/index.d.ts +2 -0
- package/dist/types/scopes/index.d.ts.map +1 -1
- package/dist/types/scopes/suggestion.d.ts +20 -0
- package/dist/types/scopes/suggestion.d.ts.map +1 -0
- package/dist/types/scopes/suggestion.js +2 -0
- package/dist/types/scopes/suggestion.js.map +1 -0
- package/dist/types/scopes/suggestions.d.ts +20 -0
- package/dist/types/scopes/suggestions.d.ts.map +1 -0
- package/dist/types/scopes/suggestions.js +2 -0
- package/dist/types/scopes/suggestions.js.map +1 -0
- package/dist/types/store-augmentation.d.ts +4 -0
- package/dist/types/store-augmentation.d.ts.map +1 -1
- package/dist/utils/idUtils.d.ts +2 -0
- package/dist/utils/idUtils.d.ts.map +1 -1
- package/dist/utils/idUtils.js +3 -0
- package/dist/utils/idUtils.js.map +1 -1
- package/package.json +10 -10
- package/src/client/Suggestions.ts +74 -0
- package/src/client/Tools.ts +10 -1
- package/src/context/providers/SuggestionByIndexProvider.tsx +23 -0
- package/src/context/providers/ThreadListItemProvider.tsx +6 -8
- package/src/context/providers/index.ts +2 -1
- package/src/legacy-runtime/AssistantRuntimeProvider.tsx +8 -5
- package/src/legacy-runtime/RuntimeAdapter.ts +2 -1
- package/src/legacy-runtime/client/ThreadListRuntimeClient.ts +3 -3
- package/src/legacy-runtime/runtime/RuntimeBindings.ts +0 -4
- package/src/legacy-runtime/runtime/ThreadListRuntime.ts +7 -8
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +3 -4
- package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +19 -24
- package/src/legacy-runtime/runtime-cores/core/ThreadListRuntimeCore.tsx +1 -1
- package/src/legacy-runtime/runtime-cores/external-store/ExternalStoreThreadListRuntimeCore.tsx +1 -1
- package/src/legacy-runtime/runtime-cores/external-store/external-message-converter.tsx +42 -7
- package/src/legacy-runtime/runtime-cores/local/LocalThreadListRuntimeCore.tsx +1 -1
- package/src/legacy-runtime/runtime-cores/remote-thread-list/BaseSubscribable.tsx +3 -0
- package/src/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.tsx +20 -6
- package/src/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.tsx +2 -2
- package/src/model-context/index.ts +2 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -1
- package/src/primitives/index.ts +1 -0
- package/src/primitives/message/MessageParts.tsx +45 -1
- package/src/primitives/suggestion/SuggestionDescription.tsx +33 -0
- package/src/primitives/suggestion/SuggestionTitle.tsx +33 -0
- package/src/primitives/suggestion/SuggestionTrigger.tsx +79 -0
- package/src/primitives/suggestion/index.ts +3 -0
- package/src/primitives/thread/ThreadSuggestions.tsx +109 -0
- package/src/primitives/thread/index.ts +4 -0
- package/src/tests/external-message-converter.test.ts +105 -16
- package/src/types/scopes/index.ts +12 -0
- package/src/types/scopes/suggestion.ts +20 -0
- package/src/types/scopes/suggestions.ts +21 -0
- package/src/types/store-augmentation.ts +4 -0
- package/src/utils/idUtils.tsx +4 -0
package/src/primitives/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * as MessagePartPrimitive from "./messagePart";
|
|
|
8
8
|
export * as ErrorPrimitive from "./error";
|
|
9
9
|
export * as MessagePrimitive from "./message";
|
|
10
10
|
export * as ThreadPrimitive from "./thread";
|
|
11
|
+
export * as SuggestionPrimitive from "./suggestion";
|
|
11
12
|
export * as ThreadListPrimitive from "./threadList";
|
|
12
13
|
export * as ThreadListItemPrimitive from "./threadListItem";
|
|
13
14
|
export * as ThreadListItemMorePrimitive from "./threadListItemMore";
|
|
@@ -242,6 +242,17 @@ export namespace MessagePrimitiveParts {
|
|
|
242
242
|
ReasoningGroup?: ReasoningGroupComponent;
|
|
243
243
|
}
|
|
244
244
|
| undefined;
|
|
245
|
+
/**
|
|
246
|
+
* When enabled, shows the Empty component if the last part in the message
|
|
247
|
+
* is anything other than Text or Reasoning.
|
|
248
|
+
*
|
|
249
|
+
* This can be useful to ensure there's always a visible element at the end
|
|
250
|
+
* of messages that end with non-text content like tool calls or images.
|
|
251
|
+
*
|
|
252
|
+
* @experimental This API is experimental and may change in future versions.
|
|
253
|
+
* @default true
|
|
254
|
+
*/
|
|
255
|
+
unstable_showEmptyOnNonTextEnd?: boolean | undefined;
|
|
245
256
|
};
|
|
246
257
|
}
|
|
247
258
|
|
|
@@ -429,6 +440,30 @@ const EmptyParts = memo(
|
|
|
429
440
|
prev.components?.Text === next.components?.Text,
|
|
430
441
|
);
|
|
431
442
|
|
|
443
|
+
const ConditionalEmptyImpl: FC<{
|
|
444
|
+
components: MessagePrimitiveParts.Props["components"];
|
|
445
|
+
enabled: boolean;
|
|
446
|
+
}> = ({ components, enabled }) => {
|
|
447
|
+
const shouldShowEmpty = useAuiState(({ message }) => {
|
|
448
|
+
if (!enabled) return false;
|
|
449
|
+
if (message.parts.length === 0) return false;
|
|
450
|
+
|
|
451
|
+
const lastPart = message.parts[message.parts.length - 1];
|
|
452
|
+
return lastPart?.type !== "text" && lastPart?.type !== "reasoning";
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
if (!shouldShowEmpty) return null;
|
|
456
|
+
return <EmptyParts components={components} />;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const ConditionalEmpty = memo(
|
|
460
|
+
ConditionalEmptyImpl,
|
|
461
|
+
(prev, next) =>
|
|
462
|
+
prev.enabled === next.enabled &&
|
|
463
|
+
prev.components?.Empty === next.components?.Empty &&
|
|
464
|
+
prev.components?.Text === next.components?.Text,
|
|
465
|
+
);
|
|
466
|
+
|
|
432
467
|
/**
|
|
433
468
|
* Renders the parts of a message with support for multiple content types.
|
|
434
469
|
*
|
|
@@ -455,6 +490,7 @@ const EmptyParts = memo(
|
|
|
455
490
|
*/
|
|
456
491
|
export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
457
492
|
components,
|
|
493
|
+
unstable_showEmptyOnNonTextEnd = true,
|
|
458
494
|
}) => {
|
|
459
495
|
const contentLength = useAuiState(({ message }) => message.parts.length);
|
|
460
496
|
const messageRanges = useMessagePartsGroups();
|
|
@@ -520,7 +556,15 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
|
520
556
|
});
|
|
521
557
|
}, [messageRanges, components, contentLength]);
|
|
522
558
|
|
|
523
|
-
return
|
|
559
|
+
return (
|
|
560
|
+
<>
|
|
561
|
+
{partsElements}
|
|
562
|
+
<ConditionalEmpty
|
|
563
|
+
components={components}
|
|
564
|
+
enabled={unstable_showEmptyOnNonTextEnd}
|
|
565
|
+
/>
|
|
566
|
+
</>
|
|
567
|
+
);
|
|
524
568
|
};
|
|
525
569
|
|
|
526
570
|
MessagePrimitiveParts.displayName = "MessagePrimitive.Parts";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Primitive } from "@radix-ui/react-primitive";
|
|
4
|
+
import { type ElementRef, forwardRef, ComponentPropsWithoutRef } from "react";
|
|
5
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
6
|
+
|
|
7
|
+
export namespace SuggestionPrimitiveDescription {
|
|
8
|
+
export type Element = ElementRef<typeof Primitive.span>;
|
|
9
|
+
export type Props = ComponentPropsWithoutRef<typeof Primitive.span>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders the description/label of the suggestion.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <SuggestionPrimitive.Description />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const SuggestionPrimitiveDescription = forwardRef<
|
|
21
|
+
SuggestionPrimitiveDescription.Element,
|
|
22
|
+
SuggestionPrimitiveDescription.Props
|
|
23
|
+
>((props, ref) => {
|
|
24
|
+
const label = useAuiState(({ suggestion }) => suggestion.label);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Primitive.span {...props} ref={ref}>
|
|
28
|
+
{props.children ?? label}
|
|
29
|
+
</Primitive.span>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
SuggestionPrimitiveDescription.displayName = "SuggestionPrimitive.Description";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Primitive } from "@radix-ui/react-primitive";
|
|
4
|
+
import { type ElementRef, forwardRef, ComponentPropsWithoutRef } from "react";
|
|
5
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
6
|
+
|
|
7
|
+
export namespace SuggestionPrimitiveTitle {
|
|
8
|
+
export type Element = ElementRef<typeof Primitive.span>;
|
|
9
|
+
export type Props = ComponentPropsWithoutRef<typeof Primitive.span>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Renders the title of the suggestion.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <SuggestionPrimitive.Title />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const SuggestionPrimitiveTitle = forwardRef<
|
|
21
|
+
SuggestionPrimitiveTitle.Element,
|
|
22
|
+
SuggestionPrimitiveTitle.Props
|
|
23
|
+
>((props, ref) => {
|
|
24
|
+
const title = useAuiState(({ suggestion }) => suggestion.title);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Primitive.span {...props} ref={ref}>
|
|
28
|
+
{props.children ?? title}
|
|
29
|
+
</Primitive.span>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
SuggestionPrimitiveTitle.displayName = "SuggestionPrimitive.Title";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ActionButtonElement,
|
|
5
|
+
ActionButtonProps,
|
|
6
|
+
createActionButton,
|
|
7
|
+
} from "../../utils/createActionButton";
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
import { useAuiState, useAui } from "@assistant-ui/store";
|
|
10
|
+
|
|
11
|
+
const useSuggestionTrigger = ({
|
|
12
|
+
send,
|
|
13
|
+
clearComposer = true,
|
|
14
|
+
}: {
|
|
15
|
+
/**
|
|
16
|
+
* When true, automatically sends the message.
|
|
17
|
+
* When false, replaces or appends the composer text with the suggestion - depending on the value of `clearComposer`.
|
|
18
|
+
*/
|
|
19
|
+
send?: boolean | undefined;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether to clear the composer after sending.
|
|
23
|
+
* When send is set to false, determines if composer text is replaced with suggestion (true, default),
|
|
24
|
+
* or if it's appended to the composer text (false).
|
|
25
|
+
*
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
clearComposer?: boolean | undefined;
|
|
29
|
+
}) => {
|
|
30
|
+
const aui = useAui();
|
|
31
|
+
const disabled = useAuiState(({ thread }) => thread.isDisabled);
|
|
32
|
+
const prompt = useAuiState(({ suggestion }) => suggestion.prompt);
|
|
33
|
+
|
|
34
|
+
const resolvedSend = send ?? false;
|
|
35
|
+
|
|
36
|
+
const callback = useCallback(() => {
|
|
37
|
+
const isRunning = aui.thread().getState().isRunning;
|
|
38
|
+
|
|
39
|
+
if (resolvedSend && !isRunning) {
|
|
40
|
+
aui.thread().append(prompt);
|
|
41
|
+
if (clearComposer) {
|
|
42
|
+
aui.composer().setText("");
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
if (clearComposer) {
|
|
46
|
+
aui.composer().setText(prompt);
|
|
47
|
+
} else {
|
|
48
|
+
const currentText = aui.composer().getState().text;
|
|
49
|
+
aui
|
|
50
|
+
.composer()
|
|
51
|
+
.setText(currentText.trim() ? `${currentText} ${prompt}` : prompt);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [aui, resolvedSend, clearComposer, prompt]);
|
|
55
|
+
|
|
56
|
+
if (disabled) return null;
|
|
57
|
+
return callback;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export namespace SuggestionPrimitiveTrigger {
|
|
61
|
+
export type Element = ActionButtonElement;
|
|
62
|
+
export type Props = ActionButtonProps<typeof useSuggestionTrigger>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A button that triggers the suggestion action (send or insert into composer).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <SuggestionPrimitive.Trigger send>
|
|
71
|
+
* Click me
|
|
72
|
+
* </SuggestionPrimitive.Trigger>
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const SuggestionPrimitiveTrigger = createActionButton(
|
|
76
|
+
"SuggestionPrimitive.Trigger",
|
|
77
|
+
useSuggestionTrigger,
|
|
78
|
+
["send", "clearComposer"],
|
|
79
|
+
);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ComponentType, type FC, memo, useMemo } from "react";
|
|
4
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
5
|
+
import { SuggestionByIndexProvider } from "../../context/providers";
|
|
6
|
+
|
|
7
|
+
export namespace ThreadPrimitiveSuggestions {
|
|
8
|
+
export type Props = {
|
|
9
|
+
/**
|
|
10
|
+
* Component to render for each suggestion.
|
|
11
|
+
*/
|
|
12
|
+
components: {
|
|
13
|
+
/** Component used to render each suggestion */
|
|
14
|
+
Suggestion: ComponentType;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type SuggestionComponentProps = {
|
|
20
|
+
components: ThreadPrimitiveSuggestions.Props["components"];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const SuggestionComponent: FC<SuggestionComponentProps> = ({ components }) => {
|
|
24
|
+
const Component = components.Suggestion;
|
|
25
|
+
return <Component />;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export namespace ThreadPrimitiveSuggestionByIndex {
|
|
29
|
+
export type Props = {
|
|
30
|
+
index: number;
|
|
31
|
+
components: ThreadPrimitiveSuggestions.Props["components"];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Renders a single suggestion at the specified index.
|
|
37
|
+
*
|
|
38
|
+
* This component provides suggestion context for a specific suggestion
|
|
39
|
+
* and renders it using the provided component configuration.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <ThreadPrimitive.SuggestionByIndex
|
|
44
|
+
* index={0}
|
|
45
|
+
* components={{
|
|
46
|
+
* Suggestion: MySuggestion
|
|
47
|
+
* }}
|
|
48
|
+
* />
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const ThreadPrimitiveSuggestionByIndex: FC<ThreadPrimitiveSuggestionByIndex.Props> =
|
|
52
|
+
memo(
|
|
53
|
+
({ index, components }) => {
|
|
54
|
+
return (
|
|
55
|
+
<SuggestionByIndexProvider index={index}>
|
|
56
|
+
<SuggestionComponent components={components} />
|
|
57
|
+
</SuggestionByIndexProvider>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
(prev, next) =>
|
|
61
|
+
prev.index === next.index &&
|
|
62
|
+
prev.components.Suggestion === next.components.Suggestion,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
ThreadPrimitiveSuggestionByIndex.displayName =
|
|
66
|
+
"ThreadPrimitive.SuggestionByIndex";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Renders all suggestions using the provided component configuration.
|
|
70
|
+
*
|
|
71
|
+
* This component automatically renders all suggestions from the suggestions scope,
|
|
72
|
+
* providing the appropriate suggestion context for each one.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <ThreadPrimitive.Suggestions
|
|
77
|
+
* components={{
|
|
78
|
+
* Suggestion: MySuggestion
|
|
79
|
+
* }}
|
|
80
|
+
* />
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const ThreadPrimitiveSuggestionsImpl: FC<
|
|
84
|
+
ThreadPrimitiveSuggestions.Props
|
|
85
|
+
> = ({ components }) => {
|
|
86
|
+
const suggestionsLength = useAuiState(
|
|
87
|
+
({ suggestions }) => suggestions.suggestions.length,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const suggestionElements = useMemo(() => {
|
|
91
|
+
if (suggestionsLength === 0) return null;
|
|
92
|
+
return Array.from({ length: suggestionsLength }, (_, index) => (
|
|
93
|
+
<ThreadPrimitiveSuggestionByIndex
|
|
94
|
+
key={index}
|
|
95
|
+
index={index}
|
|
96
|
+
components={components}
|
|
97
|
+
/>
|
|
98
|
+
));
|
|
99
|
+
}, [suggestionsLength, components]);
|
|
100
|
+
|
|
101
|
+
return suggestionElements;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
ThreadPrimitiveSuggestionsImpl.displayName = "ThreadPrimitive.Suggestions";
|
|
105
|
+
|
|
106
|
+
export const ThreadPrimitiveSuggestions = memo(
|
|
107
|
+
ThreadPrimitiveSuggestionsImpl,
|
|
108
|
+
(prev, next) => prev.components.Suggestion === next.components.Suggestion,
|
|
109
|
+
);
|
|
@@ -9,3 +9,7 @@ export { ThreadPrimitiveMessages as Messages } from "./ThreadMessages";
|
|
|
9
9
|
export { ThreadPrimitiveMessageByIndex as MessageByIndex } from "./ThreadMessages";
|
|
10
10
|
export { ThreadPrimitiveScrollToBottom as ScrollToBottom } from "./ThreadScrollToBottom";
|
|
11
11
|
export { ThreadPrimitiveSuggestion as Suggestion } from "./ThreadSuggestion";
|
|
12
|
+
export {
|
|
13
|
+
ThreadPrimitiveSuggestions as Suggestions,
|
|
14
|
+
ThreadPrimitiveSuggestionByIndex as SuggestionByIndex,
|
|
15
|
+
} from "./ThreadSuggestions";
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { convertExternalMessages } from "../legacy-runtime/runtime-cores/external-store/external-message-converter";
|
|
3
3
|
import type { useExternalMessageConverter } from "../legacy-runtime/runtime-cores/external-store/external-message-converter";
|
|
4
|
+
import { isErrorMessageId } from "../utils/idUtils";
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
* Tests for the external message converter, specifically the joinExternalMessages logic.
|
|
7
|
-
*/
|
|
8
6
|
describe("convertExternalMessages", () => {
|
|
9
7
|
describe("reasoning part merging", () => {
|
|
10
|
-
/**
|
|
11
|
-
* Tests that reasoning parts with the same parentId are merged together.
|
|
12
|
-
* The text should be concatenated with a double newline separator.
|
|
13
|
-
*/
|
|
14
8
|
it("should merge reasoning parts with the same parentId", () => {
|
|
15
9
|
const messages = [
|
|
16
10
|
{
|
|
@@ -56,9 +50,6 @@ describe("convertExternalMessages", () => {
|
|
|
56
50
|
expect((reasoningParts[0] as any).parentId).toBe("parent1");
|
|
57
51
|
});
|
|
58
52
|
|
|
59
|
-
/**
|
|
60
|
-
* Tests that reasoning parts without parentId remain separate.
|
|
61
|
-
*/
|
|
62
53
|
it("should keep reasoning parts without parentId separate", () => {
|
|
63
54
|
const messages = [
|
|
64
55
|
{
|
|
@@ -89,9 +80,6 @@ describe("convertExternalMessages", () => {
|
|
|
89
80
|
expect((reasoningParts[1] as any).text).toBe("Second reasoning");
|
|
90
81
|
});
|
|
91
82
|
|
|
92
|
-
/**
|
|
93
|
-
* Tests that reasoning parts with different parentIds remain separate.
|
|
94
|
-
*/
|
|
95
83
|
it("should keep reasoning parts with different parentIds separate", () => {
|
|
96
84
|
const messages = [
|
|
97
85
|
{
|
|
@@ -134,9 +122,6 @@ describe("convertExternalMessages", () => {
|
|
|
134
122
|
expect((reasoningParts[1] as any).parentId).toBe("parent2");
|
|
135
123
|
});
|
|
136
124
|
|
|
137
|
-
/**
|
|
138
|
-
* Tests that tool result merging still works correctly alongside reasoning merging.
|
|
139
|
-
*/
|
|
140
125
|
it("should still merge tool results with matching tool calls", () => {
|
|
141
126
|
const messages = [
|
|
142
127
|
{
|
|
@@ -174,4 +159,108 @@ describe("convertExternalMessages", () => {
|
|
|
174
159
|
expect((toolCallParts[0] as any).result).toEqual({ data: "result" });
|
|
175
160
|
});
|
|
176
161
|
});
|
|
162
|
+
|
|
163
|
+
describe("synthetic error message", () => {
|
|
164
|
+
it("should create synthetic error message when error exists and no messages", () => {
|
|
165
|
+
const messages: never[] = [];
|
|
166
|
+
const callback: useExternalMessageConverter.Callback<never> = (msg) =>
|
|
167
|
+
msg;
|
|
168
|
+
|
|
169
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
170
|
+
error: "API key is missing",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result).toHaveLength(1);
|
|
174
|
+
expect(result[0]!.role).toBe("assistant");
|
|
175
|
+
expect(result[0]!.content).toHaveLength(0);
|
|
176
|
+
expect(result[0]!.status).toEqual({
|
|
177
|
+
type: "incomplete",
|
|
178
|
+
reason: "error",
|
|
179
|
+
error: "API key is missing",
|
|
180
|
+
});
|
|
181
|
+
expect(isErrorMessageId(result[0]!.id)).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should create synthetic error message when error exists and last message is user", () => {
|
|
185
|
+
const messages = [
|
|
186
|
+
{
|
|
187
|
+
id: "user1",
|
|
188
|
+
role: "user" as const,
|
|
189
|
+
content: "Hello",
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const callback: useExternalMessageConverter.Callback<
|
|
194
|
+
(typeof messages)[number]
|
|
195
|
+
> = (msg) => msg;
|
|
196
|
+
|
|
197
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
198
|
+
error: { message: "Invalid API key" },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result).toHaveLength(2);
|
|
202
|
+
expect(result[0]!.role).toBe("user");
|
|
203
|
+
expect(result[1]!.role).toBe("assistant");
|
|
204
|
+
expect(result[1]!.content).toHaveLength(0);
|
|
205
|
+
expect(result[1]!.status).toEqual({
|
|
206
|
+
type: "incomplete",
|
|
207
|
+
reason: "error",
|
|
208
|
+
error: { message: "Invalid API key" },
|
|
209
|
+
});
|
|
210
|
+
expect(isErrorMessageId(result[1]!.id)).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should not create synthetic error message when last message is assistant", () => {
|
|
214
|
+
const messages = [
|
|
215
|
+
{
|
|
216
|
+
id: "user1",
|
|
217
|
+
role: "user" as const,
|
|
218
|
+
content: "Hello",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "assistant1",
|
|
222
|
+
role: "assistant" as const,
|
|
223
|
+
content: "Hi there",
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const callback: useExternalMessageConverter.Callback<
|
|
228
|
+
(typeof messages)[number]
|
|
229
|
+
> = (msg) => msg;
|
|
230
|
+
|
|
231
|
+
const result = convertExternalMessages(messages, callback, false, {
|
|
232
|
+
error: "Connection error",
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(result).toHaveLength(2);
|
|
236
|
+
expect(result[0]!.role).toBe("user");
|
|
237
|
+
expect(result[1]!.role).toBe("assistant");
|
|
238
|
+
expect(result[1]!.id).toBe("assistant1");
|
|
239
|
+
expect(result[1]!.status).toMatchObject({
|
|
240
|
+
type: "incomplete",
|
|
241
|
+
reason: "error",
|
|
242
|
+
error: "Connection error",
|
|
243
|
+
});
|
|
244
|
+
expect(isErrorMessageId(result[1]!.id)).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should not create synthetic message when no error", () => {
|
|
248
|
+
const messages = [
|
|
249
|
+
{
|
|
250
|
+
id: "user1",
|
|
251
|
+
role: "user" as const,
|
|
252
|
+
content: "Hello",
|
|
253
|
+
},
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const callback: useExternalMessageConverter.Callback<
|
|
257
|
+
(typeof messages)[number]
|
|
258
|
+
> = (msg) => msg;
|
|
259
|
+
|
|
260
|
+
const result = convertExternalMessages(messages, callback, false, {});
|
|
261
|
+
|
|
262
|
+
expect(result).toHaveLength(1);
|
|
263
|
+
expect(result[0]!.role).toBe("user");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
177
266
|
});
|
|
@@ -43,6 +43,18 @@ export type {
|
|
|
43
43
|
AttachmentClientSchema,
|
|
44
44
|
} from "./attachment";
|
|
45
45
|
export type { ToolsState, ToolsMethods, ToolsClientSchema } from "./tools";
|
|
46
|
+
export type {
|
|
47
|
+
SuggestionsState,
|
|
48
|
+
SuggestionsMethods,
|
|
49
|
+
SuggestionsClientSchema,
|
|
50
|
+
Suggestion,
|
|
51
|
+
} from "./suggestions";
|
|
52
|
+
export type {
|
|
53
|
+
SuggestionState,
|
|
54
|
+
SuggestionMethods,
|
|
55
|
+
SuggestionMeta,
|
|
56
|
+
SuggestionClientSchema,
|
|
57
|
+
} from "./suggestion";
|
|
46
58
|
export type {
|
|
47
59
|
ModelContextState,
|
|
48
60
|
ModelContextMethods,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type SuggestionState = {
|
|
2
|
+
title: string;
|
|
3
|
+
label: string;
|
|
4
|
+
prompt: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type SuggestionMethods = {
|
|
8
|
+
getState(): SuggestionState;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SuggestionMeta = {
|
|
12
|
+
source: "suggestions";
|
|
13
|
+
query: { index: number };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type SuggestionClientSchema = {
|
|
17
|
+
state: SuggestionState;
|
|
18
|
+
methods: SuggestionMethods;
|
|
19
|
+
meta: SuggestionMeta;
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SuggestionMethods } from "./suggestion";
|
|
2
|
+
|
|
3
|
+
export type Suggestion = {
|
|
4
|
+
title: string;
|
|
5
|
+
label: string;
|
|
6
|
+
prompt: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type SuggestionsState = {
|
|
10
|
+
suggestions: Suggestion[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SuggestionsMethods = {
|
|
14
|
+
getState(): SuggestionsState;
|
|
15
|
+
suggestion(query: { index: number }): SuggestionMethods;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SuggestionsClientSchema = {
|
|
19
|
+
state: SuggestionsState;
|
|
20
|
+
methods: SuggestionsMethods;
|
|
21
|
+
};
|
|
@@ -9,6 +9,8 @@ import type { ComposerClientSchema } from "./scopes/composer";
|
|
|
9
9
|
import type { AttachmentClientSchema } from "./scopes/attachment";
|
|
10
10
|
import type { ToolsClientSchema } from "./scopes/tools";
|
|
11
11
|
import type { ModelContextClientSchema } from "./scopes/modelContext";
|
|
12
|
+
import type { SuggestionsClientSchema } from "./scopes/suggestions";
|
|
13
|
+
import type { SuggestionClientSchema } from "./scopes/suggestion";
|
|
12
14
|
|
|
13
15
|
declare module "@assistant-ui/store" {
|
|
14
16
|
interface ClientRegistry {
|
|
@@ -21,5 +23,7 @@ declare module "@assistant-ui/store" {
|
|
|
21
23
|
attachment: AttachmentClientSchema;
|
|
22
24
|
tools: ToolsClientSchema;
|
|
23
25
|
modelContext: ModelContextClientSchema;
|
|
26
|
+
suggestions: SuggestionsClientSchema;
|
|
27
|
+
suggestion: SuggestionClientSchema;
|
|
24
28
|
}
|
|
25
29
|
}
|
package/src/utils/idUtils.tsx
CHANGED
|
@@ -8,3 +8,7 @@ export const generateId = customAlphabet(
|
|
|
8
8
|
const optimisticPrefix = "__optimistic__";
|
|
9
9
|
export const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
10
10
|
export const isOptimisticId = (id: string) => id.startsWith(optimisticPrefix);
|
|
11
|
+
|
|
12
|
+
const errorPrefix = "__error__";
|
|
13
|
+
export const generateErrorMessageId = () => `${errorPrefix}${generateId()}`;
|
|
14
|
+
export const isErrorMessageId = (id: string) => id.startsWith(errorPrefix);
|