@agent-native/toolkit 0.9.0 → 0.10.0
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/README.md +12 -3
- package/agent-native.eject.json +71 -0
- package/dist/chat-history/ChatHistoryRail.d.ts +33 -0
- package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryRail.js +35 -0
- package/dist/chat-history/ChatHistoryRail.js.map +1 -0
- package/dist/chat-history/ChatHistoryRail.spec.d.ts +2 -0
- package/dist/chat-history/ChatHistoryRail.spec.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryRail.spec.js +157 -0
- package/dist/chat-history/ChatHistoryRail.spec.js.map +1 -0
- package/dist/chat-history/index.d.ts +2 -0
- package/dist/chat-history/index.d.ts.map +1 -1
- package/dist/chat-history/index.js +2 -0
- package/dist/chat-history/index.js.map +1 -1
- package/dist/chat-history/useChatHistoryRailController.d.ts +26 -0
- package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.js +36 -0
- package/dist/chat-history/useChatHistoryRailController.js.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.spec.d.ts +2 -0
- package/dist/chat-history/useChatHistoryRailController.spec.d.ts.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.spec.js +101 -0
- package/dist/chat-history/useChatHistoryRailController.spec.js.map +1 -0
- package/dist/chat-history.css +61 -0
- package/dist/composer/ComposerPlusMenu.js +1 -1
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +22 -4
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.d.ts +5 -1
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/RealtimeVoiceMode.js +27 -8
- package/dist/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.spec.js +51 -11
- package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -1
- package/dist/composer/VoiceButton.d.ts +2 -0
- package/dist/composer/VoiceButton.d.ts.map +1 -1
- package/dist/composer/VoiceButton.js +33 -1
- package/dist/composer/VoiceButton.js.map +1 -1
- package/dist/composer/VoiceButton.spec.js +9 -1
- package/dist/composer/VoiceButton.spec.js.map +1 -1
- package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -1
- package/dist/composer/realtime-voice-audio-level.js +2 -1
- package/dist/composer/realtime-voice-audio-level.js.map +1 -1
- package/dist/composer/realtime-voice-audio-level.spec.js +7 -0
- package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.d.ts +7 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.js +95 -23
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.spec.js +58 -9
- package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
- package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts +7 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts.map +1 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.js +84 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.js.map +1 -0
- package/dist/conformance/conformance.spec.d.ts +2 -0
- package/dist/conformance/conformance.spec.d.ts.map +1 -0
- package/dist/conformance/conformance.spec.js +58 -0
- package/dist/conformance/conformance.spec.js.map +1 -0
- package/dist/conformance/index.d.ts +4 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +4 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/runner.d.ts +12 -0
- package/dist/conformance/runner.d.ts.map +1 -0
- package/dist/conformance/runner.js +441 -0
- package/dist/conformance/runner.js.map +1 -0
- package/dist/conformance/types.d.ts +26 -0
- package/dist/conformance/types.d.ts.map +1 -0
- package/dist/conformance/types.js +13 -0
- package/dist/conformance/types.js.map +1 -0
- package/dist/conformance/version.d.ts +6 -0
- package/dist/conformance/version.d.ts.map +1 -0
- package/dist/conformance/version.js +14 -0
- package/dist/conformance/version.js.map +1 -0
- package/dist/design-system/components.d.ts +64 -0
- package/dist/design-system/components.d.ts.map +1 -0
- package/dist/design-system/components.js +50 -0
- package/dist/design-system/components.js.map +1 -0
- package/dist/design-system/context.d.ts +11 -0
- package/dist/design-system/context.d.ts.map +1 -0
- package/dist/design-system/context.js +17 -0
- package/dist/design-system/context.js.map +1 -0
- package/dist/design-system/default-adapter.d.ts +3 -0
- package/dist/design-system/default-adapter.d.ts.map +1 -0
- package/dist/design-system/default-adapter.js +166 -0
- package/dist/design-system/default-adapter.js.map +1 -0
- package/dist/design-system/default-adapter.spec.d.ts +2 -0
- package/dist/design-system/default-adapter.spec.d.ts.map +1 -0
- package/dist/design-system/default-adapter.spec.js +96 -0
- package/dist/design-system/default-adapter.spec.js.map +1 -0
- package/dist/design-system/definition.d.ts +9 -0
- package/dist/design-system/definition.d.ts.map +1 -0
- package/dist/design-system/definition.js +4 -0
- package/dist/design-system/definition.js.map +1 -0
- package/dist/design-system/design-system.spec.d.ts +2 -0
- package/dist/design-system/design-system.spec.d.ts.map +1 -0
- package/dist/design-system/design-system.spec.js +121 -0
- package/dist/design-system/design-system.spec.js.map +1 -0
- package/dist/design-system/error-boundary.d.ts +18 -0
- package/dist/design-system/error-boundary.d.ts.map +1 -0
- package/dist/design-system/error-boundary.js +16 -0
- package/dist/design-system/error-boundary.js.map +1 -0
- package/dist/design-system/index.d.ts +8 -0
- package/dist/design-system/index.d.ts.map +1 -0
- package/dist/design-system/index.js +8 -0
- package/dist/design-system/index.js.map +1 -0
- package/dist/design-system/theme.d.ts +30 -0
- package/dist/design-system/theme.d.ts.map +1 -0
- package/dist/design-system/theme.js +95 -0
- package/dist/design-system/theme.js.map +1 -0
- package/dist/design-system/theme.spec.d.ts +2 -0
- package/dist/design-system/theme.spec.d.ts.map +1 -0
- package/dist/design-system/theme.spec.js +43 -0
- package/dist/design-system/theme.spec.js.map +1 -0
- package/dist/design-system/types.d.ts +288 -0
- package/dist/design-system/types.d.ts.map +1 -0
- package/dist/design-system/types.js +2 -0
- package/dist/design-system/types.js.map +1 -0
- package/dist/provider.d.ts +4 -3
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +46 -4
- package/dist/provider.js.map +1 -1
- package/dist/styles.css +35 -0
- package/dist/ui/button.d.ts +5 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +25 -4
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/dialog.d.ts +4 -0
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +2 -2
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/popover.d.ts +1 -0
- package/dist/ui/popover.d.ts.map +1 -1
- package/dist/ui/popover.js +2 -2
- package/dist/ui/popover.js.map +1 -1
- package/dist/ui/select.d.ts +3 -1
- package/dist/ui/select.d.ts.map +1 -1
- package/dist/ui/select.js +1 -1
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.d.ts +6 -2
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +12 -15
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/tooltip.d.ts +3 -1
- package/dist/ui/tooltip.d.ts.map +1 -1
- package/dist/ui/tooltip.js +2 -2
- package/dist/ui/tooltip.js.map +1 -1
- package/package.json +31 -1
- package/src/chat-history/ChatHistoryRail.spec.tsx +312 -0
- package/src/chat-history/ChatHistoryRail.tsx +150 -0
- package/src/chat-history/index.ts +14 -0
- package/src/chat-history/useChatHistoryRailController.spec.tsx +131 -0
- package/src/chat-history/useChatHistoryRailController.ts +76 -0
- package/src/chat-history.css +61 -0
- package/src/composer/ComposerPlusMenu.tsx +11 -8
- package/src/composer/PromptComposer.tsx +27 -6
- package/src/composer/RealtimeVoiceMode.spec.tsx +116 -11
- package/src/composer/RealtimeVoiceMode.tsx +106 -36
- package/src/composer/VoiceButton.spec.ts +17 -1
- package/src/composer/VoiceButton.tsx +49 -0
- package/src/composer/realtime-voice-audio-level.spec.ts +15 -0
- package/src/composer/realtime-voice-audio-level.ts +2 -1
- package/src/composer/useRealtimeVoiceMode.spec.ts +82 -8
- package/src/composer/useRealtimeVoiceMode.tsx +112 -25
- package/src/conformance/__fixtures__/css-in-js-adapter.tsx +401 -0
- package/src/conformance/conformance.spec.tsx +83 -0
- package/src/conformance/index.ts +3 -0
- package/src/conformance/runner.tsx +710 -0
- package/src/conformance/types.ts +45 -0
- package/src/conformance/version.ts +22 -0
- package/src/design-system/components.tsx +146 -0
- package/src/design-system/context.tsx +31 -0
- package/src/design-system/default-adapter.spec.tsx +143 -0
- package/src/design-system/default-adapter.tsx +915 -0
- package/src/design-system/definition.ts +14 -0
- package/src/design-system/design-system.spec.tsx +183 -0
- package/src/design-system/error-boundary.tsx +37 -0
- package/src/design-system/index.ts +7 -0
- package/src/design-system/theme.spec.ts +61 -0
- package/src/design-system/theme.ts +145 -0
- package/src/design-system/types.ts +363 -0
- package/src/provider.tsx +98 -5
- package/src/styles.css +35 -0
- package/src/ui/button.tsx +95 -6
- package/src/ui/dialog.tsx +48 -23
- package/src/ui/popover.tsx +16 -2
- package/src/ui/select.tsx +7 -3
- package/src/ui/sidebar.tsx +47 -48
- package/src/ui/tooltip.tsx +7 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ChatHistoryRailControllerLabels {
|
|
4
|
+
newChat: string;
|
|
5
|
+
showMore: string;
|
|
6
|
+
showLess: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface UseChatHistoryRailControllerOptions<Item> {
|
|
10
|
+
items: readonly Item[];
|
|
11
|
+
onNewChat: () => void;
|
|
12
|
+
labels: ChatHistoryRailControllerLabels;
|
|
13
|
+
previewCount?: number;
|
|
14
|
+
expandedCount?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ChatHistoryRailController<Item> {
|
|
18
|
+
expanded: boolean;
|
|
19
|
+
collapsedLimit: number;
|
|
20
|
+
expandedLimit: number;
|
|
21
|
+
visibleItems: Item[];
|
|
22
|
+
canExpand: boolean;
|
|
23
|
+
newChatLabel: string;
|
|
24
|
+
disclosureLabel: string;
|
|
25
|
+
onNewChat: () => void;
|
|
26
|
+
toggleExpanded: () => void;
|
|
27
|
+
resetExpanded: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeLimit(value: number | undefined, fallback: number): number {
|
|
31
|
+
if (value === undefined || !Number.isFinite(value)) return fallback;
|
|
32
|
+
return Math.max(1, Math.floor(value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useChatHistoryRailController<Item>({
|
|
36
|
+
items,
|
|
37
|
+
onNewChat,
|
|
38
|
+
labels,
|
|
39
|
+
previewCount,
|
|
40
|
+
expandedCount,
|
|
41
|
+
}: UseChatHistoryRailControllerOptions<Item>): ChatHistoryRailController<Item> {
|
|
42
|
+
const [expandedState, setExpandedState] = useState(false);
|
|
43
|
+
const collapsedLimit = normalizeLimit(previewCount, 5);
|
|
44
|
+
const expandedLimit = Math.max(
|
|
45
|
+
collapsedLimit,
|
|
46
|
+
normalizeLimit(expandedCount, 15),
|
|
47
|
+
);
|
|
48
|
+
const canExpand = items.length > collapsedLimit;
|
|
49
|
+
const expanded = canExpand && expandedState;
|
|
50
|
+
const visibleItems = useMemo(
|
|
51
|
+
() => items.slice(0, expanded ? expandedLimit : collapsedLimit),
|
|
52
|
+
[collapsedLimit, expanded, expandedLimit, items],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!canExpand) setExpandedState(false);
|
|
57
|
+
}, [canExpand]);
|
|
58
|
+
|
|
59
|
+
const toggleExpanded = useCallback(() => {
|
|
60
|
+
if (canExpand) setExpandedState((current) => !current);
|
|
61
|
+
}, [canExpand]);
|
|
62
|
+
const resetExpanded = useCallback(() => setExpandedState(false), []);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
expanded,
|
|
66
|
+
collapsedLimit,
|
|
67
|
+
expandedLimit,
|
|
68
|
+
visibleItems,
|
|
69
|
+
canExpand,
|
|
70
|
+
newChatLabel: labels.newChat,
|
|
71
|
+
disclosureLabel: expanded ? labels.showLess : labels.showMore,
|
|
72
|
+
onNewChat,
|
|
73
|
+
toggleExpanded,
|
|
74
|
+
resetExpanded,
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/chat-history.css
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
.an-chat-history {
|
|
17
17
|
display: flex;
|
|
18
18
|
flex-direction: column;
|
|
19
|
+
min-width: 0;
|
|
19
20
|
min-height: 0;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
|
|
50
51
|
.an-chat-history__list {
|
|
51
52
|
overflow-y: auto;
|
|
53
|
+
min-width: 0;
|
|
52
54
|
padding: 4px 0;
|
|
53
55
|
max-height: 16rem;
|
|
54
56
|
}
|
|
@@ -59,6 +61,10 @@
|
|
|
59
61
|
min-height: 0;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
.an-chat-history-rail .an-chat-history__state:empty {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
.an-chat-history__state {
|
|
63
69
|
padding: 16px 12px;
|
|
64
70
|
text-align: center;
|
|
@@ -72,6 +78,57 @@
|
|
|
72
78
|
color: hsl(38 92% 50%);
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
.an-chat-history-rail__footer {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 2px;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
padding: 1px 4px 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.an-chat-history-rail__disclosure,
|
|
90
|
+
.an-chat-history-rail__new-chat {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
border: 0;
|
|
94
|
+
border-radius: calc(var(--radius) - 2px);
|
|
95
|
+
background: transparent;
|
|
96
|
+
color: hsl(var(--muted-foreground) / 0.72);
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.an-chat-history-rail__disclosure:hover,
|
|
101
|
+
.an-chat-history-rail__new-chat:hover {
|
|
102
|
+
background: hsl(var(--accent) / 0.5);
|
|
103
|
+
color: hsl(var(--accent-foreground));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.an-chat-history-rail__disclosure:focus-visible,
|
|
107
|
+
.an-chat-history-rail__new-chat:focus-visible {
|
|
108
|
+
outline: 2px solid hsl(var(--ring));
|
|
109
|
+
outline-offset: -2px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.an-chat-history-rail__disclosure {
|
|
113
|
+
justify-content: center;
|
|
114
|
+
flex: 0 0 auto;
|
|
115
|
+
width: 30px;
|
|
116
|
+
height: auto;
|
|
117
|
+
min-height: 30px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.an-chat-history-rail__new-chat {
|
|
121
|
+
flex: 1 1 auto;
|
|
122
|
+
gap: 8px;
|
|
123
|
+
height: auto;
|
|
124
|
+
min-width: 0;
|
|
125
|
+
min-height: 30px;
|
|
126
|
+
padding: 6px 10px;
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
font-weight: 500;
|
|
129
|
+
text-align: start;
|
|
130
|
+
}
|
|
131
|
+
|
|
75
132
|
.an-chat-history__section-label {
|
|
76
133
|
padding: 6px 12px 4px;
|
|
77
134
|
font-size: 10px;
|
|
@@ -83,12 +140,14 @@
|
|
|
83
140
|
.an-chat-history--rail .an-chat-history__section {
|
|
84
141
|
display: grid;
|
|
85
142
|
gap: 1px;
|
|
143
|
+
min-width: 0;
|
|
86
144
|
}
|
|
87
145
|
|
|
88
146
|
/* ── Row ──────────────────────────────────────────────────────────────── */
|
|
89
147
|
|
|
90
148
|
.an-chat-history-row {
|
|
91
149
|
position: relative;
|
|
150
|
+
min-width: 0;
|
|
92
151
|
}
|
|
93
152
|
|
|
94
153
|
.an-chat-history--rail .an-chat-history-row {
|
|
@@ -99,6 +158,7 @@
|
|
|
99
158
|
.an-chat-history-row__button {
|
|
100
159
|
display: block;
|
|
101
160
|
width: 100%;
|
|
161
|
+
min-width: 0;
|
|
102
162
|
padding: 8px 12px;
|
|
103
163
|
border: 0;
|
|
104
164
|
background: transparent;
|
|
@@ -141,6 +201,7 @@
|
|
|
141
201
|
justify-content: space-between;
|
|
142
202
|
gap: 8px;
|
|
143
203
|
min-width: 0;
|
|
204
|
+
overflow: hidden;
|
|
144
205
|
}
|
|
145
206
|
|
|
146
207
|
.an-chat-history-row__title {
|
|
@@ -490,14 +490,17 @@ function ComposerPlusMenuFull({
|
|
|
490
490
|
<Popover open={open} onOpenChange={setOpen}>
|
|
491
491
|
<Tooltip>
|
|
492
492
|
<TooltipTrigger asChild>
|
|
493
|
-
<
|
|
494
|
-
<
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
493
|
+
<span className="inline-flex shrink-0">
|
|
494
|
+
<PopoverTrigger asChild>
|
|
495
|
+
<button
|
|
496
|
+
type="button"
|
|
497
|
+
aria-label="Add..."
|
|
498
|
+
className="shrink-0 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/50 disabled:opacity-30 disabled:cursor-not-allowed"
|
|
499
|
+
>
|
|
500
|
+
<IconPlus className="h-4 w-4" />
|
|
501
|
+
</button>
|
|
502
|
+
</PopoverTrigger>
|
|
503
|
+
</span>
|
|
501
504
|
</TooltipTrigger>
|
|
502
505
|
<TooltipContent>Add...</TooltipContent>
|
|
503
506
|
</Tooltip>
|
|
@@ -519,6 +519,7 @@ function PromptComposerInner({
|
|
|
519
519
|
resolvedModelStatusChecksEnabled,
|
|
520
520
|
);
|
|
521
521
|
const missingApiKey = agentEngineConfigured.missing;
|
|
522
|
+
const agentEngineUnavailable = agentEngineConfigured.state === "unavailable";
|
|
522
523
|
const [missingKeyBouncePulse, setMissingKeyBouncePulse] = useState(0);
|
|
523
524
|
const bounceMissingKeySetup = useCallback(() => {
|
|
524
525
|
setMissingKeyBouncePulse((pulse) => pulse + 1);
|
|
@@ -542,8 +543,13 @@ function PromptComposerInner({
|
|
|
542
543
|
timeoutMs: SUBMIT_ENGINE_STATUS_TIMEOUT_MS,
|
|
543
544
|
},
|
|
544
545
|
);
|
|
545
|
-
if (state
|
|
546
|
-
|
|
546
|
+
if (state === "missing") {
|
|
547
|
+
bounceMissingKeySetup();
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
if (typeof window !== "undefined") {
|
|
551
|
+
window.dispatchEvent(new Event("agent-engine:configured-changed"));
|
|
552
|
+
}
|
|
547
553
|
return false;
|
|
548
554
|
}, [
|
|
549
555
|
agentEngineConfigured.state,
|
|
@@ -551,6 +557,11 @@ function PromptComposerInner({
|
|
|
551
557
|
modelsAdapter,
|
|
552
558
|
resolvedModelStatusChecksEnabled,
|
|
553
559
|
]);
|
|
560
|
+
const retryAgentEngineStatus = useCallback(() => {
|
|
561
|
+
if (typeof window !== "undefined") {
|
|
562
|
+
window.dispatchEvent(new Event("agent-engine:configured-changed"));
|
|
563
|
+
}
|
|
564
|
+
}, []);
|
|
554
565
|
|
|
555
566
|
useEffect(() => {
|
|
556
567
|
if (!autoFocus) return;
|
|
@@ -613,21 +624,31 @@ function PromptComposerInner({
|
|
|
613
624
|
<AgentComposerFrame
|
|
614
625
|
className={cn(
|
|
615
626
|
"text-start",
|
|
616
|
-
missingApiKey && "cursor-pointer",
|
|
627
|
+
(missingApiKey || agentEngineUnavailable) && "cursor-pointer",
|
|
617
628
|
className,
|
|
618
629
|
)}
|
|
619
630
|
rootClassName={rootClassName}
|
|
620
631
|
style={style}
|
|
621
632
|
rootStyle={rootStyle}
|
|
622
633
|
layoutVariant={layoutVariant}
|
|
623
|
-
onClick={
|
|
634
|
+
onClick={
|
|
635
|
+
missingApiKey
|
|
636
|
+
? bounceMissingKeySetup
|
|
637
|
+
: agentEngineUnavailable
|
|
638
|
+
? retryAgentEngineStatus
|
|
639
|
+
: undefined
|
|
640
|
+
}
|
|
624
641
|
>
|
|
625
642
|
<PromptAttachmentStrip />
|
|
626
643
|
<TiptapComposer
|
|
627
644
|
focusRef={handleRef}
|
|
628
|
-
disabled={disabled || missingApiKey}
|
|
645
|
+
disabled={disabled || missingApiKey || agentEngineUnavailable}
|
|
629
646
|
placeholder={
|
|
630
|
-
missingApiKey
|
|
647
|
+
missingApiKey
|
|
648
|
+
? "Connect AI above to continue..."
|
|
649
|
+
: agentEngineUnavailable
|
|
650
|
+
? "Unable to check AI connection. Click to retry."
|
|
651
|
+
: placeholder
|
|
631
652
|
}
|
|
632
653
|
initialText={initialText}
|
|
633
654
|
initialTextKey={initialTextKey}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
|
|
15
15
|
const copy: RealtimeVoiceModeCopy = {
|
|
16
16
|
entryButtonLabel: "Use microphone",
|
|
17
|
-
promptTitle: "
|
|
17
|
+
promptTitle: "Use your voice",
|
|
18
18
|
promptDescription:
|
|
19
19
|
"Voice mode keeps listening while the agent navigates and takes actions.",
|
|
20
20
|
setupTitle: "Set up voice mode",
|
|
@@ -23,8 +23,9 @@ const copy: RealtimeVoiceModeCopy = {
|
|
|
23
23
|
connectBuilder: "Connect Builder.io",
|
|
24
24
|
useOpenAiKey: "Add your own keys",
|
|
25
25
|
startWithOpenAiKey: "Start with OpenAI key",
|
|
26
|
-
startVoiceMode: "
|
|
27
|
-
keepDictating: "
|
|
26
|
+
startVoiceMode: "Real-time voice",
|
|
27
|
+
keepDictating: "Dictate",
|
|
28
|
+
rememberPreference: "Remember my preference",
|
|
28
29
|
showChat: "Show chat",
|
|
29
30
|
hideChat: "Hide chat",
|
|
30
31
|
endVoiceMode: "End voice mode",
|
|
@@ -127,23 +128,27 @@ describe("RealtimeVoiceMode", () => {
|
|
|
127
128
|
|
|
128
129
|
act(() => microphone?.click());
|
|
129
130
|
|
|
130
|
-
expect(document.body.textContent).toContain("
|
|
131
|
+
expect(document.body.textContent).toContain("Use your voice");
|
|
131
132
|
const prompt = document.querySelector<HTMLElement>('[role="dialog"]');
|
|
132
133
|
expect(prompt?.className).toContain(
|
|
133
|
-
"w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,
|
|
134
|
+
"w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,18rem),18rem)]",
|
|
134
135
|
);
|
|
135
|
-
expect(document.body.textContent).toContain(
|
|
136
|
+
expect(document.body.textContent).not.toContain(
|
|
137
|
+
"Voice mode keeps listening while the agent navigates and takes actions.",
|
|
138
|
+
);
|
|
139
|
+
expect(document.body.textContent).toContain("Dictate");
|
|
140
|
+
expect(document.body.textContent).toContain("Remember my preference");
|
|
136
141
|
expect(onStartVoiceMode).not.toHaveBeenCalled();
|
|
137
142
|
expect(onKeepDictating).not.toHaveBeenCalled();
|
|
138
143
|
|
|
139
144
|
const startVoiceMode = Array.from(
|
|
140
145
|
document.querySelectorAll<HTMLButtonElement>("button"),
|
|
141
|
-
).find((button) => button.textContent?.includes("
|
|
146
|
+
).find((button) => button.textContent?.includes("Real-time voice"));
|
|
142
147
|
act(() => startVoiceMode?.click());
|
|
143
148
|
|
|
144
149
|
expect(onStartVoiceMode).toHaveBeenCalledOnce();
|
|
145
150
|
expect(onKeepDictating).not.toHaveBeenCalled();
|
|
146
|
-
expect(document.body.textContent).not.toContain("
|
|
151
|
+
expect(document.body.textContent).not.toContain("Use your voice");
|
|
147
152
|
});
|
|
148
153
|
|
|
149
154
|
it("does not start realtime voice while provider readiness is unresolved", () => {
|
|
@@ -162,14 +167,14 @@ describe("RealtimeVoiceMode", () => {
|
|
|
162
167
|
|
|
163
168
|
const startVoiceMode = Array.from(
|
|
164
169
|
document.querySelectorAll<HTMLButtonElement>("button"),
|
|
165
|
-
).find((button) => button.textContent?.includes("
|
|
170
|
+
).find((button) => button.textContent?.includes("Real-time voice"));
|
|
166
171
|
expect(startVoiceMode?.disabled).toBe(true);
|
|
167
172
|
act(() => startVoiceMode?.click());
|
|
168
173
|
expect(onStartVoiceMode).not.toHaveBeenCalled();
|
|
169
174
|
|
|
170
175
|
const keepDictating = Array.from(
|
|
171
176
|
document.querySelectorAll<HTMLButtonElement>("button"),
|
|
172
|
-
).find((button) => button.textContent === "
|
|
177
|
+
).find((button) => button.textContent === "Dictate");
|
|
173
178
|
expect(keepDictating?.disabled).toBe(false);
|
|
174
179
|
});
|
|
175
180
|
|
|
@@ -188,13 +193,83 @@ describe("RealtimeVoiceMode", () => {
|
|
|
188
193
|
|
|
189
194
|
const keepDictating = Array.from(
|
|
190
195
|
document.querySelectorAll<HTMLButtonElement>("button"),
|
|
191
|
-
).find((button) => button.textContent === "
|
|
196
|
+
).find((button) => button.textContent === "Dictate");
|
|
192
197
|
act(() => keepDictating?.click());
|
|
193
198
|
|
|
194
199
|
expect(onKeepDictating).toHaveBeenCalledOnce();
|
|
195
200
|
expect(onStartVoiceMode).not.toHaveBeenCalled();
|
|
196
201
|
});
|
|
197
202
|
|
|
203
|
+
it("remembers the selected input mode only when requested", () => {
|
|
204
|
+
const onStartVoiceMode = vi.fn();
|
|
205
|
+
const onRememberPreference = vi.fn();
|
|
206
|
+
|
|
207
|
+
render(
|
|
208
|
+
<RealtimeVoiceModeEntry
|
|
209
|
+
copy={copy}
|
|
210
|
+
open
|
|
211
|
+
onStartVoiceMode={onStartVoiceMode}
|
|
212
|
+
onKeepDictating={vi.fn()}
|
|
213
|
+
onRememberPreference={onRememberPreference}
|
|
214
|
+
/>,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const remember = document.querySelector<HTMLElement>('[role="checkbox"]');
|
|
218
|
+
act(() => remember?.click());
|
|
219
|
+
const realtime = Array.from(
|
|
220
|
+
document.querySelectorAll<HTMLButtonElement>("button"),
|
|
221
|
+
).find((button) => button.textContent?.includes("Real-time voice"));
|
|
222
|
+
act(() => realtime?.click());
|
|
223
|
+
|
|
224
|
+
expect(onRememberPreference).toHaveBeenCalledWith("realtime");
|
|
225
|
+
expect(onStartVoiceMode).toHaveBeenCalledOnce();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("bypasses the chooser for a remembered input mode", () => {
|
|
229
|
+
const onStartVoiceMode = vi.fn();
|
|
230
|
+
const onKeepDictating = vi.fn();
|
|
231
|
+
|
|
232
|
+
render(
|
|
233
|
+
<RealtimeVoiceModeEntry
|
|
234
|
+
copy={copy}
|
|
235
|
+
preferredMode="dictation"
|
|
236
|
+
onStartVoiceMode={onStartVoiceMode}
|
|
237
|
+
onKeepDictating={onKeepDictating}
|
|
238
|
+
/>,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const microphone = document.querySelector<HTMLButtonElement>(
|
|
242
|
+
'button[aria-label="Use microphone"]',
|
|
243
|
+
);
|
|
244
|
+
act(() => microphone?.click());
|
|
245
|
+
|
|
246
|
+
expect(onKeepDictating).toHaveBeenCalledOnce();
|
|
247
|
+
expect(onStartVoiceMode).not.toHaveBeenCalled();
|
|
248
|
+
expect(document.body.textContent).not.toContain("Use your voice");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("shows setup instead of starting a remembered realtime mode without a provider", () => {
|
|
252
|
+
const onStartVoiceMode = vi.fn();
|
|
253
|
+
|
|
254
|
+
render(
|
|
255
|
+
<RealtimeVoiceModeEntry
|
|
256
|
+
copy={copy}
|
|
257
|
+
setupRequired
|
|
258
|
+
preferredMode="realtime"
|
|
259
|
+
onStartVoiceMode={onStartVoiceMode}
|
|
260
|
+
onKeepDictating={vi.fn()}
|
|
261
|
+
/>,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const microphone = document.querySelector<HTMLButtonElement>(
|
|
265
|
+
'button[aria-label="Use microphone"]',
|
|
266
|
+
);
|
|
267
|
+
act(() => microphone?.click());
|
|
268
|
+
|
|
269
|
+
expect(onStartVoiceMode).not.toHaveBeenCalled();
|
|
270
|
+
expect(document.body.textContent).toContain("Set up voice mode");
|
|
271
|
+
});
|
|
272
|
+
|
|
198
273
|
it("makes Builder the primary setup action and own keys the secondary", () => {
|
|
199
274
|
const onConnectBuilder = vi.fn();
|
|
200
275
|
const onUseOpenAiKey = vi.fn();
|
|
@@ -653,6 +728,10 @@ describe("RealtimeVoiceMode", () => {
|
|
|
653
728
|
expect(
|
|
654
729
|
document.querySelector('[data-realtime-voice-waveform="true"]'),
|
|
655
730
|
).not.toBeNull();
|
|
731
|
+
const centerBar = document.querySelectorAll<HTMLElement>(
|
|
732
|
+
'[data-realtime-voice-waveform="true"] > span',
|
|
733
|
+
)[2];
|
|
734
|
+
expect(centerBar?.style.transform).toBe("scaleY(0.76)");
|
|
656
735
|
|
|
657
736
|
act(() => audioLevels.set({ input: 0, output: 0.8 }));
|
|
658
737
|
expect(
|
|
@@ -687,6 +766,32 @@ describe("RealtimeVoiceMode", () => {
|
|
|
687
766
|
},
|
|
688
767
|
);
|
|
689
768
|
|
|
769
|
+
it("shines only while the agent is working", () => {
|
|
770
|
+
render(
|
|
771
|
+
<RealtimeVoiceModeDock
|
|
772
|
+
state="working"
|
|
773
|
+
copy={copy}
|
|
774
|
+
chatVisible={false}
|
|
775
|
+
onToggleChat={vi.fn()}
|
|
776
|
+
onEndVoiceMode={vi.fn()}
|
|
777
|
+
/>,
|
|
778
|
+
);
|
|
779
|
+
expect(
|
|
780
|
+
document.querySelector(".agent-realtime-voice-working"),
|
|
781
|
+
).not.toBeNull();
|
|
782
|
+
|
|
783
|
+
render(
|
|
784
|
+
<RealtimeVoiceModeDock
|
|
785
|
+
state="listening"
|
|
786
|
+
copy={copy}
|
|
787
|
+
chatVisible={false}
|
|
788
|
+
onToggleChat={vi.fn()}
|
|
789
|
+
onEndVoiceMode={vi.fn()}
|
|
790
|
+
/>,
|
|
791
|
+
);
|
|
792
|
+
expect(document.querySelector(".agent-realtime-voice-working")).toBeNull();
|
|
793
|
+
});
|
|
794
|
+
|
|
690
795
|
it("shows an unmistakable loader and ignores early audio until connected", () => {
|
|
691
796
|
const audioLevels = createRealtimeVoiceAudioLevelStore();
|
|
692
797
|
audioLevels.set({ input: 0.8, output: 0 });
|