@agent-native/core 0.7.53 → 0.7.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +46 -8
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/cli/templates-meta.js +1 -1
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/composer/useVoiceDictation.d.ts +5 -3
- package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
- package/dist/client/composer/useVoiceDictation.js +37 -19
- package/dist/client/composer/useVoiceDictation.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +7 -5
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.d.ts +4 -2
- package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +135 -56
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/server/voice-providers-status.d.ts +7 -0
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +3 -1
- package/dist/server/voice-providers-status.js.map +1 -1
- package/docs/content/sharing.md +155 -0
- package/docs/content/template-clips.md +8 -5
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* <VoiceTranscriptionSection /> —
|
|
3
|
+
* <VoiceTranscriptionSection /> — source + cleanup settings for voice input.
|
|
4
4
|
*
|
|
5
5
|
* Writes the selection to application_state under `voice-transcription-prefs`
|
|
6
|
-
* so the composer's `useVoiceDictation` hook picks it up on next record.
|
|
6
|
+
* so the composer's `useVoiceDictation` hook picks it up on next record. The
|
|
7
|
+
* legacy `provider` field is still written alongside `transcriptionMode` so
|
|
8
|
+
* older clients continue to normalize safely.
|
|
7
9
|
*
|
|
8
10
|
* Provider status comes from `/_agent-native/voice-providers/status`, which
|
|
9
11
|
* mirrors the server transcription route's key/env resolution.
|
|
@@ -16,22 +18,54 @@ const PREFS_URL = agentNativePath("/_agent-native/application-state/voice-transc
|
|
|
16
18
|
const CLEANUP_PREFS_URL = agentNativePath("/_agent-native/application-state/voice-cleanup-prefs");
|
|
17
19
|
const SECRETS_URL = agentNativePath("/_agent-native/secrets");
|
|
18
20
|
const PROVIDER_STATUS_URL = agentNativePath("/_agent-native/voice-providers/status");
|
|
19
|
-
const
|
|
21
|
+
const DEFAULT_TRANSCRIPTION_MODE = "batch";
|
|
22
|
+
const DEFAULT_BATCH_PROVIDER = "auto";
|
|
23
|
+
const GOOGLE_REALTIME_STREAMING_ENABLED = false;
|
|
20
24
|
function isProvider(value) {
|
|
21
|
-
return (value === "
|
|
25
|
+
return (value === "auto" ||
|
|
26
|
+
value === "openai" ||
|
|
22
27
|
value === "builder-gemini" ||
|
|
23
28
|
value === "builder" ||
|
|
24
29
|
value === "browser" ||
|
|
25
30
|
value === "gemini" ||
|
|
26
31
|
value === "groq");
|
|
27
32
|
}
|
|
33
|
+
function isTranscriptionMode(value) {
|
|
34
|
+
return (value === "mac-native" || value === "google-realtime" || value === "batch");
|
|
35
|
+
}
|
|
36
|
+
function normalizeProvider(value) {
|
|
37
|
+
if (!isProvider(value))
|
|
38
|
+
return null;
|
|
39
|
+
return value === "builder" ? "builder-gemini" : value;
|
|
40
|
+
}
|
|
41
|
+
function legacyModeFromProvider(provider) {
|
|
42
|
+
if (provider === "browser")
|
|
43
|
+
return "mac-native";
|
|
44
|
+
return "batch";
|
|
45
|
+
}
|
|
46
|
+
function providerForMode(mode, currentProvider) {
|
|
47
|
+
if (mode === "mac-native")
|
|
48
|
+
return "browser";
|
|
49
|
+
if (mode === "google-realtime")
|
|
50
|
+
return "auto";
|
|
51
|
+
if (!currentProvider || currentProvider === "browser") {
|
|
52
|
+
return DEFAULT_BATCH_PROVIDER;
|
|
53
|
+
}
|
|
54
|
+
return currentProvider;
|
|
55
|
+
}
|
|
56
|
+
function batchProvider(provider) {
|
|
57
|
+
if (!provider || provider === "browser")
|
|
58
|
+
return DEFAULT_BATCH_PROVIDER;
|
|
59
|
+
return provider;
|
|
60
|
+
}
|
|
28
61
|
export function VoiceTranscriptionSection() {
|
|
29
|
-
const [
|
|
62
|
+
const [transcriptionMode, setTranscriptionMode] = useState(null);
|
|
63
|
+
const [provider, setProvider] = useState(DEFAULT_BATCH_PROVIDER);
|
|
30
64
|
const [instructions, setInstructions] = useState("");
|
|
31
|
-
const [hasStoredProvider, setHasStoredProvider] = useState(false);
|
|
32
65
|
const [openAiConfigured, setOpenAiConfigured] = useState(null);
|
|
33
66
|
const [geminiConfigured, setGeminiConfigured] = useState(null);
|
|
34
67
|
const [groqConfigured, setGroqConfigured] = useState(null);
|
|
68
|
+
const [googleRealtimeConfigured, setGoogleRealtimeConfigured] = useState(null);
|
|
35
69
|
const [saving, setSaving] = useState(false);
|
|
36
70
|
const [saveError, setSaveError] = useState(null);
|
|
37
71
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
@@ -64,14 +98,22 @@ export function VoiceTranscriptionSection() {
|
|
|
64
98
|
setCleanupEnabled(!!builderStatus.configured);
|
|
65
99
|
}
|
|
66
100
|
}, [builderStatus?.configured, cleanupEnabled]);
|
|
67
|
-
const toggleCleanup = (next) => {
|
|
101
|
+
const toggleCleanup = async (next) => {
|
|
68
102
|
const previous = cleanupEnabled;
|
|
69
103
|
setCleanupEnabled(next);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(CLEANUP_PREFS_URL, {
|
|
106
|
+
method: "PUT",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({ enabled: next }),
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
throw new Error(`HTTP ${res.status}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
setCleanupEnabled(previous);
|
|
116
|
+
}
|
|
75
117
|
};
|
|
76
118
|
useEffect(() => {
|
|
77
119
|
let cancelled = false;
|
|
@@ -80,31 +122,32 @@ export function VoiceTranscriptionSection() {
|
|
|
80
122
|
.then((body) => {
|
|
81
123
|
if (cancelled)
|
|
82
124
|
return;
|
|
83
|
-
const
|
|
84
|
-
|
|
125
|
+
const value = body?.value ?? body;
|
|
126
|
+
const p = normalizeProvider(body?.provider ??
|
|
127
|
+
body?.value?.provider);
|
|
128
|
+
const storedMode = isTranscriptionMode(value?.transcriptionMode)
|
|
129
|
+
? value.transcriptionMode
|
|
130
|
+
: null;
|
|
131
|
+
const mode = storedMode ??
|
|
132
|
+
(p ? legacyModeFromProvider(p) : DEFAULT_TRANSCRIPTION_MODE);
|
|
85
133
|
const savedInstructions = body?.instructions ??
|
|
86
134
|
body?.value?.instructions;
|
|
87
|
-
|
|
88
|
-
setProvider(
|
|
89
|
-
? p === "builder"
|
|
90
|
-
? "builder-gemini"
|
|
91
|
-
: p
|
|
92
|
-
: DEFAULT_PROVIDER);
|
|
135
|
+
setTranscriptionMode(mode);
|
|
136
|
+
setProvider(providerForMode(mode, p));
|
|
93
137
|
if (typeof savedInstructions === "string") {
|
|
94
138
|
setInstructions(savedInstructions);
|
|
95
139
|
}
|
|
96
140
|
})
|
|
97
|
-
.catch(() =>
|
|
141
|
+
.catch(() => {
|
|
142
|
+
if (!cancelled) {
|
|
143
|
+
setTranscriptionMode(DEFAULT_TRANSCRIPTION_MODE);
|
|
144
|
+
setProvider(DEFAULT_BATCH_PROVIDER);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
98
147
|
return () => {
|
|
99
148
|
cancelled = true;
|
|
100
149
|
};
|
|
101
150
|
}, []);
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (hasStoredProvider || provider === null)
|
|
104
|
-
return;
|
|
105
|
-
if (builderStatus?.configured)
|
|
106
|
-
setProvider("builder-gemini");
|
|
107
|
-
}, [builderStatus?.configured, hasStoredProvider, provider]);
|
|
108
151
|
useEffect(() => {
|
|
109
152
|
let cancelled = false;
|
|
110
153
|
fetch(PROVIDER_STATUS_URL)
|
|
@@ -116,6 +159,7 @@ export function VoiceTranscriptionSection() {
|
|
|
116
159
|
setOpenAiConfigured(status.openai);
|
|
117
160
|
setGeminiConfigured(status.gemini);
|
|
118
161
|
setGroqConfigured(status.groq);
|
|
162
|
+
setGoogleRealtimeConfigured(!!status.googleRealtime);
|
|
119
163
|
return;
|
|
120
164
|
}
|
|
121
165
|
return fetch(SECRETS_URL)
|
|
@@ -127,6 +171,7 @@ export function VoiceTranscriptionSection() {
|
|
|
127
171
|
setOpenAiConfigured(find("OPENAI_API_KEY")?.status === "set");
|
|
128
172
|
setGeminiConfigured(find("GEMINI_API_KEY")?.status === "set");
|
|
129
173
|
setGroqConfigured(find("GROQ_API_KEY")?.status === "set");
|
|
174
|
+
setGoogleRealtimeConfigured(find("GOOGLE_APPLICATION_CREDENTIALS")?.status === "set");
|
|
130
175
|
});
|
|
131
176
|
})
|
|
132
177
|
.catch(() => {
|
|
@@ -134,13 +179,14 @@ export function VoiceTranscriptionSection() {
|
|
|
134
179
|
setOpenAiConfigured(false);
|
|
135
180
|
setGeminiConfigured(false);
|
|
136
181
|
setGroqConfigured(false);
|
|
182
|
+
setGoogleRealtimeConfigured(false);
|
|
137
183
|
}
|
|
138
184
|
});
|
|
139
185
|
return () => {
|
|
140
186
|
cancelled = true;
|
|
141
187
|
};
|
|
142
188
|
}, []);
|
|
143
|
-
const persist = useCallback(async (nextProvider, nextInstructions, previous) => {
|
|
189
|
+
const persist = useCallback(async (nextMode, nextProvider, nextInstructions, previous) => {
|
|
144
190
|
setSaving(true);
|
|
145
191
|
setSaveError(null);
|
|
146
192
|
try {
|
|
@@ -148,6 +194,7 @@ export function VoiceTranscriptionSection() {
|
|
|
148
194
|
method: "PUT",
|
|
149
195
|
headers: { "Content-Type": "application/json" },
|
|
150
196
|
body: JSON.stringify({
|
|
197
|
+
transcriptionMode: nextMode,
|
|
151
198
|
provider: nextProvider,
|
|
152
199
|
instructions: nextInstructions.trim(),
|
|
153
200
|
}),
|
|
@@ -158,6 +205,7 @@ export function VoiceTranscriptionSection() {
|
|
|
158
205
|
}
|
|
159
206
|
catch (err) {
|
|
160
207
|
// Revert the optimistic update so the UI matches server state.
|
|
208
|
+
setTranscriptionMode(previous.transcriptionMode);
|
|
161
209
|
setProvider(previous.provider);
|
|
162
210
|
setInstructions(previous.instructions);
|
|
163
211
|
setSaveError(`Couldn't save: ${err?.message ?? "network error"}. Try again.`);
|
|
@@ -166,46 +214,77 @@ export function VoiceTranscriptionSection() {
|
|
|
166
214
|
setSaving(false);
|
|
167
215
|
}
|
|
168
216
|
}, []);
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
217
|
+
const focusKey = (key) => {
|
|
218
|
+
if (typeof window === "undefined")
|
|
219
|
+
return;
|
|
220
|
+
window.location.hash = `#secrets:${key}`;
|
|
221
|
+
};
|
|
222
|
+
const chooseSource = (next) => {
|
|
223
|
+
if (next === transcriptionMode)
|
|
171
224
|
return;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
225
|
+
if (next === "google-realtime" &&
|
|
226
|
+
(!googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED)) {
|
|
227
|
+
setShowAdvanced(true);
|
|
228
|
+
focusKey("GOOGLE_APPLICATION_CREDENTIALS");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const previous = { transcriptionMode, provider, instructions };
|
|
232
|
+
const nextProvider = providerForMode(next, provider);
|
|
233
|
+
setTranscriptionMode(next);
|
|
234
|
+
setProvider(nextProvider);
|
|
235
|
+
void persist(next, nextProvider, instructions, previous);
|
|
236
|
+
};
|
|
237
|
+
const chooseBatchProvider = (next) => {
|
|
238
|
+
const nextProvider = batchProvider(normalizeProvider(next));
|
|
239
|
+
if (transcriptionMode === "batch" && nextProvider === provider)
|
|
240
|
+
return;
|
|
241
|
+
const previous = { transcriptionMode, provider, instructions };
|
|
242
|
+
setTranscriptionMode("batch");
|
|
243
|
+
setProvider(nextProvider);
|
|
244
|
+
void persist("batch", nextProvider, instructions, previous);
|
|
176
245
|
};
|
|
177
246
|
const updateInstructions = (next) => {
|
|
178
|
-
const previous = { provider, instructions };
|
|
247
|
+
const previous = { transcriptionMode, provider, instructions };
|
|
179
248
|
setInstructions(next);
|
|
180
|
-
if (
|
|
181
|
-
void persist(provider, next, previous);
|
|
249
|
+
if (transcriptionMode) {
|
|
250
|
+
void persist(transcriptionMode, provider, next, previous);
|
|
251
|
+
}
|
|
182
252
|
};
|
|
183
|
-
if (
|
|
253
|
+
if (transcriptionMode === null) {
|
|
184
254
|
return (_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(IconLoader2, { size: 10, className: "animate-spin" }), "Loading\u2026"] }));
|
|
185
255
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
window.location.hash = `#secrets:${key}`;
|
|
190
|
-
};
|
|
191
|
-
return (_jsxs("div", { className: "space-y-2", children: [_jsx(ProviderOption, { id: "builder-gemini", selected: provider === "builder-gemini", onSelect: () => choose("builder-gemini"), disabled: !builderStatus?.configured, title: "Builder.io Connect (recommended)", subtitle: builderStatus?.configured
|
|
192
|
-
? "Fast Gemini Flash-Lite transcription and cleanup through Builder.io. No Google key needed."
|
|
193
|
-
: "One-click connect — uses Gemini Flash-Lite without a Google key.", rightSlot: builderStatus?.configured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Connected"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
194
|
-
e.stopPropagation();
|
|
195
|
-
const url = new URL(agentNativePath("/_agent-native/builder/connect"), window.location.origin).href;
|
|
196
|
-
window.open(url, "_blank", "noopener,noreferrer,width=600,height=700");
|
|
197
|
-
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Connect Builder.io", _jsx(IconExternalLink, { size: 10 })] })) }), _jsxs("div", { className: "flex items-start justify-between gap-3 rounded-md border border-border bg-accent/30 px-2.5 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[11px] font-medium text-foreground", children: "Cleanup transcripts with AI" }), _jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Polish punctuation, casing, and remove filler words after each recording." })] }), _jsx("button", { type: "button", role: "switch", "aria-checked": !!cleanupEnabled, onClick: () => toggleCleanup(!cleanupEnabled), className: `relative inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full transition-colors ${cleanupEnabled
|
|
198
|
-
? "bg-[#625DF5]"
|
|
199
|
-
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"}`, children: _jsx("span", { className: `inline-block h-3 w-3 transform rounded-full bg-background transition-transform ${cleanupEnabled ? "translate-x-3.5" : "translate-x-0.5"}` }) })] }), _jsxs("div", { className: "rounded-md border border-border bg-background", children: [_jsxs("button", { type: "button", onClick: () => setShowAdvanced((v) => !v), className: "w-full flex items-center justify-between gap-2 px-2.5 py-2 cursor-pointer", children: [_jsxs("span", { className: "text-[11px] font-medium text-foreground inline-flex items-center gap-1", children: [showAdvanced ? (_jsx(IconChevronDown, { size: 12 })) : (_jsx(IconChevronRight, { size: 12 })), "Add API keys"] }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: "OpenAI \u00B7 Gemini \u00B7 Groq \u00B7 browser" })] }), showAdvanced && (_jsxs("div", { className: "px-2 pb-2 space-y-2", children: [_jsx(ProviderOption, { id: "openai", selected: provider === "openai", onSelect: () => choose("openai"), title: "OpenAI Whisper", subtitle: "Best quality. Requires an OpenAI API key.", rightSlot: openAiConfigured === null ? null : openAiConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Key set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
256
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "rounded-md border border-border bg-background p-2", children: [_jsx("div", { className: "mb-2 flex items-start justify-between gap-3 px-0.5", children: _jsxs("div", { children: [_jsx("div", { className: "text-[11px] font-medium text-foreground", children: "Live transcription" }), _jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Choose where real-time words come from. Batch still runs after recording stops." })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsx(ProviderOption, { id: "mac-native", selected: transcriptionMode === "mac-native", onSelect: () => chooseSource("mac-native"), title: "Mac Native", subtitle: "Free and fast in the macOS Tauri app. Web clients use the existing browser-native path when available.", rightSlot: _jsx("span", { className: "text-[10px] text-muted-foreground", children: "Tauri default" }) }), _jsx(ProviderOption, { id: "google-realtime", selected: transcriptionMode === "google-realtime", onSelect: () => chooseSource("google-realtime"), disabled: !googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED, title: "Google Realtime (coming soon)", subtitle: googleRealtimeConfigured
|
|
257
|
+
? "Credential detected. The streaming WebSocket path is not enabled yet, so keep using Mac Native or Batch."
|
|
258
|
+
: "BYOK only for v1. Configure Google service account before selecting this source.", rightSlot: googleRealtimeConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Credential set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
200
259
|
e.stopPropagation();
|
|
201
|
-
|
|
202
|
-
|
|
260
|
+
setShowAdvanced(true);
|
|
261
|
+
focusKey("GOOGLE_APPLICATION_CREDENTIALS");
|
|
262
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:bg-accent/40 hover:text-foreground", children: ["Configure", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "batch", selected: transcriptionMode === "batch", onSelect: () => chooseSource("batch"), title: "Batch", subtitle: "Universal fallback. Sends audio after recording stops through Builder Gemini, Gemini, Groq, then OpenAI." })] })] }), _jsxs("div", { className: "flex items-start justify-between gap-3 rounded-md border border-border bg-accent/30 px-2.5 py-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-[11px] font-medium text-foreground", children: "AI cleanup" }), _jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Polish punctuation, casing, filler words, titles, and summaries after capture. Builder Gemini is tried first; BYOK Gemini is the fallback." })] }), _jsxs("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [_jsx("button", { type: "button", role: "switch", "aria-checked": !!cleanupEnabled, onClick: () => toggleCleanup(!cleanupEnabled), className: `relative inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full transition-colors ${cleanupEnabled
|
|
263
|
+
? "bg-[#625DF5]"
|
|
264
|
+
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"}`, children: _jsx("span", { className: `inline-block h-3 w-3 transform rounded-full bg-background transition-transform ${cleanupEnabled ? "translate-x-3.5" : "translate-x-0.5"}` }) }), cleanupEnabled && (_jsx("span", { className: "text-[10px] text-muted-foreground", children: builderStatus?.configured
|
|
265
|
+
? "Builder ready"
|
|
266
|
+
: geminiConfigured
|
|
267
|
+
? "Gemini key set"
|
|
268
|
+
: "Needs key" }))] })] }), _jsxs("div", { className: "rounded-md border border-border bg-background", children: [_jsxs("button", { type: "button", onClick: () => setShowAdvanced((v) => !v), className: "w-full flex items-center justify-between gap-2 px-2.5 py-2 cursor-pointer", children: [_jsxs("span", { className: "text-[11px] font-medium text-foreground inline-flex items-center gap-1", children: [showAdvanced ? (_jsx(IconChevronDown, { size: 12 })) : (_jsx(IconChevronRight, { size: 12 })), "Add API keys"] }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: "Google STT \u00B7 Gemini \u00B7 Groq \u00B7 OpenAI" })] }), showAdvanced && (_jsxs("div", { className: "px-2 pb-2 space-y-2", children: [_jsx(ProviderOption, { id: "google-service-account", selected: transcriptionMode === "google-realtime", onSelect: () => chooseSource("google-realtime"), disabled: !googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED, title: "Google Speech-to-Text service account", subtitle: "Service-account JSON for the future WebSocket to Google StreamingRecognize path. Saved credentials are detected now; streaming is still coming soon.", rightSlot: googleRealtimeConfigured ===
|
|
269
|
+
null ? null : googleRealtimeConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Credential set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
270
|
+
e.stopPropagation();
|
|
271
|
+
focusKey("GOOGLE_APPLICATION_CREDENTIALS");
|
|
272
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Configure", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "auto", selected: transcriptionMode === "batch" && provider === "auto", onSelect: () => chooseBatchProvider("auto"), title: "Automatic batch fallback", subtitle: "Keep the current Clips fallback chain: Builder Gemini, Gemini, Groq, then OpenAI." }), _jsx(ProviderOption, { id: "builder-gemini", selected: transcriptionMode === "batch" && provider === "builder-gemini", onSelect: () => chooseBatchProvider("builder-gemini"), disabled: !builderStatus?.configured, title: "Builder.io Connect", subtitle: builderStatus?.configured
|
|
273
|
+
? "Use Builder-hosted Gemini Flash-Lite for batch transcription and cleanup."
|
|
274
|
+
: "One-click connect for Gemini Flash-Lite cleanup and batch transcription. No Google key needed.", rightSlot: builderStatus?.configured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Connected"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
275
|
+
e.stopPropagation();
|
|
276
|
+
const url = new URL(agentNativePath("/_agent-native/builder/connect"), window.location.origin).href;
|
|
277
|
+
window.open(url, "_blank", "noopener,noreferrer,width=600,height=700");
|
|
278
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Connect Builder.io", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "gemini", selected: transcriptionMode === "batch" && provider === "gemini", onSelect: () => chooseBatchProvider("gemini"), title: "Google Gemini", subtitle: "BYOK Gemini for AI cleanup and optional strict batch transcription.", rightSlot: geminiConfigured === null ? null : geminiConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Key set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
203
279
|
e.stopPropagation();
|
|
204
280
|
focusKey("GEMINI_API_KEY");
|
|
205
|
-
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Add key", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "
|
|
281
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Add key", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "openai", selected: transcriptionMode === "batch" && provider === "openai", onSelect: () => chooseBatchProvider("openai"), title: "OpenAI Whisper", subtitle: "Batch Whisper provider. Requires an OpenAI API key.", rightSlot: openAiConfigured === null ? null : openAiConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Key set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
282
|
+
e.stopPropagation();
|
|
283
|
+
focusKey("OPENAI_API_KEY");
|
|
284
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Add key", _jsx(IconExternalLink, { size: 10 })] })) }), _jsx(ProviderOption, { id: "groq", selected: transcriptionMode === "batch" && provider === "groq", onSelect: () => chooseBatchProvider("groq"), title: "Groq Whisper", subtitle: "Fast Whisper batch provider. Requires a Groq API key.", rightSlot: groqConfigured === null ? null : groqConfigured ? (_jsxs("span", { className: "flex items-center gap-1 text-[10px] text-green-500", children: [_jsx(IconCheck, { size: 10 }), "Key set"] })) : (_jsxs("button", { type: "button", onClick: (e) => {
|
|
206
285
|
e.stopPropagation();
|
|
207
286
|
focusKey("GROQ_API_KEY");
|
|
208
|
-
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Add key", _jsx(IconExternalLink, { size: 10 })] })) })
|
|
287
|
+
}, className: "inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40", children: ["Add key", _jsx(IconExternalLink, { size: 10 })] })) })] }))] }), (cleanupEnabled || transcriptionMode === "batch") && (_jsxs("div", { className: "rounded-md border border-border bg-accent/20 px-2.5 py-2", children: [_jsx("label", { htmlFor: "voice-transcription-instructions", className: "block text-[10px] font-medium text-foreground", children: "Custom instructions" }), _jsx("textarea", { id: "voice-transcription-instructions", value: instructions, onChange: (event) => updateInstructions(event.target.value), placeholder: "Names, casing, punctuation, style, or terms to preserve.", className: "mt-1 min-h-16 w-full resize-y rounded border border-border bg-background px-2 py-1.5 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent" }), _jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Included with batch transcription and AI cleanup." })] })), saving && _jsx("p", { className: "text-[10px] text-muted-foreground", children: "Saving\u2026" }), saveError && !saving && (_jsx("p", { className: "text-[10px] text-red-500", role: "alert", children: saveError }))] }));
|
|
209
288
|
}
|
|
210
289
|
function ProviderOption({ id, selected, disabled, onSelect, title, subtitle, rightSlot, }) {
|
|
211
290
|
const select = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoiceTranscriptionSection.js","sourceRoot":"","sources":["../../../src/client/settings/VoiceTranscriptionSection.tsx"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;AAEH,OAAc,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AA4BzD,MAAM,SAAS,GAAG,eAAe,CAC/B,4DAA4D,CAC7D,CAAC;AACF,MAAM,iBAAiB,GAAG,eAAe,CACvC,sDAAsD,CACvD,CAAC;AACF,MAAM,WAAW,GAAG,eAAe,CAAC,wBAAwB,CAAC,CAAC;AAC9D,MAAM,mBAAmB,GAAG,eAAe,CACzC,uCAAuC,CACxC,CAAC;AACF,MAAM,gBAAgB,GAAa,SAAS,CAAC;AAE7C,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,KAAK,KAAK,QAAQ;QAClB,KAAK,KAAK,gBAAgB;QAC1B,KAAK,KAAK,SAAS;QACnB,KAAK,KAAK,SAAS;QACnB,KAAK,KAAK,QAAQ;QAClB,KAAK,KAAK,MAAM,CACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CACtD,IAAI,CACL,CAAC;IACF,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CACtD,IAAI,CACL,CAAC;IACF,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAC;IAC3E,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAC;IAC3E,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAErD,6DAA6D;IAC7D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,KAAK,CAAC,iBAAiB,CAAC;aACrB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACrC,IAAI,CACH,CACE,IAGQ,EACR,EAAE;YACF,IAAI,SAAS;gBAAE,OAAO;YACtB,MAAM,MAAM,GACT,IAAqC,EAAE,OAAO;gBAC9C,IAAiD,EAAE,KAAK,EAAE,OAAO,CAAC;YACrE,IAAI,OAAO,MAAM,KAAK,SAAS;gBAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;;gBACtD,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,qCAAqC;QACrE,CAAC,CACF;aACA,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,cAAc,KAAK,IAAI;YAAE,OAAO;QACpC,IAAI,aAAa,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;YAC5C,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC;IAEhD,MAAM,aAAa,GAAG,CAAC,IAAa,EAAE,EAAE;QACtC,MAAM,QAAQ,GAAG,cAAc,CAAC;QAChC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACxB,KAAK,CAAC,iBAAiB,EAAE;YACvB,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,KAAK,CAAC,SAAS,CAAC;aACb,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACrC,IAAI,CAAC,CAAC,IAAsC,EAAE,EAAE;YAC/C,IAAI,SAAS;gBAAE,OAAO;YACtB,MAAM,CAAC,GACJ,IAAqB,EAAE,QAAQ;gBAC/B,IAAiC,EAAE,KAAK,EAAE,QAAQ,CAAC;YACtD,MAAM,iBAAiB,GACpB,IAAqB,EAAE,YAAY;gBACnC,IAAiC,EAAE,KAAK,EAAE,YAAY,CAAC;YAC1D,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,WAAW,CACT,UAAU,CAAC,CAAC,CAAC;gBACX,CAAC,CAAC,CAAC,KAAK,SAAS;oBACf,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,gBAAgB,CACrB,CAAC;YACF,IAAI,OAAO,iBAAiB,KAAK,QAAQ,EAAE,CAAC;gBAC1C,eAAe,CAAC,iBAAiB,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC5D,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,iBAAiB,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO;QACnD,IAAI,aAAa,EAAE,UAAU;YAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC/D,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,EAAE,iBAAiB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,KAAK,CAAC,mBAAmB,CAAC;aACvB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACrC,IAAI,CAAC,CAAC,MAA6B,EAAE,EAAE;YACtC,IAAI,SAAS;gBAAE,OAAO;YACtB,IAAI,MAAM,EAAE,CAAC;gBACX,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACnC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACnC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,OAAO,KAAK,CAAC,WAAW,CAAC;iBACtB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;iBACnC,IAAI,CAAC,CAAC,IAAoB,EAAE,EAAE;gBAC7B,IAAI,SAAS;oBAAE,OAAO;gBACtB,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE,CAC3B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC/D,mBAAmB,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC;gBAC9D,mBAAmB,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC;gBAC9D,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC;YAC5D,CAAC,CAAC,CAAC;QACP,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC3B,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC3B,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;QACL,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EACH,YAAsB,EACtB,gBAAwB,EACxB,QAA6D,EAC7D,EAAE;QACF,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBACjC,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,QAAQ,EAAE,YAAY;oBACtB,YAAY,EAAE,gBAAgB,CAAC,IAAI,EAAE;iBACtC,CAAC;aACH,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,+DAA+D;YAC/D,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC/B,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YACvC,YAAY,CACV,kBAAmB,GAAa,EAAE,OAAO,IAAI,eAAe,cAAc,CAC3E,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,IAAc,EAAE,EAAE;QAChC,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC9B,MAAM,QAAQ,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QAC5C,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC3B,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,KAAK,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC,CAAC;IAEF,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QAC5C,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,QAAQ;YAAE,KAAK,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,CACL,eAAK,SAAS,EAAC,6DAA6D,aAC1E,KAAC,WAAW,IAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAC,cAAc,GAAG,qBAE9C,CACP,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAE;QAC/B,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,YAAY,GAAG,EAAE,CAAC;IAC3C,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,KAAC,cAAc,IACb,EAAE,EAAC,gBAAgB,EACnB,QAAQ,EAAE,QAAQ,KAAK,gBAAgB,EACvC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,EACxC,QAAQ,EAAE,CAAC,aAAa,EAAE,UAAU,EACpC,KAAK,EAAC,kCAAkC,EACxC,QAAQ,EACN,aAAa,EAAE,UAAU;oBACvB,CAAC,CAAC,4FAA4F;oBAC9F,CAAC,CAAC,kEAAkE,EAExE,SAAS,EACP,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC,CAC1B,gBAAM,SAAS,EAAC,oDAAoD,aAClE,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,iBAElB,CACR,CAAC,CAAC,CAAC,CACF,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wBACb,CAAC,CAAC,eAAe,EAAE,CAAC;wBACpB,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,eAAe,CAAC,gCAAgC,CAAC,EACjD,MAAM,CAAC,QAAQ,CAAC,MAAM,CACvB,CAAC,IAAI,CAAC;wBACP,MAAM,CAAC,IAAI,CACT,GAAG,EACH,QAAQ,EACR,0CAA0C,CAC3C,CAAC;oBACJ,CAAC,EACD,SAAS,EAAC,oJAAoJ,mCAG9J,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,GAAI,IACvB,CACV,GAEH,EAGF,eAAK,SAAS,EAAC,iGAAiG,aAC9G,eAAK,SAAS,EAAC,SAAS,aACtB,cAAK,SAAS,EAAC,yCAAyC,4CAElD,EACN,YAAG,SAAS,EAAC,0CAA0C,0FAGnD,IACA,EACN,iBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,QAAQ,kBACC,CAAC,CAAC,cAAc,EAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,cAAc,CAAC,EAC7C,SAAS,EAAE,oGACT,cAAc;4BACZ,CAAC,CAAC,cAAc;4BAChB,CAAC,CAAC,qDACN,EAAE,YAEF,eACE,SAAS,EAAE,kFACT,cAAc,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,iBACvC,EAAE,GACF,GACK,IACL,EAGN,eAAK,SAAS,EAAC,+CAA+C,aAC5D,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EACzC,SAAS,EAAC,2EAA2E,aAErF,gBAAM,SAAS,EAAC,wEAAwE,aACrF,YAAY,CAAC,CAAC,CAAC,CACd,KAAC,eAAe,IAAC,IAAI,EAAE,EAAE,GAAI,CAC9B,CAAC,CAAC,CAAC,CACF,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,GAAI,CAC/B,oBAEI,EACP,eAAM,SAAS,EAAC,mCAAmC,gEAE5C,IACA,EAER,YAAY,IAAI,CACf,eAAK,SAAS,EAAC,qBAAqB,aAClC,KAAC,cAAc,IACb,EAAE,EAAC,QAAQ,EACX,QAAQ,EAAE,QAAQ,KAAK,QAAQ,EAC/B,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAChC,KAAK,EAAC,gBAAgB,EACtB,QAAQ,EAAC,2CAA2C,EACpD,SAAS,EACP,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CACpD,gBAAM,SAAS,EAAC,oDAAoD,aAClE,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,eAElB,CACR,CAAC,CAAC,CAAC,CACF,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wCACb,CAAC,CAAC,eAAe,EAAE,CAAC;wCACpB,QAAQ,CAAC,gBAAgB,CAAC,CAAC;oCAC7B,CAAC,EACD,SAAS,EAAC,oJAAoJ,wBAG9J,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,GAAI,IACvB,CACV,GAEH,EAEF,KAAC,cAAc,IACb,EAAE,EAAC,QAAQ,EACX,QAAQ,EAAE,QAAQ,KAAK,QAAQ,EAC/B,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAChC,KAAK,EAAC,eAAe,EACrB,QAAQ,EAAC,sEAAsE,EAC/E,SAAS,EACP,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CACpD,gBAAM,SAAS,EAAC,oDAAoD,aAClE,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,eAElB,CACR,CAAC,CAAC,CAAC,CACF,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wCACb,CAAC,CAAC,eAAe,EAAE,CAAC;wCACpB,QAAQ,CAAC,gBAAgB,CAAC,CAAC;oCAC7B,CAAC,EACD,SAAS,EAAC,oJAAoJ,wBAG9J,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,GAAI,IACvB,CACV,GAEH,EAEF,KAAC,cAAc,IACb,EAAE,EAAC,MAAM,EACT,QAAQ,EAAE,QAAQ,KAAK,MAAM,EAC7B,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAC9B,KAAK,EAAC,cAAc,EACpB,QAAQ,EAAC,qDAAqD,EAC9D,SAAS,EACP,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAChD,gBAAM,SAAS,EAAC,oDAAoD,aAClE,KAAC,SAAS,IAAC,IAAI,EAAE,EAAE,GAAI,eAElB,CACR,CAAC,CAAC,CAAC,CACF,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wCACb,CAAC,CAAC,eAAe,EAAE,CAAC;wCACpB,QAAQ,CAAC,cAAc,CAAC,CAAC;oCAC3B,CAAC,EACD,SAAS,EAAC,oJAAoJ,wBAG9J,KAAC,gBAAgB,IAAC,IAAI,EAAE,EAAE,GAAI,IACvB,CACV,GAEH,EAEF,KAAC,cAAc,IACb,EAAE,EAAC,SAAS,EACZ,QAAQ,EAAE,QAAQ,KAAK,SAAS,EAChC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EACjC,KAAK,EAAC,gBAAgB,EACtB,QAAQ,EAAC,oDAAoD,GAC7D,IACE,CACP,IACG,EAEL,QAAQ,KAAK,SAAS,IAAI,CACzB,eAAK,SAAS,EAAC,0DAA0D,aACvE,gBACE,OAAO,EAAC,kCAAkC,EAC1C,SAAS,EAAC,+CAA+C,oCAGnD,EACR,mBACE,EAAE,EAAC,kCAAkC,EACrC,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAC3D,WAAW,EAAC,0DAA0D,EACtE,SAAS,EAAC,mMAAmM,GAC7M,EACF,YAAG,SAAS,EAAC,wCAAwC,mEAEjD,IACA,CACP,EAEA,MAAM,IAAI,YAAG,SAAS,EAAC,mCAAmC,6BAAY,EACtE,SAAS,IAAI,CAAC,MAAM,IAAI,CACvB,YAAG,SAAS,EAAC,0BAA0B,EAAC,IAAI,EAAC,OAAO,YACjD,SAAS,GACR,CACL,IACG,CACP,CAAC;AACJ,CAAC;AAYD,SAAS,cAAc,CAAC,EACtB,EAAE,EACF,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,SAAS,GACW;IACpB,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,IAAI,CAAC,QAAQ;YAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAA0C,EAAE,EAAE;QAC/D,IAAI,QAAQ;YAAE,OAAO;QACrB,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YAC/C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,eACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,kBACN,QAAQ,mBACP,QAAQ,IAAI,SAAS,EACpC,SAAS,EAAE,yEACT,QAAQ;YACN,CAAC,CAAC,kCAAkC;YACpC,CAAC,CAAC,+CACN,IAAI,QAAQ,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,EAAE,EAAE,aAErD,eACE,SAAS,EAAE,sFACT,QAAQ;oBACN,CAAC,CAAC,+BAA+B;oBACjC,CAAC,CAAC,0CACN,EAAE,YAED,QAAQ,IAAI,CACX,eAAM,SAAS,EAAC,wCAAwC,GAAG,CAC5D,GACI,EACP,eAAK,SAAS,EAAC,gBAAgB,aAC7B,eAAK,SAAS,EAAC,yCAAyC,aACtD,cAAK,SAAS,EAAC,yCAAyC,YAAE,KAAK,GAAO,EACrE,SAAS,IAAI,cAAK,SAAS,EAAC,UAAU,YAAE,SAAS,GAAO,IACrD,EACL,QAAQ,IAAI,CACX,YAAG,SAAS,EAAC,0CAA0C,YAAE,QAAQ,GAAK,CACvE,IACG,IACF,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,KAAC,cAAc,IAAC,IAAI,EAAE,EAAE,GAAI,CAAC;AACtC,CAAC","sourcesContent":["/**\n * <VoiceTranscriptionSection /> — provider picker for composer voice input.\n *\n * Writes the selection to application_state under `voice-transcription-prefs`\n * so the composer's `useVoiceDictation` hook picks it up on next record.\n *\n * Provider status comes from `/_agent-native/voice-providers/status`, which\n * mirrors the server transcription route's key/env resolution.\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { agentNativePath } from \"../api-path.js\";\nimport {\n IconCheck,\n IconChevronDown,\n IconChevronRight,\n IconExternalLink,\n IconLoader2,\n IconMicrophone,\n} from \"@tabler/icons-react\";\nimport { useBuilderStatus } from \"./useBuilderStatus.js\";\n\ntype Provider =\n | \"openai\"\n | \"builder-gemini\"\n | \"builder\"\n | \"browser\"\n | \"gemini\"\n | \"groq\";\n\ninterface Prefs {\n provider: Provider;\n instructions?: string;\n}\n\ninterface SecretStatus {\n key: string;\n status: \"set\" | \"unset\" | \"invalid\";\n}\n\ninterface ProviderStatus {\n builder: boolean;\n gemini: boolean;\n openai: boolean;\n groq: boolean;\n browser: true;\n}\n\nconst PREFS_URL = agentNativePath(\n \"/_agent-native/application-state/voice-transcription-prefs\",\n);\nconst CLEANUP_PREFS_URL = agentNativePath(\n \"/_agent-native/application-state/voice-cleanup-prefs\",\n);\nconst SECRETS_URL = agentNativePath(\"/_agent-native/secrets\");\nconst PROVIDER_STATUS_URL = agentNativePath(\n \"/_agent-native/voice-providers/status\",\n);\nconst DEFAULT_PROVIDER: Provider = \"browser\";\n\nfunction isProvider(value: unknown): value is Provider {\n return (\n value === \"openai\" ||\n value === \"builder-gemini\" ||\n value === \"builder\" ||\n value === \"browser\" ||\n value === \"gemini\" ||\n value === \"groq\"\n );\n}\n\nexport function VoiceTranscriptionSection() {\n const [provider, setProvider] = useState<Provider | null>(null);\n const [instructions, setInstructions] = useState(\"\");\n const [hasStoredProvider, setHasStoredProvider] = useState(false);\n const [openAiConfigured, setOpenAiConfigured] = useState<boolean | null>(\n null,\n );\n const [geminiConfigured, setGeminiConfigured] = useState<boolean | null>(\n null,\n );\n const [groqConfigured, setGroqConfigured] = useState<boolean | null>(null);\n const [saving, setSaving] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [showAdvanced, setShowAdvanced] = useState(false);\n const [cleanupEnabled, setCleanupEnabled] = useState<boolean | null>(null);\n const { status: builderStatus } = useBuilderStatus();\n\n // Read cleanup pref (default: true if Builder is connected).\n useEffect(() => {\n let cancelled = false;\n fetch(CLEANUP_PREFS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then(\n (\n body:\n | { enabled?: boolean }\n | { value?: { enabled?: boolean } }\n | null,\n ) => {\n if (cancelled) return;\n const stored =\n (body as { enabled?: boolean } | null)?.enabled ??\n (body as { value?: { enabled?: boolean } } | null)?.value?.enabled;\n if (typeof stored === \"boolean\") setCleanupEnabled(stored);\n else setCleanupEnabled(null); // resolve once builderStatus arrives\n },\n )\n .catch(() => !cancelled && setCleanupEnabled(null));\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (cleanupEnabled !== null) return;\n if (builderStatus?.configured !== undefined) {\n setCleanupEnabled(!!builderStatus.configured);\n }\n }, [builderStatus?.configured, cleanupEnabled]);\n\n const toggleCleanup = (next: boolean) => {\n const previous = cleanupEnabled;\n setCleanupEnabled(next);\n fetch(CLEANUP_PREFS_URL, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ enabled: next }),\n }).catch(() => setCleanupEnabled(previous));\n };\n\n useEffect(() => {\n let cancelled = false;\n fetch(PREFS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then((body: Prefs | { value?: Prefs } | null) => {\n if (cancelled) return;\n const p =\n (body as Prefs | null)?.provider ??\n (body as { value?: Prefs } | null)?.value?.provider;\n const savedInstructions =\n (body as Prefs | null)?.instructions ??\n (body as { value?: Prefs } | null)?.value?.instructions;\n setHasStoredProvider(isProvider(p));\n setProvider(\n isProvider(p)\n ? p === \"builder\"\n ? \"builder-gemini\"\n : p\n : DEFAULT_PROVIDER,\n );\n if (typeof savedInstructions === \"string\") {\n setInstructions(savedInstructions);\n }\n })\n .catch(() => !cancelled && setProvider(DEFAULT_PROVIDER));\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (hasStoredProvider || provider === null) return;\n if (builderStatus?.configured) setProvider(\"builder-gemini\");\n }, [builderStatus?.configured, hasStoredProvider, provider]);\n\n useEffect(() => {\n let cancelled = false;\n fetch(PROVIDER_STATUS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then((status: ProviderStatus | null) => {\n if (cancelled) return;\n if (status) {\n setOpenAiConfigured(status.openai);\n setGeminiConfigured(status.gemini);\n setGroqConfigured(status.groq);\n return;\n }\n return fetch(SECRETS_URL)\n .then((r) => (r.ok ? r.json() : []))\n .then((list: SecretStatus[]) => {\n if (cancelled) return;\n const find = (key: string) =>\n Array.isArray(list) ? list.find((s) => s.key === key) : null;\n setOpenAiConfigured(find(\"OPENAI_API_KEY\")?.status === \"set\");\n setGeminiConfigured(find(\"GEMINI_API_KEY\")?.status === \"set\");\n setGroqConfigured(find(\"GROQ_API_KEY\")?.status === \"set\");\n });\n })\n .catch(() => {\n if (!cancelled) {\n setOpenAiConfigured(false);\n setGeminiConfigured(false);\n setGroqConfigured(false);\n }\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n const persist = useCallback(\n async (\n nextProvider: Provider,\n nextInstructions: string,\n previous: { provider: Provider | null; instructions: string },\n ) => {\n setSaving(true);\n setSaveError(null);\n try {\n const res = await fetch(PREFS_URL, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n provider: nextProvider,\n instructions: nextInstructions.trim(),\n }),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n } catch (err) {\n // Revert the optimistic update so the UI matches server state.\n setProvider(previous.provider);\n setInstructions(previous.instructions);\n setSaveError(\n `Couldn't save: ${(err as Error)?.message ?? \"network error\"}. Try again.`,\n );\n } finally {\n setSaving(false);\n }\n },\n [],\n );\n\n const choose = (next: Provider) => {\n if (next === provider) return;\n const previous = { provider, instructions };\n setHasStoredProvider(true);\n setProvider(next);\n void persist(next, instructions, previous);\n };\n\n const updateInstructions = (next: string) => {\n const previous = { provider, instructions };\n setInstructions(next);\n if (provider) void persist(provider, next, previous);\n };\n\n if (provider === null) {\n return (\n <div className=\"flex items-center gap-1.5 text-[10px] text-muted-foreground\">\n <IconLoader2 size={10} className=\"animate-spin\" />\n Loading…\n </div>\n );\n }\n\n const focusKey = (key: string) => {\n if (typeof window === \"undefined\") return;\n window.location.hash = `#secrets:${key}`;\n };\n\n return (\n <div className=\"space-y-2\">\n <ProviderOption\n id=\"builder-gemini\"\n selected={provider === \"builder-gemini\"}\n onSelect={() => choose(\"builder-gemini\")}\n disabled={!builderStatus?.configured}\n title=\"Builder.io Connect (recommended)\"\n subtitle={\n builderStatus?.configured\n ? \"Fast Gemini Flash-Lite transcription and cleanup through Builder.io. No Google key needed.\"\n : \"One-click connect — uses Gemini Flash-Lite without a Google key.\"\n }\n rightSlot={\n builderStatus?.configured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Connected\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n const url = new URL(\n agentNativePath(\"/_agent-native/builder/connect\"),\n window.location.origin,\n ).href;\n window.open(\n url,\n \"_blank\",\n \"noopener,noreferrer,width=600,height=700\",\n );\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Connect Builder.io\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n {/* Cleanup-with-AI toggle — defaults to on if Builder is connected. */}\n <div className=\"flex items-start justify-between gap-3 rounded-md border border-border bg-accent/30 px-2.5 py-2\">\n <div className=\"min-w-0\">\n <div className=\"text-[11px] font-medium text-foreground\">\n Cleanup transcripts with AI\n </div>\n <p className=\"text-[10px] text-muted-foreground mt-0.5\">\n Polish punctuation, casing, and remove filler words after each\n recording.\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={!!cleanupEnabled}\n onClick={() => toggleCleanup(!cleanupEnabled)}\n className={`relative inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full transition-colors ${\n cleanupEnabled\n ? \"bg-[#625DF5]\"\n : \"bg-muted-foreground/30 hover:bg-muted-foreground/50\"\n }`}\n >\n <span\n className={`inline-block h-3 w-3 transform rounded-full bg-background transition-transform ${\n cleanupEnabled ? \"translate-x-3.5\" : \"translate-x-0.5\"\n }`}\n />\n </button>\n </div>\n\n {/* Single \"Add API keys\" disclosure — collapses BYOK rows + browser. */}\n <div className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={() => setShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between gap-2 px-2.5 py-2 cursor-pointer\"\n >\n <span className=\"text-[11px] font-medium text-foreground inline-flex items-center gap-1\">\n {showAdvanced ? (\n <IconChevronDown size={12} />\n ) : (\n <IconChevronRight size={12} />\n )}\n Add API keys\n </span>\n <span className=\"text-[10px] text-muted-foreground\">\n OpenAI · Gemini · Groq · browser\n </span>\n </button>\n\n {showAdvanced && (\n <div className=\"px-2 pb-2 space-y-2\">\n <ProviderOption\n id=\"openai\"\n selected={provider === \"openai\"}\n onSelect={() => choose(\"openai\")}\n title=\"OpenAI Whisper\"\n subtitle=\"Best quality. Requires an OpenAI API key.\"\n rightSlot={\n openAiConfigured === null ? null : openAiConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"OPENAI_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"gemini\"\n selected={provider === \"gemini\"}\n onSelect={() => choose(\"gemini\")}\n title=\"Google Gemini\"\n subtitle=\"Fast transcription via Gemini Flash Lite. Requires a Gemini API key.\"\n rightSlot={\n geminiConfigured === null ? null : geminiConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"GEMINI_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"groq\"\n selected={provider === \"groq\"}\n onSelect={() => choose(\"groq\")}\n title=\"Groq Whisper\"\n subtitle=\"Fastest Whisper inference. Requires a Groq API key.\"\n rightSlot={\n groqConfigured === null ? null : groqConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"GROQ_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"browser\"\n selected={provider === \"browser\"}\n onSelect={() => choose(\"browser\")}\n title=\"Native browser\"\n subtitle=\"Free built-in speech recognition. No key required.\"\n />\n </div>\n )}\n </div>\n\n {provider !== \"browser\" && (\n <div className=\"rounded-md border border-border bg-accent/20 px-2.5 py-2\">\n <label\n htmlFor=\"voice-transcription-instructions\"\n className=\"block text-[10px] font-medium text-foreground\"\n >\n Custom instructions\n </label>\n <textarea\n id=\"voice-transcription-instructions\"\n value={instructions}\n onChange={(event) => updateInstructions(event.target.value)}\n placeholder=\"Names, casing, punctuation, style, or terms to preserve.\"\n className=\"mt-1 min-h-16 w-full resize-y rounded border border-border bg-background px-2 py-1.5 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent\"\n />\n <p className=\"mt-1 text-[10px] text-muted-foreground\">\n Included with LLM-based transcription and cleanup.\n </p>\n </div>\n )}\n\n {saving && <p className=\"text-[10px] text-muted-foreground\">Saving…</p>}\n {saveError && !saving && (\n <p className=\"text-[10px] text-red-500\" role=\"alert\">\n {saveError}\n </p>\n )}\n </div>\n );\n}\n\ninterface ProviderOptionProps {\n id: string;\n selected: boolean;\n disabled?: boolean;\n onSelect: () => void;\n title: string;\n subtitle?: React.ReactNode;\n rightSlot?: React.ReactNode;\n}\n\nfunction ProviderOption({\n id,\n selected,\n disabled,\n onSelect,\n title,\n subtitle,\n rightSlot,\n}: ProviderOptionProps) {\n const select = () => {\n if (!disabled) onSelect();\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n onClick={select}\n onKeyDown={onKeyDown}\n aria-pressed={selected}\n aria-disabled={disabled || undefined}\n className={`w-full text-left rounded-md border px-2.5 py-2 flex items-start gap-2 ${\n selected\n ? \"border-[#625DF5] bg-[#625DF5]/10\"\n : \"border-border bg-accent/30 hover:bg-accent/50\"\n } ${disabled ? \"opacity-60 cursor-not-allowed\" : \"\"}`}\n >\n <span\n className={`mt-[2px] shrink-0 flex h-3.5 w-3.5 items-center justify-center rounded-full border ${\n selected\n ? \"border-[#625DF5] bg-[#625DF5]\"\n : \"border-muted-foreground/40 bg-background\"\n }`}\n >\n {selected && (\n <span className=\"h-1.5 w-1.5 rounded-full bg-background\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-[11px] font-medium text-foreground\">{title}</div>\n {rightSlot && <div className=\"shrink-0\">{rightSlot}</div>}\n </div>\n {subtitle && (\n <p className=\"text-[10px] text-muted-foreground mt-0.5\">{subtitle}</p>\n )}\n </div>\n </div>\n );\n}\n\nexport function VoiceTranscriptionIcon() {\n return <IconMicrophone size={14} />;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"VoiceTranscriptionSection.js","sourceRoot":"","sources":["../../../src/client/settings/VoiceTranscriptionSection.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * <VoiceTranscriptionSection /> — source + cleanup settings for voice input.\n *\n * Writes the selection to application_state under `voice-transcription-prefs`\n * so the composer's `useVoiceDictation` hook picks it up on next record. The\n * legacy `provider` field is still written alongside `transcriptionMode` so\n * older clients continue to normalize safely.\n *\n * Provider status comes from `/_agent-native/voice-providers/status`, which\n * mirrors the server transcription route's key/env resolution.\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { agentNativePath } from \"../api-path.js\";\nimport {\n IconCheck,\n IconChevronDown,\n IconChevronRight,\n IconExternalLink,\n IconLoader2,\n IconMicrophone,\n} from \"@tabler/icons-react\";\nimport { useBuilderStatus } from \"./useBuilderStatus.js\";\n\ntype TranscriptionMode = \"mac-native\" | \"google-realtime\" | \"batch\";\n\ntype Provider =\n | \"auto\"\n | \"openai\"\n | \"builder-gemini\"\n | \"builder\"\n | \"browser\"\n | \"gemini\"\n | \"groq\";\n\ninterface Prefs {\n transcriptionMode?: TranscriptionMode;\n provider?: Provider;\n instructions?: string;\n}\n\ninterface SecretStatus {\n key: string;\n status: \"set\" | \"unset\" | \"invalid\";\n}\n\ninterface ProviderStatus {\n builder: boolean;\n gemini: boolean;\n openai: boolean;\n groq: boolean;\n googleRealtime?: boolean;\n browser: true;\n native?: true;\n}\n\nconst PREFS_URL = agentNativePath(\n \"/_agent-native/application-state/voice-transcription-prefs\",\n);\nconst CLEANUP_PREFS_URL = agentNativePath(\n \"/_agent-native/application-state/voice-cleanup-prefs\",\n);\nconst SECRETS_URL = agentNativePath(\"/_agent-native/secrets\");\nconst PROVIDER_STATUS_URL = agentNativePath(\n \"/_agent-native/voice-providers/status\",\n);\nconst DEFAULT_TRANSCRIPTION_MODE: TranscriptionMode = \"batch\";\nconst DEFAULT_BATCH_PROVIDER: Provider = \"auto\";\nconst GOOGLE_REALTIME_STREAMING_ENABLED = false;\n\nfunction isProvider(value: unknown): value is Provider {\n return (\n value === \"auto\" ||\n value === \"openai\" ||\n value === \"builder-gemini\" ||\n value === \"builder\" ||\n value === \"browser\" ||\n value === \"gemini\" ||\n value === \"groq\"\n );\n}\n\nfunction isTranscriptionMode(value: unknown): value is TranscriptionMode {\n return (\n value === \"mac-native\" || value === \"google-realtime\" || value === \"batch\"\n );\n}\n\nfunction normalizeProvider(value: unknown): Provider | null {\n if (!isProvider(value)) return null;\n return value === \"builder\" ? \"builder-gemini\" : value;\n}\n\nfunction legacyModeFromProvider(provider: Provider | null): TranscriptionMode {\n if (provider === \"browser\") return \"mac-native\";\n return \"batch\";\n}\n\nfunction providerForMode(\n mode: TranscriptionMode,\n currentProvider: Provider | null,\n): Provider {\n if (mode === \"mac-native\") return \"browser\";\n if (mode === \"google-realtime\") return \"auto\";\n if (!currentProvider || currentProvider === \"browser\") {\n return DEFAULT_BATCH_PROVIDER;\n }\n return currentProvider;\n}\n\nfunction batchProvider(provider: Provider | null): Provider {\n if (!provider || provider === \"browser\") return DEFAULT_BATCH_PROVIDER;\n return provider;\n}\n\nexport function VoiceTranscriptionSection() {\n const [transcriptionMode, setTranscriptionMode] =\n useState<TranscriptionMode | null>(null);\n const [provider, setProvider] = useState<Provider>(DEFAULT_BATCH_PROVIDER);\n const [instructions, setInstructions] = useState(\"\");\n const [openAiConfigured, setOpenAiConfigured] = useState<boolean | null>(\n null,\n );\n const [geminiConfigured, setGeminiConfigured] = useState<boolean | null>(\n null,\n );\n const [groqConfigured, setGroqConfigured] = useState<boolean | null>(null);\n const [googleRealtimeConfigured, setGoogleRealtimeConfigured] = useState<\n boolean | null\n >(null);\n const [saving, setSaving] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [showAdvanced, setShowAdvanced] = useState(false);\n const [cleanupEnabled, setCleanupEnabled] = useState<boolean | null>(null);\n const { status: builderStatus } = useBuilderStatus();\n\n // Read cleanup pref (default: true if Builder is connected).\n useEffect(() => {\n let cancelled = false;\n fetch(CLEANUP_PREFS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then(\n (\n body:\n | { enabled?: boolean }\n | { value?: { enabled?: boolean } }\n | null,\n ) => {\n if (cancelled) return;\n const stored =\n (body as { enabled?: boolean } | null)?.enabled ??\n (body as { value?: { enabled?: boolean } } | null)?.value?.enabled;\n if (typeof stored === \"boolean\") setCleanupEnabled(stored);\n else setCleanupEnabled(null); // resolve once builderStatus arrives\n },\n )\n .catch(() => !cancelled && setCleanupEnabled(null));\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (cleanupEnabled !== null) return;\n if (builderStatus?.configured !== undefined) {\n setCleanupEnabled(!!builderStatus.configured);\n }\n }, [builderStatus?.configured, cleanupEnabled]);\n\n const toggleCleanup = async (next: boolean) => {\n const previous = cleanupEnabled;\n setCleanupEnabled(next);\n try {\n const res = await fetch(CLEANUP_PREFS_URL, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ enabled: next }),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n } catch {\n setCleanupEnabled(previous);\n }\n };\n\n useEffect(() => {\n let cancelled = false;\n fetch(PREFS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then((body: Prefs | { value?: Prefs } | null) => {\n if (cancelled) return;\n const value =\n (body as { value?: Prefs } | null)?.value ?? (body as Prefs | null);\n const p = normalizeProvider(\n (body as Prefs | null)?.provider ??\n (body as { value?: Prefs } | null)?.value?.provider,\n );\n const storedMode = isTranscriptionMode(value?.transcriptionMode)\n ? value.transcriptionMode\n : null;\n const mode =\n storedMode ??\n (p ? legacyModeFromProvider(p) : DEFAULT_TRANSCRIPTION_MODE);\n const savedInstructions =\n (body as Prefs | null)?.instructions ??\n (body as { value?: Prefs } | null)?.value?.instructions;\n setTranscriptionMode(mode);\n setProvider(providerForMode(mode, p));\n if (typeof savedInstructions === \"string\") {\n setInstructions(savedInstructions);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setTranscriptionMode(DEFAULT_TRANSCRIPTION_MODE);\n setProvider(DEFAULT_BATCH_PROVIDER);\n }\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n fetch(PROVIDER_STATUS_URL)\n .then((r) => (r.ok ? r.json() : null))\n .then((status: ProviderStatus | null) => {\n if (cancelled) return;\n if (status) {\n setOpenAiConfigured(status.openai);\n setGeminiConfigured(status.gemini);\n setGroqConfigured(status.groq);\n setGoogleRealtimeConfigured(!!status.googleRealtime);\n return;\n }\n return fetch(SECRETS_URL)\n .then((r) => (r.ok ? r.json() : []))\n .then((list: SecretStatus[]) => {\n if (cancelled) return;\n const find = (key: string) =>\n Array.isArray(list) ? list.find((s) => s.key === key) : null;\n setOpenAiConfigured(find(\"OPENAI_API_KEY\")?.status === \"set\");\n setGeminiConfigured(find(\"GEMINI_API_KEY\")?.status === \"set\");\n setGroqConfigured(find(\"GROQ_API_KEY\")?.status === \"set\");\n setGoogleRealtimeConfigured(\n find(\"GOOGLE_APPLICATION_CREDENTIALS\")?.status === \"set\",\n );\n });\n })\n .catch(() => {\n if (!cancelled) {\n setOpenAiConfigured(false);\n setGeminiConfigured(false);\n setGroqConfigured(false);\n setGoogleRealtimeConfigured(false);\n }\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n const persist = useCallback(\n async (\n nextMode: TranscriptionMode,\n nextProvider: Provider,\n nextInstructions: string,\n previous: {\n transcriptionMode: TranscriptionMode | null;\n provider: Provider;\n instructions: string;\n },\n ) => {\n setSaving(true);\n setSaveError(null);\n try {\n const res = await fetch(PREFS_URL, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n transcriptionMode: nextMode,\n provider: nextProvider,\n instructions: nextInstructions.trim(),\n }),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n } catch (err) {\n // Revert the optimistic update so the UI matches server state.\n setTranscriptionMode(previous.transcriptionMode);\n setProvider(previous.provider);\n setInstructions(previous.instructions);\n setSaveError(\n `Couldn't save: ${(err as Error)?.message ?? \"network error\"}. Try again.`,\n );\n } finally {\n setSaving(false);\n }\n },\n [],\n );\n\n const focusKey = (key: string) => {\n if (typeof window === \"undefined\") return;\n window.location.hash = `#secrets:${key}`;\n };\n\n const chooseSource = (next: TranscriptionMode) => {\n if (next === transcriptionMode) return;\n if (\n next === \"google-realtime\" &&\n (!googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED)\n ) {\n setShowAdvanced(true);\n focusKey(\"GOOGLE_APPLICATION_CREDENTIALS\");\n return;\n }\n const previous = { transcriptionMode, provider, instructions };\n const nextProvider = providerForMode(next, provider);\n setTranscriptionMode(next);\n setProvider(nextProvider);\n void persist(next, nextProvider, instructions, previous);\n };\n\n const chooseBatchProvider = (next: Provider) => {\n const nextProvider = batchProvider(normalizeProvider(next));\n if (transcriptionMode === \"batch\" && nextProvider === provider) return;\n const previous = { transcriptionMode, provider, instructions };\n setTranscriptionMode(\"batch\");\n setProvider(nextProvider);\n void persist(\"batch\", nextProvider, instructions, previous);\n };\n\n const updateInstructions = (next: string) => {\n const previous = { transcriptionMode, provider, instructions };\n setInstructions(next);\n if (transcriptionMode) {\n void persist(transcriptionMode, provider, next, previous);\n }\n };\n\n if (transcriptionMode === null) {\n return (\n <div className=\"flex items-center gap-1.5 text-[10px] text-muted-foreground\">\n <IconLoader2 size={10} className=\"animate-spin\" />\n Loading…\n </div>\n );\n }\n\n return (\n <div className=\"space-y-2\">\n <div className=\"rounded-md border border-border bg-background p-2\">\n <div className=\"mb-2 flex items-start justify-between gap-3 px-0.5\">\n <div>\n <div className=\"text-[11px] font-medium text-foreground\">\n Live transcription\n </div>\n <p className=\"mt-0.5 text-[10px] text-muted-foreground\">\n Choose where real-time words come from. Batch still runs after\n recording stops.\n </p>\n </div>\n </div>\n <div className=\"space-y-2\">\n <ProviderOption\n id=\"mac-native\"\n selected={transcriptionMode === \"mac-native\"}\n onSelect={() => chooseSource(\"mac-native\")}\n title=\"Mac Native\"\n subtitle=\"Free and fast in the macOS Tauri app. Web clients use the existing browser-native path when available.\"\n rightSlot={\n <span className=\"text-[10px] text-muted-foreground\">\n Tauri default\n </span>\n }\n />\n <ProviderOption\n id=\"google-realtime\"\n selected={transcriptionMode === \"google-realtime\"}\n onSelect={() => chooseSource(\"google-realtime\")}\n disabled={\n !googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED\n }\n title=\"Google Realtime (coming soon)\"\n subtitle={\n googleRealtimeConfigured\n ? \"Credential detected. The streaming WebSocket path is not enabled yet, so keep using Mac Native or Batch.\"\n : \"BYOK only for v1. Configure Google service account before selecting this source.\"\n }\n rightSlot={\n googleRealtimeConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Credential set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowAdvanced(true);\n focusKey(\"GOOGLE_APPLICATION_CREDENTIALS\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:bg-accent/40 hover:text-foreground\"\n >\n Configure\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n <ProviderOption\n id=\"batch\"\n selected={transcriptionMode === \"batch\"}\n onSelect={() => chooseSource(\"batch\")}\n title=\"Batch\"\n subtitle=\"Universal fallback. Sends audio after recording stops through Builder Gemini, Gemini, Groq, then OpenAI.\"\n />\n </div>\n </div>\n\n <div className=\"flex items-start justify-between gap-3 rounded-md border border-border bg-accent/30 px-2.5 py-2\">\n <div className=\"min-w-0\">\n <div className=\"text-[11px] font-medium text-foreground\">\n AI cleanup\n </div>\n <p className=\"text-[10px] text-muted-foreground mt-0.5\">\n Polish punctuation, casing, filler words, titles, and summaries\n after capture. Builder Gemini is tried first; BYOK Gemini is the\n fallback.\n </p>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={!!cleanupEnabled}\n onClick={() => toggleCleanup(!cleanupEnabled)}\n className={`relative inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full transition-colors ${\n cleanupEnabled\n ? \"bg-[#625DF5]\"\n : \"bg-muted-foreground/30 hover:bg-muted-foreground/50\"\n }`}\n >\n <span\n className={`inline-block h-3 w-3 transform rounded-full bg-background transition-transform ${\n cleanupEnabled ? \"translate-x-3.5\" : \"translate-x-0.5\"\n }`}\n />\n </button>\n {cleanupEnabled && (\n <span className=\"text-[10px] text-muted-foreground\">\n {builderStatus?.configured\n ? \"Builder ready\"\n : geminiConfigured\n ? \"Gemini key set\"\n : \"Needs key\"}\n </span>\n )}\n </div>\n </div>\n\n <div className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={() => setShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between gap-2 px-2.5 py-2 cursor-pointer\"\n >\n <span className=\"text-[11px] font-medium text-foreground inline-flex items-center gap-1\">\n {showAdvanced ? (\n <IconChevronDown size={12} />\n ) : (\n <IconChevronRight size={12} />\n )}\n Add API keys\n </span>\n <span className=\"text-[10px] text-muted-foreground\">\n Google STT · Gemini · Groq · OpenAI\n </span>\n </button>\n\n {showAdvanced && (\n <div className=\"px-2 pb-2 space-y-2\">\n <ProviderOption\n id=\"google-service-account\"\n selected={transcriptionMode === \"google-realtime\"}\n onSelect={() => chooseSource(\"google-realtime\")}\n disabled={\n !googleRealtimeConfigured || !GOOGLE_REALTIME_STREAMING_ENABLED\n }\n title=\"Google Speech-to-Text service account\"\n subtitle=\"Service-account JSON for the future WebSocket to Google StreamingRecognize path. Saved credentials are detected now; streaming is still coming soon.\"\n rightSlot={\n googleRealtimeConfigured ===\n null ? null : googleRealtimeConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Credential set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"GOOGLE_APPLICATION_CREDENTIALS\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Configure\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"auto\"\n selected={transcriptionMode === \"batch\" && provider === \"auto\"}\n onSelect={() => chooseBatchProvider(\"auto\")}\n title=\"Automatic batch fallback\"\n subtitle=\"Keep the current Clips fallback chain: Builder Gemini, Gemini, Groq, then OpenAI.\"\n />\n\n <ProviderOption\n id=\"builder-gemini\"\n selected={\n transcriptionMode === \"batch\" && provider === \"builder-gemini\"\n }\n onSelect={() => chooseBatchProvider(\"builder-gemini\")}\n disabled={!builderStatus?.configured}\n title=\"Builder.io Connect\"\n subtitle={\n builderStatus?.configured\n ? \"Use Builder-hosted Gemini Flash-Lite for batch transcription and cleanup.\"\n : \"One-click connect for Gemini Flash-Lite cleanup and batch transcription. No Google key needed.\"\n }\n rightSlot={\n builderStatus?.configured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Connected\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n const url = new URL(\n agentNativePath(\"/_agent-native/builder/connect\"),\n window.location.origin,\n ).href;\n window.open(\n url,\n \"_blank\",\n \"noopener,noreferrer,width=600,height=700\",\n );\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Connect Builder.io\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"gemini\"\n selected={transcriptionMode === \"batch\" && provider === \"gemini\"}\n onSelect={() => chooseBatchProvider(\"gemini\")}\n title=\"Google Gemini\"\n subtitle=\"BYOK Gemini for AI cleanup and optional strict batch transcription.\"\n rightSlot={\n geminiConfigured === null ? null : geminiConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"GEMINI_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"openai\"\n selected={transcriptionMode === \"batch\" && provider === \"openai\"}\n onSelect={() => chooseBatchProvider(\"openai\")}\n title=\"OpenAI Whisper\"\n subtitle=\"Batch Whisper provider. Requires an OpenAI API key.\"\n rightSlot={\n openAiConfigured === null ? null : openAiConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"OPENAI_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n\n <ProviderOption\n id=\"groq\"\n selected={transcriptionMode === \"batch\" && provider === \"groq\"}\n onSelect={() => chooseBatchProvider(\"groq\")}\n title=\"Groq Whisper\"\n subtitle=\"Fast Whisper batch provider. Requires a Groq API key.\"\n rightSlot={\n groqConfigured === null ? null : groqConfigured ? (\n <span className=\"flex items-center gap-1 text-[10px] text-green-500\">\n <IconCheck size={10} />\n Key set\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n focusKey(\"GROQ_API_KEY\");\n }}\n className=\"inline-flex items-center gap-1 rounded border border-border px-2 py-0.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-accent/40\"\n >\n Add key\n <IconExternalLink size={10} />\n </button>\n )\n }\n />\n </div>\n )}\n </div>\n\n {(cleanupEnabled || transcriptionMode === \"batch\") && (\n <div className=\"rounded-md border border-border bg-accent/20 px-2.5 py-2\">\n <label\n htmlFor=\"voice-transcription-instructions\"\n className=\"block text-[10px] font-medium text-foreground\"\n >\n Custom instructions\n </label>\n <textarea\n id=\"voice-transcription-instructions\"\n value={instructions}\n onChange={(event) => updateInstructions(event.target.value)}\n placeholder=\"Names, casing, punctuation, style, or terms to preserve.\"\n className=\"mt-1 min-h-16 w-full resize-y rounded border border-border bg-background px-2 py-1.5 text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 focus:ring-1 focus:ring-accent\"\n />\n <p className=\"mt-1 text-[10px] text-muted-foreground\">\n Included with batch transcription and AI cleanup.\n </p>\n </div>\n )}\n\n {saving && <p className=\"text-[10px] text-muted-foreground\">Saving…</p>}\n {saveError && !saving && (\n <p className=\"text-[10px] text-red-500\" role=\"alert\">\n {saveError}\n </p>\n )}\n </div>\n );\n}\n\ninterface ProviderOptionProps {\n id: string;\n selected: boolean;\n disabled?: boolean;\n onSelect: () => void;\n title: string;\n subtitle?: React.ReactNode;\n rightSlot?: React.ReactNode;\n}\n\nfunction ProviderOption({\n id,\n selected,\n disabled,\n onSelect,\n title,\n subtitle,\n rightSlot,\n}: ProviderOptionProps) {\n const select = () => {\n if (!disabled) onSelect();\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onSelect();\n }\n };\n\n return (\n <div\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n onClick={select}\n onKeyDown={onKeyDown}\n aria-pressed={selected}\n aria-disabled={disabled || undefined}\n className={`w-full text-left rounded-md border px-2.5 py-2 flex items-start gap-2 ${\n selected\n ? \"border-[#625DF5] bg-[#625DF5]/10\"\n : \"border-border bg-accent/30 hover:bg-accent/50\"\n } ${disabled ? \"opacity-60 cursor-not-allowed\" : \"\"}`}\n >\n <span\n className={`mt-[2px] shrink-0 flex h-3.5 w-3.5 items-center justify-center rounded-full border ${\n selected\n ? \"border-[#625DF5] bg-[#625DF5]\"\n : \"border-muted-foreground/40 bg-background\"\n }`}\n >\n {selected && (\n <span className=\"h-1.5 w-1.5 rounded-full bg-background\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-[11px] font-medium text-foreground\">{title}</div>\n {rightSlot && <div className=\"shrink-0\">{rightSlot}</div>}\n </div>\n {subtitle && (\n <p className=\"text-[10px] text-muted-foreground mt-0.5\">{subtitle}</p>\n )}\n </div>\n </div>\n );\n}\n\nexport function VoiceTranscriptionIcon() {\n return <IconMicrophone size={14} />;\n}\n"]}
|
|
@@ -3,6 +3,13 @@ export interface VoiceProvidersStatus {
|
|
|
3
3
|
gemini: boolean;
|
|
4
4
|
openai: boolean;
|
|
5
5
|
groq: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Google Speech-to-Text realtime streaming is BYOK-only for v1. This only
|
|
8
|
+
* reports whether a service-account credential is configured; the actual
|
|
9
|
+
* stream belongs on a dedicated WebSocket -> StreamingRecognize path, not on
|
|
10
|
+
* the batch transcribe route.
|
|
11
|
+
*/
|
|
12
|
+
googleRealtime: boolean;
|
|
6
13
|
/** Always true — the Web Speech API is available in WebKit-based clients. */
|
|
7
14
|
browser: true;
|
|
8
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-providers-status.d.ts","sourceRoot":"","sources":["../../src/server/voice-providers-status.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,IAAI,CAAC;IACd;;;;;OAKG;IACH,MAAM,EAAE,IAAI,CAAC;CACd;AAED,wBAAgB,iCAAiC;;
|
|
1
|
+
{"version":3,"file":"voice-providers-status.d.ts","sourceRoot":"","sources":["../../src/server/voice-providers-status.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd;;;;;OAKG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,6EAA6E;IAC7E,OAAO,EAAE,IAAI,CAAC;IACd;;;;;OAKG;IACH,MAAM,EAAE,IAAI,CAAC;CACd;AAED,wBAAgB,iCAAiC;;IAmEhD"}
|
|
@@ -65,16 +65,18 @@ export function createVoiceProvidersStatusHandler() {
|
|
|
65
65
|
catch {
|
|
66
66
|
builder = false;
|
|
67
67
|
}
|
|
68
|
-
const [gemini, openai, groq] = await Promise.all([
|
|
68
|
+
const [gemini, openai, groq, googleRealtime] = await Promise.all([
|
|
69
69
|
hasKey("GEMINI_API_KEY"),
|
|
70
70
|
hasKey("OPENAI_API_KEY"),
|
|
71
71
|
hasKey("GROQ_API_KEY"),
|
|
72
|
+
hasKey("GOOGLE_APPLICATION_CREDENTIALS"),
|
|
72
73
|
]);
|
|
73
74
|
const status = {
|
|
74
75
|
builder,
|
|
75
76
|
gemini,
|
|
76
77
|
openai,
|
|
77
78
|
groq,
|
|
79
|
+
googleRealtime,
|
|
78
80
|
browser: true,
|
|
79
81
|
native: true,
|
|
80
82
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-providers-status.js","sourceRoot":"","sources":["../../src/server/voice-providers-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,iBAAiB,GAElB,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"voice-providers-status.js","sourceRoot":"","sources":["../../src/server/voice-providers-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,kBAAkB,EAClB,SAAS,EACT,iBAAiB,GAElB,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAyB7D,MAAM,UAAU,iCAAiC;IAC/C,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAc,EAAE,EAAE;QACjD,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC;YAC/B,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;QACzC,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAE1D,KAAK,UAAU,MAAM,CAAC,GAAW;YAC/B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;gBAC1C,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;oBACpB,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC/C,CAAC;gBACD,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC;oBACrC,GAAG;oBACH,KAAK,EAAE,MAAM;oBACb,OAAO,EAAE,OAAO,CAAC,KAAK;iBACvB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;gBACrB,IAAI,UAAU,EAAE,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAClE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBACnD,OAAO,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,OAAO,EAAE,KAAK;gBAC3B,CAAC,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;gBAC9C,CAAC,CAAC,IAAI,CAAC;YACT,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,2BAA2B,EAAE,CAAC;YACpD,OAAO;gBACL,CAAC,OAAO,EAAE,KAAK;oBACb,CAAC,CAAC,MAAM,qBAAqB,CACzB;wBACE,SAAS,EAAE,OAAO,CAAC,KAAK;wBACxB,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,SAAS;qBAClC,EACD,OAAO,CACR;oBACH,CAAC,CAAC,MAAM,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/D,MAAM,CAAC,gBAAgB,CAAC;YACxB,MAAM,CAAC,gBAAgB,CAAC;YACxB,MAAM,CAAC,cAAc,CAAC;YACtB,MAAM,CAAC,gCAAgC,CAAC;SACzC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAyB;YACnC,OAAO;YACP,MAAM;YACN,MAAM;YACN,IAAI;YACJ,cAAc;YACd,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,IAAI;SACb,CAAC;QACF,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * GET /_agent-native/voice-providers/status\n *\n * Reports which voice transcription providers are configured for the\n * current user. The desktop Settings UI uses this to show \"Connect\" vs\n * \"Connected\" status pills next to each provider option.\n *\n * Resolution mirrors `transcribe-voice.ts`: we try the user-scoped\n * encrypted secret first (set via the sidebar settings UI) and fall back\n * to `resolveCredential()` (env var + SQL settings store). Each lookup is\n * wrapped in try/catch — one provider's failure must never break the\n * whole response.\n *\n * Returns booleans only — never the actual key material.\n */\nimport {\n defineEventHandler,\n getMethod,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\nimport { readAppSecret } from \"../secrets/storage.js\";\nimport { resolveCredential } from \"../credentials/index.js\";\nimport { getSession } from \"./auth.js\";\nimport { resolveHasBuilderPrivateKey } from \"./credential-provider.js\";\nimport { getOrgContext } from \"../org/context.js\";\nimport { runWithRequestContext } from \"./request-context.js\";\n\nexport interface VoiceProvidersStatus {\n builder: boolean;\n gemini: boolean;\n openai: boolean;\n groq: boolean;\n /**\n * Google Speech-to-Text realtime streaming is BYOK-only for v1. This only\n * reports whether a service-account credential is configured; the actual\n * stream belongs on a dedicated WebSocket -> StreamingRecognize path, not on\n * the batch transcribe route.\n */\n googleRealtime: boolean;\n /** Always true — the Web Speech API is available in WebKit-based clients. */\n browser: true;\n /**\n * Apple's SFSpeechRecognizer + AVAudioEngine, exposed by the Tauri\n * desktop client. Always reported as `true` from the server — the\n * desktop client gates this on macOS at the Tauri-command boundary, so\n * non-macOS hosts return a clear error instead of attempting to use it.\n */\n native: true;\n}\n\nexport function createVoiceProvidersStatusHandler() {\n return defineEventHandler(async (event: H3Event) => {\n if (getMethod(event) !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n\n const session = await getSession(event).catch(() => null);\n\n async function hasKey(key: string): Promise<boolean> {\n try {\n const ctx = { userEmail: session?.email };\n if (!session?.email) {\n const v = await resolveCredential(key, ctx);\n return typeof v === \"string\" && v.length > 0;\n }\n const userSecret = await readAppSecret({\n key,\n scope: \"user\",\n scopeId: session.email,\n }).catch(() => null);\n if (userSecret?.value && userSecret.value.length > 0) return true;\n const fallback = await resolveCredential(key, ctx);\n return typeof fallback === \"string\" && fallback.length > 0;\n } catch {\n return false;\n }\n }\n\n let builder = false;\n try {\n const orgCtx = session?.email\n ? await getOrgContext(event).catch(() => null)\n : null;\n const resolve = () => resolveHasBuilderPrivateKey();\n builder =\n (session?.email\n ? await runWithRequestContext(\n {\n userEmail: session.email,\n orgId: orgCtx?.orgId ?? undefined,\n },\n resolve,\n )\n : await resolve()) === true;\n } catch {\n builder = false;\n }\n\n const [gemini, openai, groq, googleRealtime] = await Promise.all([\n hasKey(\"GEMINI_API_KEY\"),\n hasKey(\"OPENAI_API_KEY\"),\n hasKey(\"GROQ_API_KEY\"),\n hasKey(\"GOOGLE_APPLICATION_CREDENTIALS\"),\n ]);\n\n const status: VoiceProvidersStatus = {\n builder,\n gemini,\n openai,\n groq,\n googleRealtime,\n browser: true,\n native: true,\n };\n return status;\n });\n}\n"]}
|