bosun 0.36.2 → 0.36.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-prompts.mjs +95 -0
- package/analyze-agent-work-helpers.mjs +308 -0
- package/analyze-agent-work.mjs +926 -0
- package/autofix.mjs +2 -0
- package/bosun.schema.json +101 -3
- package/codex-shell.mjs +85 -10
- package/desktop/main.mjs +871 -48
- package/desktop/preload.mjs +54 -1
- package/desktop-shortcut.mjs +90 -11
- package/git-editor-fix.mjs +273 -0
- package/mcp-registry.mjs +579 -0
- package/meeting-workflow-service.mjs +631 -0
- package/monitor.mjs +18 -103
- package/package.json +21 -2
- package/primary-agent.mjs +32 -12
- package/session-tracker.mjs +68 -0
- package/setup-web-server.mjs +20 -10
- package/setup.mjs +376 -83
- package/startup-service.mjs +51 -6
- package/stream-resilience.mjs +17 -7
- package/ui/app.js +164 -4
- package/ui/components/agent-selector.js +145 -1
- package/ui/components/chat-view.js +161 -15
- package/ui/components/session-list.js +2 -2
- package/ui/components/shared.js +188 -15
- package/ui/modules/icons.js +13 -0
- package/ui/modules/utils.js +44 -0
- package/ui/modules/voice-client-sdk.js +733 -0
- package/ui/modules/voice-overlay.js +128 -15
- package/ui/modules/voice.js +15 -6
- package/ui/setup.html +281 -81
- package/ui/styles/components.css +99 -3
- package/ui/styles/sessions.css +122 -14
- package/ui/styles.css +14 -0
- package/ui/tabs/agents.js +1 -1
- package/ui/tabs/chat.js +123 -14
- package/ui/tabs/control.js +16 -22
- package/ui/tabs/dashboard.js +85 -8
- package/ui/tabs/library.js +113 -17
- package/ui/tabs/settings.js +116 -2
- package/ui/tabs/tasks.js +388 -39
- package/ui/tabs/telemetry.js +0 -1
- package/ui/tabs/workflows.js +4 -0
- package/ui-server.mjs +400 -22
- package/update-check.mjs +41 -13
- package/voice-action-dispatcher.mjs +844 -0
- package/voice-agents-sdk.mjs +664 -0
- package/voice-auth-manager.mjs +164 -0
- package/voice-relay.mjs +1194 -0
- package/voice-tools.mjs +914 -0
- package/workflow-templates/agents.mjs +6 -2
- package/workflow-templates/github.mjs +154 -12
- package/workflow-templates.mjs +3 -0
- package/github-reconciler.mjs +0 -506
- package/merge-strategy.mjs +0 -1210
- package/pr-cleanup-daemon.mjs +0 -992
- package/workspace-reaper.mjs +0 -405
package/ui/setup.html
CHANGED
|
@@ -715,6 +715,107 @@ function App() {
|
|
|
715
715
|
const [azureOpenaiRealtimeEndpoint, setAzureOpenaiRealtimeEndpoint] = useState("");
|
|
716
716
|
const [azureOpenaiRealtimeApiKey, setAzureOpenaiRealtimeApiKey] = useState("");
|
|
717
717
|
const [azureOpenaiRealtimeDeployment, setAzureOpenaiRealtimeDeployment] = useState("gpt-4o-realtime-preview");
|
|
718
|
+
const [voiceProviders, setVoiceProviders] = useState([
|
|
719
|
+
{
|
|
720
|
+
id: Date.now(),
|
|
721
|
+
provider: voiceProvider,
|
|
722
|
+
model: voiceModel,
|
|
723
|
+
visionModel: voiceVisionModel,
|
|
724
|
+
voiceId,
|
|
725
|
+
azureDeployment: azureOpenaiRealtimeDeployment,
|
|
726
|
+
},
|
|
727
|
+
]);
|
|
728
|
+
|
|
729
|
+
const VOICE_PROVIDER_MODEL_DEFAULTS = {
|
|
730
|
+
openai: { model: "gpt-4o-realtime-preview-2024-12-17", visionModel: "gpt-4.1-mini" },
|
|
731
|
+
azure: { model: "gpt-4o-realtime-preview-2024-12-17", visionModel: "gpt-4.1-mini" },
|
|
732
|
+
claude: { model: "claude-3-7-sonnet-latest", visionModel: "claude-3-7-sonnet-latest" },
|
|
733
|
+
gemini: { model: "gemini-2.5-pro", visionModel: "gemini-2.5-flash" },
|
|
734
|
+
fallback: { model: "", visionModel: "" },
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const getVoiceProviderModelDefaults = (provider) =>
|
|
738
|
+
VOICE_PROVIDER_MODEL_DEFAULTS[String(provider || "fallback").toLowerCase()] || VOICE_PROVIDER_MODEL_DEFAULTS.fallback;
|
|
739
|
+
|
|
740
|
+
const normalizeVoiceProviderEntry = (entry = {}, fallback = {}) => {
|
|
741
|
+
const allowedProviders = ["openai", "azure", "claude", "gemini", "fallback"];
|
|
742
|
+
const provider = String(entry.provider || fallback.provider || "fallback").trim().toLowerCase();
|
|
743
|
+
const normalizedProvider = allowedProviders.includes(provider) ? provider : "fallback";
|
|
744
|
+
const defaults_ = getVoiceProviderModelDefaults(normalizedProvider);
|
|
745
|
+
const model = String(entry.model ?? fallback.model ?? defaults_.model ?? "").trim();
|
|
746
|
+
const visionModel = String(entry.visionModel ?? fallback.visionModel ?? defaults_.visionModel ?? "").trim();
|
|
747
|
+
const normalizedVoiceId = String(entry.voiceId ?? fallback.voiceId ?? "alloy").trim() || "alloy";
|
|
748
|
+
const normalizedAzureDeployment = String(
|
|
749
|
+
entry.azureDeployment ??
|
|
750
|
+
fallback.azureDeployment ??
|
|
751
|
+
(normalizedProvider === "azure" ? "gpt-4o-realtime-preview" : ""),
|
|
752
|
+
).trim();
|
|
753
|
+
return {
|
|
754
|
+
id: entry.id || Date.now() + Math.random(),
|
|
755
|
+
provider: normalizedProvider,
|
|
756
|
+
model,
|
|
757
|
+
visionModel,
|
|
758
|
+
voiceId: normalizedVoiceId,
|
|
759
|
+
azureDeployment: normalizedAzureDeployment,
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const normalizeVoiceProviders = (providers, fallback = {}) => {
|
|
764
|
+
const normalized = (Array.isArray(providers) ? providers : [])
|
|
765
|
+
.slice(0, 5)
|
|
766
|
+
.map((provider_) => normalizeVoiceProviderEntry(provider_, fallback));
|
|
767
|
+
if (normalized.length > 0) return normalized;
|
|
768
|
+
return [normalizeVoiceProviderEntry({}, fallback)];
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const updateVoiceProviderRow = (idx, field, value) => {
|
|
772
|
+
setVoiceProviders((prev) => {
|
|
773
|
+
const list = normalizeVoiceProviders(prev);
|
|
774
|
+
const target = list[idx];
|
|
775
|
+
if (!target) return list;
|
|
776
|
+
if (field === "provider") {
|
|
777
|
+
const nextProvider = String(value || "fallback").trim().toLowerCase();
|
|
778
|
+
const defaults_ = getVoiceProviderModelDefaults(nextProvider);
|
|
779
|
+
list[idx] = normalizeVoiceProviderEntry({
|
|
780
|
+
...target,
|
|
781
|
+
provider: nextProvider,
|
|
782
|
+
model: defaults_.model,
|
|
783
|
+
visionModel: defaults_.visionModel,
|
|
784
|
+
azureDeployment:
|
|
785
|
+
nextProvider === "azure"
|
|
786
|
+
? String(target.azureDeployment || "gpt-4o-realtime-preview")
|
|
787
|
+
: "",
|
|
788
|
+
});
|
|
789
|
+
return list;
|
|
790
|
+
}
|
|
791
|
+
list[idx] = normalizeVoiceProviderEntry({ ...target, [field]: value });
|
|
792
|
+
return list;
|
|
793
|
+
});
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
const addVoiceProviderRow = () => {
|
|
797
|
+
setVoiceProviders((prev) => {
|
|
798
|
+
const list = normalizeVoiceProviders(prev);
|
|
799
|
+
if (list.length >= 5) return list;
|
|
800
|
+
const first = list[0] || normalizeVoiceProviderEntry();
|
|
801
|
+
return [
|
|
802
|
+
...list,
|
|
803
|
+
normalizeVoiceProviderEntry({
|
|
804
|
+
provider: "fallback",
|
|
805
|
+
voiceId: first.voiceId || "alloy",
|
|
806
|
+
}),
|
|
807
|
+
];
|
|
808
|
+
});
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const removeVoiceProviderRow = (idx) => {
|
|
812
|
+
setVoiceProviders((prev) => {
|
|
813
|
+
const list = normalizeVoiceProviders(prev);
|
|
814
|
+
if (list.length <= 1) return list;
|
|
815
|
+
const next = list.filter((_, i) => i !== idx);
|
|
816
|
+
return next.length > 0 ? next : list;
|
|
817
|
+
});
|
|
818
|
+
};
|
|
718
819
|
|
|
719
820
|
const getWorkflowProfileById = (profileId, profileList = workflowProfiles) =>
|
|
720
821
|
(profileList || []).find((profile_) => profile_.id === profileId) || null;
|
|
@@ -1055,6 +1156,32 @@ function App() {
|
|
|
1055
1156
|
if (env.AZURE_OPENAI_REALTIME_ENDPOINT) { setAzureOpenaiRealtimeEndpoint(env.AZURE_OPENAI_REALTIME_ENDPOINT); envLoaded = true; }
|
|
1056
1157
|
if (env.AZURE_OPENAI_REALTIME_API_KEY) { setAzureOpenaiRealtimeApiKey(env.AZURE_OPENAI_REALTIME_API_KEY); envLoaded = true; }
|
|
1057
1158
|
if (env.AZURE_OPENAI_REALTIME_DEPLOYMENT) { setAzureOpenaiRealtimeDeployment(env.AZURE_OPENAI_REALTIME_DEPLOYMENT); envLoaded = true; }
|
|
1159
|
+
const resolvedVoiceProvider = String(env.VOICE_PROVIDER || existingVoice.provider || d.voiceProvider || voiceProvider || "openai").trim().toLowerCase();
|
|
1160
|
+
const resolvedVoiceModel = String(env.VOICE_MODEL || existingVoice.model || d.voiceModel || voiceModel || "gpt-4o-realtime-preview-2024-12-17").trim();
|
|
1161
|
+
const resolvedVoiceVisionModel = String(env.VOICE_VISION_MODEL || existingVoice.visionModel || d.voiceVisionModel || voiceVisionModel || "gpt-4.1-mini").trim();
|
|
1162
|
+
const resolvedVoiceId = String(env.VOICE_ID || existingVoice.voiceId || d.voiceId || voiceId || "alloy").trim();
|
|
1163
|
+
const resolvedAzureDeployment = String(env.AZURE_OPENAI_REALTIME_DEPLOYMENT || existingVoice.azureDeployment || d.azureOpenaiRealtimeDeployment || azureOpenaiRealtimeDeployment || "gpt-4o-realtime-preview").trim();
|
|
1164
|
+
if (Array.isArray(existingVoice.providers) && existingVoice.providers.length > 0) {
|
|
1165
|
+
setVoiceProviders(
|
|
1166
|
+
normalizeVoiceProviders(existingVoice.providers, {
|
|
1167
|
+
provider: resolvedVoiceProvider,
|
|
1168
|
+
model: resolvedVoiceModel,
|
|
1169
|
+
visionModel: resolvedVoiceVisionModel,
|
|
1170
|
+
voiceId: resolvedVoiceId,
|
|
1171
|
+
azureDeployment: resolvedAzureDeployment,
|
|
1172
|
+
}),
|
|
1173
|
+
);
|
|
1174
|
+
} else {
|
|
1175
|
+
setVoiceProviders(
|
|
1176
|
+
normalizeVoiceProviders([], {
|
|
1177
|
+
provider: resolvedVoiceProvider,
|
|
1178
|
+
model: resolvedVoiceModel,
|
|
1179
|
+
visionModel: resolvedVoiceVisionModel,
|
|
1180
|
+
voiceId: resolvedVoiceId,
|
|
1181
|
+
azureDeployment: resolvedAzureDeployment,
|
|
1182
|
+
}),
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1058
1185
|
// Multi-workspace: load workspaces[] from existing config
|
|
1059
1186
|
if (statusData.existingConfig?.workspaces?.length > 0) {
|
|
1060
1187
|
setMultiWorkspaceEnabled(true);
|
|
@@ -1250,6 +1377,21 @@ function App() {
|
|
|
1250
1377
|
setError(null);
|
|
1251
1378
|
try {
|
|
1252
1379
|
const filteredRepos = repos.filter((r) => r.trim());
|
|
1380
|
+
const normalizedVoiceProviders = normalizeVoiceProviders(voiceProviders, {
|
|
1381
|
+
provider: voiceProvider,
|
|
1382
|
+
model: voiceModel,
|
|
1383
|
+
visionModel: voiceVisionModel,
|
|
1384
|
+
voiceId,
|
|
1385
|
+
azureDeployment: azureOpenaiRealtimeDeployment,
|
|
1386
|
+
});
|
|
1387
|
+
const primaryVoiceProvider = normalizedVoiceProviders[0] || normalizeVoiceProviderEntry({
|
|
1388
|
+
provider: voiceProvider,
|
|
1389
|
+
model: voiceModel,
|
|
1390
|
+
visionModel: voiceVisionModel,
|
|
1391
|
+
voiceId,
|
|
1392
|
+
azureDeployment: azureOpenaiRealtimeDeployment,
|
|
1393
|
+
});
|
|
1394
|
+
const voiceFailoverMaxAttempts = Math.max(1, Math.min(normalizedVoiceProviders.length, 5));
|
|
1253
1395
|
const result = await apiPost("apply", {
|
|
1254
1396
|
env: {
|
|
1255
1397
|
bosunHome,
|
|
@@ -1311,17 +1453,20 @@ function App() {
|
|
|
1311
1453
|
copilotMcpConfig,
|
|
1312
1454
|
// Voice assistant
|
|
1313
1455
|
voiceEnabled,
|
|
1314
|
-
voiceProvider,
|
|
1315
|
-
voiceModel,
|
|
1316
|
-
voiceVisionModel,
|
|
1317
|
-
voiceId,
|
|
1456
|
+
voiceProvider: primaryVoiceProvider.provider || voiceProvider,
|
|
1457
|
+
voiceModel: primaryVoiceProvider.model || voiceModel,
|
|
1458
|
+
voiceVisionModel: primaryVoiceProvider.visionModel || voiceVisionModel,
|
|
1459
|
+
voiceId: primaryVoiceProvider.voiceId || voiceId,
|
|
1318
1460
|
voiceTurnDetection,
|
|
1319
1461
|
voiceFallbackMode,
|
|
1320
1462
|
voiceDelegateExecutor,
|
|
1321
1463
|
openaiRealtimeApiKey,
|
|
1322
1464
|
azureOpenaiRealtimeEndpoint,
|
|
1323
1465
|
azureOpenaiRealtimeApiKey,
|
|
1324
|
-
azureOpenaiRealtimeDeployment,
|
|
1466
|
+
azureOpenaiRealtimeDeployment: primaryVoiceProvider.azureDeployment || azureOpenaiRealtimeDeployment,
|
|
1467
|
+
voiceProviders: normalizedVoiceProviders,
|
|
1468
|
+
voiceFailoverEnabled: true,
|
|
1469
|
+
voiceFailoverMaxAttempts,
|
|
1325
1470
|
// Infrastructure
|
|
1326
1471
|
containerEnabled,
|
|
1327
1472
|
containerRuntime,
|
|
@@ -1367,6 +1512,25 @@ function App() {
|
|
|
1367
1512
|
maxPersistedRuns: Number(workflowMaxPersistedRuns) || 200,
|
|
1368
1513
|
maxConcurrentBranches: Number(workflowMaxConcurrentBranches) || 8,
|
|
1369
1514
|
},
|
|
1515
|
+
voice: {
|
|
1516
|
+
enabled: voiceEnabled !== false,
|
|
1517
|
+
provider: primaryVoiceProvider.provider || voiceProvider,
|
|
1518
|
+
model: primaryVoiceProvider.model || voiceModel,
|
|
1519
|
+
visionModel: primaryVoiceProvider.visionModel || voiceVisionModel,
|
|
1520
|
+
voiceId: primaryVoiceProvider.voiceId || voiceId,
|
|
1521
|
+
turnDetection: voiceTurnDetection,
|
|
1522
|
+
fallbackMode: voiceFallbackMode,
|
|
1523
|
+
delegateExecutor: voiceDelegateExecutor,
|
|
1524
|
+
azureDeployment: primaryVoiceProvider.azureDeployment || azureOpenaiRealtimeDeployment,
|
|
1525
|
+
openaiApiKey: openaiRealtimeApiKey,
|
|
1526
|
+
azureEndpoint: azureOpenaiRealtimeEndpoint,
|
|
1527
|
+
azureApiKey: azureOpenaiRealtimeApiKey,
|
|
1528
|
+
providers: normalizedVoiceProviders.slice(0, 5),
|
|
1529
|
+
failover: {
|
|
1530
|
+
enabled: true,
|
|
1531
|
+
maxAttempts: voiceFailoverMaxAttempts,
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1370
1534
|
workspaces: multiWorkspaceEnabled
|
|
1371
1535
|
? workspaces.filter((ws) => ws.name.trim()).map((ws) => ({
|
|
1372
1536
|
id: ws.id,
|
|
@@ -2693,52 +2857,90 @@ function App() {
|
|
|
2693
2857
|
<div class="hint">Allows live voice/video calls from chat. Tier 2 browser fallback works without cloud keys.</div>
|
|
2694
2858
|
</div>
|
|
2695
2859
|
${voiceEnabled && html`
|
|
2696
|
-
<div class="
|
|
2697
|
-
<
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
<
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
<
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2860
|
+
<div class="form-group">
|
|
2861
|
+
<label>Voice Providers (priority order)</label>
|
|
2862
|
+
<div class="hint">Configure up to 5 providers. Bosun will try them in order.</div>
|
|
2863
|
+
</div>
|
|
2864
|
+
${(voiceProviders || []).slice(0, 5).map((providerRow, idx) => html`
|
|
2865
|
+
<div style="border:1px solid var(--border-primary);border-radius:var(--radius-sm);padding:12px;margin-bottom:10px">
|
|
2866
|
+
<div class="executor-grid">
|
|
2867
|
+
<div class="form-group">
|
|
2868
|
+
<label>Provider ${idx + 1}</label>
|
|
2869
|
+
<select value=${providerRow.provider} onchange=${(e) => updateVoiceProviderRow(idx, "provider", e.target.value)}>
|
|
2870
|
+
<option value="openai">OpenAI Realtime</option>
|
|
2871
|
+
<option value="azure">Azure OpenAI Realtime</option>
|
|
2872
|
+
<option value="claude">Claude</option>
|
|
2873
|
+
<option value="gemini">Gemini</option>
|
|
2874
|
+
<option value="fallback">Browser Fallback</option>
|
|
2875
|
+
</select>
|
|
2876
|
+
</div>
|
|
2877
|
+
<div class="form-group">
|
|
2878
|
+
<label>Model</label>
|
|
2879
|
+
<input
|
|
2880
|
+
type="text"
|
|
2881
|
+
value=${providerRow.model}
|
|
2882
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "model", e.target.value)}
|
|
2883
|
+
placeholder="Provider model"
|
|
2884
|
+
/>
|
|
2885
|
+
</div>
|
|
2886
|
+
<div class="form-group">
|
|
2887
|
+
<label>Vision Model</label>
|
|
2888
|
+
<input
|
|
2889
|
+
type="text"
|
|
2890
|
+
value=${providerRow.visionModel}
|
|
2891
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "visionModel", e.target.value)}
|
|
2892
|
+
placeholder="Provider vision model"
|
|
2893
|
+
/>
|
|
2894
|
+
</div>
|
|
2895
|
+
<div class="form-group">
|
|
2896
|
+
<label>Voice Persona</label>
|
|
2897
|
+
<select value=${providerRow.voiceId} onchange=${(e) => updateVoiceProviderRow(idx, "voiceId", e.target.value)}>
|
|
2898
|
+
<option value="alloy">alloy</option>
|
|
2899
|
+
<option value="ash">ash</option>
|
|
2900
|
+
<option value="ballad">ballad</option>
|
|
2901
|
+
<option value="coral">coral</option>
|
|
2902
|
+
<option value="echo">echo</option>
|
|
2903
|
+
<option value="fable">fable</option>
|
|
2904
|
+
<option value="nova">nova</option>
|
|
2905
|
+
<option value="onyx">onyx</option>
|
|
2906
|
+
<option value="sage">sage</option>
|
|
2907
|
+
<option value="shimmer">shimmer</option>
|
|
2908
|
+
<option value="verse">verse</option>
|
|
2909
|
+
</select>
|
|
2910
|
+
</div>
|
|
2911
|
+
${providerRow.provider === "azure" && html`
|
|
2912
|
+
<div class="form-group">
|
|
2913
|
+
<label>Azure Deployment</label>
|
|
2914
|
+
<input
|
|
2915
|
+
type="text"
|
|
2916
|
+
value=${providerRow.azureDeployment || ""}
|
|
2917
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "azureDeployment", e.target.value)}
|
|
2918
|
+
placeholder="gpt-4o-realtime-preview"
|
|
2919
|
+
/>
|
|
2920
|
+
</div>
|
|
2921
|
+
`}
|
|
2922
|
+
</div>
|
|
2923
|
+
<div style="display:flex;justify-content:flex-end">
|
|
2924
|
+
<button
|
|
2925
|
+
class="btn btn-sm btn-danger"
|
|
2926
|
+
onclick=${() => removeVoiceProviderRow(idx)}
|
|
2927
|
+
disabled=${(voiceProviders || []).length <= 1}
|
|
2928
|
+
>
|
|
2929
|
+
Remove
|
|
2930
|
+
</button>
|
|
2931
|
+
</div>
|
|
2741
2932
|
</div>
|
|
2933
|
+
`)}
|
|
2934
|
+
<div style="display:flex;justify-content:flex-end;margin-bottom:12px">
|
|
2935
|
+
<button
|
|
2936
|
+
class="btn btn-sm"
|
|
2937
|
+
onclick=${addVoiceProviderRow}
|
|
2938
|
+
disabled=${(voiceProviders || []).length >= 5}
|
|
2939
|
+
>
|
|
2940
|
+
Add Provider
|
|
2941
|
+
</button>
|
|
2942
|
+
</div>
|
|
2943
|
+
<div class="executor-grid">
|
|
2742
2944
|
<div class="form-group">
|
|
2743
2945
|
<label>Turn Detection</label>
|
|
2744
2946
|
<select value=${voiceTurnDetection} onchange=${(e) => setVoiceTurnDetection(e.target.value)}>
|
|
@@ -2765,38 +2967,25 @@ function App() {
|
|
|
2765
2967
|
</select>
|
|
2766
2968
|
</div>
|
|
2767
2969
|
</div>
|
|
2768
|
-
|
|
2970
|
+
<div class="form-group">
|
|
2971
|
+
<label>OpenAI Realtime API Key</label>
|
|
2972
|
+
<input
|
|
2973
|
+
type="password"
|
|
2974
|
+
value=${openaiRealtimeApiKey}
|
|
2975
|
+
oninput=${(e) => setOpenaiRealtimeApiKey(e.target.value)}
|
|
2976
|
+
placeholder="Optional - defaults to OPENAI_API_KEY"
|
|
2977
|
+
/>
|
|
2978
|
+
<div class="hint">Leave blank to use OPENAI_API_KEY.</div>
|
|
2979
|
+
</div>
|
|
2980
|
+
<div class="executor-grid">
|
|
2769
2981
|
<div class="form-group">
|
|
2770
|
-
<label>
|
|
2982
|
+
<label>Azure Realtime Endpoint</label>
|
|
2771
2983
|
<input
|
|
2772
|
-
type="
|
|
2773
|
-
value=${
|
|
2774
|
-
oninput=${(e) =>
|
|
2775
|
-
placeholder="
|
|
2984
|
+
type="text"
|
|
2985
|
+
value=${azureOpenaiRealtimeEndpoint}
|
|
2986
|
+
oninput=${(e) => setAzureOpenaiRealtimeEndpoint(e.target.value)}
|
|
2987
|
+
placeholder="https://<resource>.openai.azure.com"
|
|
2776
2988
|
/>
|
|
2777
|
-
<div class="hint">Leave blank to use OPENAI_API_KEY.</div>
|
|
2778
|
-
</div>
|
|
2779
|
-
`}
|
|
2780
|
-
${(voiceProvider === "auto" || voiceProvider === "azure") && html`
|
|
2781
|
-
<div class="executor-grid">
|
|
2782
|
-
<div class="form-group">
|
|
2783
|
-
<label>Azure Realtime Endpoint</label>
|
|
2784
|
-
<input
|
|
2785
|
-
type="text"
|
|
2786
|
-
value=${azureOpenaiRealtimeEndpoint}
|
|
2787
|
-
oninput=${(e) => setAzureOpenaiRealtimeEndpoint(e.target.value)}
|
|
2788
|
-
placeholder="https://<resource>.openai.azure.com"
|
|
2789
|
-
/>
|
|
2790
|
-
</div>
|
|
2791
|
-
<div class="form-group">
|
|
2792
|
-
<label>Azure Realtime Deployment</label>
|
|
2793
|
-
<input
|
|
2794
|
-
type="text"
|
|
2795
|
-
value=${azureOpenaiRealtimeDeployment}
|
|
2796
|
-
oninput=${(e) => setAzureOpenaiRealtimeDeployment(e.target.value)}
|
|
2797
|
-
placeholder="gpt-4o-realtime-preview"
|
|
2798
|
-
/>
|
|
2799
|
-
</div>
|
|
2800
2989
|
</div>
|
|
2801
2990
|
<div class="form-group">
|
|
2802
2991
|
<label>Azure Realtime API Key</label>
|
|
@@ -2807,7 +2996,7 @@ function App() {
|
|
|
2807
2996
|
placeholder="Optional - defaults to AZURE_OPENAI_API_KEY"
|
|
2808
2997
|
/>
|
|
2809
2998
|
</div>
|
|
2810
|
-
|
|
2999
|
+
</div>
|
|
2811
3000
|
`}
|
|
2812
3001
|
<//>
|
|
2813
3002
|
|
|
@@ -2879,6 +3068,17 @@ function App() {
|
|
|
2879
3068
|
|
|
2880
3069
|
const StepReview = () => {
|
|
2881
3070
|
const filteredRepos = repos.filter((r) => r.trim());
|
|
3071
|
+
const reviewVoiceProviders = normalizeVoiceProviders(voiceProviders, {
|
|
3072
|
+
provider: voiceProvider,
|
|
3073
|
+
model: voiceModel,
|
|
3074
|
+
visionModel: voiceVisionModel,
|
|
3075
|
+
voiceId,
|
|
3076
|
+
azureDeployment: azureOpenaiRealtimeDeployment,
|
|
3077
|
+
});
|
|
3078
|
+
const voiceProviderChainSummary = reviewVoiceProviders
|
|
3079
|
+
.map((provider_) => provider_.provider)
|
|
3080
|
+
.filter(Boolean)
|
|
3081
|
+
.join(" -> ");
|
|
2882
3082
|
|
|
2883
3083
|
return html`
|
|
2884
3084
|
<h2>Review & Apply</h2>
|
|
@@ -2919,7 +3119,7 @@ function App() {
|
|
|
2919
3119
|
<th>Voice</th>
|
|
2920
3120
|
<td>
|
|
2921
3121
|
${voiceEnabled
|
|
2922
|
-
? `${voiceProvider} (${voiceFallbackMode} fallback)`
|
|
3122
|
+
? `${voiceProviderChainSummary || voiceProvider} (${voiceFallbackMode} fallback)`
|
|
2923
3123
|
: "Disabled"}
|
|
2924
3124
|
</td>
|
|
2925
3125
|
</tr>
|
package/ui/styles/components.css
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
/* ─── Component Styles — iOS-style Clean Design ─── */
|
|
2
2
|
|
|
3
|
+
/* ─── Telemetry Tab ─── */
|
|
4
|
+
.telemetry-tab {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: 12px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.telemetry-grid {
|
|
11
|
+
display: grid;
|
|
12
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
13
|
+
gap: 12px;
|
|
14
|
+
align-items: stretch;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.telemetry-grid > .card {
|
|
18
|
+
margin-bottom: 0;
|
|
19
|
+
min-height: 240px;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.telemetry-grid > .card .empty-state {
|
|
25
|
+
flex: 1;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.telemetry-list {
|
|
32
|
+
list-style: none;
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.telemetry-list li {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
gap: 10px;
|
|
42
|
+
padding: 8px 0;
|
|
43
|
+
border-bottom: 1px solid var(--border);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.telemetry-list li:last-child {
|
|
47
|
+
border-bottom: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.telemetry-label {
|
|
51
|
+
min-width: 0;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
text-overflow: ellipsis;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.telemetry-count {
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
color: var(--text-primary);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (max-width: 960px) {
|
|
63
|
+
.telemetry-grid {
|
|
64
|
+
grid-template-columns: 1fr;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
3
68
|
/* ─── Cards ─── */
|
|
4
69
|
.card {
|
|
5
70
|
background: var(--bg-card);
|
|
@@ -3262,9 +3327,9 @@ select.input {
|
|
|
3262
3327
|
|
|
3263
3328
|
/* ─── Control Unit (sticky) ─── */
|
|
3264
3329
|
.control-unit-card {
|
|
3265
|
-
position:
|
|
3266
|
-
top:
|
|
3267
|
-
z-index:
|
|
3330
|
+
position: relative;
|
|
3331
|
+
top: auto;
|
|
3332
|
+
z-index: auto;
|
|
3268
3333
|
background: var(--bg-card);
|
|
3269
3334
|
}
|
|
3270
3335
|
|
|
@@ -3496,6 +3561,31 @@ select.input {
|
|
|
3496
3561
|
color: #fff;
|
|
3497
3562
|
}
|
|
3498
3563
|
|
|
3564
|
+
/* ─── Shared Save/Discard Bar ─── */
|
|
3565
|
+
.ve-save-discard-bar {
|
|
3566
|
+
display: flex;
|
|
3567
|
+
align-items: center;
|
|
3568
|
+
justify-content: space-between;
|
|
3569
|
+
gap: 10px;
|
|
3570
|
+
margin-top: 12px;
|
|
3571
|
+
padding: 10px 12px;
|
|
3572
|
+
border: 1px solid var(--border);
|
|
3573
|
+
border-radius: var(--radius-lg);
|
|
3574
|
+
background: var(--bg-card);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
.ve-save-discard-message {
|
|
3578
|
+
font-size: 12px;
|
|
3579
|
+
color: var(--text-secondary);
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
.ve-save-discard-actions {
|
|
3583
|
+
display: flex;
|
|
3584
|
+
gap: 8px;
|
|
3585
|
+
align-items: center;
|
|
3586
|
+
flex-wrap: wrap;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3499
3589
|
/* ─── Toggle disabled ─── */
|
|
3500
3590
|
.toggle-wrap.disabled {
|
|
3501
3591
|
opacity: 0.4;
|
|
@@ -3690,6 +3780,12 @@ select.input {
|
|
|
3690
3780
|
display: flex;
|
|
3691
3781
|
flex-direction: column;
|
|
3692
3782
|
gap: 16px;
|
|
3783
|
+
min-height: 0;
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
.control-main .card,
|
|
3787
|
+
.control-side .card {
|
|
3788
|
+
overflow: visible;
|
|
3693
3789
|
}
|
|
3694
3790
|
|
|
3695
3791
|
.control-hero {
|