@phenx-inc/ctlsurf 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/headless/index.mjs +26 -10
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +31 -9
- package/out/preload/index.js +8 -0
- package/out/renderer/assets/{cssMode-DbMmcl1h.js → cssMode-BQN8v2ok.js} +3 -3
- package/out/renderer/assets/{freemarker2-CvaHiy92.js → freemarker2-DbxGYYVp.js} +1 -1
- package/out/renderer/assets/{handlebars-D58lUIOu.js → handlebars-3auU1CAd.js} +1 -1
- package/out/renderer/assets/{html-D1h1aJbM.js → html-D8xFiRmI.js} +1 -1
- package/out/renderer/assets/{htmlMode-BdkAp9qr.js → htmlMode-M3MApZ4n.js} +3 -3
- package/out/renderer/assets/{index-B60JU1yI.js → index---H6cxNl.js} +854 -38
- package/out/renderer/assets/{index-DJFYmHjz.css → index-B-iM7dFC.css} +269 -0
- package/out/renderer/assets/{javascript-CXqZcnvb.js → javascript-BO_ViZM5.js} +2 -2
- package/out/renderer/assets/{jsonMode-BuVr-eSl.js → jsonMode-CKp2zvZu.js} +3 -3
- package/out/renderer/assets/{liquid-LKu0Wd0B.js → liquid-C1eHcrht.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-Cjr_4HGs.js → lspLanguageFeatures-CHWJx_Tl.js} +1 -1
- package/out/renderer/assets/{mdx-Bl84ILla.js → mdx-Qqdtk7fL.js} +1 -1
- package/out/renderer/assets/{python-0sFd9G1k.js → python-DKu7rNbs.js} +1 -1
- package/out/renderer/assets/{razor-Cqcu1rLJ.js → razor-BOMpCo6z.js} +1 -1
- package/out/renderer/assets/{tsMode-CYd3NUkW.js → tsMode-yAjlPR-D.js} +1 -1
- package/out/renderer/assets/{typescript-rkc9lhpi.js → typescript-BiJRCUcL.js} +1 -1
- package/out/renderer/assets/{xml-EsHEUps1.js → xml-D4PvYeQq.js} +1 -1
- package/out/renderer/assets/{yaml-B9-nQ_s2.js → yaml-BeHVkmnS.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.ts +7 -0
- package/src/main/orchestrator.ts +38 -9
- package/src/preload/index.ts +11 -0
- package/src/renderer/App.tsx +39 -6
- package/src/renderer/components/FloatingMic.tsx +128 -0
- package/src/renderer/components/SpeakControls.tsx +235 -0
- package/src/renderer/components/VoiceInput.tsx +170 -6
- package/src/renderer/lib/localWhisper.ts +48 -4
- package/src/renderer/lib/speech.ts +299 -0
- package/src/renderer/styles.css +269 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BQN8v2ok.js","./lspLanguageFeatures-CHWJx_Tl.js","./htmlMode-M3MApZ4n.js","./jsonMode-CKp2zvZu.js","./javascript-BO_ViZM5.js","./typescript-BiJRCUcL.js"])))=>i.map(i=>d[i]);
|
|
2
2
|
function getDefaultExportFromCjs(x) {
|
|
3
3
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
4
|
}
|
|
@@ -19163,6 +19163,26 @@ const __vitePreload = function preload(baseModule, deps, importerUrl) {
|
|
|
19163
19163
|
};
|
|
19164
19164
|
const MODEL = "Xenova/whisper-base";
|
|
19165
19165
|
const TARGET_SAMPLE_RATE = 16e3;
|
|
19166
|
+
const SILENCE_RMS = 8e-3;
|
|
19167
|
+
const LOW_CONFIDENCE_RMS = 0.02;
|
|
19168
|
+
const FILLER_PHRASES = /* @__PURE__ */ new Set([
|
|
19169
|
+
"you",
|
|
19170
|
+
"thank you",
|
|
19171
|
+
"thank you very much",
|
|
19172
|
+
"thank you for watching",
|
|
19173
|
+
"thanks for watching",
|
|
19174
|
+
"please subscribe"
|
|
19175
|
+
]);
|
|
19176
|
+
function computeRms(pcm) {
|
|
19177
|
+
if (pcm.length === 0) return 0;
|
|
19178
|
+
let sum2 = 0;
|
|
19179
|
+
for (let i2 = 0; i2 < pcm.length; i2++) sum2 += pcm[i2] * pcm[i2];
|
|
19180
|
+
return Math.sqrt(sum2 / pcm.length);
|
|
19181
|
+
}
|
|
19182
|
+
function isFillerOnly(text2) {
|
|
19183
|
+
const norm = text2.toLowerCase().replace(/[.!?,…"']/g, "").replace(/\s+/g, " ").trim();
|
|
19184
|
+
return FILLER_PHRASES.has(norm);
|
|
19185
|
+
}
|
|
19166
19186
|
let transcriberPromise = null;
|
|
19167
19187
|
async function loadTranscriber(onProgress) {
|
|
19168
19188
|
if (!transcriberPromise) {
|
|
@@ -19213,25 +19233,42 @@ async function blobToPcm16k(blob) {
|
|
|
19213
19233
|
return rendered.getChannelData(0);
|
|
19214
19234
|
}
|
|
19215
19235
|
async function transcribeBlob(blob, onProgress) {
|
|
19216
|
-
const transcriber = await loadTranscriber(onProgress);
|
|
19217
19236
|
const pcm = await blobToPcm16k(blob);
|
|
19218
19237
|
if (!pcm) return "";
|
|
19238
|
+
const rms = computeRms(pcm);
|
|
19239
|
+
if (rms < SILENCE_RMS) {
|
|
19240
|
+
console.info(`[voice] near-silent clip (rms=${rms.toFixed(4)}); skipping transcription`);
|
|
19241
|
+
return "";
|
|
19242
|
+
}
|
|
19243
|
+
const transcriber = await loadTranscriber(onProgress);
|
|
19219
19244
|
const result = await transcriber(pcm);
|
|
19220
|
-
const text2 = Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text;
|
|
19221
|
-
|
|
19245
|
+
const text2 = (Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text || "").trim();
|
|
19246
|
+
if (text2 && rms < LOW_CONFIDENCE_RMS && isFillerOnly(text2)) {
|
|
19247
|
+
console.info(`[voice] dropping filler-only output "${text2}" from quiet clip (rms=${rms.toFixed(4)})`);
|
|
19248
|
+
return "";
|
|
19249
|
+
}
|
|
19250
|
+
return text2;
|
|
19222
19251
|
}
|
|
19223
19252
|
function getRecognitionCtor() {
|
|
19224
19253
|
const w = window;
|
|
19225
19254
|
return w.SpeechRecognition || w.webkitSpeechRecognition || null;
|
|
19226
19255
|
}
|
|
19227
|
-
const ENGINE_KEY = "ctlsurf.voiceEngine";
|
|
19256
|
+
const ENGINE_KEY$1 = "ctlsurf.voiceEngine";
|
|
19257
|
+
const DEVICE_KEY = "ctlsurf.voiceDeviceId";
|
|
19258
|
+
function loadDeviceId() {
|
|
19259
|
+
try {
|
|
19260
|
+
return localStorage.getItem(DEVICE_KEY) || null;
|
|
19261
|
+
} catch {
|
|
19262
|
+
return null;
|
|
19263
|
+
}
|
|
19264
|
+
}
|
|
19228
19265
|
const WEB_SPEECH_SUPPORTED = getRecognitionCtor() !== null;
|
|
19229
19266
|
const LOCAL_SUPPORTED = typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" && typeof OfflineAudioContext !== "undefined";
|
|
19230
19267
|
const ANY_SUPPORTED = WEB_SPEECH_SUPPORTED || LOCAL_SUPPORTED;
|
|
19231
19268
|
function loadInitialEngine() {
|
|
19232
19269
|
if (!WEB_SPEECH_SUPPORTED && LOCAL_SUPPORTED) return "local";
|
|
19233
19270
|
try {
|
|
19234
|
-
if (localStorage.getItem(ENGINE_KEY) === "local" && LOCAL_SUPPORTED) return "local";
|
|
19271
|
+
if (localStorage.getItem(ENGINE_KEY$1) === "local" && LOCAL_SUPPORTED) return "local";
|
|
19235
19272
|
} catch {
|
|
19236
19273
|
}
|
|
19237
19274
|
return WEB_SPEECH_SUPPORTED ? "web-speech" : "local";
|
|
@@ -19245,13 +19282,21 @@ function describeMicError(err) {
|
|
|
19245
19282
|
if (name === "NotFoundError") return "No microphone found";
|
|
19246
19283
|
return "Could not start microphone";
|
|
19247
19284
|
}
|
|
19248
|
-
function VoiceInput({ onTranscript }) {
|
|
19249
|
-
const [engine,
|
|
19285
|
+
function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
19286
|
+
const [engine, setEngine2] = reactExports.useState(loadInitialEngine);
|
|
19250
19287
|
const [phase, setPhase] = reactExports.useState("idle");
|
|
19251
19288
|
const [interim, setInterim] = reactExports.useState("");
|
|
19252
19289
|
const [modelPct, setModelPct] = reactExports.useState(null);
|
|
19253
19290
|
const [error, setError] = reactExports.useState(null);
|
|
19254
19291
|
const [notice, setNotice] = reactExports.useState(null);
|
|
19292
|
+
const [devices, setDevices] = reactExports.useState([]);
|
|
19293
|
+
const [selectedDeviceId, setSelectedDeviceId] = reactExports.useState(loadDeviceId);
|
|
19294
|
+
const [showDevicePicker, setShowDevicePicker] = reactExports.useState(false);
|
|
19295
|
+
const selectedDeviceIdRef = reactExports.useRef(selectedDeviceId);
|
|
19296
|
+
reactExports.useEffect(() => {
|
|
19297
|
+
selectedDeviceIdRef.current = selectedDeviceId;
|
|
19298
|
+
}, [selectedDeviceId]);
|
|
19299
|
+
const wrapRef = reactExports.useRef(null);
|
|
19255
19300
|
const recognitionRef = reactExports.useRef(null);
|
|
19256
19301
|
const finalRef = reactExports.useRef("");
|
|
19257
19302
|
const streamRef = reactExports.useRef(null);
|
|
@@ -19278,16 +19323,89 @@ function VoiceInput({ onTranscript }) {
|
|
|
19278
19323
|
}, [notice]);
|
|
19279
19324
|
const switchToLocal = reactExports.useCallback((reason) => {
|
|
19280
19325
|
try {
|
|
19281
|
-
localStorage.setItem(ENGINE_KEY, "local");
|
|
19326
|
+
localStorage.setItem(ENGINE_KEY$1, "local");
|
|
19282
19327
|
} catch {
|
|
19283
19328
|
}
|
|
19284
|
-
|
|
19329
|
+
setEngine2("local");
|
|
19285
19330
|
setNotice(reason);
|
|
19286
19331
|
}, []);
|
|
19287
19332
|
const stopStream = reactExports.useCallback(() => {
|
|
19288
19333
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
19289
19334
|
streamRef.current = null;
|
|
19290
19335
|
}, []);
|
|
19336
|
+
const refreshDevices = reactExports.useCallback(async () => {
|
|
19337
|
+
if (!navigator.mediaDevices?.enumerateDevices) return;
|
|
19338
|
+
try {
|
|
19339
|
+
const all = await navigator.mediaDevices.enumerateDevices();
|
|
19340
|
+
setDevices(all.filter((d) => d.kind === "audioinput"));
|
|
19341
|
+
} catch {
|
|
19342
|
+
}
|
|
19343
|
+
}, []);
|
|
19344
|
+
const ensureDeviceLabels = reactExports.useCallback(async () => {
|
|
19345
|
+
if (!navigator.mediaDevices?.enumerateDevices) return;
|
|
19346
|
+
try {
|
|
19347
|
+
const all = await navigator.mediaDevices.enumerateDevices();
|
|
19348
|
+
const inputs = all.filter((d) => d.kind === "audioinput");
|
|
19349
|
+
if (inputs.length && inputs.every((d) => !d.label)) {
|
|
19350
|
+
const s = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
19351
|
+
s.getTracks().forEach((t) => t.stop());
|
|
19352
|
+
}
|
|
19353
|
+
} catch {
|
|
19354
|
+
}
|
|
19355
|
+
await refreshDevices();
|
|
19356
|
+
}, [refreshDevices]);
|
|
19357
|
+
const toggleDevicePicker = reactExports.useCallback(() => {
|
|
19358
|
+
setShowDevicePicker((open) => !open);
|
|
19359
|
+
}, []);
|
|
19360
|
+
reactExports.useEffect(() => {
|
|
19361
|
+
if (showDevicePicker) void ensureDeviceLabels();
|
|
19362
|
+
}, [showDevicePicker, ensureDeviceLabels]);
|
|
19363
|
+
const chooseDevice = reactExports.useCallback((id) => {
|
|
19364
|
+
setSelectedDeviceId(id);
|
|
19365
|
+
try {
|
|
19366
|
+
if (id) localStorage.setItem(DEVICE_KEY, id);
|
|
19367
|
+
else localStorage.removeItem(DEVICE_KEY);
|
|
19368
|
+
} catch {
|
|
19369
|
+
}
|
|
19370
|
+
setShowDevicePicker(false);
|
|
19371
|
+
}, []);
|
|
19372
|
+
const getStream = reactExports.useCallback(async () => {
|
|
19373
|
+
const id = selectedDeviceIdRef.current;
|
|
19374
|
+
try {
|
|
19375
|
+
return await navigator.mediaDevices.getUserMedia({
|
|
19376
|
+
audio: id ? { deviceId: { exact: id } } : true
|
|
19377
|
+
});
|
|
19378
|
+
} catch (err) {
|
|
19379
|
+
if (id && err?.name === "OverconstrainedError") {
|
|
19380
|
+
try {
|
|
19381
|
+
localStorage.removeItem(DEVICE_KEY);
|
|
19382
|
+
} catch {
|
|
19383
|
+
}
|
|
19384
|
+
setSelectedDeviceId(null);
|
|
19385
|
+
setNotice("Saved microphone unavailable — using system default.");
|
|
19386
|
+
return navigator.mediaDevices.getUserMedia({ audio: true });
|
|
19387
|
+
}
|
|
19388
|
+
throw err;
|
|
19389
|
+
}
|
|
19390
|
+
}, []);
|
|
19391
|
+
reactExports.useEffect(() => {
|
|
19392
|
+
const md = navigator.mediaDevices;
|
|
19393
|
+
if (!md?.addEventListener) return;
|
|
19394
|
+
const onChange = () => {
|
|
19395
|
+
void refreshDevices();
|
|
19396
|
+
};
|
|
19397
|
+
md.addEventListener("devicechange", onChange);
|
|
19398
|
+
void refreshDevices();
|
|
19399
|
+
return () => md.removeEventListener("devicechange", onChange);
|
|
19400
|
+
}, [refreshDevices]);
|
|
19401
|
+
reactExports.useEffect(() => {
|
|
19402
|
+
if (!showDevicePicker) return;
|
|
19403
|
+
const onDocDown = (e) => {
|
|
19404
|
+
if (!wrapRef.current?.contains(e.target)) setShowDevicePicker(false);
|
|
19405
|
+
};
|
|
19406
|
+
document.addEventListener("mousedown", onDocDown);
|
|
19407
|
+
return () => document.removeEventListener("mousedown", onDocDown);
|
|
19408
|
+
}, [showDevicePicker]);
|
|
19291
19409
|
const startWebSpeech = reactExports.useCallback(() => {
|
|
19292
19410
|
const Ctor = getRecognitionCtor();
|
|
19293
19411
|
if (!Ctor || recognitionRef.current) return;
|
|
@@ -19361,6 +19479,7 @@ function VoiceInput({ onTranscript }) {
|
|
|
19361
19479
|
try {
|
|
19362
19480
|
const text2 = await transcribeBlob(blob, handleModelProgress);
|
|
19363
19481
|
if (text2) onTranscriptRef.current(text2);
|
|
19482
|
+
else setNotice("No speech detected — check the mic source (▾).");
|
|
19364
19483
|
} catch (err) {
|
|
19365
19484
|
setError("On-device transcription failed");
|
|
19366
19485
|
console.error("[voice] local transcription failed", err);
|
|
@@ -19375,7 +19494,7 @@ function VoiceInput({ onTranscript }) {
|
|
|
19375
19494
|
setInterim("");
|
|
19376
19495
|
cancelGestureRef.current = false;
|
|
19377
19496
|
try {
|
|
19378
|
-
const stream = await
|
|
19497
|
+
const stream = await getStream();
|
|
19379
19498
|
if (cancelGestureRef.current) {
|
|
19380
19499
|
stream.getTracks().forEach((t) => t.stop());
|
|
19381
19500
|
setPhase("idle");
|
|
@@ -19399,7 +19518,7 @@ function VoiceInput({ onTranscript }) {
|
|
|
19399
19518
|
setError(describeMicError(err));
|
|
19400
19519
|
console.error("[voice] getUserMedia failed", err);
|
|
19401
19520
|
}
|
|
19402
|
-
}, [runLocalTranscription, stopStream]);
|
|
19521
|
+
}, [runLocalTranscription, stopStream, getStream]);
|
|
19403
19522
|
const stopLocal = reactExports.useCallback(() => {
|
|
19404
19523
|
cancelGestureRef.current = true;
|
|
19405
19524
|
const rec = recorderRef.current;
|
|
@@ -19441,12 +19560,21 @@ function VoiceInput({ onTranscript }) {
|
|
|
19441
19560
|
else if (notice && phase === "idle") chip = { kind: "notice", text: notice };
|
|
19442
19561
|
else if (listening) chip = { kind: "listening", text: interim || (engine === "local" ? "Recording…" : "Listening…") };
|
|
19443
19562
|
else if (busy) chip = { kind: "busy", text: modelPct !== null ? `Downloading voice model… ${modelPct}%` : "Transcribing…" };
|
|
19444
|
-
|
|
19563
|
+
const floating = variant === "floating";
|
|
19564
|
+
const btnClass = floating ? `voice-btn voice-btn-floating ${listening ? "listening" : ""} ${busy ? "busy" : ""}` : `titlebar-btn titlebar-icon-btn voice-btn ${listening ? "listening" : ""} ${busy ? "busy" : ""}`;
|
|
19565
|
+
const realDevices = devices.filter(
|
|
19566
|
+
(d) => d.deviceId && d.deviceId !== "default" && d.deviceId !== "communications"
|
|
19567
|
+
);
|
|
19568
|
+
const rawDefault = devices.find((d) => d.deviceId === "default")?.label;
|
|
19569
|
+
const defaultLabel = rawDefault ? `System default · ${rawDefault.replace(/^Default\s*-\s*/i, "")}` : "System default";
|
|
19570
|
+
const activeLabel = selectedDeviceId ? realDevices.find((d) => d.deviceId === selectedDeviceId)?.label || "Selected microphone" : defaultLabel;
|
|
19571
|
+
const showSourcePicker = floating && LOCAL_SUPPORTED;
|
|
19572
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "voice-input-wrap", ref: wrapRef, children: [
|
|
19445
19573
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19446
19574
|
"button",
|
|
19447
19575
|
{
|
|
19448
19576
|
type: "button",
|
|
19449
|
-
className:
|
|
19577
|
+
className: btnClass,
|
|
19450
19578
|
disabled: !ANY_SUPPORTED,
|
|
19451
19579
|
onPointerDown: handlePointerDown,
|
|
19452
19580
|
onPointerUp: handlePointerUp,
|
|
@@ -19460,7 +19588,645 @@ function VoiceInput({ onTranscript }) {
|
|
|
19460
19588
|
]
|
|
19461
19589
|
}
|
|
19462
19590
|
),
|
|
19463
|
-
|
|
19591
|
+
showSourcePicker && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19592
|
+
"button",
|
|
19593
|
+
{
|
|
19594
|
+
type: "button",
|
|
19595
|
+
className: "voice-source-btn",
|
|
19596
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
19597
|
+
onClick: toggleDevicePicker,
|
|
19598
|
+
title: `Mic source: ${activeLabel}`,
|
|
19599
|
+
"aria-label": "Choose microphone source",
|
|
19600
|
+
"aria-expanded": showDevicePicker,
|
|
19601
|
+
children: "▾"
|
|
19602
|
+
}
|
|
19603
|
+
),
|
|
19604
|
+
showSourcePicker && showDevicePicker && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "voice-source-menu", role: "menu", children: [
|
|
19605
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "voice-source-head", children: "Microphone source" }),
|
|
19606
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19607
|
+
"button",
|
|
19608
|
+
{
|
|
19609
|
+
type: "button",
|
|
19610
|
+
role: "menuitemradio",
|
|
19611
|
+
"aria-checked": selectedDeviceId === null,
|
|
19612
|
+
className: `voice-source-item ${selectedDeviceId === null ? "active" : ""}`,
|
|
19613
|
+
onClick: () => chooseDevice(null),
|
|
19614
|
+
children: [
|
|
19615
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-check", children: selectedDeviceId === null ? "✓" : "" }),
|
|
19616
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-label", children: defaultLabel })
|
|
19617
|
+
]
|
|
19618
|
+
}
|
|
19619
|
+
),
|
|
19620
|
+
realDevices.map((d, i2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19621
|
+
"button",
|
|
19622
|
+
{
|
|
19623
|
+
type: "button",
|
|
19624
|
+
role: "menuitemradio",
|
|
19625
|
+
"aria-checked": selectedDeviceId === d.deviceId,
|
|
19626
|
+
className: `voice-source-item ${selectedDeviceId === d.deviceId ? "active" : ""}`,
|
|
19627
|
+
onClick: () => chooseDevice(d.deviceId),
|
|
19628
|
+
children: [
|
|
19629
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-check", children: selectedDeviceId === d.deviceId ? "✓" : "" }),
|
|
19630
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-label", children: d.label || `Microphone ${i2 + 1}` })
|
|
19631
|
+
]
|
|
19632
|
+
},
|
|
19633
|
+
d.deviceId
|
|
19634
|
+
)),
|
|
19635
|
+
realDevices.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "voice-source-empty", children: "No microphones found" })
|
|
19636
|
+
] }),
|
|
19637
|
+
chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
|
|
19638
|
+
] });
|
|
19639
|
+
}
|
|
19640
|
+
const POS_KEY = "ctlsurf.floatingMicPos";
|
|
19641
|
+
const EDGE = 20;
|
|
19642
|
+
const TOP_MIN = 46;
|
|
19643
|
+
const BOTTOM_GAP = 36;
|
|
19644
|
+
function loadPos() {
|
|
19645
|
+
try {
|
|
19646
|
+
const raw = localStorage.getItem(POS_KEY);
|
|
19647
|
+
if (raw) {
|
|
19648
|
+
const p = JSON.parse(raw);
|
|
19649
|
+
if (typeof p.x === "number" && typeof p.y === "number") return { x: p.x, y: p.y };
|
|
19650
|
+
}
|
|
19651
|
+
} catch {
|
|
19652
|
+
}
|
|
19653
|
+
return null;
|
|
19654
|
+
}
|
|
19655
|
+
function FloatingMic({ onTranscript, onHide }) {
|
|
19656
|
+
const [pos, setPos] = reactExports.useState(loadPos);
|
|
19657
|
+
const elRef = reactExports.useRef(null);
|
|
19658
|
+
const dragRef = reactExports.useRef(null);
|
|
19659
|
+
const clamp2 = reactExports.useCallback((x, y) => {
|
|
19660
|
+
const el = elRef.current;
|
|
19661
|
+
const w = el?.offsetWidth ?? 64;
|
|
19662
|
+
const h2 = el?.offsetHeight ?? 90;
|
|
19663
|
+
return {
|
|
19664
|
+
x: Math.max(EDGE, Math.min(x, window.innerWidth - w - EDGE)),
|
|
19665
|
+
y: Math.max(TOP_MIN, Math.min(y, window.innerHeight - h2 - BOTTOM_GAP))
|
|
19666
|
+
};
|
|
19667
|
+
}, []);
|
|
19668
|
+
reactExports.useEffect(() => {
|
|
19669
|
+
if (pos) return;
|
|
19670
|
+
const el = elRef.current;
|
|
19671
|
+
const w = el?.offsetWidth ?? 64;
|
|
19672
|
+
const h2 = el?.offsetHeight ?? 90;
|
|
19673
|
+
setPos({
|
|
19674
|
+
x: window.innerWidth - w - EDGE,
|
|
19675
|
+
y: window.innerHeight - h2 - BOTTOM_GAP
|
|
19676
|
+
});
|
|
19677
|
+
}, [pos]);
|
|
19678
|
+
reactExports.useEffect(() => {
|
|
19679
|
+
const onResize = () => setPos((p) => p ? clamp2(p.x, p.y) : p);
|
|
19680
|
+
window.addEventListener("resize", onResize);
|
|
19681
|
+
return () => window.removeEventListener("resize", onResize);
|
|
19682
|
+
}, [clamp2]);
|
|
19683
|
+
const onHandleDown = reactExports.useCallback((e) => {
|
|
19684
|
+
const el = elRef.current;
|
|
19685
|
+
if (!el) return;
|
|
19686
|
+
e.preventDefault();
|
|
19687
|
+
const rect = el.getBoundingClientRect();
|
|
19688
|
+
dragRef.current = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
|
19689
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
19690
|
+
}, []);
|
|
19691
|
+
const onHandleMove = reactExports.useCallback((e) => {
|
|
19692
|
+
const d = dragRef.current;
|
|
19693
|
+
if (!d) return;
|
|
19694
|
+
setPos(clamp2(e.clientX - d.dx, e.clientY - d.dy));
|
|
19695
|
+
}, [clamp2]);
|
|
19696
|
+
const onHandleUp = reactExports.useCallback((e) => {
|
|
19697
|
+
if (!dragRef.current) return;
|
|
19698
|
+
dragRef.current = null;
|
|
19699
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
19700
|
+
setPos((p) => {
|
|
19701
|
+
if (p) {
|
|
19702
|
+
try {
|
|
19703
|
+
localStorage.setItem(POS_KEY, JSON.stringify(p));
|
|
19704
|
+
} catch {
|
|
19705
|
+
}
|
|
19706
|
+
}
|
|
19707
|
+
return p;
|
|
19708
|
+
});
|
|
19709
|
+
}, []);
|
|
19710
|
+
const style = pos ? { left: pos.x, top: pos.y } : { left: -9999, top: -9999, visibility: "hidden" };
|
|
19711
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: elRef, className: "floating-mic", style, children: [
|
|
19712
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19713
|
+
"div",
|
|
19714
|
+
{
|
|
19715
|
+
className: "floating-mic-handle",
|
|
19716
|
+
onPointerDown: onHandleDown,
|
|
19717
|
+
onPointerMove: onHandleMove,
|
|
19718
|
+
onPointerUp: onHandleUp,
|
|
19719
|
+
onPointerCancel: onHandleUp,
|
|
19720
|
+
title: "Drag to move",
|
|
19721
|
+
"aria-label": "Drag floating mic",
|
|
19722
|
+
children: [
|
|
19723
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "floating-mic-grip", "aria-hidden": "true", children: "⠿" }),
|
|
19724
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19725
|
+
"button",
|
|
19726
|
+
{
|
|
19727
|
+
type: "button",
|
|
19728
|
+
className: "floating-mic-hide",
|
|
19729
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
19730
|
+
onClick: onHide,
|
|
19731
|
+
title: "Hide floating mic",
|
|
19732
|
+
"aria-label": "Hide floating mic",
|
|
19733
|
+
children: "×"
|
|
19734
|
+
}
|
|
19735
|
+
)
|
|
19736
|
+
]
|
|
19737
|
+
}
|
|
19738
|
+
),
|
|
19739
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript })
|
|
19740
|
+
] });
|
|
19741
|
+
}
|
|
19742
|
+
const ENGINE_KEY = "ctlsurf.tts.engine";
|
|
19743
|
+
const VOICE_KEY = "ctlsurf.tts.voiceURI";
|
|
19744
|
+
const RATE_KEY = "ctlsurf.tts.rate";
|
|
19745
|
+
const MAX_SPEAK_CHARS = 1600;
|
|
19746
|
+
const MAX_CHUNK_CHARS = 280;
|
|
19747
|
+
function cleanForSpeech(input) {
|
|
19748
|
+
let t = input;
|
|
19749
|
+
t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
|
|
19750
|
+
const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
|
|
19751
|
+
return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
|
|
19752
|
+
});
|
|
19753
|
+
t = t.replace(/```/g, " (code) ");
|
|
19754
|
+
t = t.replace(/`([^`]+)`/g, "$1");
|
|
19755
|
+
t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
19756
|
+
t = t.replace(/https?:\/\/\S+/g, "link");
|
|
19757
|
+
t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
|
|
19758
|
+
t = t.replace(/[*_#>]+/g, " ");
|
|
19759
|
+
t = t.replace(/^\s*[-•]\s+/gm, ", ");
|
|
19760
|
+
t = t.replace(/<[^>]+>/g, " ");
|
|
19761
|
+
t = t.replace(/\s+/g, " ").trim();
|
|
19762
|
+
if (t.length > MAX_SPEAK_CHARS) {
|
|
19763
|
+
t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
|
|
19764
|
+
}
|
|
19765
|
+
return t;
|
|
19766
|
+
}
|
|
19767
|
+
function splitIntoChunks(text2) {
|
|
19768
|
+
if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
|
|
19769
|
+
const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
|
|
19770
|
+
const chunks = [];
|
|
19771
|
+
let buf = "";
|
|
19772
|
+
for (const s of sentences) {
|
|
19773
|
+
if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
|
|
19774
|
+
chunks.push(buf.trim());
|
|
19775
|
+
buf = "";
|
|
19776
|
+
}
|
|
19777
|
+
if (s.length > MAX_CHUNK_CHARS) {
|
|
19778
|
+
for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
|
|
19779
|
+
} else {
|
|
19780
|
+
buf += s;
|
|
19781
|
+
}
|
|
19782
|
+
}
|
|
19783
|
+
if (buf.trim()) chunks.push(buf.trim());
|
|
19784
|
+
return chunks;
|
|
19785
|
+
}
|
|
19786
|
+
function getEngine() {
|
|
19787
|
+
try {
|
|
19788
|
+
return localStorage.getItem(ENGINE_KEY) === "neural" ? "neural" : "web";
|
|
19789
|
+
} catch {
|
|
19790
|
+
return "web";
|
|
19791
|
+
}
|
|
19792
|
+
}
|
|
19793
|
+
function setEngine(id) {
|
|
19794
|
+
try {
|
|
19795
|
+
localStorage.setItem(ENGINE_KEY, id);
|
|
19796
|
+
} catch {
|
|
19797
|
+
}
|
|
19798
|
+
}
|
|
19799
|
+
function getVoiceURI() {
|
|
19800
|
+
try {
|
|
19801
|
+
return localStorage.getItem(VOICE_KEY) || null;
|
|
19802
|
+
} catch {
|
|
19803
|
+
return null;
|
|
19804
|
+
}
|
|
19805
|
+
}
|
|
19806
|
+
function setVoiceURI(uri) {
|
|
19807
|
+
try {
|
|
19808
|
+
uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
|
|
19809
|
+
} catch {
|
|
19810
|
+
}
|
|
19811
|
+
}
|
|
19812
|
+
function getRate() {
|
|
19813
|
+
try {
|
|
19814
|
+
const n2 = Number(localStorage.getItem(RATE_KEY));
|
|
19815
|
+
return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
|
|
19816
|
+
} catch {
|
|
19817
|
+
return 1;
|
|
19818
|
+
}
|
|
19819
|
+
}
|
|
19820
|
+
function setRate(rate) {
|
|
19821
|
+
try {
|
|
19822
|
+
localStorage.setItem(RATE_KEY, String(rate));
|
|
19823
|
+
} catch {
|
|
19824
|
+
}
|
|
19825
|
+
}
|
|
19826
|
+
function listWebVoices() {
|
|
19827
|
+
if (typeof speechSynthesis === "undefined") return [];
|
|
19828
|
+
return speechSynthesis.getVoices();
|
|
19829
|
+
}
|
|
19830
|
+
const NEURAL_MODEL = "Xenova/mms-tts-eng";
|
|
19831
|
+
let synthPromise = null;
|
|
19832
|
+
function loadSynthesizer(onProgress) {
|
|
19833
|
+
if (!synthPromise) {
|
|
19834
|
+
synthPromise = (async () => {
|
|
19835
|
+
const { pipeline, env: env2 } = await __vitePreload(async () => {
|
|
19836
|
+
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-DtSCnG36.js");
|
|
19837
|
+
return { pipeline: pipeline2, env: env3 };
|
|
19838
|
+
}, true ? [] : void 0, import.meta.url);
|
|
19839
|
+
env2.allowLocalModels = false;
|
|
19840
|
+
const common = { progress_callback: onProgress };
|
|
19841
|
+
const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
|
|
19842
|
+
if (hasWebGpu) {
|
|
19843
|
+
try {
|
|
19844
|
+
const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
|
|
19845
|
+
console.info("[tts] neural backend: webgpu");
|
|
19846
|
+
return s2;
|
|
19847
|
+
} catch (err) {
|
|
19848
|
+
console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
|
|
19849
|
+
}
|
|
19850
|
+
}
|
|
19851
|
+
const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
|
|
19852
|
+
console.info("[tts] neural backend: wasm");
|
|
19853
|
+
return s;
|
|
19854
|
+
})();
|
|
19855
|
+
synthPromise.catch(() => {
|
|
19856
|
+
synthPromise = null;
|
|
19857
|
+
});
|
|
19858
|
+
}
|
|
19859
|
+
return synthPromise;
|
|
19860
|
+
}
|
|
19861
|
+
class SpeechController {
|
|
19862
|
+
queue = [];
|
|
19863
|
+
draining = false;
|
|
19864
|
+
audioCtx = null;
|
|
19865
|
+
currentSource = null;
|
|
19866
|
+
generation = 0;
|
|
19867
|
+
// bumped by stop() to abort in-flight work
|
|
19868
|
+
active = false;
|
|
19869
|
+
onModelProgress = null;
|
|
19870
|
+
// Fires true while a reply is being spoken/queued, false when idle — drives
|
|
19871
|
+
// the visible Stop button.
|
|
19872
|
+
onActivityChange = null;
|
|
19873
|
+
onError = null;
|
|
19874
|
+
// Prime/resume the AudioContext from a user gesture so neural playback isn't
|
|
19875
|
+
// blocked by the browser autoplay policy (the System engine is unaffected).
|
|
19876
|
+
unlock() {
|
|
19877
|
+
try {
|
|
19878
|
+
const ctx = this.ensureCtx();
|
|
19879
|
+
if (ctx.state === "suspended") void ctx.resume();
|
|
19880
|
+
} catch {
|
|
19881
|
+
}
|
|
19882
|
+
}
|
|
19883
|
+
// Start loading the neural model in the background (e.g. when the user picks
|
|
19884
|
+
// the neural engine) so the first utterance doesn't pay download/compile time.
|
|
19885
|
+
warmup() {
|
|
19886
|
+
if (getEngine() !== "neural") return;
|
|
19887
|
+
void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
|
|
19888
|
+
this.onModelProgress?.(null);
|
|
19889
|
+
});
|
|
19890
|
+
}
|
|
19891
|
+
enqueue(rawText) {
|
|
19892
|
+
const text2 = cleanForSpeech(rawText);
|
|
19893
|
+
if (!text2) return;
|
|
19894
|
+
this.queue.push(...splitIntoChunks(text2));
|
|
19895
|
+
void this.drain();
|
|
19896
|
+
}
|
|
19897
|
+
stop() {
|
|
19898
|
+
this.generation++;
|
|
19899
|
+
this.queue = [];
|
|
19900
|
+
this.draining = false;
|
|
19901
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
19902
|
+
try {
|
|
19903
|
+
speechSynthesis.cancel();
|
|
19904
|
+
} catch {
|
|
19905
|
+
}
|
|
19906
|
+
}
|
|
19907
|
+
if (this.currentSource) {
|
|
19908
|
+
try {
|
|
19909
|
+
this.currentSource.stop();
|
|
19910
|
+
} catch {
|
|
19911
|
+
}
|
|
19912
|
+
this.currentSource = null;
|
|
19913
|
+
}
|
|
19914
|
+
this.onModelProgress?.(null);
|
|
19915
|
+
this.setActive(false);
|
|
19916
|
+
}
|
|
19917
|
+
ensureCtx() {
|
|
19918
|
+
if (!this.audioCtx) {
|
|
19919
|
+
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
19920
|
+
this.audioCtx = new Ctx();
|
|
19921
|
+
}
|
|
19922
|
+
return this.audioCtx;
|
|
19923
|
+
}
|
|
19924
|
+
setActive(a) {
|
|
19925
|
+
if (this.active === a) return;
|
|
19926
|
+
this.active = a;
|
|
19927
|
+
this.onActivityChange?.(a);
|
|
19928
|
+
}
|
|
19929
|
+
async drain() {
|
|
19930
|
+
if (this.draining) return;
|
|
19931
|
+
this.draining = true;
|
|
19932
|
+
const gen = this.generation;
|
|
19933
|
+
this.setActive(true);
|
|
19934
|
+
while (this.queue.length && gen === this.generation) {
|
|
19935
|
+
const chunk = this.queue.shift();
|
|
19936
|
+
try {
|
|
19937
|
+
if (getEngine() === "neural") await this.speakNeural(chunk, gen);
|
|
19938
|
+
else await this.speakWeb(chunk, gen);
|
|
19939
|
+
} catch (err) {
|
|
19940
|
+
console.error("[tts] speak failed", err);
|
|
19941
|
+
this.onModelProgress?.(null);
|
|
19942
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
19943
|
+
this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
|
|
19944
|
+
}
|
|
19945
|
+
}
|
|
19946
|
+
if (gen === this.generation) {
|
|
19947
|
+
this.draining = false;
|
|
19948
|
+
this.setActive(false);
|
|
19949
|
+
}
|
|
19950
|
+
}
|
|
19951
|
+
speakWeb(text2, gen) {
|
|
19952
|
+
return new Promise((resolve2) => {
|
|
19953
|
+
if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
|
|
19954
|
+
const u = new SpeechSynthesisUtterance(text2);
|
|
19955
|
+
u.rate = getRate();
|
|
19956
|
+
const wantUri = getVoiceURI();
|
|
19957
|
+
if (wantUri) {
|
|
19958
|
+
const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
|
|
19959
|
+
if (v2) u.voice = v2;
|
|
19960
|
+
}
|
|
19961
|
+
u.onend = () => resolve2();
|
|
19962
|
+
u.onerror = () => resolve2();
|
|
19963
|
+
speechSynthesis.speak(u);
|
|
19964
|
+
});
|
|
19965
|
+
}
|
|
19966
|
+
async speakNeural(text2, gen) {
|
|
19967
|
+
console.info("[tts] neural: loading model…");
|
|
19968
|
+
this.onModelProgress?.({ status: "loading" });
|
|
19969
|
+
const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
|
|
19970
|
+
if (gen !== this.generation) return;
|
|
19971
|
+
console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
|
|
19972
|
+
const out = await synth(text2);
|
|
19973
|
+
const raw = Array.isArray(out) ? out[0] : out;
|
|
19974
|
+
this.onModelProgress?.(null);
|
|
19975
|
+
if (gen !== this.generation) return;
|
|
19976
|
+
if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
|
|
19977
|
+
console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
|
|
19978
|
+
await this.playPcm(raw.audio, raw.sampling_rate, gen);
|
|
19979
|
+
}
|
|
19980
|
+
async playPcm(pcm, sampleRate, gen) {
|
|
19981
|
+
if (gen !== this.generation) return;
|
|
19982
|
+
const ctx = this.ensureCtx();
|
|
19983
|
+
if (ctx.state === "suspended") {
|
|
19984
|
+
try {
|
|
19985
|
+
await ctx.resume();
|
|
19986
|
+
} catch {
|
|
19987
|
+
}
|
|
19988
|
+
}
|
|
19989
|
+
if (gen !== this.generation) return;
|
|
19990
|
+
return new Promise((resolve2) => {
|
|
19991
|
+
const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
|
|
19992
|
+
buffer.getChannelData(0).set(pcm);
|
|
19993
|
+
const source = ctx.createBufferSource();
|
|
19994
|
+
source.buffer = buffer;
|
|
19995
|
+
source.connect(ctx.destination);
|
|
19996
|
+
source.onended = () => {
|
|
19997
|
+
if (this.currentSource === source) this.currentSource = null;
|
|
19998
|
+
resolve2();
|
|
19999
|
+
};
|
|
20000
|
+
this.currentSource = source;
|
|
20001
|
+
source.start();
|
|
20002
|
+
});
|
|
20003
|
+
}
|
|
20004
|
+
}
|
|
20005
|
+
const speech = new SpeechController();
|
|
20006
|
+
const SAMPLE = "This is how spoken agent replies will sound.";
|
|
20007
|
+
function SpeakControls() {
|
|
20008
|
+
const [enabled, setEnabled] = reactExports.useState(false);
|
|
20009
|
+
const [engine, setEngineState] = reactExports.useState(getEngine);
|
|
20010
|
+
const [voices, setVoices] = reactExports.useState([]);
|
|
20011
|
+
const [voiceURI, setVoiceURIState] = reactExports.useState(getVoiceURI);
|
|
20012
|
+
const [rate, setRateState] = reactExports.useState(getRate);
|
|
20013
|
+
const [showMenu, setShowMenu] = reactExports.useState(false);
|
|
20014
|
+
const [modelPct, setModelPct] = reactExports.useState(null);
|
|
20015
|
+
const [speaking, setSpeaking] = reactExports.useState(false);
|
|
20016
|
+
const [error, setError] = reactExports.useState(null);
|
|
20017
|
+
const wrapRef = reactExports.useRef(null);
|
|
20018
|
+
reactExports.useEffect(() => {
|
|
20019
|
+
let alive = true;
|
|
20020
|
+
window.worker.getSpeakReplies().then((r) => {
|
|
20021
|
+
if (alive) setEnabled(!!r.enabled);
|
|
20022
|
+
}).catch(() => {
|
|
20023
|
+
});
|
|
20024
|
+
return () => {
|
|
20025
|
+
alive = false;
|
|
20026
|
+
};
|
|
20027
|
+
}, []);
|
|
20028
|
+
reactExports.useEffect(() => {
|
|
20029
|
+
const off = window.worker.onAgentMessage((text2) => speech.enqueue(text2));
|
|
20030
|
+
return off;
|
|
20031
|
+
}, []);
|
|
20032
|
+
reactExports.useEffect(() => {
|
|
20033
|
+
speech.onModelProgress = (p) => {
|
|
20034
|
+
if (p && p.status === "progress" && typeof p.progress === "number") {
|
|
20035
|
+
setModelPct(Math.min(100, Math.round(p.progress)));
|
|
20036
|
+
} else if (!p) {
|
|
20037
|
+
setModelPct(null);
|
|
20038
|
+
}
|
|
20039
|
+
};
|
|
20040
|
+
speech.onActivityChange = (a) => setSpeaking(a);
|
|
20041
|
+
speech.onError = (msg) => setError(msg);
|
|
20042
|
+
return () => {
|
|
20043
|
+
speech.onModelProgress = null;
|
|
20044
|
+
speech.onActivityChange = null;
|
|
20045
|
+
speech.onError = null;
|
|
20046
|
+
};
|
|
20047
|
+
}, []);
|
|
20048
|
+
reactExports.useEffect(() => {
|
|
20049
|
+
if (!error) return;
|
|
20050
|
+
const t = setTimeout(() => setError(null), 4e3);
|
|
20051
|
+
return () => clearTimeout(t);
|
|
20052
|
+
}, [error]);
|
|
20053
|
+
reactExports.useEffect(() => {
|
|
20054
|
+
const load = () => setVoices(listWebVoices());
|
|
20055
|
+
load();
|
|
20056
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
20057
|
+
speechSynthesis.addEventListener("voiceschanged", load);
|
|
20058
|
+
return () => speechSynthesis.removeEventListener("voiceschanged", load);
|
|
20059
|
+
}
|
|
20060
|
+
}, []);
|
|
20061
|
+
reactExports.useEffect(() => {
|
|
20062
|
+
if (!showMenu) return;
|
|
20063
|
+
const onDown = (e) => {
|
|
20064
|
+
if (!wrapRef.current?.contains(e.target)) setShowMenu(false);
|
|
20065
|
+
};
|
|
20066
|
+
document.addEventListener("mousedown", onDown);
|
|
20067
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
20068
|
+
}, [showMenu]);
|
|
20069
|
+
const toggle = reactExports.useCallback(async () => {
|
|
20070
|
+
const next = !enabled;
|
|
20071
|
+
setEnabled(next);
|
|
20072
|
+
if (next) speech.unlock();
|
|
20073
|
+
else speech.stop();
|
|
20074
|
+
try {
|
|
20075
|
+
await window.worker.setSpeakReplies(next);
|
|
20076
|
+
} catch {
|
|
20077
|
+
}
|
|
20078
|
+
}, [enabled]);
|
|
20079
|
+
const chooseEngine = reactExports.useCallback((id) => {
|
|
20080
|
+
setEngine(id);
|
|
20081
|
+
setEngineState(id);
|
|
20082
|
+
speech.stop();
|
|
20083
|
+
if (id === "neural") speech.warmup();
|
|
20084
|
+
}, []);
|
|
20085
|
+
const chooseVoice = reactExports.useCallback((uri) => {
|
|
20086
|
+
const v2 = uri || null;
|
|
20087
|
+
setVoiceURI(v2);
|
|
20088
|
+
setVoiceURIState(v2);
|
|
20089
|
+
}, []);
|
|
20090
|
+
const changeRate = reactExports.useCallback((r) => {
|
|
20091
|
+
setRate(r);
|
|
20092
|
+
setRateState(r);
|
|
20093
|
+
}, []);
|
|
20094
|
+
const testVoice = reactExports.useCallback(() => {
|
|
20095
|
+
speech.unlock();
|
|
20096
|
+
speech.stop();
|
|
20097
|
+
speech.enqueue(SAMPLE);
|
|
20098
|
+
}, []);
|
|
20099
|
+
const title = enabled ? "Spoken replies on — click to mute" : "Speak agent replies (off)";
|
|
20100
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-controls", ref: wrapRef, children: [
|
|
20101
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20102
|
+
"button",
|
|
20103
|
+
{
|
|
20104
|
+
type: "button",
|
|
20105
|
+
className: `titlebar-btn titlebar-icon-btn speak-btn ${enabled ? "active" : ""}`,
|
|
20106
|
+
onClick: toggle,
|
|
20107
|
+
title,
|
|
20108
|
+
"aria-label": "Toggle spoken agent replies",
|
|
20109
|
+
"aria-pressed": enabled,
|
|
20110
|
+
children: [
|
|
20111
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20112
|
+
"svg",
|
|
20113
|
+
{
|
|
20114
|
+
viewBox: "0 0 24 24",
|
|
20115
|
+
width: "13",
|
|
20116
|
+
height: "13",
|
|
20117
|
+
fill: "none",
|
|
20118
|
+
stroke: "currentColor",
|
|
20119
|
+
strokeWidth: "2",
|
|
20120
|
+
strokeLinecap: "round",
|
|
20121
|
+
strokeLinejoin: "round",
|
|
20122
|
+
"aria-hidden": "true",
|
|
20123
|
+
children: [
|
|
20124
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
|
|
20125
|
+
enabled ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
20126
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
20127
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
20128
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "23", y1: "9", x2: "17", y2: "15" })
|
|
20129
|
+
]
|
|
20130
|
+
}
|
|
20131
|
+
),
|
|
20132
|
+
modelPct !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "speak-pct", children: [
|
|
20133
|
+
modelPct,
|
|
20134
|
+
"%"
|
|
20135
|
+
] })
|
|
20136
|
+
]
|
|
20137
|
+
}
|
|
20138
|
+
),
|
|
20139
|
+
speaking && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20140
|
+
"button",
|
|
20141
|
+
{
|
|
20142
|
+
type: "button",
|
|
20143
|
+
className: "titlebar-btn titlebar-icon-btn speak-stop",
|
|
20144
|
+
onClick: () => speech.stop(),
|
|
20145
|
+
title: "Stop speaking",
|
|
20146
|
+
"aria-label": "Stop speaking",
|
|
20147
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "11", height: "11", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "5", y: "5", width: "14", height: "14", rx: "2" }) })
|
|
20148
|
+
}
|
|
20149
|
+
),
|
|
20150
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20151
|
+
"button",
|
|
20152
|
+
{
|
|
20153
|
+
type: "button",
|
|
20154
|
+
className: "titlebar-btn speak-caret",
|
|
20155
|
+
onClick: () => setShowMenu((v2) => !v2),
|
|
20156
|
+
title: "Voice options",
|
|
20157
|
+
"aria-label": "Voice options",
|
|
20158
|
+
"aria-expanded": showMenu,
|
|
20159
|
+
children: "▾"
|
|
20160
|
+
}
|
|
20161
|
+
),
|
|
20162
|
+
showMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu", role: "menu", children: [
|
|
20163
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-menu-head", children: "Engine" }),
|
|
20164
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20165
|
+
"button",
|
|
20166
|
+
{
|
|
20167
|
+
type: "button",
|
|
20168
|
+
className: `speak-menu-item ${engine === "web" ? "active" : ""}`,
|
|
20169
|
+
onClick: () => chooseEngine("web"),
|
|
20170
|
+
children: [
|
|
20171
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "speak-menu-check", children: engine === "web" ? "✓" : "" }),
|
|
20172
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "System voice (instant)" })
|
|
20173
|
+
]
|
|
20174
|
+
}
|
|
20175
|
+
),
|
|
20176
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20177
|
+
"button",
|
|
20178
|
+
{
|
|
20179
|
+
type: "button",
|
|
20180
|
+
className: `speak-menu-item ${engine === "neural" ? "active" : ""}`,
|
|
20181
|
+
onClick: () => chooseEngine("neural"),
|
|
20182
|
+
children: [
|
|
20183
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "speak-menu-check", children: engine === "neural" ? "✓" : "" }),
|
|
20184
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Neural voice (downloads)" })
|
|
20185
|
+
]
|
|
20186
|
+
}
|
|
20187
|
+
),
|
|
20188
|
+
engine === "web" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
20189
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-menu-head", children: "Voice" }),
|
|
20190
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20191
|
+
"select",
|
|
20192
|
+
{
|
|
20193
|
+
className: "speak-select",
|
|
20194
|
+
value: voiceURI || "",
|
|
20195
|
+
onChange: (e) => chooseVoice(e.target.value),
|
|
20196
|
+
children: [
|
|
20197
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "System default" }),
|
|
20198
|
+
voices.map((v2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: v2.voiceURI, children: [
|
|
20199
|
+
v2.name,
|
|
20200
|
+
" ",
|
|
20201
|
+
v2.lang ? `(${v2.lang})` : ""
|
|
20202
|
+
] }, v2.voiceURI))
|
|
20203
|
+
]
|
|
20204
|
+
}
|
|
20205
|
+
)
|
|
20206
|
+
] }),
|
|
20207
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu-head", children: [
|
|
20208
|
+
"Rate · ",
|
|
20209
|
+
rate.toFixed(1),
|
|
20210
|
+
"×"
|
|
20211
|
+
] }),
|
|
20212
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20213
|
+
"input",
|
|
20214
|
+
{
|
|
20215
|
+
className: "speak-rate",
|
|
20216
|
+
type: "range",
|
|
20217
|
+
min: 0.5,
|
|
20218
|
+
max: 2,
|
|
20219
|
+
step: 0.1,
|
|
20220
|
+
value: rate,
|
|
20221
|
+
onChange: (e) => changeRate(Number(e.target.value))
|
|
20222
|
+
}
|
|
20223
|
+
),
|
|
20224
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu-row", children: [
|
|
20225
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "speak-menu-btn", onClick: testVoice, children: "Test" }),
|
|
20226
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "speak-menu-btn", onClick: () => speech.stop(), children: "Stop" })
|
|
20227
|
+
] })
|
|
20228
|
+
] }),
|
|
20229
|
+
error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-error", children: error })
|
|
19464
20230
|
] });
|
|
19465
20231
|
}
|
|
19466
20232
|
function CtlsurfPanel({ navigate }) {
|
|
@@ -207057,7 +207823,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
|
|
|
207057
207823
|
modeConfigurationDefault$2
|
|
207058
207824
|
);
|
|
207059
207825
|
function getMode$3() {
|
|
207060
|
-
return __vitePreload(() => import("./cssMode-
|
|
207826
|
+
return __vitePreload(() => import("./cssMode-BQN8v2ok.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
|
|
207061
207827
|
}
|
|
207062
207828
|
languages.onLanguage("less", () => {
|
|
207063
207829
|
getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
|
|
@@ -207162,7 +207928,7 @@ const razorLanguageService = registerHTMLLanguageService(
|
|
|
207162
207928
|
);
|
|
207163
207929
|
const razorDefaults = razorLanguageService.defaults;
|
|
207164
207930
|
function getMode$2() {
|
|
207165
|
-
return __vitePreload(() => import("./htmlMode-
|
|
207931
|
+
return __vitePreload(() => import("./htmlMode-M3MApZ4n.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
|
|
207166
207932
|
}
|
|
207167
207933
|
function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
|
|
207168
207934
|
const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
|
|
@@ -207246,7 +208012,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
|
|
|
207246
208012
|
);
|
|
207247
208013
|
const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
|
|
207248
208014
|
function getMode$1() {
|
|
207249
|
-
return __vitePreload(() => import("./jsonMode-
|
|
208015
|
+
return __vitePreload(() => import("./jsonMode-CKp2zvZu.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
|
|
207250
208016
|
}
|
|
207251
208017
|
languages.register({
|
|
207252
208018
|
id: "json",
|
|
@@ -207492,7 +208258,7 @@ const getJavaScriptWorker = () => {
|
|
|
207492
208258
|
return getMode().then((mode) => mode.getJavaScriptWorker());
|
|
207493
208259
|
};
|
|
207494
208260
|
function getMode() {
|
|
207495
|
-
return __vitePreload(() => import("./tsMode-
|
|
208261
|
+
return __vitePreload(() => import("./tsMode-yAjlPR-D.js"), true ? [] : void 0, import.meta.url);
|
|
207496
208262
|
}
|
|
207497
208263
|
languages.onLanguage("typescript", () => {
|
|
207498
208264
|
return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
|
|
@@ -207687,49 +208453,49 @@ registerLanguage({
|
|
|
207687
208453
|
extensions: [".ftl", ".ftlh", ".ftlx"],
|
|
207688
208454
|
aliases: ["FreeMarker2", "Apache FreeMarker2"],
|
|
207689
208455
|
loader: () => {
|
|
207690
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208456
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207691
208457
|
}
|
|
207692
208458
|
});
|
|
207693
208459
|
registerLanguage({
|
|
207694
208460
|
id: "freemarker2.tag-angle.interpolation-dollar",
|
|
207695
208461
|
aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
|
|
207696
208462
|
loader: () => {
|
|
207697
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208463
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
|
|
207698
208464
|
}
|
|
207699
208465
|
});
|
|
207700
208466
|
registerLanguage({
|
|
207701
208467
|
id: "freemarker2.tag-bracket.interpolation-dollar",
|
|
207702
208468
|
aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
|
|
207703
208469
|
loader: () => {
|
|
207704
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208470
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
|
|
207705
208471
|
}
|
|
207706
208472
|
});
|
|
207707
208473
|
registerLanguage({
|
|
207708
208474
|
id: "freemarker2.tag-angle.interpolation-bracket",
|
|
207709
208475
|
aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
|
|
207710
208476
|
loader: () => {
|
|
207711
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208477
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
|
|
207712
208478
|
}
|
|
207713
208479
|
});
|
|
207714
208480
|
registerLanguage({
|
|
207715
208481
|
id: "freemarker2.tag-bracket.interpolation-bracket",
|
|
207716
208482
|
aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
|
|
207717
208483
|
loader: () => {
|
|
207718
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208484
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
|
|
207719
208485
|
}
|
|
207720
208486
|
});
|
|
207721
208487
|
registerLanguage({
|
|
207722
208488
|
id: "freemarker2.tag-auto.interpolation-dollar",
|
|
207723
208489
|
aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
|
|
207724
208490
|
loader: () => {
|
|
207725
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208491
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207726
208492
|
}
|
|
207727
208493
|
});
|
|
207728
208494
|
registerLanguage({
|
|
207729
208495
|
id: "freemarker2.tag-auto.interpolation-bracket",
|
|
207730
208496
|
aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
|
|
207731
208497
|
loader: () => {
|
|
207732
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208498
|
+
return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
|
|
207733
208499
|
}
|
|
207734
208500
|
});
|
|
207735
208501
|
registerLanguage({
|
|
@@ -207750,7 +208516,7 @@ registerLanguage({
|
|
|
207750
208516
|
extensions: [".handlebars", ".hbs"],
|
|
207751
208517
|
aliases: ["Handlebars", "handlebars", "hbs"],
|
|
207752
208518
|
mimetypes: ["text/x-handlebars-template"],
|
|
207753
|
-
loader: () => __vitePreload(() => import("./handlebars-
|
|
208519
|
+
loader: () => __vitePreload(() => import("./handlebars-3auU1CAd.js"), true ? [] : void 0, import.meta.url)
|
|
207754
208520
|
});
|
|
207755
208521
|
registerLanguage({
|
|
207756
208522
|
id: "hcl",
|
|
@@ -207763,7 +208529,7 @@ registerLanguage({
|
|
|
207763
208529
|
extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
|
|
207764
208530
|
aliases: ["HTML", "htm", "html", "xhtml"],
|
|
207765
208531
|
mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
|
|
207766
|
-
loader: () => __vitePreload(() => import("./html-
|
|
208532
|
+
loader: () => __vitePreload(() => import("./html-D8xFiRmI.js"), true ? [] : void 0, import.meta.url)
|
|
207767
208533
|
});
|
|
207768
208534
|
registerLanguage({
|
|
207769
208535
|
id: "ini",
|
|
@@ -207786,7 +208552,7 @@ registerLanguage({
|
|
|
207786
208552
|
filenames: ["jakefile"],
|
|
207787
208553
|
aliases: ["JavaScript", "javascript", "js"],
|
|
207788
208554
|
mimetypes: ["text/javascript"],
|
|
207789
|
-
loader: () => __vitePreload(() => import("./javascript-
|
|
208555
|
+
loader: () => __vitePreload(() => import("./javascript-BO_ViZM5.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
|
|
207790
208556
|
});
|
|
207791
208557
|
registerLanguage({
|
|
207792
208558
|
id: "julia",
|
|
@@ -207825,7 +208591,7 @@ registerLanguage({
|
|
|
207825
208591
|
extensions: [".liquid", ".html.liquid"],
|
|
207826
208592
|
aliases: ["Liquid", "liquid"],
|
|
207827
208593
|
mimetypes: ["application/liquid"],
|
|
207828
|
-
loader: () => __vitePreload(() => import("./liquid-
|
|
208594
|
+
loader: () => __vitePreload(() => import("./liquid-C1eHcrht.js"), true ? [] : void 0, import.meta.url)
|
|
207829
208595
|
});
|
|
207830
208596
|
registerLanguage({
|
|
207831
208597
|
id: "m3",
|
|
@@ -207843,7 +208609,7 @@ registerLanguage({
|
|
|
207843
208609
|
id: "mdx",
|
|
207844
208610
|
extensions: [".mdx"],
|
|
207845
208611
|
aliases: ["MDX", "mdx"],
|
|
207846
|
-
loader: () => __vitePreload(() => import("./mdx-
|
|
208612
|
+
loader: () => __vitePreload(() => import("./mdx-Qqdtk7fL.js"), true ? [] : void 0, import.meta.url)
|
|
207847
208613
|
});
|
|
207848
208614
|
registerLanguage({
|
|
207849
208615
|
id: "mips",
|
|
@@ -207942,7 +208708,7 @@ registerLanguage({
|
|
|
207942
208708
|
extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
|
|
207943
208709
|
aliases: ["Python", "py"],
|
|
207944
208710
|
firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
|
|
207945
|
-
loader: () => __vitePreload(() => import("./python-
|
|
208711
|
+
loader: () => __vitePreload(() => import("./python-DKu7rNbs.js"), true ? [] : void 0, import.meta.url)
|
|
207946
208712
|
});
|
|
207947
208713
|
registerLanguage({
|
|
207948
208714
|
id: "qsharp",
|
|
@@ -207961,7 +208727,7 @@ registerLanguage({
|
|
|
207961
208727
|
extensions: [".cshtml"],
|
|
207962
208728
|
aliases: ["Razor", "razor"],
|
|
207963
208729
|
mimetypes: ["text/x-cshtml"],
|
|
207964
|
-
loader: () => __vitePreload(() => import("./razor-
|
|
208730
|
+
loader: () => __vitePreload(() => import("./razor-BOMpCo6z.js"), true ? [] : void 0, import.meta.url)
|
|
207965
208731
|
});
|
|
207966
208732
|
registerLanguage({
|
|
207967
208733
|
id: "redis",
|
|
@@ -208094,7 +208860,7 @@ registerLanguage({
|
|
|
208094
208860
|
aliases: ["TypeScript", "ts", "typescript"],
|
|
208095
208861
|
mimetypes: ["text/typescript"],
|
|
208096
208862
|
loader: () => {
|
|
208097
|
-
return __vitePreload(() => import("./typescript-
|
|
208863
|
+
return __vitePreload(() => import("./typescript-BiJRCUcL.js"), true ? [] : void 0, import.meta.url);
|
|
208098
208864
|
}
|
|
208099
208865
|
});
|
|
208100
208866
|
registerLanguage({
|
|
@@ -208139,14 +208905,14 @@ registerLanguage({
|
|
|
208139
208905
|
firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
|
|
208140
208906
|
aliases: ["XML", "xml"],
|
|
208141
208907
|
mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
|
|
208142
|
-
loader: () => __vitePreload(() => import("./xml-
|
|
208908
|
+
loader: () => __vitePreload(() => import("./xml-D4PvYeQq.js"), true ? [] : void 0, import.meta.url)
|
|
208143
208909
|
});
|
|
208144
208910
|
registerLanguage({
|
|
208145
208911
|
id: "yaml",
|
|
208146
208912
|
extensions: [".yaml", ".yml"],
|
|
208147
208913
|
aliases: ["YAML", "yaml", "YML", "yml"],
|
|
208148
208914
|
mimetypes: ["application/x-yaml", "text/x-yaml"],
|
|
208149
|
-
loader: () => __vitePreload(() => import("./yaml-
|
|
208915
|
+
loader: () => __vitePreload(() => import("./yaml-BeHVkmnS.js"), true ? [] : void 0, import.meta.url)
|
|
208150
208916
|
});
|
|
208151
208917
|
var __defProp = Object.defineProperty;
|
|
208152
208918
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -212549,6 +213315,20 @@ function App() {
|
|
|
212549
213315
|
const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
|
|
212550
213316
|
const [trackingActive, setTrackingActive] = reactExports.useState(false);
|
|
212551
213317
|
const [showTicketPanel, setShowTicketPanel] = reactExports.useState(false);
|
|
213318
|
+
const [showFloatingMic, setShowFloatingMic] = reactExports.useState(() => {
|
|
213319
|
+
try {
|
|
213320
|
+
return localStorage.getItem("ctlsurf.floatingMicVisible") !== "false";
|
|
213321
|
+
} catch {
|
|
213322
|
+
return true;
|
|
213323
|
+
}
|
|
213324
|
+
});
|
|
213325
|
+
const setFloatingMicVisible = reactExports.useCallback((v2) => {
|
|
213326
|
+
setShowFloatingMic(v2);
|
|
213327
|
+
try {
|
|
213328
|
+
localStorage.setItem("ctlsurf.floatingMicVisible", String(v2));
|
|
213329
|
+
} catch {
|
|
213330
|
+
}
|
|
213331
|
+
}, []);
|
|
212552
213332
|
const [pickerTargetTabId, setPickerTargetTabId] = reactExports.useState(tabs[0].id);
|
|
212553
213333
|
const [showAgentPicker, setShowAgentPicker] = reactExports.useState(true);
|
|
212554
213334
|
const visiblePaneIds = findPaneIds(layout2);
|
|
@@ -212619,7 +213399,7 @@ function App() {
|
|
|
212619
213399
|
const handleVoiceTranscript = reactExports.useCallback((text2) => {
|
|
212620
213400
|
const trimmed = text2.trim();
|
|
212621
213401
|
if (!trimmed) return;
|
|
212622
|
-
window.worker.writePty(activeTabId, trimmed);
|
|
213402
|
+
window.worker.writePty(activeTabId, trimmed + "\r");
|
|
212623
213403
|
focusTerminal(activeTabId);
|
|
212624
213404
|
}, [activeTabId]);
|
|
212625
213405
|
const cwdRef = reactExports.useRef(null);
|
|
@@ -212883,7 +213663,36 @@ function App() {
|
|
|
212883
213663
|
]
|
|
212884
213664
|
}
|
|
212885
213665
|
),
|
|
212886
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
213666
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
213667
|
+
"button",
|
|
213668
|
+
{
|
|
213669
|
+
className: `titlebar-btn titlebar-icon-btn ${showFloatingMic ? "active" : ""}`,
|
|
213670
|
+
onClick: () => setFloatingMicVisible(!showFloatingMic),
|
|
213671
|
+
title: showFloatingMic ? "Hide floating mic" : "Show floating mic",
|
|
213672
|
+
"aria-label": "Toggle floating mic",
|
|
213673
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
213674
|
+
"svg",
|
|
213675
|
+
{
|
|
213676
|
+
viewBox: "0 0 24 24",
|
|
213677
|
+
width: "13",
|
|
213678
|
+
height: "13",
|
|
213679
|
+
fill: "none",
|
|
213680
|
+
stroke: "currentColor",
|
|
213681
|
+
strokeWidth: "2",
|
|
213682
|
+
strokeLinecap: "round",
|
|
213683
|
+
strokeLinejoin: "round",
|
|
213684
|
+
"aria-hidden": "true",
|
|
213685
|
+
children: [
|
|
213686
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
|
|
213687
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
213688
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
|
|
213689
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
|
|
213690
|
+
]
|
|
213691
|
+
}
|
|
213692
|
+
)
|
|
213693
|
+
}
|
|
213694
|
+
),
|
|
213695
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpeakControls, {}),
|
|
212887
213696
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
|
|
212888
213697
|
agents.map((a) => {
|
|
212889
213698
|
const activeTab = tabs.find((t) => t.id === activeTabId);
|
|
@@ -212937,6 +213746,13 @@ function App() {
|
|
|
212937
213746
|
}
|
|
212938
213747
|
}
|
|
212939
213748
|
}
|
|
213749
|
+
),
|
|
213750
|
+
showFloatingMic && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
213751
|
+
FloatingMic,
|
|
213752
|
+
{
|
|
213753
|
+
onTranscript: handleVoiceTranscript,
|
|
213754
|
+
onHide: () => setFloatingMicVisible(false)
|
|
213755
|
+
}
|
|
212940
213756
|
)
|
|
212941
213757
|
] });
|
|
212942
213758
|
}
|