@agent-native/core 0.98.0 → 0.98.1
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +24 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/action-change-marker.ts +8 -1
- package/corpus/core/src/changelog/parse.ts +8 -3
- package/corpus/core/src/client/AgentPanel.tsx +2 -3
- package/corpus/core/src/client/AssistantChat.tsx +83 -39
- package/corpus/core/src/client/analytics.ts +29 -0
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +16 -2
- package/corpus/core/src/client/chat-model-groups.ts +28 -0
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +442 -85
- package/corpus/core/src/client/composer/TiptapComposer.tsx +46 -29
- package/corpus/core/src/client/composer/VoiceButton.tsx +2 -1
- package/corpus/core/src/client/composer/index.ts +5 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +1206 -67
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/progress/RunsTray.tsx +13 -1
- package/corpus/core/src/client/settings/SecretsSection.tsx +387 -266
- package/corpus/core/src/client/settings/SettingsPanel.tsx +117 -38
- package/corpus/core/src/client/use-action.ts +12 -0
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +8 -1
- package/corpus/core/src/client/use-agent-engine-configured.ts +17 -3
- package/corpus/core/src/client/use-db-sync.ts +84 -34
- package/corpus/core/src/client/use-pausing-interval.ts +14 -4
- package/corpus/core/src/integrations/scope-store.ts +1 -1
- package/corpus/core/src/integrations/usage-budget-store.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +44 -1
- package/corpus/core/src/mcp-client/workspace-servers.ts +4 -4
- package/corpus/core/src/secrets/crypto.ts +28 -8
- package/corpus/core/src/server/action-change.ts +5 -1
- package/corpus/core/src/server/action-routes.ts +8 -0
- package/corpus/core/src/server/poll.ts +1 -0
- package/corpus/core/src/server/realtime-voice.ts +299 -13
- package/corpus/core/src/styles/agent-native.css +2 -2
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/corpus/core/src/vite/client.ts +9 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +10 -5
- package/corpus/templates/analytics/AGENTS.md +9 -13
- package/corpus/templates/analytics/actions/navigate.ts +59 -5
- package/corpus/templates/analytics/actions/view-screen.ts +3 -6
- package/corpus/templates/analytics/app/components/ProviderCorpusJobNotifier.tsx +20 -3
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +2 -1
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -6
- package/corpus/templates/analytics/app/global.css +19 -20
- package/corpus/templates/analytics/app/lib/provider-corpus-job-sync.ts +26 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +35 -341
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +38 -7
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +1 -1
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +3 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +333 -272
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +72 -17
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +297 -108
- package/corpus/templates/analytics/app/root.tsx +28 -8
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +2 -1
- package/corpus/templates/analytics/changelog/2026-06-25-referrer-dashboard-panels-show-external-domains.md +2 -1
- package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +1 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-no-longer-repeatedly-restarts-background-requests-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-analytics-only-asks-for-replay-storage-when-you-enable-sessi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-charts-now-show-their-loading-skeleton-while-refreshing.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-realtime-voice-now-shows-a-clear-connection-indicator-comple.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-event-counts-stay-hidden-until-recordings-finish-load.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-storage-key-mismatches-show-actionable-setup-guidance.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-replay-timelines-group-continuous-scrolling-and-dev-tools-co.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-filters-now-stay-compact-in-a-settings-menu-at-mediu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replay-console-errors-now-link-to-matching-issues-or.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-load-large-recordings-faster-and-surface-fai.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-preserve-recorded-styles-and-viewport-layout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-11-session-replays-stay-centered-instead-of-clipping-wide-recor.md +6 -0
- package/corpus/templates/analytics/package.json +1 -1
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -303
- package/corpus/templates/analytics/server/handlers/session-replay.ts +57 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +16 -3
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +12 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +208 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +7 -79
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/assets/app/global.css +4 -4
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +0 -1
- package/corpus/templates/assets/app/routes/library.tsx +0 -1
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/brain/app/global.css +5 -5
- package/corpus/templates/brain/app/root.tsx +0 -3
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/calendar/app/root.tsx +1 -0
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +74 -8
- package/corpus/templates/clips/app/hooks/use-library.ts +12 -25
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +20 -22
- package/corpus/templates/clips/changelog/2026-07-11-generated-titles-no-longer-cause-continuous-background-refreshes.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-11-s3-storage-setup-stays-collapsed-until-you-choose-it-as-the-.md +6 -0
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/content/app/hooks/use-comments.ts +0 -1
- package/corpus/templates/content/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/corpus/templates/design/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -2
- package/corpus/templates/macros/app/root.tsx +0 -3
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/mail/app/hooks/use-compose-state.ts +1 -0
- package/corpus/templates/mail/app/root.tsx +1 -12
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +5 -2
- package/corpus/templates/plan/app/hooks/use-plans.ts +6 -13
- package/corpus/templates/plan/app/pages/PlansPage.tsx +5 -27
- package/corpus/templates/plan/changelog/2026-07-11-plan-refreshes-only-when-content-changes.md +6 -0
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -0
- package/corpus/templates/slides/app/hooks/use-slide-comments.ts +7 -31
- package/corpus/templates/slides/changelog/2026-07-11-comments-now-update-from-live-collaboration-events-without-c.md +6 -0
- package/dist/action-change-marker.d.ts +1 -0
- package/dist/action-change-marker.d.ts.map +1 -1
- package/dist/action-change-marker.js +7 -1
- package/dist/action-change-marker.js.map +1 -1
- package/dist/changelog/parse.d.ts +4 -2
- package/dist/changelog/parse.d.ts.map +1 -1
- package/dist/changelog/parse.js +5 -3
- package/dist/changelog/parse.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +7 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +62 -40
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +26 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +9 -2
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +24 -0
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +40 -4
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +172 -42
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -0
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +47 -29
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +2 -2
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/index.d.ts +2 -2
- package/dist/client/composer/index.d.ts.map +1 -1
- package/dist/client/composer/index.js +1 -1
- package/dist/client/composer/index.js.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.d.ts +100 -3
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +952 -51
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +10 -1
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts +2 -3
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +46 -22
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +9 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +46 -12
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-action.d.ts +2 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +10 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +2 -0
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +7 -1
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +17 -3
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +8 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +57 -32
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-pausing-interval.d.ts.map +1 -1
- package/dist/client/use-pausing-interval.js +16 -4
- package/dist/client/use-pausing-interval.js.map +1 -1
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/scope-store.js.map +1 -1
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/integrations/usage-budget-store.js.map +1 -1
- package/dist/localization/default-messages.d.ts +41 -1
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +41 -1
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp-client/workspace-servers.js +4 -4
- package/dist/mcp-client/workspace-servers.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/crypto.d.ts +7 -6
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +30 -11
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-change.d.ts +1 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +4 -1
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +5 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +1 -0
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-voice.d.ts +7 -0
- package/dist/server/realtime-voice.d.ts.map +1 -1
- package/dist/server/realtime-voice.js +222 -12
- package/dist/server/realtime-voice.js.map +1 -1
- package/dist/styles/agent-native.css +2 -2
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +6 -1
- package/dist/vite/client.js.map +1 -1
- package/package.json +2 -2
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +2 -2
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +7 -3
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +39 -9
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { Button } from "@agent-native/toolkit/ui/button";
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectGroup,
|
|
6
|
+
SelectItem,
|
|
7
|
+
SelectTrigger,
|
|
8
|
+
} from "@agent-native/toolkit/ui/select";
|
|
2
9
|
import {
|
|
3
10
|
IconAlertTriangle,
|
|
11
|
+
IconBrain,
|
|
12
|
+
IconLanguage,
|
|
4
13
|
IconLoader2,
|
|
5
14
|
IconMicrophone,
|
|
6
15
|
IconPhoneOff,
|
|
@@ -9,14 +18,17 @@ import {
|
|
|
9
18
|
} from "@tabler/icons-react";
|
|
10
19
|
import {
|
|
11
20
|
type MouseEvent,
|
|
21
|
+
useCallback,
|
|
12
22
|
useEffect,
|
|
13
23
|
useId,
|
|
24
|
+
useRef,
|
|
14
25
|
useState,
|
|
15
26
|
useSyncExternalStore,
|
|
16
27
|
} from "react";
|
|
17
28
|
|
|
18
29
|
import {
|
|
19
30
|
Popover,
|
|
31
|
+
PopoverAnchor,
|
|
20
32
|
PopoverContent,
|
|
21
33
|
PopoverTrigger,
|
|
22
34
|
} from "../components/ui/popover.js";
|
|
@@ -57,13 +69,39 @@ export interface RealtimeVoiceModeCopy {
|
|
|
57
69
|
showChat: string;
|
|
58
70
|
hideChat: string;
|
|
59
71
|
endVoiceMode: string;
|
|
60
|
-
|
|
72
|
+
voiceSettings: string;
|
|
73
|
+
settings: {
|
|
74
|
+
microphone: string;
|
|
75
|
+
defaultMicrophone: string;
|
|
76
|
+
microphoneSwitchFailed: string;
|
|
77
|
+
language: string;
|
|
78
|
+
autoLanguage: string;
|
|
79
|
+
languages: Record<
|
|
80
|
+
Exclude<
|
|
81
|
+
import("./useRealtimeVoiceMode.js").RealtimeVoiceLanguage,
|
|
82
|
+
"auto"
|
|
83
|
+
>,
|
|
84
|
+
string
|
|
85
|
+
>;
|
|
86
|
+
intelligence: string;
|
|
87
|
+
intelligenceLevels: Record<
|
|
88
|
+
import("./useRealtimeVoiceMode.js").RealtimeVoiceIntelligence,
|
|
89
|
+
string
|
|
90
|
+
>;
|
|
91
|
+
voiceStyle: string;
|
|
92
|
+
voiceChangePending: string;
|
|
93
|
+
voiceDescriptions: Record<
|
|
94
|
+
import("./useRealtimeVoiceMode.js").RealtimeVoice,
|
|
95
|
+
string
|
|
96
|
+
>;
|
|
97
|
+
};
|
|
61
98
|
status: Record<RealtimeVoiceModeState, string>;
|
|
62
99
|
errors: {
|
|
63
100
|
unsupported: string;
|
|
64
101
|
responseFailed: string;
|
|
65
102
|
sessionFailed: string;
|
|
66
103
|
channelDisconnected: string;
|
|
104
|
+
connectionTimedOut?: string;
|
|
67
105
|
connectionFailed: string;
|
|
68
106
|
offerFailed: string;
|
|
69
107
|
};
|
|
@@ -72,6 +110,7 @@ export interface RealtimeVoiceModeCopy {
|
|
|
72
110
|
export interface RealtimeVoiceModeEntryProps {
|
|
73
111
|
copy: RealtimeVoiceModeCopy;
|
|
74
112
|
disabled?: boolean;
|
|
113
|
+
providerStatusPending?: boolean;
|
|
75
114
|
open?: boolean;
|
|
76
115
|
onOpenChange?: (open: boolean) => void;
|
|
77
116
|
onStartVoiceMode: () => void;
|
|
@@ -92,6 +131,7 @@ export interface RealtimeVoiceModeEntryProps {
|
|
|
92
131
|
export function RealtimeVoiceModeEntry({
|
|
93
132
|
copy,
|
|
94
133
|
disabled,
|
|
134
|
+
providerStatusPending = false,
|
|
95
135
|
open: controlledOpen,
|
|
96
136
|
onOpenChange,
|
|
97
137
|
onStartVoiceMode,
|
|
@@ -107,6 +147,16 @@ export function RealtimeVoiceModeEntry({
|
|
|
107
147
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
108
148
|
const titleId = useId();
|
|
109
149
|
const descriptionId = useId();
|
|
150
|
+
const [collisionBoundary, setCollisionBoundary] =
|
|
151
|
+
useState<HTMLElement | null>(null);
|
|
152
|
+
|
|
153
|
+
const setTriggerNode = useCallback((node: HTMLButtonElement | null) => {
|
|
154
|
+
const nextBoundary =
|
|
155
|
+
node?.closest<HTMLElement>(".agent-panel-root") ?? null;
|
|
156
|
+
setCollisionBoundary((currentBoundary) =>
|
|
157
|
+
currentBoundary === nextBoundary ? currentBoundary : nextBoundary,
|
|
158
|
+
);
|
|
159
|
+
}, []);
|
|
110
160
|
|
|
111
161
|
const setOpen = (nextOpen: boolean) => {
|
|
112
162
|
if (controlledOpen === undefined) setUncontrolledOpen(nextOpen);
|
|
@@ -124,6 +174,7 @@ export function RealtimeVoiceModeEntry({
|
|
|
124
174
|
<TooltipTrigger asChild>
|
|
125
175
|
<PopoverTrigger asChild>
|
|
126
176
|
<Button
|
|
177
|
+
ref={setTriggerNode}
|
|
127
178
|
type="button"
|
|
128
179
|
variant="ghost"
|
|
129
180
|
size="icon"
|
|
@@ -146,11 +197,14 @@ export function RealtimeVoiceModeEntry({
|
|
|
146
197
|
side="top"
|
|
147
198
|
align="end"
|
|
148
199
|
sideOffset={10}
|
|
200
|
+
collisionBoundary={collisionBoundary ?? undefined}
|
|
201
|
+
collisionPadding={16}
|
|
202
|
+
data-collision-boundary={collisionBoundary ? "agent-panel" : "viewport"}
|
|
149
203
|
className={cn(
|
|
150
204
|
"p-4",
|
|
151
205
|
setupRequired
|
|
152
|
-
? "w-[min(
|
|
153
|
-
: "w-[min(
|
|
206
|
+
? "w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,30rem),30rem)]"
|
|
207
|
+
: "w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,22rem),22rem)]",
|
|
154
208
|
)}
|
|
155
209
|
aria-labelledby={titleId}
|
|
156
210
|
aria-describedby={descriptionId}
|
|
@@ -168,7 +222,7 @@ export function RealtimeVoiceModeEntry({
|
|
|
168
222
|
</p>
|
|
169
223
|
</div>
|
|
170
224
|
|
|
171
|
-
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:flex-
|
|
225
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:flex-wrap sm:justify-end">
|
|
172
226
|
<Button
|
|
173
227
|
type="button"
|
|
174
228
|
variant="ghost"
|
|
@@ -206,9 +260,14 @@ export function RealtimeVoiceModeEntry({
|
|
|
206
260
|
<Button
|
|
207
261
|
type="button"
|
|
208
262
|
size="sm"
|
|
263
|
+
disabled={providerStatusPending}
|
|
209
264
|
onClick={() => choose(onStartVoiceMode)}
|
|
210
265
|
>
|
|
211
|
-
|
|
266
|
+
{providerStatusPending ? (
|
|
267
|
+
<IconLoader2 className="animate-spin" />
|
|
268
|
+
) : (
|
|
269
|
+
<IconMicrophone />
|
|
270
|
+
)}
|
|
212
271
|
{copy.startVoiceMode}
|
|
213
272
|
</Button>
|
|
214
273
|
)}
|
|
@@ -226,11 +285,35 @@ export interface RealtimeVoiceModeDockProps {
|
|
|
226
285
|
audioLevels?: RealtimeVoiceAudioLevelStore;
|
|
227
286
|
onToggleChat: () => void;
|
|
228
287
|
onEndVoiceMode: () => void;
|
|
229
|
-
|
|
288
|
+
settings?: RealtimeVoiceModeInlineSettings;
|
|
230
289
|
errorMessage?: string | null;
|
|
231
290
|
className?: string;
|
|
232
291
|
}
|
|
233
292
|
|
|
293
|
+
export interface RealtimeVoiceModeSettingOption {
|
|
294
|
+
value: string;
|
|
295
|
+
label: string;
|
|
296
|
+
description?: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface RealtimeVoiceModeSelectSetting {
|
|
300
|
+
label: string;
|
|
301
|
+
value: string;
|
|
302
|
+
options: readonly RealtimeVoiceModeSettingOption[];
|
|
303
|
+
onValueChange: (value: string) => void;
|
|
304
|
+
disabled?: boolean;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface RealtimeVoiceModeInlineSettings {
|
|
308
|
+
dialogLabel: string;
|
|
309
|
+
appliesNextConversationNote?: string;
|
|
310
|
+
microphoneError?: string;
|
|
311
|
+
microphone: RealtimeVoiceModeSelectSetting;
|
|
312
|
+
language: RealtimeVoiceModeSelectSetting;
|
|
313
|
+
intelligence: RealtimeVoiceModeSelectSetting;
|
|
314
|
+
voiceStyle: RealtimeVoiceModeSelectSetting;
|
|
315
|
+
}
|
|
316
|
+
|
|
234
317
|
const SILENT_AUDIO_LEVELS = createRealtimeVoiceAudioLevelStore();
|
|
235
318
|
const WAVEFORM_WEIGHTS = [0.55, 0.82, 1, 0.82, 0.55];
|
|
236
319
|
const AUDIO_ACTIVITY_THRESHOLD = 0.035;
|
|
@@ -255,7 +338,7 @@ function VoiceWaveform({
|
|
|
255
338
|
}: {
|
|
256
339
|
level: number;
|
|
257
340
|
reducedMotion: boolean;
|
|
258
|
-
activity: "user" | "assistant";
|
|
341
|
+
activity: "idle" | "user" | "assistant";
|
|
259
342
|
}) {
|
|
260
343
|
const visibleLevel = reducedMotion ? 0.45 : level;
|
|
261
344
|
return (
|
|
@@ -269,45 +352,265 @@ function VoiceWaveform({
|
|
|
269
352
|
<span
|
|
270
353
|
key={index}
|
|
271
354
|
className="h-5 w-0.5 origin-center rounded-full bg-current transition-transform duration-75 ease-out motion-reduce:transition-none"
|
|
272
|
-
style={{
|
|
355
|
+
style={{
|
|
356
|
+
transform: `scaleY(${0.2 + visibleLevel * 0.8 * weight})`,
|
|
357
|
+
}}
|
|
273
358
|
/>
|
|
274
359
|
))}
|
|
275
360
|
</span>
|
|
276
361
|
);
|
|
277
362
|
}
|
|
278
363
|
|
|
364
|
+
function VoiceConnectingIndicator() {
|
|
365
|
+
return (
|
|
366
|
+
<IconLoader2
|
|
367
|
+
aria-hidden="true"
|
|
368
|
+
className="size-6 animate-spin motion-reduce:animate-none"
|
|
369
|
+
data-realtime-voice-connecting-indicator="true"
|
|
370
|
+
/>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
279
374
|
const ORB_STATE_CLASSES: Record<RealtimeVoiceModeState, string> = {
|
|
280
375
|
connecting:
|
|
281
|
-
"bg-
|
|
376
|
+
"bg-background/65 text-foreground ring-border/60 hover:bg-background/75",
|
|
282
377
|
listening:
|
|
283
|
-
"bg-
|
|
378
|
+
"bg-background/65 text-foreground ring-border/60 hover:bg-background/75",
|
|
284
379
|
speaking:
|
|
285
|
-
"bg-
|
|
380
|
+
"bg-background/65 text-foreground ring-border/60 hover:bg-background/75",
|
|
286
381
|
working:
|
|
287
|
-
"bg-
|
|
382
|
+
"bg-background/65 text-foreground ring-border/60 hover:bg-background/75",
|
|
288
383
|
error:
|
|
289
|
-
"bg-destructive text-destructive
|
|
290
|
-
ending: "cursor-wait bg-
|
|
384
|
+
"bg-destructive/20 text-destructive ring-destructive/30 hover:bg-destructive/25",
|
|
385
|
+
ending: "cursor-wait bg-background/65 text-muted-foreground ring-border/60",
|
|
291
386
|
};
|
|
292
387
|
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
388
|
+
function useChatPanelTranslation(chatVisible: boolean): number {
|
|
389
|
+
const [translation, setTranslation] = useState(0);
|
|
390
|
+
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
if (!chatVisible || typeof window === "undefined") {
|
|
393
|
+
setTranslation(0);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const direction = window.getComputedStyle(
|
|
398
|
+
document.documentElement,
|
|
399
|
+
).direction;
|
|
400
|
+
const inlineEnd = direction === "rtl" ? "left" : "right";
|
|
401
|
+
|
|
402
|
+
const update = () => {
|
|
403
|
+
const panels = Array.from(
|
|
404
|
+
document.querySelectorAll<HTMLElement>(
|
|
405
|
+
`.agent-sidebar-panel[data-agent-sidebar-position="${inlineEnd}"][data-agent-sidebar-state="open"]`,
|
|
406
|
+
),
|
|
307
407
|
);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
408
|
+
const panel = panels.find((candidate) => {
|
|
409
|
+
const rect = candidate.getBoundingClientRect();
|
|
410
|
+
return rect.width > 0 && rect.height > 0;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (!panel) {
|
|
414
|
+
setTranslation(0);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// A fullscreen chat has no unobscured side to move into. Keep the dock
|
|
419
|
+
// at its normal edge so it stays reachable instead of pinning it to the
|
|
420
|
+
// opposite side of the same overlay.
|
|
421
|
+
if (panel.dataset.agentSidebarLayout === "fullscreen") {
|
|
422
|
+
setTranslation(0);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const rect = panel.getBoundingClientRect();
|
|
427
|
+
const overlap =
|
|
428
|
+
inlineEnd === "right"
|
|
429
|
+
? Math.max(0, window.innerWidth - rect.left)
|
|
430
|
+
: Math.max(0, rect.right);
|
|
431
|
+
const maximumShift = Math.max(0, window.innerWidth - 96);
|
|
432
|
+
const distance = Math.min(overlap, maximumShift);
|
|
433
|
+
setTranslation(inlineEnd === "right" ? -distance : distance);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
update();
|
|
437
|
+
const frame = window.requestAnimationFrame(update);
|
|
438
|
+
window.addEventListener("resize", update);
|
|
439
|
+
|
|
440
|
+
const resizeObserver =
|
|
441
|
+
typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update);
|
|
442
|
+
const observedPanels = new WeakSet<HTMLElement>();
|
|
443
|
+
const observePanels = () => {
|
|
444
|
+
document
|
|
445
|
+
.querySelectorAll<HTMLElement>(".agent-sidebar-panel")
|
|
446
|
+
.forEach((panel) => {
|
|
447
|
+
if (!observedPanels.has(panel)) {
|
|
448
|
+
observedPanels.add(panel);
|
|
449
|
+
resizeObserver?.observe(panel);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
observePanels();
|
|
454
|
+
|
|
455
|
+
const mutationObserver = new MutationObserver(() => {
|
|
456
|
+
observePanels();
|
|
457
|
+
update();
|
|
458
|
+
});
|
|
459
|
+
mutationObserver.observe(document.body, {
|
|
460
|
+
attributes: true,
|
|
461
|
+
attributeFilter: [
|
|
462
|
+
"data-agent-sidebar-layout",
|
|
463
|
+
"data-agent-sidebar-state",
|
|
464
|
+
"style",
|
|
465
|
+
],
|
|
466
|
+
childList: true,
|
|
467
|
+
subtree: true,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return () => {
|
|
471
|
+
window.cancelAnimationFrame(frame);
|
|
472
|
+
window.removeEventListener("resize", update);
|
|
473
|
+
resizeObserver?.disconnect();
|
|
474
|
+
mutationObserver.disconnect();
|
|
475
|
+
};
|
|
476
|
+
}, [chatVisible]);
|
|
477
|
+
|
|
478
|
+
return translation;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function VoiceSettingRow({
|
|
482
|
+
icon: Icon,
|
|
483
|
+
setting,
|
|
484
|
+
onSelectOpenChange,
|
|
485
|
+
}: {
|
|
486
|
+
icon: typeof IconLanguage;
|
|
487
|
+
setting: RealtimeVoiceModeSelectSetting;
|
|
488
|
+
onSelectOpenChange: (open: boolean) => void;
|
|
489
|
+
}) {
|
|
490
|
+
const selected = setting.options.find(
|
|
491
|
+
(option) => option.value === setting.value,
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<Select
|
|
496
|
+
value={setting.value}
|
|
497
|
+
onValueChange={setting.onValueChange}
|
|
498
|
+
onOpenChange={onSelectOpenChange}
|
|
499
|
+
disabled={setting.disabled}
|
|
500
|
+
>
|
|
501
|
+
<SelectTrigger
|
|
502
|
+
aria-label={`${setting.label}: ${selected?.label ?? setting.value}`}
|
|
503
|
+
className="h-11 rounded-none border-0 bg-transparent px-3 shadow-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
|
|
504
|
+
>
|
|
505
|
+
<div className="flex min-w-0 flex-1 items-center gap-2.5">
|
|
506
|
+
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
|
507
|
+
<span className="shrink-0 text-sm font-medium text-foreground">
|
|
508
|
+
{setting.label}
|
|
509
|
+
</span>
|
|
510
|
+
<span className="ms-auto min-w-0 truncate text-end text-sm text-muted-foreground">
|
|
511
|
+
{selected?.label ?? setting.value}
|
|
512
|
+
</span>
|
|
513
|
+
</div>
|
|
514
|
+
</SelectTrigger>
|
|
515
|
+
<SelectContent
|
|
516
|
+
align="end"
|
|
517
|
+
className="min-w-56"
|
|
518
|
+
data-realtime-voice-setting-options="true"
|
|
519
|
+
>
|
|
520
|
+
<SelectGroup>
|
|
521
|
+
{setting.options.map((option) => (
|
|
522
|
+
<SelectItem key={option.value} value={option.value}>
|
|
523
|
+
<span className="grid gap-0.5">
|
|
524
|
+
<span>{option.label}</span>
|
|
525
|
+
{option.description ? (
|
|
526
|
+
<span className="text-xs text-muted-foreground">
|
|
527
|
+
{option.description}
|
|
528
|
+
</span>
|
|
529
|
+
) : null}
|
|
530
|
+
</span>
|
|
531
|
+
</SelectItem>
|
|
532
|
+
))}
|
|
533
|
+
</SelectGroup>
|
|
534
|
+
</SelectContent>
|
|
535
|
+
</Select>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function VoiceInlineSettings({
|
|
540
|
+
settings,
|
|
541
|
+
disabled,
|
|
542
|
+
onSelectOpenChange,
|
|
543
|
+
}: {
|
|
544
|
+
settings: RealtimeVoiceModeInlineSettings;
|
|
545
|
+
disabled: boolean;
|
|
546
|
+
onSelectOpenChange: (open: boolean) => void;
|
|
547
|
+
}) {
|
|
548
|
+
const selectedVoice = settings.voiceStyle.options.find(
|
|
549
|
+
(option) => option.value === settings.voiceStyle.value,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
<div data-realtime-voice-settings="true">
|
|
554
|
+
<h2 className="sr-only">{settings.dialogLabel}</h2>
|
|
555
|
+
<div className="grid gap-0.5 px-4 pb-3 pt-4 text-center">
|
|
556
|
+
<div className="truncate text-base font-semibold text-foreground">
|
|
557
|
+
{selectedVoice?.label ?? settings.voiceStyle.value}
|
|
558
|
+
</div>
|
|
559
|
+
{selectedVoice?.description ? (
|
|
560
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
561
|
+
{selectedVoice.description}
|
|
562
|
+
</div>
|
|
563
|
+
) : null}
|
|
564
|
+
</div>
|
|
565
|
+
<div className="mx-2 mb-2 overflow-hidden rounded-xl border border-border/70 bg-background/70">
|
|
566
|
+
<VoiceSettingRow
|
|
567
|
+
icon={IconMicrophone}
|
|
568
|
+
setting={{
|
|
569
|
+
...settings.microphone,
|
|
570
|
+
disabled: disabled || settings.microphone.disabled,
|
|
571
|
+
}}
|
|
572
|
+
onSelectOpenChange={onSelectOpenChange}
|
|
573
|
+
/>
|
|
574
|
+
<div className="mx-3 h-px bg-border/70" />
|
|
575
|
+
<VoiceSettingRow
|
|
576
|
+
icon={IconLanguage}
|
|
577
|
+
setting={{
|
|
578
|
+
...settings.language,
|
|
579
|
+
disabled: disabled || settings.language.disabled,
|
|
580
|
+
}}
|
|
581
|
+
onSelectOpenChange={onSelectOpenChange}
|
|
582
|
+
/>
|
|
583
|
+
<div className="mx-3 h-px bg-border/70" />
|
|
584
|
+
<VoiceSettingRow
|
|
585
|
+
icon={IconBrain}
|
|
586
|
+
setting={{
|
|
587
|
+
...settings.intelligence,
|
|
588
|
+
disabled: disabled || settings.intelligence.disabled,
|
|
589
|
+
}}
|
|
590
|
+
onSelectOpenChange={onSelectOpenChange}
|
|
591
|
+
/>
|
|
592
|
+
<div className="mx-3 h-px bg-border/70" />
|
|
593
|
+
<VoiceSettingRow
|
|
594
|
+
icon={IconVolume}
|
|
595
|
+
setting={{
|
|
596
|
+
...settings.voiceStyle,
|
|
597
|
+
disabled: disabled || settings.voiceStyle.disabled,
|
|
598
|
+
}}
|
|
599
|
+
onSelectOpenChange={onSelectOpenChange}
|
|
600
|
+
/>
|
|
601
|
+
</div>
|
|
602
|
+
{settings.microphoneError ? (
|
|
603
|
+
<p className="px-4 pb-3 text-center text-xs text-destructive">
|
|
604
|
+
{settings.microphoneError}
|
|
605
|
+
</p>
|
|
606
|
+
) : null}
|
|
607
|
+
{settings.appliesNextConversationNote ? (
|
|
608
|
+
<p className="px-4 pb-3 text-center text-xs text-muted-foreground">
|
|
609
|
+
{settings.appliesNextConversationNote}
|
|
610
|
+
</p>
|
|
611
|
+
) : null}
|
|
612
|
+
</div>
|
|
613
|
+
);
|
|
311
614
|
}
|
|
312
615
|
|
|
313
616
|
/**
|
|
@@ -321,32 +624,69 @@ export function RealtimeVoiceModeDock({
|
|
|
321
624
|
audioLevels = SILENT_AUDIO_LEVELS,
|
|
322
625
|
onToggleChat,
|
|
323
626
|
onEndVoiceMode,
|
|
324
|
-
|
|
627
|
+
settings,
|
|
325
628
|
errorMessage,
|
|
326
629
|
className,
|
|
327
630
|
}: RealtimeVoiceModeDockProps) {
|
|
328
631
|
const statusId = useId();
|
|
329
632
|
const controlsId = useId();
|
|
330
633
|
const [controlsOpen, setControlsOpen] = useState(false);
|
|
634
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
635
|
+
const selectInteractionRef = useRef(false);
|
|
636
|
+
const selectInteractionFrameRef = useRef<number | null>(null);
|
|
331
637
|
const levels = useSyncExternalStore(
|
|
332
638
|
audioLevels.subscribe,
|
|
333
639
|
audioLevels.getSnapshot,
|
|
334
640
|
audioLevels.getSnapshot,
|
|
335
641
|
);
|
|
336
642
|
const reducedMotion = usePrefersReducedMotion();
|
|
337
|
-
|
|
338
|
-
|
|
643
|
+
// Microphone metering begins while the SDP request is still in flight. Keep
|
|
644
|
+
// the connecting affordance authoritative until WebRTC is established;
|
|
645
|
+
// otherwise speaking into the mic replaces the loader with a waveform and
|
|
646
|
+
// makes a stalled connection look like a live call.
|
|
647
|
+
const connected =
|
|
648
|
+
state === "listening" || state === "speaking" || state === "working";
|
|
649
|
+
const activity = connected
|
|
650
|
+
? levels.output > AUDIO_ACTIVITY_THRESHOLD
|
|
339
651
|
? "assistant"
|
|
340
652
|
: levels.input > AUDIO_ACTIVITY_THRESHOLD
|
|
341
653
|
? "user"
|
|
342
|
-
: "idle"
|
|
654
|
+
: "idle"
|
|
655
|
+
: "idle";
|
|
343
656
|
const activityLevel = activity === "assistant" ? levels.output : levels.input;
|
|
344
657
|
const toggleLabel = chatVisible ? copy.hideChat : copy.showChat;
|
|
345
658
|
const ending = state === "ending";
|
|
346
659
|
const errorDetailVisible = state === "error" && Boolean(errorMessage);
|
|
347
|
-
const
|
|
660
|
+
const controlsVisible = controlsOpen || settingsOpen;
|
|
661
|
+
const chatPanelTranslation = useChatPanelTranslation(chatVisible);
|
|
662
|
+
|
|
663
|
+
const handleSelectOpenChange = useCallback((open: boolean) => {
|
|
664
|
+
if (selectInteractionFrameRef.current !== null) {
|
|
665
|
+
window.cancelAnimationFrame(selectInteractionFrameRef.current);
|
|
666
|
+
selectInteractionFrameRef.current = null;
|
|
667
|
+
}
|
|
668
|
+
selectInteractionRef.current = true;
|
|
669
|
+
if (!open) {
|
|
670
|
+
// Radix closes the portalled Select before its focus/outside events have
|
|
671
|
+
// fully settled. Keep the parent protected through the current frame.
|
|
672
|
+
selectInteractionFrameRef.current = window.requestAnimationFrame(() => {
|
|
673
|
+
selectInteractionRef.current = false;
|
|
674
|
+
selectInteractionFrameRef.current = null;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}, []);
|
|
678
|
+
|
|
679
|
+
useEffect(
|
|
680
|
+
() => () => {
|
|
681
|
+
if (selectInteractionFrameRef.current !== null) {
|
|
682
|
+
window.cancelAnimationFrame(selectInteractionFrameRef.current);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
[],
|
|
686
|
+
);
|
|
348
687
|
|
|
349
688
|
const closeControlsUnlessFocused = (event: MouseEvent<HTMLDivElement>) => {
|
|
689
|
+
if (settingsOpen) return;
|
|
350
690
|
if (!event.currentTarget.contains(document.activeElement)) {
|
|
351
691
|
setControlsOpen(false);
|
|
352
692
|
}
|
|
@@ -355,12 +695,16 @@ export function RealtimeVoiceModeDock({
|
|
|
355
695
|
return (
|
|
356
696
|
<div
|
|
357
697
|
className={cn(
|
|
358
|
-
"pointer-events-none fixed bottom-4 end-4 flex max-w-[calc(100vw-2rem)] flex-col items-end gap-2",
|
|
698
|
+
"pointer-events-none fixed bottom-4 end-4 flex max-w-[calc(100vw-2rem)] flex-col items-end gap-2 transition-transform duration-200 ease-[var(--ease-collapse)] motion-reduce:transition-none",
|
|
359
699
|
className,
|
|
360
700
|
)}
|
|
361
|
-
style={{
|
|
701
|
+
style={{
|
|
702
|
+
zIndex: 270,
|
|
703
|
+
transform: `translateX(${chatPanelTranslation}px)`,
|
|
704
|
+
}}
|
|
362
705
|
data-realtime-voice-state={state}
|
|
363
706
|
data-realtime-voice-activity={activity}
|
|
707
|
+
data-realtime-voice-chat-offset={chatPanelTranslation}
|
|
364
708
|
>
|
|
365
709
|
{errorDetailVisible ? (
|
|
366
710
|
<div
|
|
@@ -386,42 +730,72 @@ export function RealtimeVoiceModeDock({
|
|
|
386
730
|
>
|
|
387
731
|
<div
|
|
388
732
|
id={controlsId}
|
|
389
|
-
data-realtime-voice-controls={
|
|
733
|
+
data-realtime-voice-controls={controlsVisible ? "open" : "closed"}
|
|
390
734
|
className={cn(
|
|
391
|
-
"flex items-center gap-1 rounded-full border border-border/70 bg-background/95 p-1
|
|
392
|
-
|
|
735
|
+
"flex items-center gap-1 rounded-full border border-border/70 bg-background/95 p-1 shadow-lg backdrop-blur-md transition-[transform,opacity] duration-150 ease-out motion-reduce:transition-none",
|
|
736
|
+
controlsVisible
|
|
393
737
|
? "pointer-events-auto translate-x-0 opacity-100"
|
|
394
738
|
: "pointer-events-none opacity-0 ltr:translate-x-2 rtl:-translate-x-2 group-hover:pointer-events-auto group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-x-0 group-focus-within:opacity-100",
|
|
395
739
|
)}
|
|
396
740
|
>
|
|
397
|
-
<
|
|
741
|
+
<span
|
|
398
742
|
id={statusId}
|
|
399
743
|
role={state === "error" && !errorDetailVisible ? "alert" : "status"}
|
|
400
744
|
aria-live={
|
|
401
745
|
state === "error" && !errorDetailVisible ? "assertive" : "polite"
|
|
402
746
|
}
|
|
403
|
-
className="
|
|
747
|
+
className="sr-only"
|
|
404
748
|
>
|
|
405
749
|
{copy.status[state]}
|
|
406
|
-
</
|
|
750
|
+
</span>
|
|
407
751
|
|
|
408
|
-
{
|
|
409
|
-
<
|
|
410
|
-
<
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
752
|
+
{settings ? (
|
|
753
|
+
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
|
|
754
|
+
<Tooltip>
|
|
755
|
+
<TooltipTrigger asChild>
|
|
756
|
+
<PopoverAnchor asChild>
|
|
757
|
+
<Button
|
|
758
|
+
type="button"
|
|
759
|
+
variant="ghost"
|
|
760
|
+
size="icon"
|
|
761
|
+
disabled={ending}
|
|
762
|
+
onClick={() => setSettingsOpen((open) => !open)}
|
|
763
|
+
aria-label={copy.voiceSettings}
|
|
764
|
+
aria-expanded={settingsOpen}
|
|
765
|
+
className="size-8 rounded-full text-muted-foreground transition-transform duration-150 ease-out active:scale-[0.97]"
|
|
766
|
+
>
|
|
767
|
+
<IconSettings />
|
|
768
|
+
</Button>
|
|
769
|
+
</PopoverAnchor>
|
|
770
|
+
</TooltipTrigger>
|
|
771
|
+
<TooltipContent>{copy.voiceSettings}</TooltipContent>
|
|
772
|
+
</Tooltip>
|
|
773
|
+
<PopoverContent
|
|
774
|
+
side="top"
|
|
775
|
+
align="end"
|
|
776
|
+
sideOffset={10}
|
|
777
|
+
className="w-[min(20rem,calc(100vw-2rem))] overflow-hidden p-0"
|
|
778
|
+
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
779
|
+
onInteractOutside={(event) => {
|
|
780
|
+
const target = event.target;
|
|
781
|
+
if (
|
|
782
|
+
selectInteractionRef.current ||
|
|
783
|
+
(target instanceof Element &&
|
|
784
|
+
target.closest(
|
|
785
|
+
'[data-realtime-voice-setting-options="true"]',
|
|
786
|
+
))
|
|
787
|
+
) {
|
|
788
|
+
event.preventDefault();
|
|
789
|
+
}
|
|
790
|
+
}}
|
|
791
|
+
>
|
|
792
|
+
<VoiceInlineSettings
|
|
793
|
+
settings={settings}
|
|
415
794
|
disabled={ending}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
<IconSettings />
|
|
421
|
-
</Button>
|
|
422
|
-
</TooltipTrigger>
|
|
423
|
-
<TooltipContent>{copy.microphoneSettings}</TooltipContent>
|
|
424
|
-
</Tooltip>
|
|
795
|
+
onSelectOpenChange={handleSelectOpenChange}
|
|
796
|
+
/>
|
|
797
|
+
</PopoverContent>
|
|
798
|
+
</Popover>
|
|
425
799
|
) : null}
|
|
426
800
|
|
|
427
801
|
<Tooltip>
|
|
@@ -454,43 +828,26 @@ export function RealtimeVoiceModeDock({
|
|
|
454
828
|
}}
|
|
455
829
|
aria-label={toggleLabel}
|
|
456
830
|
aria-pressed={chatVisible}
|
|
831
|
+
data-realtime-voice-chat-toggle="orb"
|
|
457
832
|
aria-describedby={statusId}
|
|
458
833
|
aria-controls={controlsId}
|
|
459
|
-
aria-expanded={
|
|
834
|
+
aria-expanded={controlsVisible}
|
|
460
835
|
className={cn(
|
|
461
|
-
"relative isolate size-16 overflow-visible rounded-full ring-
|
|
836
|
+
"relative isolate size-16 overflow-visible rounded-full ring-1 backdrop-blur-xl transition-transform duration-150 ease-out focus-visible:ring-offset-2 active:scale-[0.97] motion-reduce:transition-none",
|
|
462
837
|
ORB_STATE_CLASSES[state],
|
|
463
838
|
)}
|
|
464
839
|
>
|
|
465
|
-
<span
|
|
466
|
-
aria-hidden="true"
|
|
467
|
-
className={cn(
|
|
468
|
-
"pointer-events-none absolute -inset-2 -z-10 rounded-full bg-current opacity-0 blur-md transition-[transform,opacity] duration-150 ease-out motion-reduce:transition-none",
|
|
469
|
-
activity !== "idle" && "opacity-20",
|
|
470
|
-
)}
|
|
471
|
-
style={{ transform: `scale(${orbScale})` }}
|
|
472
|
-
/>
|
|
473
|
-
<span
|
|
474
|
-
aria-hidden="true"
|
|
475
|
-
className="pointer-events-none absolute inset-1 overflow-hidden rounded-full bg-gradient-to-br from-background/70 via-background/15 to-transparent opacity-70"
|
|
476
|
-
>
|
|
477
|
-
<span className="absolute start-2.5 top-2 size-3 rounded-full bg-background/70 blur-[1px]" />
|
|
478
|
-
<span
|
|
479
|
-
className="absolute -bottom-2 -end-1 h-8 w-10 rounded-full bg-background/25 blur-md transition-transform duration-150 ease-out motion-reduce:transition-none"
|
|
480
|
-
style={{
|
|
481
|
-
transform: `scale(${reducedMotion ? 1 : 1 + activityLevel * 0.25})`,
|
|
482
|
-
}}
|
|
483
|
-
/>
|
|
484
|
-
</span>
|
|
485
840
|
<span className="relative z-10 flex items-center justify-center">
|
|
486
|
-
{
|
|
487
|
-
<
|
|
488
|
-
) : (
|
|
841
|
+
{state === "connecting" ? (
|
|
842
|
+
<VoiceConnectingIndicator />
|
|
843
|
+
) : state !== "error" ? (
|
|
489
844
|
<VoiceWaveform
|
|
490
845
|
level={activityLevel}
|
|
491
846
|
reducedMotion={reducedMotion}
|
|
492
847
|
activity={activity}
|
|
493
848
|
/>
|
|
849
|
+
) : (
|
|
850
|
+
<IconAlertTriangle />
|
|
494
851
|
)}
|
|
495
852
|
</span>
|
|
496
853
|
</Button>
|