bosun 0.37.0 → 0.37.2
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/.env.example +4 -1
- package/agent-tool-config.mjs +338 -0
- package/bosun-skills.mjs +59 -4
- package/bosun.schema.json +1 -1
- package/desktop/launch.mjs +18 -0
- package/desktop/main.mjs +52 -13
- package/fleet-coordinator.mjs +34 -1
- package/kanban-adapter.mjs +30 -3
- package/library-manager.mjs +66 -0
- package/maintenance.mjs +30 -5
- package/monitor.mjs +56 -0
- package/package.json +4 -1
- package/setup-web-server.mjs +73 -12
- package/setup.mjs +3 -3
- package/ui/app.js +40 -3
- package/ui/components/session-list.js +25 -7
- package/ui/components/workspace-switcher.js +48 -1
- package/ui/demo.html +176 -0
- package/ui/modules/mic-track-registry.js +83 -0
- package/ui/modules/settings-schema.js +4 -1
- package/ui/modules/state.js +25 -0
- package/ui/modules/streaming.js +1 -1
- package/ui/modules/voice-barge-in.js +27 -0
- package/ui/modules/voice-client-sdk.js +268 -42
- package/ui/modules/voice-client.js +665 -61
- package/ui/modules/voice-overlay.js +829 -47
- package/ui/setup.html +151 -9
- package/ui/styles.css +258 -0
- package/ui/tabs/chat.js +11 -0
- package/ui/tabs/library.js +890 -15
- package/ui/tabs/settings.js +51 -11
- package/ui/tabs/telemetry.js +327 -105
- package/ui/tabs/workflows.js +86 -0
- package/ui-server.mjs +1201 -107
- package/voice-action-dispatcher.mjs +81 -0
- package/voice-agents-sdk.mjs +2 -2
- package/voice-relay.mjs +131 -14
- package/voice-tools.mjs +475 -9
- package/workflow-engine.mjs +54 -0
- package/workflow-nodes.mjs +177 -28
- package/workflow-templates/github.mjs +205 -94
- package/workflow-templates/task-batch.mjs +247 -0
- package/workflow-templates.mjs +15 -0
package/ui/setup.html
CHANGED
|
@@ -1006,6 +1006,9 @@ function App() {
|
|
|
1006
1006
|
const [voiceTurnDetection, setVoiceTurnDetection] = useState("server_vad");
|
|
1007
1007
|
const [voiceFallbackMode, setVoiceFallbackMode] = useState("browser");
|
|
1008
1008
|
const [voiceDelegateExecutor, setVoiceDelegateExecutor] = useState("codex-sdk");
|
|
1009
|
+
const [voiceTranscriptionEnabled, setVoiceTranscriptionEnabled] = useState(true);
|
|
1010
|
+
const [voiceTranscriptionModel, setVoiceTranscriptionModel] = useState("gpt-4o-transcribe");
|
|
1011
|
+
const [voiceAzureTranscriptionEnabled, setVoiceAzureTranscriptionEnabled] = useState(false);
|
|
1009
1012
|
const [openaiRealtimeApiKey, setOpenaiRealtimeApiKey] = useState("");
|
|
1010
1013
|
const [azureOpenaiRealtimeEndpoint, setAzureOpenaiRealtimeEndpoint] = useState("");
|
|
1011
1014
|
const [azureOpenaiRealtimeApiKey, setAzureOpenaiRealtimeApiKey] = useState("");
|
|
@@ -1262,6 +1265,9 @@ function App() {
|
|
|
1262
1265
|
const defaultModel = provider === "custom"
|
|
1263
1266
|
? (ALL_ENDPOINT_MODEL_OPTIONS[0] || "gpt-realtime-1.5")
|
|
1264
1267
|
: (getVoiceProviderModelDefaults(provider).model || "gpt-realtime-1.5");
|
|
1268
|
+
const transcriptionEnabled = ep.transcriptionEnabled == null
|
|
1269
|
+
? provider !== "azure"
|
|
1270
|
+
: ep.transcriptionEnabled !== false;
|
|
1265
1271
|
return {
|
|
1266
1272
|
id: ep.id || `ep-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1267
1273
|
name: String(ep.name || "").trim(),
|
|
@@ -1274,6 +1280,8 @@ function App() {
|
|
|
1274
1280
|
weight: typeof ep.weight === "number" ? ep.weight : 100,
|
|
1275
1281
|
enabled: ep.enabled !== false,
|
|
1276
1282
|
visionModel: String(ep.visionModel || "").trim(),
|
|
1283
|
+
transcriptionModel: String(ep.transcriptionModel || "").trim(),
|
|
1284
|
+
transcriptionEnabled,
|
|
1277
1285
|
authSource,
|
|
1278
1286
|
};
|
|
1279
1287
|
};
|
|
@@ -1298,6 +1306,7 @@ function App() {
|
|
|
1298
1306
|
? String(next.endpoint || "")
|
|
1299
1307
|
: getDefaultVoiceEndpointUrl(nextProvider, next.authSource || "apiKey");
|
|
1300
1308
|
if (nextProvider !== "azure") next.deployment = "";
|
|
1309
|
+
next.transcriptionEnabled = nextProvider === "azure" ? false : true;
|
|
1301
1310
|
}
|
|
1302
1311
|
if (field === "authSource" && !isEndpointUrlEditable(next.provider)) {
|
|
1303
1312
|
next.endpoint = getDefaultVoiceEndpointUrl(next.provider, String(value || "apiKey"));
|
|
@@ -1550,6 +1559,9 @@ function App() {
|
|
|
1550
1559
|
if (d.voiceTurnDetection) { setVoiceTurnDetection(d.voiceTurnDetection); }
|
|
1551
1560
|
if (d.voiceFallbackMode) { setVoiceFallbackMode(d.voiceFallbackMode); }
|
|
1552
1561
|
if (d.voiceDelegateExecutor) { setVoiceDelegateExecutor(d.voiceDelegateExecutor); }
|
|
1562
|
+
if (d.voiceTranscriptionEnabled !== undefined) { setVoiceTranscriptionEnabled(d.voiceTranscriptionEnabled !== false); }
|
|
1563
|
+
if (d.voiceTranscriptionModel) { setVoiceTranscriptionModel(d.voiceTranscriptionModel); }
|
|
1564
|
+
if (d.voiceAzureTranscriptionEnabled !== undefined) { setVoiceAzureTranscriptionEnabled(d.voiceAzureTranscriptionEnabled === true); }
|
|
1553
1565
|
if (d.openaiRealtimeApiKey) { setOpenaiRealtimeApiKey(d.openaiRealtimeApiKey); }
|
|
1554
1566
|
if (d.azureOpenaiRealtimeEndpoint) { setAzureOpenaiRealtimeEndpoint(d.azureOpenaiRealtimeEndpoint); }
|
|
1555
1567
|
if (d.azureOpenaiRealtimeApiKey) { setAzureOpenaiRealtimeApiKey(d.azureOpenaiRealtimeApiKey); }
|
|
@@ -1574,6 +1586,9 @@ function App() {
|
|
|
1574
1586
|
if (existingVoice.turnDetection) { setVoiceTurnDetection(String(existingVoice.turnDetection)); envLoaded = true; }
|
|
1575
1587
|
if (existingVoice.fallbackMode) { setVoiceFallbackMode(String(existingVoice.fallbackMode)); envLoaded = true; }
|
|
1576
1588
|
if (existingVoice.delegateExecutor) { setVoiceDelegateExecutor(String(existingVoice.delegateExecutor)); envLoaded = true; }
|
|
1589
|
+
if (existingVoice.transcriptionEnabled != null) { setVoiceTranscriptionEnabled(existingVoice.transcriptionEnabled !== false); envLoaded = true; }
|
|
1590
|
+
if (existingVoice.transcriptionModel) { setVoiceTranscriptionModel(String(existingVoice.transcriptionModel)); envLoaded = true; }
|
|
1591
|
+
if (existingVoice.azureTranscriptionEnabled != null) { setVoiceAzureTranscriptionEnabled(existingVoice.azureTranscriptionEnabled === true); envLoaded = true; }
|
|
1577
1592
|
if (existingVoice.openaiApiKey) { setOpenaiRealtimeApiKey(String(existingVoice.openaiApiKey)); envLoaded = true; }
|
|
1578
1593
|
if (existingVoice.azureEndpoint) { setAzureOpenaiRealtimeEndpoint(String(existingVoice.azureEndpoint)); envLoaded = true; }
|
|
1579
1594
|
if (existingVoice.azureApiKey) { setAzureOpenaiRealtimeApiKey(String(existingVoice.azureApiKey)); envLoaded = true; }
|
|
@@ -1586,10 +1601,29 @@ function App() {
|
|
|
1586
1601
|
// Auto-convert legacy flat fields to endpoint cards
|
|
1587
1602
|
const legacyEndpoints = [];
|
|
1588
1603
|
if (existingVoice.azureEndpoint || existingVoice.azureApiKey) {
|
|
1589
|
-
legacyEndpoints.push(normalizeVoiceEndpoint({
|
|
1604
|
+
legacyEndpoints.push(normalizeVoiceEndpoint({
|
|
1605
|
+
name: "azure-primary",
|
|
1606
|
+
provider: "azure",
|
|
1607
|
+
endpoint: existingVoice.azureEndpoint || "",
|
|
1608
|
+
deployment: existingVoice.azureDeployment || "gpt-realtime-1.5",
|
|
1609
|
+
apiKey: existingVoice.azureApiKey || "",
|
|
1610
|
+
role: "primary",
|
|
1611
|
+
weight: 100,
|
|
1612
|
+
transcriptionEnabled: existingVoice.azureTranscriptionEnabled === true,
|
|
1613
|
+
transcriptionModel: existingVoice.transcriptionModel || "gpt-4o-transcribe",
|
|
1614
|
+
}));
|
|
1590
1615
|
}
|
|
1591
1616
|
if (existingVoice.openaiApiKey) {
|
|
1592
|
-
legacyEndpoints.push(normalizeVoiceEndpoint({
|
|
1617
|
+
legacyEndpoints.push(normalizeVoiceEndpoint({
|
|
1618
|
+
name: "openai-primary",
|
|
1619
|
+
provider: "openai",
|
|
1620
|
+
apiKey: existingVoice.openaiApiKey || "",
|
|
1621
|
+
model: existingVoice.model || "gpt-realtime-1.5",
|
|
1622
|
+
role: legacyEndpoints.length === 0 ? "primary" : "backup",
|
|
1623
|
+
weight: legacyEndpoints.length === 0 ? 100 : 50,
|
|
1624
|
+
transcriptionEnabled: existingVoice.transcriptionEnabled !== false,
|
|
1625
|
+
transcriptionModel: existingVoice.transcriptionModel || "gpt-4o-transcribe",
|
|
1626
|
+
}));
|
|
1593
1627
|
}
|
|
1594
1628
|
if (legacyEndpoints.length > 0) setVoiceEndpoints(legacyEndpoints);
|
|
1595
1629
|
}
|
|
@@ -1723,6 +1757,9 @@ function App() {
|
|
|
1723
1757
|
if (env.VOICE_TURN_DETECTION) { setVoiceTurnDetection(env.VOICE_TURN_DETECTION); envLoaded = true; }
|
|
1724
1758
|
if (env.VOICE_FALLBACK_MODE) { setVoiceFallbackMode(env.VOICE_FALLBACK_MODE); envLoaded = true; }
|
|
1725
1759
|
if (env.VOICE_DELEGATE_EXECUTOR) { setVoiceDelegateExecutor(env.VOICE_DELEGATE_EXECUTOR); envLoaded = true; }
|
|
1760
|
+
if (env.VOICE_TRANSCRIPTION_ENABLED !== undefined) { setVoiceTranscriptionEnabled(env.VOICE_TRANSCRIPTION_ENABLED !== "false"); envLoaded = true; }
|
|
1761
|
+
if (env.VOICE_TRANSCRIPTION_MODEL) { setVoiceTranscriptionModel(env.VOICE_TRANSCRIPTION_MODEL); envLoaded = true; }
|
|
1762
|
+
if (env.VOICE_AZURE_TRANSCRIPTION_ENABLED !== undefined) { setVoiceAzureTranscriptionEnabled(env.VOICE_AZURE_TRANSCRIPTION_ENABLED === "true"); envLoaded = true; }
|
|
1726
1763
|
if (env.OPENAI_REALTIME_API_KEY) { setOpenaiRealtimeApiKey(env.OPENAI_REALTIME_API_KEY); envLoaded = true; }
|
|
1727
1764
|
if (env.AZURE_OPENAI_REALTIME_ENDPOINT) { setAzureOpenaiRealtimeEndpoint(env.AZURE_OPENAI_REALTIME_ENDPOINT); envLoaded = true; }
|
|
1728
1765
|
if (env.AZURE_OPENAI_REALTIME_API_KEY) { setAzureOpenaiRealtimeApiKey(env.AZURE_OPENAI_REALTIME_API_KEY); envLoaded = true; }
|
|
@@ -1730,10 +1767,29 @@ function App() {
|
|
|
1730
1767
|
// Build voiceEndpoints from env vars if not already populated from config
|
|
1731
1768
|
const envEps = [];
|
|
1732
1769
|
if (env.AZURE_OPENAI_REALTIME_ENDPOINT || env.AZURE_OPENAI_REALTIME_API_KEY) {
|
|
1733
|
-
envEps.push(normalizeVoiceEndpoint({
|
|
1770
|
+
envEps.push(normalizeVoiceEndpoint({
|
|
1771
|
+
name: "azure-primary",
|
|
1772
|
+
provider: "azure",
|
|
1773
|
+
endpoint: env.AZURE_OPENAI_REALTIME_ENDPOINT || "",
|
|
1774
|
+
deployment: env.AZURE_OPENAI_REALTIME_DEPLOYMENT || "gpt-realtime-1.5",
|
|
1775
|
+
apiKey: env.AZURE_OPENAI_REALTIME_API_KEY || "",
|
|
1776
|
+
role: "primary",
|
|
1777
|
+
weight: 100,
|
|
1778
|
+
transcriptionEnabled: env.VOICE_AZURE_TRANSCRIPTION_ENABLED === "true",
|
|
1779
|
+
transcriptionModel: env.VOICE_TRANSCRIPTION_MODEL || "gpt-4o-transcribe",
|
|
1780
|
+
}));
|
|
1734
1781
|
}
|
|
1735
1782
|
if (env.OPENAI_REALTIME_API_KEY) {
|
|
1736
|
-
envEps.push(normalizeVoiceEndpoint({
|
|
1783
|
+
envEps.push(normalizeVoiceEndpoint({
|
|
1784
|
+
name: "openai-primary",
|
|
1785
|
+
provider: "openai",
|
|
1786
|
+
apiKey: env.OPENAI_REALTIME_API_KEY,
|
|
1787
|
+
model: env.VOICE_MODEL || "gpt-realtime-1.5",
|
|
1788
|
+
role: envEps.length === 0 ? "primary" : "backup",
|
|
1789
|
+
weight: envEps.length === 0 ? 100 : 50,
|
|
1790
|
+
transcriptionEnabled: env.VOICE_TRANSCRIPTION_ENABLED !== "false",
|
|
1791
|
+
transcriptionModel: env.VOICE_TRANSCRIPTION_MODEL || "gpt-4o-transcribe",
|
|
1792
|
+
}));
|
|
1737
1793
|
}
|
|
1738
1794
|
if (envEps.length > 0) { setVoiceEndpoints((prev) => (prev.length > 0 ? prev : envEps)); envLoaded = true; }
|
|
1739
1795
|
const resolvedVoiceProvider = String(env.VOICE_PROVIDER || existingVoice.provider || d.voiceProvider || voiceProvider || "openai").trim().toLowerCase();
|
|
@@ -2093,6 +2149,9 @@ function App() {
|
|
|
2093
2149
|
voiceTurnDetection,
|
|
2094
2150
|
voiceFallbackMode,
|
|
2095
2151
|
voiceDelegateExecutor,
|
|
2152
|
+
voiceTranscriptionEnabled,
|
|
2153
|
+
voiceTranscriptionModel,
|
|
2154
|
+
voiceAzureTranscriptionEnabled,
|
|
2096
2155
|
openaiRealtimeApiKey,
|
|
2097
2156
|
azureOpenaiRealtimeEndpoint,
|
|
2098
2157
|
azureOpenaiRealtimeApiKey,
|
|
@@ -2167,12 +2226,29 @@ function App() {
|
|
|
2167
2226
|
turnDetection: voiceTurnDetection,
|
|
2168
2227
|
fallbackMode: voiceFallbackMode,
|
|
2169
2228
|
delegateExecutor: voiceDelegateExecutor,
|
|
2229
|
+
transcriptionEnabled: voiceTranscriptionEnabled !== false,
|
|
2230
|
+
transcriptionModel: String(voiceTranscriptionModel || "").trim() || "gpt-4o-transcribe",
|
|
2231
|
+
azureTranscriptionEnabled: voiceAzureTranscriptionEnabled === true,
|
|
2170
2232
|
azureDeployment: primaryAzureEp?.deployment || primaryVoiceProvider.azureDeployment || azureOpenaiRealtimeDeployment,
|
|
2171
2233
|
openaiApiKey: primaryOpenAIEp?.apiKey || openaiRealtimeApiKey,
|
|
2172
2234
|
azureEndpoint: primaryAzureEp?.endpoint || azureOpenaiRealtimeEndpoint,
|
|
2173
2235
|
azureApiKey: primaryAzureEp?.apiKey || azureOpenaiRealtimeApiKey,
|
|
2174
2236
|
providers: normalizedVoiceProviders.slice(0, 5),
|
|
2175
|
-
voiceEndpoints: voiceEndpoints.filter((ep) => ep.endpoint || ep.apiKey || ep.deployment || ep.model).map((ep) => ({
|
|
2237
|
+
voiceEndpoints: voiceEndpoints.filter((ep) => ep.endpoint || ep.apiKey || ep.deployment || ep.model).map((ep) => ({
|
|
2238
|
+
id: ep.id,
|
|
2239
|
+
name: ep.name,
|
|
2240
|
+
provider: ep.provider,
|
|
2241
|
+
endpoint: ep.endpoint,
|
|
2242
|
+
deployment: ep.deployment,
|
|
2243
|
+
apiKey: ep.apiKey,
|
|
2244
|
+
model: ep.model,
|
|
2245
|
+
visionModel: ep.visionModel,
|
|
2246
|
+
transcriptionModel: ep.transcriptionModel,
|
|
2247
|
+
transcriptionEnabled: ep.transcriptionEnabled !== false,
|
|
2248
|
+
role: ep.role,
|
|
2249
|
+
weight: ep.weight,
|
|
2250
|
+
enabled: ep.enabled,
|
|
2251
|
+
})),
|
|
2176
2252
|
failover: {
|
|
2177
2253
|
enabled: true,
|
|
2178
2254
|
maxAttempts: voiceFailoverMaxAttempts,
|
|
@@ -3781,6 +3857,40 @@ function App() {
|
|
|
3781
3857
|
</select>
|
|
3782
3858
|
<div class="hint">Pick the same executor family you trust for task execution to keep behavior consistent.</div>
|
|
3783
3859
|
</div>
|
|
3860
|
+
<div class="form-group">
|
|
3861
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none">
|
|
3862
|
+
<input
|
|
3863
|
+
type="checkbox"
|
|
3864
|
+
checked=${voiceTranscriptionEnabled}
|
|
3865
|
+
onchange=${(e) => setVoiceTranscriptionEnabled(e.target.checked)}
|
|
3866
|
+
style="width:auto;padding:0;margin:0;accent-color:var(--accent);cursor:pointer"
|
|
3867
|
+
/>
|
|
3868
|
+
Enable Input Transcription (OpenAI-compatible)
|
|
3869
|
+
</label>
|
|
3870
|
+
<div class="hint">Controls VOICE_TRANSCRIPTION_ENABLED for default realtime sessions.</div>
|
|
3871
|
+
</div>
|
|
3872
|
+
<div class="form-group">
|
|
3873
|
+
<label>Transcription Model</label>
|
|
3874
|
+
<input
|
|
3875
|
+
type="text"
|
|
3876
|
+
value=${voiceTranscriptionModel}
|
|
3877
|
+
oninput=${(e) => setVoiceTranscriptionModel(e.target.value)}
|
|
3878
|
+
placeholder="gpt-4o-transcribe"
|
|
3879
|
+
/>
|
|
3880
|
+
<div class="hint">Controls VOICE_TRANSCRIPTION_MODEL for OpenAI/Azure endpoint defaults.</div>
|
|
3881
|
+
</div>
|
|
3882
|
+
<div class="form-group">
|
|
3883
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none">
|
|
3884
|
+
<input
|
|
3885
|
+
type="checkbox"
|
|
3886
|
+
checked=${voiceAzureTranscriptionEnabled}
|
|
3887
|
+
onchange=${(e) => setVoiceAzureTranscriptionEnabled(e.target.checked)}
|
|
3888
|
+
style="width:auto;padding:0;margin:0;accent-color:var(--accent);cursor:pointer"
|
|
3889
|
+
/>
|
|
3890
|
+
Enable Azure Input Transcription
|
|
3891
|
+
</label>
|
|
3892
|
+
<div class="hint">Controls VOICE_AZURE_TRANSCRIPTION_ENABLED. Default is OFF to reduce Azure item-level transcription errors.</div>
|
|
3893
|
+
</div>
|
|
3784
3894
|
</div>
|
|
3785
3895
|
<div class="form-group">
|
|
3786
3896
|
<label style="font-weight:600;display:block;margin-bottom:4px">Voice Endpoints</label>
|
|
@@ -3850,9 +3960,14 @@ function App() {
|
|
|
3850
3960
|
<input type="text" value=${ep.endpoint} oninput=${(e) => updateVoiceEndpoint(i, "endpoint", e.target.value)} placeholder="https://<resource>.openai.azure.com" />
|
|
3851
3961
|
</div>
|
|
3852
3962
|
<div class="form-group">
|
|
3853
|
-
<label>Deployment</label>
|
|
3854
|
-
<input type="text" value=${ep.deployment} oninput=${(e) => updateVoiceEndpoint(i, "deployment", e.target.value)} placeholder="gpt-realtime
|
|
3855
|
-
<div class="hint">
|
|
3963
|
+
<label>Deployment Name</label>
|
|
3964
|
+
<input type="text" value=${ep.deployment} oninput=${(e) => updateVoiceEndpoint(i, "deployment", e.target.value)} placeholder="my-gpt-4o-realtime" />
|
|
3965
|
+
<div class="hint">The deployment name from Azure AI Foundry (not the model name). Find it under your resource → Deployments. Leave empty to test credentials only.</div>
|
|
3966
|
+
</div>
|
|
3967
|
+
<div class="form-group">
|
|
3968
|
+
<label>Audio Model (Realtime)</label>
|
|
3969
|
+
<input type="text" value=${ep.model} oninput=${(e) => updateVoiceEndpoint(i, "model", e.target.value)} placeholder="gpt-4o-realtime-preview" />
|
|
3970
|
+
<div class="hint">The underlying model name (e.g. gpt-4o-realtime-preview). Used at runtime.</div>
|
|
3856
3971
|
</div>
|
|
3857
3972
|
`}
|
|
3858
3973
|
${ep.provider === "custom" && html`
|
|
@@ -3933,6 +4048,34 @@ function App() {
|
|
|
3933
4048
|
<input type="text" value=${ep.visionModel || ""} oninput=${(e) => updateVoiceEndpoint(i, "visionModel", e.target.value)} placeholder="e.g. gpt-4.1-nano" />
|
|
3934
4049
|
<div class="hint">Optional model for vision/image tasks on this endpoint.</div>
|
|
3935
4050
|
</div>
|
|
4051
|
+
${(ep.provider === "openai" || ep.provider === "azure") && html`
|
|
4052
|
+
<div class="form-group">
|
|
4053
|
+
<label>Transcription Model</label>
|
|
4054
|
+
<input
|
|
4055
|
+
type="text"
|
|
4056
|
+
value=${ep.transcriptionModel || ""}
|
|
4057
|
+
oninput=${(e) => updateVoiceEndpoint(i, "transcriptionModel", e.target.value)}
|
|
4058
|
+
placeholder="gpt-4o-transcribe"
|
|
4059
|
+
/>
|
|
4060
|
+
<div class="hint">Leave blank to use global VOICE_TRANSCRIPTION_MODEL.</div>
|
|
4061
|
+
</div>
|
|
4062
|
+
<div class="form-group">
|
|
4063
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none">
|
|
4064
|
+
<input
|
|
4065
|
+
type="checkbox"
|
|
4066
|
+
checked=${ep.transcriptionEnabled !== false}
|
|
4067
|
+
onchange=${(e) => updateVoiceEndpoint(i, "transcriptionEnabled", e.target.checked)}
|
|
4068
|
+
style="width:auto;padding:0;margin:0;accent-color:var(--accent);cursor:pointer"
|
|
4069
|
+
/>
|
|
4070
|
+
Enable Input Transcription
|
|
4071
|
+
</label>
|
|
4072
|
+
<div class="hint">
|
|
4073
|
+
${ep.provider === "azure"
|
|
4074
|
+
? "Azure defaults to OFF unless explicitly enabled."
|
|
4075
|
+
: "OpenAI defaults to ON unless explicitly disabled."}
|
|
4076
|
+
</div>
|
|
4077
|
+
</div>
|
|
4078
|
+
`}
|
|
3936
4079
|
</div>
|
|
3937
4080
|
<!-- Test Connection -->
|
|
3938
4081
|
<div style="display:flex;align-items:center;gap:10px;margin-top:8px">
|
|
@@ -4333,4 +4476,3 @@ render(html`<${App} />`, document.getElementById("app"));
|
|
|
4333
4476
|
</body>
|
|
4334
4477
|
<script defer src="https://cloud.umami.is/script.js" data-website-id="24c5d605-7f25-4be5-875e-25c8f3cb4059"></script>
|
|
4335
4478
|
</html>
|
|
4336
|
-
|
package/ui/styles.css
CHANGED
|
@@ -392,3 +392,261 @@
|
|
|
392
392
|
color: var(--text-hint);
|
|
393
393
|
margin-top: 4px;
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
/* ─── Usage Analytics Tab ─── */
|
|
397
|
+
.analytics-tab {
|
|
398
|
+
display: flex;
|
|
399
|
+
flex-direction: column;
|
|
400
|
+
gap: 18px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.analytics-header {
|
|
404
|
+
flex-wrap: wrap;
|
|
405
|
+
gap: 10px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.analytics-title-row {
|
|
409
|
+
display: flex;
|
|
410
|
+
align-items: baseline;
|
|
411
|
+
gap: 12px;
|
|
412
|
+
flex-wrap: wrap;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.analytics-since {
|
|
416
|
+
font-size: 12px;
|
|
417
|
+
color: var(--text-hint);
|
|
418
|
+
font-weight: 400;
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: 4px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.analytics-header-actions {
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
gap: 8px;
|
|
428
|
+
flex-wrap: wrap;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* Period toggle */
|
|
432
|
+
.analytics-period-toggle {
|
|
433
|
+
display: flex;
|
|
434
|
+
border: 1px solid var(--border);
|
|
435
|
+
border-radius: 8px;
|
|
436
|
+
overflow: hidden;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.analytics-period-btn {
|
|
440
|
+
background: none;
|
|
441
|
+
border: none;
|
|
442
|
+
color: var(--text-hint);
|
|
443
|
+
padding: 4px 12px;
|
|
444
|
+
font-size: 12px;
|
|
445
|
+
font-weight: 500;
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
transition: background 0.15s, color 0.15s;
|
|
448
|
+
border-right: 1px solid var(--border);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.analytics-period-btn:last-child {
|
|
452
|
+
border-right: none;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.analytics-period-btn:hover {
|
|
456
|
+
background: var(--surface-hover);
|
|
457
|
+
color: var(--text-primary);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.analytics-period-btn.active {
|
|
461
|
+
background: var(--accent-muted);
|
|
462
|
+
color: var(--accent);
|
|
463
|
+
font-weight: 600;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Stats row */
|
|
467
|
+
.analytics-stats-row {
|
|
468
|
+
display: grid;
|
|
469
|
+
gap: 12px;
|
|
470
|
+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.analytics-stat {
|
|
474
|
+
display: flex;
|
|
475
|
+
align-items: center;
|
|
476
|
+
gap: 12px;
|
|
477
|
+
padding: 14px 16px;
|
|
478
|
+
background: var(--surface);
|
|
479
|
+
border: 1px solid var(--border);
|
|
480
|
+
border-radius: 12px;
|
|
481
|
+
transition: border-color 0.15s;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.analytics-stat:hover {
|
|
485
|
+
border-color: var(--accent-muted);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.analytics-stat-icon {
|
|
489
|
+
font-size: 20px;
|
|
490
|
+
opacity: 0.8;
|
|
491
|
+
flex-shrink: 0;
|
|
492
|
+
width: 28px;
|
|
493
|
+
text-align: center;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.analytics-stat-label {
|
|
497
|
+
font-size: 11px;
|
|
498
|
+
text-transform: uppercase;
|
|
499
|
+
letter-spacing: 0.08em;
|
|
500
|
+
color: var(--text-hint);
|
|
501
|
+
line-height: 1;
|
|
502
|
+
margin-bottom: 5px;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.analytics-stat-value {
|
|
506
|
+
font-size: 22px;
|
|
507
|
+
font-weight: 700;
|
|
508
|
+
color: var(--text-primary);
|
|
509
|
+
line-height: 1;
|
|
510
|
+
letter-spacing: -0.02em;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Trend chart card */
|
|
514
|
+
.analytics-trend-card {
|
|
515
|
+
overflow: hidden;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.analytics-trend-header {
|
|
519
|
+
display: flex;
|
|
520
|
+
align-items: center;
|
|
521
|
+
justify-content: space-between;
|
|
522
|
+
margin-bottom: 10px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.analytics-trend-tabs {
|
|
526
|
+
display: flex;
|
|
527
|
+
gap: 4px;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.analytics-trend-tab {
|
|
531
|
+
background: none;
|
|
532
|
+
border: 1px solid var(--border);
|
|
533
|
+
border-radius: 6px;
|
|
534
|
+
color: var(--text-hint);
|
|
535
|
+
padding: 3px 10px;
|
|
536
|
+
font-size: 11px;
|
|
537
|
+
font-weight: 500;
|
|
538
|
+
cursor: pointer;
|
|
539
|
+
transition: all 0.15s;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.analytics-trend-tab:hover {
|
|
543
|
+
background: var(--surface-hover);
|
|
544
|
+
color: var(--text-primary);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.analytics-trend-tab.active {
|
|
548
|
+
background: var(--accent-muted);
|
|
549
|
+
border-color: var(--accent);
|
|
550
|
+
color: var(--accent);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* Legend */
|
|
554
|
+
.analytics-legend {
|
|
555
|
+
margin-bottom: 8px;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.analytics-legend-group {
|
|
559
|
+
display: flex;
|
|
560
|
+
flex-wrap: wrap;
|
|
561
|
+
align-items: center;
|
|
562
|
+
gap: 10px;
|
|
563
|
+
font-size: 11px;
|
|
564
|
+
color: var(--text-secondary);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.analytics-legend-category {
|
|
568
|
+
font-weight: 700;
|
|
569
|
+
font-size: 10px;
|
|
570
|
+
letter-spacing: 0.08em;
|
|
571
|
+
text-transform: uppercase;
|
|
572
|
+
color: var(--text-hint);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.analytics-legend-item {
|
|
576
|
+
display: flex;
|
|
577
|
+
align-items: center;
|
|
578
|
+
gap: 5px;
|
|
579
|
+
font-size: 11px;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.analytics-legend-dot {
|
|
583
|
+
display: inline-block;
|
|
584
|
+
width: 8px;
|
|
585
|
+
height: 8px;
|
|
586
|
+
border-radius: 50%;
|
|
587
|
+
flex-shrink: 0;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* SVG chart */
|
|
591
|
+
.analytics-trend-svg {
|
|
592
|
+
display: block;
|
|
593
|
+
width: 100%;
|
|
594
|
+
height: auto;
|
|
595
|
+
max-height: 160px;
|
|
596
|
+
margin-top: 4px;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* Top-N grid */
|
|
600
|
+
.analytics-top-grid {
|
|
601
|
+
display: grid;
|
|
602
|
+
gap: 14px;
|
|
603
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/* Bar chart */
|
|
607
|
+
.analytics-bar-list {
|
|
608
|
+
list-style: none;
|
|
609
|
+
padding: 0;
|
|
610
|
+
margin: 0;
|
|
611
|
+
display: flex;
|
|
612
|
+
flex-direction: column;
|
|
613
|
+
gap: 8px;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.analytics-bar-row {
|
|
617
|
+
display: flex;
|
|
618
|
+
align-items: center;
|
|
619
|
+
gap: 8px;
|
|
620
|
+
font-size: 12px;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.analytics-bar-label {
|
|
624
|
+
flex: 0 0 90px;
|
|
625
|
+
color: var(--text-secondary);
|
|
626
|
+
white-space: nowrap;
|
|
627
|
+
overflow: hidden;
|
|
628
|
+
text-overflow: ellipsis;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.analytics-bar-track {
|
|
632
|
+
flex: 1;
|
|
633
|
+
height: 6px;
|
|
634
|
+
background: var(--surface-hover);
|
|
635
|
+
border-radius: 3px;
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.analytics-bar-fill {
|
|
640
|
+
height: 100%;
|
|
641
|
+
border-radius: 3px;
|
|
642
|
+
transition: width 0.4s ease;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.analytics-bar-count {
|
|
646
|
+
flex: 0 0 28px;
|
|
647
|
+
text-align: right;
|
|
648
|
+
font-weight: 600;
|
|
649
|
+
color: var(--text-primary);
|
|
650
|
+
font-size: 12px;
|
|
651
|
+
}
|
|
652
|
+
|
package/ui/tabs/chat.js
CHANGED
|
@@ -334,6 +334,17 @@ export function ChatTab() {
|
|
|
334
334
|
};
|
|
335
335
|
}, []);
|
|
336
336
|
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
const onWorkspaceSwitched = () => {
|
|
339
|
+
selectedSessionId.value = null;
|
|
340
|
+
loadSessions({ type: "primary" }).catch(() => {});
|
|
341
|
+
};
|
|
342
|
+
window.addEventListener("ve:workspace-switched", onWorkspaceSwitched);
|
|
343
|
+
return () => {
|
|
344
|
+
window.removeEventListener("ve:workspace-switched", onWorkspaceSwitched);
|
|
345
|
+
};
|
|
346
|
+
}, []);
|
|
347
|
+
|
|
337
348
|
/* ── Track mobile viewport to avoid auto-select loops ── */
|
|
338
349
|
useEffect(() => {
|
|
339
350
|
const mq = globalThis.matchMedia?.("(max-width: 768px)");
|