@agent-native/toolkit 0.221.0-same-host-mcp-20261027-20261027081850 → 0.221.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/agent-native.eject.json +16 -1
- package/dist/app/agentkit/react/components.d.ts +5 -3
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +103 -42
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +93 -65
- package/dist/app/agentkit/react/voice-transcripts.d.ts +22 -1
- package/dist/app/agentkit/react/voice-transcripts.d.ts.map +1 -1
- package/dist/app/agentkit/react/voice-transcripts.js +209 -8
- package/dist/app/agentkit/react/voice-transcripts.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +55 -40
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.d.ts +1 -0
- package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.js +18 -6
- package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
- package/dist/app/chat/agentkit-chat/index.d.ts +1 -1
- package/dist/app/chat/agentkit-chat/index.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/index.js +1 -1
- package/dist/app/chat/agentkit-chat/index.js.map +1 -1
- package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
- package/dist/app/chat/chat/run-recovery.js +5 -1
- package/dist/app/chat/chat/run-recovery.js.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.js +2 -0
- package/dist/app/chat/composer/runtime-adapters.js.map +1 -1
- package/dist/app/chat/reply-connection-suggestion.d.ts +29 -0
- package/dist/app/chat/reply-connection-suggestion.d.ts.map +1 -0
- package/dist/app/chat/reply-connection-suggestion.js +42 -0
- package/dist/app/chat/reply-connection-suggestion.js.map +1 -0
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +7 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +7 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +7 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +7 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +7 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +7 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +7 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +7 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +7 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +7 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +7 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +7 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/mcp-approval/McpApprovalReview.d.ts +25 -0
- package/dist/app/mcp-approval/McpApprovalReview.d.ts.map +1 -0
- package/dist/app/mcp-approval/McpApprovalReview.js +342 -0
- package/dist/app/mcp-approval/McpApprovalReview.js.map +1 -0
- package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
- package/dist/app/onboarding/FirstRunOnboarding.js +299 -71
- package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
- package/dist/app/onboarding/GettingStartedCard.js +1 -1
- package/dist/app/onboarding/GettingStartedCard.js.map +1 -1
- package/dist/app/onboarding/first-run-builder-credits-flag.d.ts +2 -0
- package/dist/app/onboarding/first-run-builder-credits-flag.d.ts.map +1 -0
- package/dist/app/onboarding/first-run-builder-credits-flag.js +8 -0
- package/dist/app/onboarding/first-run-builder-credits-flag.js.map +1 -0
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +14 -1
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/resources/McpConnectionSuggestion.d.ts +2 -4
- package/dist/app/resources/McpConnectionSuggestion.d.ts.map +1 -1
- package/dist/app/resources/McpConnectionSuggestion.js +59 -36
- package/dist/app/resources/McpConnectionSuggestion.js.map +1 -1
- package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
- package/dist/app/settings/account/PreferencesSettings.js +14 -1
- package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
- package/dist/app/settings/account/ThemeFamilySettings.js +2 -2
- package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -1
- package/dist/app/settings/account/search-entries.d.ts +1 -0
- package/dist/app/settings/account/search-entries.d.ts.map +1 -1
- package/dist/app/settings/account/search-entries.js +5 -0
- package/dist/app/settings/account/search-entries.js.map +1 -1
- package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
- package/dist/app/settings/shell/SettingsShell.js +26 -2
- package/dist/app/settings/shell/SettingsShell.js.map +1 -1
- package/dist/app/shared/AppShellSkeleton.d.ts.map +1 -1
- package/dist/app/shared/AppShellSkeleton.js +4 -10
- package/dist/app/shared/AppShellSkeleton.js.map +1 -1
- package/dist/app/shared/EnvironmentBadge.d.ts +2 -1
- package/dist/app/shared/EnvironmentBadge.d.ts.map +1 -1
- package/dist/app/shared/EnvironmentBadge.js +2 -3
- package/dist/app/shared/EnvironmentBadge.js.map +1 -1
- package/dist/app/shared/is-builder-io-employee.d.ts +8 -0
- package/dist/app/shared/is-builder-io-employee.d.ts.map +1 -0
- package/dist/app/shared/is-builder-io-employee.js +10 -0
- package/dist/app/shared/is-builder-io-employee.js.map +1 -0
- package/dist/app/styles/agent-conversation.css +30 -17
- package/dist/app/styles/theme-families.css +22 -5
- package/dist/app/styles/tokens/agent-kit.css +3 -3
- package/dist/composer/PromptComposer.d.ts +15 -3
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +74 -16
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.d.ts +7 -0
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/RealtimeVoiceMode.js +9 -2
- package/dist/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +7 -0
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +12 -4
- package/dist/composer/TiptapComposer.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 +5 -4
- package/dist/composer/VoiceButton.js.map +1 -1
- package/dist/composer/index.d.ts +2 -2
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +1 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/prompt-attachments.d.ts +20 -0
- package/dist/composer/prompt-attachments.d.ts.map +1 -1
- package/dist/composer/prompt-attachments.js +188 -11
- package/dist/composer/prompt-attachments.js.map +1 -1
- package/dist/composer/realtime-voice-transcript.d.ts +27 -0
- package/dist/composer/realtime-voice-transcript.d.ts.map +1 -1
- package/dist/composer/realtime-voice-transcript.js +34 -1
- package/dist/composer/realtime-voice-transcript.js.map +1 -1
- package/dist/composer/realtime-voice-turn-metrics.d.ts +43 -0
- package/dist/composer/realtime-voice-turn-metrics.d.ts.map +1 -0
- package/dist/composer/realtime-voice-turn-metrics.js +132 -0
- package/dist/composer/realtime-voice-turn-metrics.js.map +1 -0
- package/dist/composer/runtime-adapters.d.ts +4 -1
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js +2 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.d.ts +61 -9
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.js +536 -99
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/composer/voice-chat-icon.d.ts +7 -0
- package/dist/composer/voice-chat-icon.d.ts.map +1 -0
- package/dist/composer/voice-chat-icon.js +14 -0
- package/dist/composer/voice-chat-icon.js.map +1 -0
- package/dist/editor/DragHandle.d.ts +12 -1
- package/dist/editor/DragHandle.d.ts.map +1 -1
- package/dist/editor/DragHandle.js +165 -41
- package/dist/editor/DragHandle.js.map +1 -1
- package/dist/editor/RegistryBlockNode.d.ts +20 -0
- package/dist/editor/RegistryBlockNode.d.ts.map +1 -1
- package/dist/editor/RegistryBlockNode.js +58 -13
- package/dist/editor/RegistryBlockNode.js.map +1 -1
- package/dist/editor/index.d.ts +2 -2
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/editor/index.js.map +1 -1
- package/dist/styles.css +2 -0
- package/dist/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/ui/dropdown-menu.js +5 -1
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/package.json +14 -4
- package/src/app/agentkit/react/components.regenerate-readiness.spec.tsx +3 -1
- package/src/app/agentkit/react/components.spec.tsx +24 -4
- package/src/app/agentkit/react/components.tsx +174 -44
- package/src/app/agentkit/react/composer-context.spec.tsx +3 -0
- package/src/app/agentkit/react/disclosures.spec.tsx +124 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +8 -2
- package/src/app/agentkit/react/message-actions.spec.tsx +161 -5
- package/src/app/agentkit/react/saved-reply-reload.spec.tsx +206 -0
- package/src/app/agentkit/react/styles.css +93 -65
- package/src/app/agentkit/react/voice-transcripts.spec.tsx +239 -4
- package/src/app/agentkit/react/voice-transcripts.ts +245 -8
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +59 -11
- package/src/app/chat/AgentKitAssistantChat.tsx +74 -62
- package/src/app/chat/agentkit-chat/guided-answers-context.spec.ts +9 -0
- package/src/app/chat/agentkit-chat/guided-questions.tsx +17 -7
- package/src/app/chat/agentkit-chat/index.ts +1 -0
- package/src/app/chat/chat/run-recovery.spec.tsx +45 -0
- package/src/app/chat/chat/run-recovery.tsx +6 -1
- package/src/app/chat/composer/runtime-adapters.tsx +2 -0
- package/src/app/chat/reply-connection-suggestion.spec.ts +121 -0
- package/src/app/chat/reply-connection-suggestion.ts +76 -0
- package/src/app/i18n/catalogs/ar-SA.ts +10 -0
- package/src/app/i18n/catalogs/de-DE.ts +10 -0
- package/src/app/i18n/catalogs/en-US.ts +10 -0
- package/src/app/i18n/catalogs/es-ES.ts +10 -0
- package/src/app/i18n/catalogs/fr-FR.ts +10 -0
- package/src/app/i18n/catalogs/hi-IN.ts +10 -0
- package/src/app/i18n/catalogs/ja-JP.ts +10 -0
- package/src/app/i18n/catalogs/ko-KR.ts +10 -0
- package/src/app/i18n/catalogs/pt-BR.ts +10 -0
- package/src/app/i18n/catalogs/zh-CN.ts +10 -0
- package/src/app/i18n/catalogs/zh-TW.ts +10 -0
- package/src/app/mcp-approval/McpApprovalReview.spec.tsx +687 -0
- package/src/app/mcp-approval/McpApprovalReview.tsx +913 -0
- package/src/app/onboarding/FirstRunOnboarding.spec.tsx +736 -26
- package/src/app/onboarding/FirstRunOnboarding.tsx +473 -145
- package/src/app/onboarding/GettingStartedCard.tsx +1 -1
- package/src/app/onboarding/first-run-builder-credits-flag.spec.ts +13 -0
- package/src/app/onboarding/first-run-builder-credits-flag.ts +9 -0
- package/src/app/providers/AppProviders.spec.tsx +109 -0
- package/src/app/providers/AppProviders.tsx +53 -1
- package/src/app/resources/McpConnectionSuggestion.spec.ts +77 -12
- package/src/app/resources/McpConnectionSuggestion.tsx +69 -51
- package/src/app/settings/VoiceChatSettings.spec.tsx +6 -1
- package/src/app/settings/account/PreferencesSettings.spec.tsx +73 -0
- package/src/app/settings/account/PreferencesSettings.tsx +24 -7
- package/src/app/settings/account/ThemeFamilySettings.tsx +2 -2
- package/src/app/settings/account/search-entries.spec.ts +14 -0
- package/src/app/settings/account/search-entries.ts +9 -0
- package/src/app/settings/shell/SettingsShell.tsx +29 -2
- package/src/app/shared/AppShellSkeleton.spec.tsx +2 -2
- package/src/app/shared/AppShellSkeleton.tsx +3 -16
- package/src/app/shared/EnvironmentBadge.tsx +3 -4
- package/src/app/shared/is-builder-io-employee.ts +17 -0
- package/src/app/styles/agent-conversation.css +30 -17
- package/src/app/styles/theme-families.css +22 -5
- package/src/app/styles/theme-families.spec.ts +73 -0
- package/src/app/styles/tokens/agent-kit.css +3 -3
- package/src/composer/AgentSuggestionBar.overflow.spec.tsx +1 -1
- package/src/composer/AttachmentChip.spec.tsx +87 -0
- package/src/composer/PromptComposer.context.spec.tsx +8 -4
- package/src/composer/PromptComposer.setup-card.spec.tsx +170 -0
- package/src/composer/PromptComposer.tsx +125 -14
- package/src/composer/RealtimeVoiceMode.spec.tsx +48 -0
- package/src/composer/RealtimeVoiceMode.tsx +45 -2
- package/src/composer/TiptapComposer.tsx +25 -0
- package/src/composer/TiptapComposer.voice-toolbar.spec.tsx +4 -1
- package/src/composer/VoiceButton.tsx +8 -3
- package/src/composer/index.ts +4 -0
- package/src/composer/prompt-attachments.spec.ts +160 -8
- package/src/composer/prompt-attachments.ts +221 -12
- package/src/composer/realtime-voice-transcript.spec.ts +36 -0
- package/src/composer/realtime-voice-transcript.ts +56 -1
- package/src/composer/realtime-voice-turn-metrics.spec.ts +175 -0
- package/src/composer/realtime-voice-turn-metrics.ts +192 -0
- package/src/composer/runtime-adapters.tsx +5 -1
- package/src/composer/useRealtimeVoiceMode.session.spec.tsx +487 -2
- package/src/composer/useRealtimeVoiceMode.spec.ts +267 -65
- package/src/composer/useRealtimeVoiceMode.tsx +716 -139
- package/src/composer/voice-chat-icon.tsx +19 -0
- package/src/editor/DragHandle.spec.ts +217 -0
- package/src/editor/DragHandle.ts +214 -55
- package/src/editor/RegistryBlockNode.spec.ts +172 -2
- package/src/editor/RegistryBlockNode.tsx +91 -13
- package/src/editor/index.ts +4 -0
- package/src/styles.css +2 -0
- package/src/ui/dropdown-menu.tsx +4 -0
|
@@ -1258,9 +1258,9 @@ describe("controlled composer context", () => {
|
|
|
1258
1258
|
expect(
|
|
1259
1259
|
container.querySelector('[data-testid="provider-setup"]'),
|
|
1260
1260
|
).not.toBeNull();
|
|
1261
|
-
//
|
|
1261
|
+
// Nothing can be typed until AI is connected.
|
|
1262
1262
|
expect(
|
|
1263
|
-
container.querySelector('[contenteditable="
|
|
1263
|
+
container.querySelector('[contenteditable="false"]'),
|
|
1264
1264
|
).not.toBeNull();
|
|
1265
1265
|
const uploadTrigger = container.querySelector<HTMLButtonElement>(
|
|
1266
1266
|
'button[aria-label="Add context"]',
|
|
@@ -1271,7 +1271,7 @@ describe("controlled composer context", () => {
|
|
|
1271
1271
|
container
|
|
1272
1272
|
.querySelector('[role="textbox"]')
|
|
1273
1273
|
?.getAttribute("aria-disabled"),
|
|
1274
|
-
).toBe("
|
|
1274
|
+
).toBe("true");
|
|
1275
1275
|
await act(async () =>
|
|
1276
1276
|
uploadTrigger.dispatchEvent(
|
|
1277
1277
|
new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
|
|
@@ -1290,6 +1290,10 @@ describe("controlled composer context", () => {
|
|
|
1290
1290
|
).toBe(false),
|
|
1291
1291
|
);
|
|
1292
1292
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
1293
|
+
expect(onDisabledClick).not.toHaveBeenCalled();
|
|
1294
|
+
await act(async () =>
|
|
1295
|
+
container.querySelector<HTMLElement>(".ProseMirror")!.click(),
|
|
1296
|
+
);
|
|
1293
1297
|
expect(onDisabledClick).toHaveBeenCalledOnce();
|
|
1294
1298
|
onDisabledClick.mockClear();
|
|
1295
1299
|
blocked = false;
|
|
@@ -1662,7 +1666,7 @@ describe("controlled composer context", () => {
|
|
|
1662
1666
|
expect(
|
|
1663
1667
|
container.querySelector('[data-testid="provider-setup"]'),
|
|
1664
1668
|
).not.toBeNull();
|
|
1665
|
-
expect(editor.getAttribute("contenteditable")).toBe("
|
|
1669
|
+
expect(editor.getAttribute("contenteditable")).toBe("false");
|
|
1666
1670
|
expect(editor.textContent).toBe("Typed while readiness was checking");
|
|
1667
1671
|
} else {
|
|
1668
1672
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { PromptComposer } from "./PromptComposer.js";
|
|
8
|
+
import {
|
|
9
|
+
ComposerRuntimeAdaptersProvider,
|
|
10
|
+
type ComposerAgentEngineState,
|
|
11
|
+
} from "./runtime-adapters.js";
|
|
12
|
+
|
|
13
|
+
let container: HTMLDivElement;
|
|
14
|
+
let root: Root;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
18
|
+
container = document.createElement("div");
|
|
19
|
+
document.body.appendChild(container);
|
|
20
|
+
root = createRoot(container);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
act(() => root.unmount());
|
|
25
|
+
container.remove();
|
|
26
|
+
vi.unstubAllGlobals();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function SetupCard({ bouncePulse }: { bouncePulse?: number }) {
|
|
30
|
+
return <div data-testid="setup-card" data-bounce={bouncePulse ?? 0} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function render({
|
|
34
|
+
readiness,
|
|
35
|
+
configured,
|
|
36
|
+
otherProviderConfigured = false,
|
|
37
|
+
catalogFailed = false,
|
|
38
|
+
onModelConnectionRequiredChange,
|
|
39
|
+
}: {
|
|
40
|
+
readiness: ComposerAgentEngineState;
|
|
41
|
+
configured: boolean;
|
|
42
|
+
otherProviderConfigured?: boolean;
|
|
43
|
+
catalogFailed?: boolean;
|
|
44
|
+
onModelConnectionRequiredChange?: (required: boolean) => void;
|
|
45
|
+
}) {
|
|
46
|
+
await act(async () => {
|
|
47
|
+
root.render(
|
|
48
|
+
<ComposerRuntimeAdaptersProvider
|
|
49
|
+
adapters={{
|
|
50
|
+
models: {
|
|
51
|
+
useAgentEngineConfigured: () => ({
|
|
52
|
+
missing: readiness === "missing",
|
|
53
|
+
state: readiness,
|
|
54
|
+
}),
|
|
55
|
+
BuilderSetupCard: SetupCard,
|
|
56
|
+
},
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<PromptComposer
|
|
60
|
+
autoFocus={false}
|
|
61
|
+
onSubmit={vi.fn()}
|
|
62
|
+
requireAgentEngine
|
|
63
|
+
modelStatusChecksEnabled={false}
|
|
64
|
+
availableModels={
|
|
65
|
+
catalogFailed
|
|
66
|
+
? []
|
|
67
|
+
: [
|
|
68
|
+
{
|
|
69
|
+
engine: "anthropic",
|
|
70
|
+
label: "Anthropic",
|
|
71
|
+
models: ["example-model"],
|
|
72
|
+
configured,
|
|
73
|
+
},
|
|
74
|
+
...(otherProviderConfigured
|
|
75
|
+
? [
|
|
76
|
+
{
|
|
77
|
+
engine: "openai",
|
|
78
|
+
label: "OpenAI",
|
|
79
|
+
models: ["other-model"],
|
|
80
|
+
configured: true,
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
: []),
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
selectedModel="example-model"
|
|
87
|
+
selectedEngine="anthropic"
|
|
88
|
+
onModelChange={vi.fn()}
|
|
89
|
+
onConnectProvider={vi.fn()}
|
|
90
|
+
modelListLoading={catalogFailed ? false : undefined}
|
|
91
|
+
onModelConnectionRequiredChange={onModelConnectionRequiredChange}
|
|
92
|
+
includeDefaultMentionSearch={false}
|
|
93
|
+
includeDefaultSlashSkills={false}
|
|
94
|
+
voiceEnabled={false}
|
|
95
|
+
/>
|
|
96
|
+
</ComposerRuntimeAdaptersProvider>,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const card = () =>
|
|
102
|
+
container.querySelector<HTMLElement>('[data-testid="setup-card"]');
|
|
103
|
+
const editor = () => container.querySelector<HTMLElement>(".ProseMirror");
|
|
104
|
+
|
|
105
|
+
describe("PromptComposer with no AI connected", () => {
|
|
106
|
+
it("shows the setup card and closes the prompt before readiness answers", async () => {
|
|
107
|
+
const onRequired = vi.fn();
|
|
108
|
+
await render({
|
|
109
|
+
readiness: "unknown",
|
|
110
|
+
configured: false,
|
|
111
|
+
onModelConnectionRequiredChange: onRequired,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(card()).not.toBeNull();
|
|
115
|
+
expect(editor()?.getAttribute("contenteditable")).toBe("false");
|
|
116
|
+
expect(onRequired).toHaveBeenLastCalledWith(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("points a click on the closed prompt at the setup card", async () => {
|
|
120
|
+
await render({ readiness: "unknown", configured: false });
|
|
121
|
+
|
|
122
|
+
await act(async () => {
|
|
123
|
+
editor()!.click();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(card()?.dataset.bounce).toBe("1");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("keeps the prompt open once readiness says AI is configured", async () => {
|
|
130
|
+
await render({ readiness: "configured", configured: false });
|
|
131
|
+
|
|
132
|
+
expect(card()).toBeNull();
|
|
133
|
+
expect(editor()?.getAttribute("contenteditable")).toBe("true");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("keeps the prompt open when only the selected model's provider is unconnected", async () => {
|
|
137
|
+
const onRequired = vi.fn();
|
|
138
|
+
await render({
|
|
139
|
+
readiness: "unknown",
|
|
140
|
+
configured: false,
|
|
141
|
+
otherProviderConfigured: true,
|
|
142
|
+
onModelConnectionRequiredChange: onRequired,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(card()).toBeNull();
|
|
146
|
+
expect(editor()?.getAttribute("contenteditable")).toBe("true");
|
|
147
|
+
expect(onRequired).not.toHaveBeenCalledWith(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("keeps the prompt open when the model catalog failed to load", async () => {
|
|
151
|
+
const onRequired = vi.fn();
|
|
152
|
+
await render({
|
|
153
|
+
readiness: "unavailable",
|
|
154
|
+
configured: false,
|
|
155
|
+
catalogFailed: true,
|
|
156
|
+
onModelConnectionRequiredChange: onRequired,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(card()).toBeNull();
|
|
160
|
+
expect(editor()?.getAttribute("contenteditable")).toBe("true");
|
|
161
|
+
expect(onRequired).not.toHaveBeenCalledWith(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("keeps the prompt open while the model list has a connected provider", async () => {
|
|
165
|
+
await render({ readiness: "unknown", configured: true });
|
|
166
|
+
|
|
167
|
+
expect(card()).toBeNull();
|
|
168
|
+
expect(editor()?.getAttribute("contenteditable")).toBe("true");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
CompositeAttachmentAdapter,
|
|
17
17
|
SimpleImageAttachmentAdapter,
|
|
18
18
|
} from "@assistant-ui/react";
|
|
19
|
-
import { IconX } from "@tabler/icons-react";
|
|
19
|
+
import { IconAlertTriangle, IconX } from "@tabler/icons-react";
|
|
20
20
|
import {
|
|
21
21
|
useCallback,
|
|
22
22
|
useEffect,
|
|
@@ -51,12 +51,19 @@ import type { ComposerTerminalModeControl } from "./ComposerPlusMenu.js";
|
|
|
51
51
|
import type { ComposerContextSnapshot } from "./context-items.js";
|
|
52
52
|
import { isPastedTextAttachmentName } from "./pasted-text.js";
|
|
53
53
|
import { PastedTextChip } from "./PastedTextChip.js";
|
|
54
|
-
import { escapePromptAttachmentAttribute } from "./prompt-attachments.js";
|
|
55
54
|
import {
|
|
55
|
+
escapePromptAttachmentAttribute,
|
|
56
|
+
getUnshrinkableImageError,
|
|
57
|
+
probeAgentPromptAttachment,
|
|
58
|
+
type AgentPromptAttachmentReadError,
|
|
59
|
+
} from "./prompt-attachments.js";
|
|
60
|
+
import {
|
|
61
|
+
type ComposerTranslate,
|
|
56
62
|
type EngineModelGroup,
|
|
57
63
|
type ComposerAgentEngineState,
|
|
58
64
|
type AgentChatContextItem,
|
|
59
65
|
type ReasoningEffort,
|
|
66
|
+
fallbackTranslate,
|
|
60
67
|
useComposerRuntimeAdapters,
|
|
61
68
|
} from "./runtime-adapters.js";
|
|
62
69
|
import {
|
|
@@ -162,6 +169,11 @@ export interface PromptComposerProps {
|
|
|
162
169
|
* whether this composer shows its own setup card.
|
|
163
170
|
*/
|
|
164
171
|
setupCardVisible?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Reports whether the loaded model list offers only connect paths, so a
|
|
174
|
+
* host that owns the setup card can show it before readiness answers.
|
|
175
|
+
*/
|
|
176
|
+
onModelConnectionRequiredChange?: (required: boolean) => void;
|
|
165
177
|
/** Disable only the send control while the submission is being accepted. */
|
|
166
178
|
sendButtonDisabled?: boolean;
|
|
167
179
|
/** Prevent submission while preserving editor focus and draft entry. */
|
|
@@ -197,6 +209,8 @@ export interface PromptComposerProps {
|
|
|
197
209
|
voiceEnabled?: boolean;
|
|
198
210
|
/** Let an empty draft turn Send into the voice-chat button (default: true). Dictation is unaffected. */
|
|
199
211
|
voiceChatEnabled?: boolean;
|
|
212
|
+
/** Starts voice chat from the empty Send slot in place of the provider's default start. */
|
|
213
|
+
onVoiceChatStart?: () => void;
|
|
200
214
|
/** Show file upload controls and pass submitted files to onSubmit (default: true). */
|
|
201
215
|
attachmentsEnabled?: boolean;
|
|
202
216
|
/** Opens host-owned storage setup when the user chooses an upload action. */
|
|
@@ -362,8 +376,19 @@ class BinaryDocumentAttachmentAdapter implements AttachmentAdapter {
|
|
|
362
376
|
|
|
363
377
|
class RasterImageAttachmentAdapter extends SimpleImageAttachmentAdapter {
|
|
364
378
|
public override accept = IMAGE_ATTACHMENT_ACCEPT;
|
|
379
|
+
private readonly translate: ComposerTranslate;
|
|
380
|
+
|
|
381
|
+
constructor(translate: ComposerTranslate) {
|
|
382
|
+
super();
|
|
383
|
+
this.translate = translate;
|
|
384
|
+
}
|
|
365
385
|
|
|
366
386
|
public override async add(state: { file: File }): Promise<PendingAttachment> {
|
|
387
|
+
const limitError = await getUnshrinkableImageError(
|
|
388
|
+
state.file,
|
|
389
|
+
this.translate,
|
|
390
|
+
);
|
|
391
|
+
if (limitError) throw new Error(limitError);
|
|
367
392
|
return { ...(await super.add(state)), id: crypto.randomUUID() };
|
|
368
393
|
}
|
|
369
394
|
}
|
|
@@ -374,9 +399,11 @@ class ChatDocumentAttachmentAdapter extends BinaryDocumentAttachmentAdapter {
|
|
|
374
399
|
public override accept = CHAT_DOCUMENT_ATTACHMENT_ACCEPT;
|
|
375
400
|
}
|
|
376
401
|
|
|
377
|
-
export function createChatAttachmentAdapter(
|
|
402
|
+
export function createChatAttachmentAdapter(
|
|
403
|
+
translate: ComposerTranslate = fallbackTranslate,
|
|
404
|
+
): AttachmentAdapter {
|
|
378
405
|
return new CompositeAttachmentAdapter([
|
|
379
|
-
new RasterImageAttachmentAdapter(),
|
|
406
|
+
new RasterImageAttachmentAdapter(translate),
|
|
380
407
|
new ChatDocumentAttachmentAdapter(),
|
|
381
408
|
new TextAttachmentAdapter(),
|
|
382
409
|
]);
|
|
@@ -576,7 +603,28 @@ function ImagePreviewLightbox({
|
|
|
576
603
|
);
|
|
577
604
|
}
|
|
578
605
|
|
|
579
|
-
function
|
|
606
|
+
function useAttachmentReadError(
|
|
607
|
+
attachment: Attachment,
|
|
608
|
+
): AgentPromptAttachmentReadError | null {
|
|
609
|
+
const file = "file" in attachment ? attachment.file : undefined;
|
|
610
|
+
const [readError, setReadError] =
|
|
611
|
+
useState<AgentPromptAttachmentReadError | null>(null);
|
|
612
|
+
useEffect(() => {
|
|
613
|
+
setReadError(null);
|
|
614
|
+
if (!file) return;
|
|
615
|
+
let active = true;
|
|
616
|
+
void probeAgentPromptAttachment(file).then((error) => {
|
|
617
|
+
if (active) setReadError(error);
|
|
618
|
+
});
|
|
619
|
+
return () => {
|
|
620
|
+
active = false;
|
|
621
|
+
};
|
|
622
|
+
}, [file]);
|
|
623
|
+
return readError;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** @internal exported for unit tests only */
|
|
627
|
+
export function AttachmentChip({
|
|
580
628
|
attachment,
|
|
581
629
|
onRemove,
|
|
582
630
|
}: {
|
|
@@ -587,8 +635,48 @@ function AttachmentChip({
|
|
|
587
635
|
const isImage =
|
|
588
636
|
getAttachmentImageFile(attachment) !== null ||
|
|
589
637
|
getAttachmentContentImageSrc(attachment) !== null;
|
|
638
|
+
const readError = useAttachmentReadError(attachment);
|
|
590
639
|
const t = useComposerRuntimeAdapters().translate!;
|
|
591
640
|
|
|
641
|
+
if (readError) {
|
|
642
|
+
const message =
|
|
643
|
+
readError === "too-large"
|
|
644
|
+
? t("agentChat.composer.attachmentTooLarge", {
|
|
645
|
+
defaultValue: "File is over 8 MB",
|
|
646
|
+
})
|
|
647
|
+
: readError === "not-utf8"
|
|
648
|
+
? t("agentChat.composer.attachmentNotUtf8", {
|
|
649
|
+
defaultValue: "File isn't UTF-8 text",
|
|
650
|
+
})
|
|
651
|
+
: t("agentChat.composer.attachmentReadFailed", {
|
|
652
|
+
defaultValue: "Couldn't read this file",
|
|
653
|
+
});
|
|
654
|
+
return (
|
|
655
|
+
<div
|
|
656
|
+
role="status"
|
|
657
|
+
title={message}
|
|
658
|
+
className="agent-composer-attachment-chip group relative inline-flex max-w-[240px] items-center gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-xs"
|
|
659
|
+
>
|
|
660
|
+
<IconAlertTriangle className="h-4 w-4 shrink-0 text-amber-500" />
|
|
661
|
+
<span className="flex min-w-0 flex-col">
|
|
662
|
+
<span className="truncate font-medium">{attachment.name}</span>
|
|
663
|
+
<span className="truncate text-muted-foreground">{message}</span>
|
|
664
|
+
</span>
|
|
665
|
+
<button
|
|
666
|
+
type="button"
|
|
667
|
+
onClick={() => onRemove(attachment.id)}
|
|
668
|
+
aria-label={t("agentChat.composer.removeAttachment", {
|
|
669
|
+
name: attachment.name,
|
|
670
|
+
defaultValue: `Remove ${attachment.name}`,
|
|
671
|
+
})}
|
|
672
|
+
className="flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground"
|
|
673
|
+
>
|
|
674
|
+
<IconX className="h-3 w-3" />
|
|
675
|
+
</button>
|
|
676
|
+
</div>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
592
680
|
if (isPastedTextAttachmentName(attachment.name)) {
|
|
593
681
|
return <PastedTextChip attachment={attachment} onRemove={onRemove} />;
|
|
594
682
|
}
|
|
@@ -704,6 +792,7 @@ function PromptComposerInner({
|
|
|
704
792
|
submissionDisabled,
|
|
705
793
|
showMissingApiKeySetup = true,
|
|
706
794
|
setupCardVisible: hostSetupCardVisible,
|
|
795
|
+
onModelConnectionRequiredChange,
|
|
707
796
|
sendButtonDisabled,
|
|
708
797
|
submitting,
|
|
709
798
|
willQueue = false,
|
|
@@ -724,6 +813,7 @@ function PromptComposerInner({
|
|
|
724
813
|
showAutoModelOption = true,
|
|
725
814
|
voiceEnabled = DEFAULT_VOICE_DICTATION_ENABLED,
|
|
726
815
|
voiceChatEnabled = true,
|
|
816
|
+
onVoiceChatStart,
|
|
727
817
|
attachmentsEnabled = true,
|
|
728
818
|
onAttachmentRequest,
|
|
729
819
|
contextButtonTooltipDisabled = false,
|
|
@@ -886,7 +976,21 @@ function PromptComposerInner({
|
|
|
886
976
|
const engineState = engineStatusChecksEnabled
|
|
887
977
|
? agentEngineConfigured.state
|
|
888
978
|
: "configured";
|
|
889
|
-
const
|
|
979
|
+
const [modelListNeedsConnection, setModelListNeedsConnection] =
|
|
980
|
+
useState(false);
|
|
981
|
+
const handleModelConnectionRequiredChange = useCallback(
|
|
982
|
+
(required: boolean) => {
|
|
983
|
+
setModelListNeedsConnection(required);
|
|
984
|
+
onModelConnectionRequiredChange?.(required);
|
|
985
|
+
},
|
|
986
|
+
[onModelConnectionRequiredChange],
|
|
987
|
+
);
|
|
988
|
+
// The model list knows nothing is connected well before the readiness
|
|
989
|
+
// check answers; an answer of "configured" still wins.
|
|
990
|
+
const missingApiKey =
|
|
991
|
+
engineStatusChecksEnabled &&
|
|
992
|
+
(engineState === "missing" ||
|
|
993
|
+
(engineState !== "configured" && modelListNeedsConnection));
|
|
890
994
|
const [setupBouncePulse, setSetupBouncePulse] = useState(0);
|
|
891
995
|
const [providerCheckPending, setProviderCheckPending] = useState(false);
|
|
892
996
|
const heldProviderDraftRef = useRef<ComposerDraftSnapshot | null>(null);
|
|
@@ -897,13 +1001,16 @@ function PromptComposerInner({
|
|
|
897
1001
|
}
|
|
898
1002
|
}, []);
|
|
899
1003
|
const engineSubmissionBlocked = shouldGateComposerForEngine(engineState);
|
|
900
|
-
// A missing provider leaves the draft editable; the send gate below holds
|
|
901
|
-
// the draft and points at the setup card instead.
|
|
902
|
-
const composerDisabled = Boolean(disabled);
|
|
903
1004
|
const ownSetupCardVisible = Boolean(
|
|
904
1005
|
missingApiKey && showMissingApiKeySetup && BuilderSetupCard,
|
|
905
1006
|
);
|
|
906
1007
|
const setupCardVisible = hostSetupCardVisible ?? ownSetupCardVisible;
|
|
1008
|
+
// With nothing connected the prompt is closed to typing and a click on it
|
|
1009
|
+
// points at the setup card; without a card on screen it stays open, since a
|
|
1010
|
+
// closed prompt with nothing to explain it is a dead end.
|
|
1011
|
+
const composerDisabled = Boolean(
|
|
1012
|
+
disabled || (missingApiKey && setupCardVisible),
|
|
1013
|
+
);
|
|
907
1014
|
const handleDisabledComposerClick = useCallback(() => {
|
|
908
1015
|
if (missingApiKey) setSetupBouncePulse((pulse) => pulse + 1);
|
|
909
1016
|
onDisabledClick?.();
|
|
@@ -1043,7 +1150,9 @@ function PromptComposerInner({
|
|
|
1043
1150
|
<AgentComposerFrame
|
|
1044
1151
|
className={cn(
|
|
1045
1152
|
"text-start",
|
|
1046
|
-
(composerDisabled ||
|
|
1153
|
+
(composerDisabled ||
|
|
1154
|
+
(engineStatusChecksEnabled && engineState === "missing") ||
|
|
1155
|
+
(missingApiKey && setupCardVisible)) &&
|
|
1047
1156
|
"agent-composer-area--attached-above",
|
|
1048
1157
|
className,
|
|
1049
1158
|
)}
|
|
@@ -1138,6 +1247,7 @@ function PromptComposerInner({
|
|
|
1138
1247
|
onSlashCommand={onSlashCommand}
|
|
1139
1248
|
voiceEnabled={voiceEnabled}
|
|
1140
1249
|
voiceChatEnabled={voiceChatEnabled}
|
|
1250
|
+
onVoiceChatStart={onVoiceChatStart}
|
|
1141
1251
|
onTextChange={onTextChange}
|
|
1142
1252
|
mentionItems={mentionItems}
|
|
1143
1253
|
mentionItemsError={mentionItemsError}
|
|
@@ -1170,6 +1280,7 @@ function PromptComposerInner({
|
|
|
1170
1280
|
onConnectLocalRuntime={onConnectLocalRuntime}
|
|
1171
1281
|
imageModelMenu={imageModelMenu}
|
|
1172
1282
|
setupCardVisible={setupCardVisible}
|
|
1283
|
+
onModelConnectionRequiredChange={handleModelConnectionRequiredChange}
|
|
1173
1284
|
/>
|
|
1174
1285
|
</AgentComposerFrame>
|
|
1175
1286
|
</>
|
|
@@ -1188,17 +1299,17 @@ function PromptComposerInner({
|
|
|
1188
1299
|
* without needing the outer chat to be mounted.
|
|
1189
1300
|
*/
|
|
1190
1301
|
function PromptComposerRuntime(props: PromptComposerProps) {
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1302
|
+
const adapters = useComposerRuntimeAdapters();
|
|
1303
|
+
const StaleIndexBoundary = adapters.agentChat.StaleIndexBoundary;
|
|
1193
1304
|
const attachmentAdapter = useMemo(
|
|
1194
1305
|
() =>
|
|
1195
1306
|
props.attachmentAdapter ??
|
|
1196
1307
|
new CompositeAttachmentAdapter([
|
|
1197
|
-
new RasterImageAttachmentAdapter(),
|
|
1308
|
+
new RasterImageAttachmentAdapter(adapters.translate),
|
|
1198
1309
|
new BinaryDocumentAttachmentAdapter(),
|
|
1199
1310
|
new TextAttachmentAdapter(),
|
|
1200
1311
|
]),
|
|
1201
|
-
[props.attachmentAdapter],
|
|
1312
|
+
[props.attachmentAdapter, adapters.translate],
|
|
1202
1313
|
);
|
|
1203
1314
|
const runtime = useLocalRuntime(NOOP_ADAPTER, {
|
|
1204
1315
|
adapters: { attachments: attachmentAdapter },
|
|
@@ -68,6 +68,12 @@ const copy: RealtimeVoiceModeCopy = {
|
|
|
68
68
|
error: "Voice mode needs attention",
|
|
69
69
|
ending: "Ending voice mode",
|
|
70
70
|
},
|
|
71
|
+
thinking: "Thinking",
|
|
72
|
+
approval: {
|
|
73
|
+
question: (toolLabel) => `Approve to run ${toolLabel}?`,
|
|
74
|
+
approve: "Approve",
|
|
75
|
+
deny: "Deny",
|
|
76
|
+
},
|
|
71
77
|
errors: {
|
|
72
78
|
unsupported: "Unsupported",
|
|
73
79
|
responseFailed: "Response failed",
|
|
@@ -93,6 +99,9 @@ function voiceSession(
|
|
|
93
99
|
start: vi.fn(async () => {}),
|
|
94
100
|
end: vi.fn(),
|
|
95
101
|
dismissError: vi.fn(),
|
|
102
|
+
currentToolName: null,
|
|
103
|
+
approvals: [],
|
|
104
|
+
resolveApproval: vi.fn(async () => {}),
|
|
96
105
|
...overrides,
|
|
97
106
|
} as unknown as RealtimeVoiceModeApi;
|
|
98
107
|
}
|
|
@@ -171,6 +180,45 @@ describe("RealtimeVoiceComposerControls", () => {
|
|
|
171
180
|
expect(new Set(bars())).toEqual(new Set(["scaleY(0.4)"]));
|
|
172
181
|
});
|
|
173
182
|
|
|
183
|
+
it("names the step it is working on, and says it is thinking between steps", () => {
|
|
184
|
+
const status = () =>
|
|
185
|
+
slot("voice-chat-status")?.querySelector('[role="status"]')?.textContent;
|
|
186
|
+
renderControls(voiceSession({ state: "working" }));
|
|
187
|
+
expect(status()).toBe("Thinking");
|
|
188
|
+
|
|
189
|
+
renderControls(
|
|
190
|
+
voiceSession({ state: "working", currentToolName: "patch-deck" }),
|
|
191
|
+
);
|
|
192
|
+
expect(status()).toBe("Patch deck");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("asks to approve a waiting tool call in place of the status", () => {
|
|
196
|
+
const voice = voiceSession({
|
|
197
|
+
state: "working",
|
|
198
|
+
approvals: [{ callId: "call-delete", toolName: "delete-slide" }],
|
|
199
|
+
});
|
|
200
|
+
renderControls(voice);
|
|
201
|
+
|
|
202
|
+
expect(slot("voice-chat-status")).toBeNull();
|
|
203
|
+
const approval = slot("voice-chat-approval")!;
|
|
204
|
+
expect(approval.querySelector('[role="status"]')?.textContent).toBe(
|
|
205
|
+
"Approve to run Delete slide?",
|
|
206
|
+
);
|
|
207
|
+
const [approve, deny] = Array.from(approval.querySelectorAll("button"));
|
|
208
|
+
act(() => approve!.click());
|
|
209
|
+
act(() => deny!.click());
|
|
210
|
+
expect(voice.resolveApproval).toHaveBeenNthCalledWith(
|
|
211
|
+
1,
|
|
212
|
+
"call-delete",
|
|
213
|
+
true,
|
|
214
|
+
);
|
|
215
|
+
expect(voice.resolveApproval).toHaveBeenNthCalledWith(
|
|
216
|
+
2,
|
|
217
|
+
"call-delete",
|
|
218
|
+
false,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
174
222
|
it("toggles speaker and microphone mute with pressed states", () => {
|
|
175
223
|
const voice = voiceSession();
|
|
176
224
|
renderControls(voice);
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
IconX,
|
|
8
8
|
} from "@tabler/icons-react";
|
|
9
9
|
|
|
10
|
+
import { Button } from "../ui/button.js";
|
|
10
11
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
|
|
12
|
+
import { realtimeVoiceToolLabel } from "./realtime-voice-transcript.js";
|
|
11
13
|
import type { RealtimeVoiceModeApi } from "./useRealtimeVoiceMode.js";
|
|
12
14
|
import { VoiceLevelWaveform } from "./voice-waveform.js";
|
|
13
15
|
|
|
@@ -55,6 +57,13 @@ export interface RealtimeVoiceModeCopy {
|
|
|
55
57
|
>;
|
|
56
58
|
};
|
|
57
59
|
status: Record<RealtimeVoiceModeState, string>;
|
|
60
|
+
/** Shown while working with no tool running: the model is deciding. */
|
|
61
|
+
thinking: string;
|
|
62
|
+
approval: {
|
|
63
|
+
question: (toolLabel: string) => string;
|
|
64
|
+
approve: string;
|
|
65
|
+
deny: string;
|
|
66
|
+
};
|
|
58
67
|
errors: {
|
|
59
68
|
unsupported: string;
|
|
60
69
|
responseFailed: string;
|
|
@@ -115,9 +124,39 @@ export function RealtimeVoiceComposerControls({
|
|
|
115
124
|
? copy.unmuteMicrophone
|
|
116
125
|
: copy.muteMicrophone;
|
|
117
126
|
|
|
127
|
+
const approval = state === "ending" ? undefined : voice.approvals[0];
|
|
128
|
+
|
|
118
129
|
return (
|
|
119
130
|
<>
|
|
120
|
-
{
|
|
131
|
+
{approval ? (
|
|
132
|
+
<div
|
|
133
|
+
data-agent-composer-slot="voice-chat-approval"
|
|
134
|
+
className="flex min-w-0 shrink items-center gap-1"
|
|
135
|
+
>
|
|
136
|
+
<span
|
|
137
|
+
role="status"
|
|
138
|
+
aria-live="polite"
|
|
139
|
+
className="min-w-0 truncate ps-1 agent-kit-caption-copy text-foreground"
|
|
140
|
+
>
|
|
141
|
+
{copy.approval.question(realtimeVoiceToolLabel(approval.toolName))}
|
|
142
|
+
</span>
|
|
143
|
+
<Button
|
|
144
|
+
type="button"
|
|
145
|
+
size="xs"
|
|
146
|
+
onClick={() => void voice.resolveApproval(approval.callId, true)}
|
|
147
|
+
>
|
|
148
|
+
{copy.approval.approve}
|
|
149
|
+
</Button>
|
|
150
|
+
<Button
|
|
151
|
+
type="button"
|
|
152
|
+
size="xs"
|
|
153
|
+
variant="ghost"
|
|
154
|
+
onClick={() => void voice.resolveApproval(approval.callId, false)}
|
|
155
|
+
>
|
|
156
|
+
{copy.approval.deny}
|
|
157
|
+
</Button>
|
|
158
|
+
</div>
|
|
159
|
+
) : state !== "error" ? (
|
|
121
160
|
<div
|
|
122
161
|
data-agent-composer-slot="voice-chat-status"
|
|
123
162
|
data-voice-speaker={
|
|
@@ -157,7 +196,11 @@ export function RealtimeVoiceComposerControls({
|
|
|
157
196
|
aria-live="polite"
|
|
158
197
|
className="min-w-0 truncate agent-kit-caption-copy text-muted-foreground"
|
|
159
198
|
>
|
|
160
|
-
{
|
|
199
|
+
{state === "working"
|
|
200
|
+
? voice.currentToolName
|
|
201
|
+
? realtimeVoiceToolLabel(voice.currentToolName)
|
|
202
|
+
: copy.thinking
|
|
203
|
+
: copy.status[state]}
|
|
161
204
|
</span>
|
|
162
205
|
</div>
|
|
163
206
|
) : null}
|