@phenx-inc/ctlsurf 0.7.0 → 0.9.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 +169 -10
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +175 -9
- package/out/preload/index.js +10 -0
- package/out/renderer/assets/{cssMode-eTXVdAkZ.js → cssMode-BdYp0fp9.js} +3 -3
- package/out/renderer/assets/{freemarker2-B5BKaiK4.js → freemarker2-Cya-9zr-.js} +1 -1
- package/out/renderer/assets/{handlebars-BIdLd2wU.js → handlebars-EJs9W5Ub.js} +1 -1
- package/out/renderer/assets/{html-BXL4cnLS.js → html-Ex4qy2lh.js} +1 -1
- package/out/renderer/assets/{htmlMode-46N3XG2c.js → htmlMode-CCm0aJ6C.js} +3 -3
- package/out/renderer/assets/{index-dRvutfbl.js → index-Cj7K9-Ba.js} +823 -137
- package/out/renderer/assets/{index-Cf-RsxoC.css → index-nvsNYP5c.css} +229 -0
- package/out/renderer/assets/{javascript-n_iZZzDX.js → javascript-B2niCouI.js} +2 -2
- package/out/renderer/assets/{jsonMode-DXDczSNu.js → jsonMode-BgCUHaFo.js} +3 -3
- package/out/renderer/assets/{liquid-B1QweUh7.js → liquid-DOQmSo4G.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DqzMqkRk.js → lspLanguageFeatures-8SIcRAMh.js} +1 -1
- package/out/renderer/assets/{mdx-BCv8lm5e.js → mdx-DuKdGNKE.js} +1 -1
- package/out/renderer/assets/{python-BLNzYwDv.js → python-BUa4XFG-.js} +1 -1
- package/out/renderer/assets/{razor-CvAww8bG.js → razor-BUw8jho0.js} +1 -1
- package/out/renderer/assets/{transformers.web-DtSCnG36.js → transformers.web-_PuZoIho.js} +824 -19
- package/out/renderer/assets/{tsMode-C7m6Kr5E.js → tsMode-8FLRnobw.js} +1 -1
- package/out/renderer/assets/{typescript-DhPw4VVg.js → typescript-rdwAjCi4.js} +1 -1
- package/out/renderer/assets/{xml-B0WLFJ2U.js → xml-HOzT59Qg.js} +1 -1
- package/out/renderer/assets/{yaml-BWyn9Wd7.js → yaml-B81JfLsW.js} +1 -1
- package/out/renderer/index.html +64 -2
- package/out/repro-toggle.mjs +305 -0
- package/package.json +1 -1
- package/src/main/index.ts +9 -0
- package/src/main/orchestrator.ts +51 -10
- package/src/main/transcripts.ts +134 -0
- package/src/preload/index.ts +14 -0
- package/src/renderer/App.tsx +30 -11
- package/src/renderer/components/FloatingMic.tsx +48 -0
- package/src/renderer/components/SpeakControls.tsx +235 -0
- package/src/renderer/components/VoiceInput.tsx +183 -140
- package/src/renderer/index.html +62 -0
- package/src/renderer/lib/localWhisper.ts +94 -6
- package/src/renderer/lib/speech.ts +317 -0
- package/src/renderer/styles.css +229 -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-BdYp0fp9.js","./lspLanguageFeatures-8SIcRAMh.js","./htmlMode-CCm0aJ6C.js","./jsonMode-BgCUHaFo.js","./javascript-B2niCouI.js","./typescript-rdwAjCi4.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,12 +19163,32 @@ 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) {
|
|
19169
19189
|
transcriberPromise = (async () => {
|
|
19170
19190
|
const { pipeline, env: env2 } = await __vitePreload(async () => {
|
|
19171
|
-
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-
|
|
19191
|
+
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
|
|
19172
19192
|
return { pipeline: pipeline2, env: env3 };
|
|
19173
19193
|
}, true ? [] : void 0, import.meta.url);
|
|
19174
19194
|
env2.allowLocalModels = false;
|
|
@@ -19212,33 +19232,63 @@ async function blobToPcm16k(blob) {
|
|
|
19212
19232
|
const rendered = await offline.startRendering();
|
|
19213
19233
|
return rendered.getChannelData(0);
|
|
19214
19234
|
}
|
|
19215
|
-
async function transcribeBlob(blob, onProgress) {
|
|
19216
|
-
const transcriber = await loadTranscriber(onProgress);
|
|
19235
|
+
async function transcribeBlob(blob, onProgress, onPartial) {
|
|
19217
19236
|
const pcm = await blobToPcm16k(blob);
|
|
19218
19237
|
if (!pcm) return "";
|
|
19219
|
-
const
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
|
|
19223
|
-
|
|
19224
|
-
const
|
|
19225
|
-
|
|
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);
|
|
19244
|
+
let streamer;
|
|
19245
|
+
if (onPartial) {
|
|
19246
|
+
try {
|
|
19247
|
+
const { WhisperTextStreamer } = await __vitePreload(async () => {
|
|
19248
|
+
const { WhisperTextStreamer: WhisperTextStreamer2 } = await import("./transformers.web-_PuZoIho.js");
|
|
19249
|
+
return { WhisperTextStreamer: WhisperTextStreamer2 };
|
|
19250
|
+
}, true ? [] : void 0, import.meta.url);
|
|
19251
|
+
const tokenizer = transcriber.tokenizer;
|
|
19252
|
+
let committed = "";
|
|
19253
|
+
let current = "";
|
|
19254
|
+
streamer = new WhisperTextStreamer(tokenizer, {
|
|
19255
|
+
on_chunk_start: () => {
|
|
19256
|
+
current = "";
|
|
19257
|
+
},
|
|
19258
|
+
callback_function: (delta) => {
|
|
19259
|
+
current += delta;
|
|
19260
|
+
onPartial((committed + current).replace(/\s+/g, " ").trim());
|
|
19261
|
+
},
|
|
19262
|
+
on_chunk_end: () => {
|
|
19263
|
+
committed = (committed + current).trim() + " ";
|
|
19264
|
+
current = "";
|
|
19265
|
+
}
|
|
19266
|
+
});
|
|
19267
|
+
} catch (err) {
|
|
19268
|
+
console.warn("[voice] streaming preview unavailable", err);
|
|
19269
|
+
}
|
|
19270
|
+
}
|
|
19271
|
+
const result = await transcriber(pcm, {
|
|
19272
|
+
chunk_length_s: 30,
|
|
19273
|
+
stride_length_s: 5,
|
|
19274
|
+
...streamer ? { streamer } : {}
|
|
19275
|
+
});
|
|
19276
|
+
const text2 = (Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text || "").trim();
|
|
19277
|
+
if (text2 && rms < LOW_CONFIDENCE_RMS && isFillerOnly(text2)) {
|
|
19278
|
+
console.info(`[voice] dropping filler-only output "${text2}" from quiet clip (rms=${rms.toFixed(4)})`);
|
|
19279
|
+
return "";
|
|
19280
|
+
}
|
|
19281
|
+
return text2;
|
|
19226
19282
|
}
|
|
19227
|
-
const
|
|
19228
|
-
|
|
19229
|
-
const LOCAL_SUPPORTED = typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" && typeof OfflineAudioContext !== "undefined";
|
|
19230
|
-
const ANY_SUPPORTED = WEB_SPEECH_SUPPORTED || LOCAL_SUPPORTED;
|
|
19231
|
-
function loadInitialEngine() {
|
|
19232
|
-
if (!WEB_SPEECH_SUPPORTED && LOCAL_SUPPORTED) return "local";
|
|
19283
|
+
const DEVICE_KEY = "ctlsurf.voiceDeviceId";
|
|
19284
|
+
function loadDeviceId() {
|
|
19233
19285
|
try {
|
|
19234
|
-
|
|
19286
|
+
return localStorage.getItem(DEVICE_KEY) || null;
|
|
19235
19287
|
} catch {
|
|
19288
|
+
return null;
|
|
19236
19289
|
}
|
|
19237
|
-
return WEB_SPEECH_SUPPORTED ? "web-speech" : "local";
|
|
19238
|
-
}
|
|
19239
|
-
function isEngineUnavailable(code) {
|
|
19240
|
-
return code === "network" || code === "service-not-allowed";
|
|
19241
19290
|
}
|
|
19291
|
+
const LOCAL_SUPPORTED = typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" && typeof OfflineAudioContext !== "undefined";
|
|
19242
19292
|
function describeMicError(err) {
|
|
19243
19293
|
const name = err?.name;
|
|
19244
19294
|
if (name === "NotAllowedError" || name === "SecurityError") return "Microphone access denied";
|
|
@@ -19246,22 +19296,23 @@ function describeMicError(err) {
|
|
|
19246
19296
|
return "Could not start microphone";
|
|
19247
19297
|
}
|
|
19248
19298
|
function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
19249
|
-
const [engine, setEngine] = reactExports.useState(loadInitialEngine);
|
|
19250
19299
|
const [phase, setPhase] = reactExports.useState("idle");
|
|
19251
|
-
const [interim, setInterim] = reactExports.useState("");
|
|
19252
19300
|
const [modelPct, setModelPct] = reactExports.useState(null);
|
|
19301
|
+
const [partial, setPartial] = reactExports.useState("");
|
|
19253
19302
|
const [error, setError] = reactExports.useState(null);
|
|
19254
19303
|
const [notice, setNotice] = reactExports.useState(null);
|
|
19255
|
-
const
|
|
19256
|
-
const
|
|
19304
|
+
const [devices, setDevices] = reactExports.useState([]);
|
|
19305
|
+
const [selectedDeviceId, setSelectedDeviceId] = reactExports.useState(loadDeviceId);
|
|
19306
|
+
const [showDevicePicker, setShowDevicePicker] = reactExports.useState(false);
|
|
19307
|
+
const selectedDeviceIdRef = reactExports.useRef(selectedDeviceId);
|
|
19308
|
+
reactExports.useEffect(() => {
|
|
19309
|
+
selectedDeviceIdRef.current = selectedDeviceId;
|
|
19310
|
+
}, [selectedDeviceId]);
|
|
19311
|
+
const wrapRef = reactExports.useRef(null);
|
|
19257
19312
|
const streamRef = reactExports.useRef(null);
|
|
19258
19313
|
const recorderRef = reactExports.useRef(null);
|
|
19259
19314
|
const chunksRef = reactExports.useRef([]);
|
|
19260
19315
|
const cancelGestureRef = reactExports.useRef(false);
|
|
19261
|
-
const engineRef = reactExports.useRef(engine);
|
|
19262
|
-
reactExports.useEffect(() => {
|
|
19263
|
-
engineRef.current = engine;
|
|
19264
|
-
}, [engine]);
|
|
19265
19316
|
const onTranscriptRef = reactExports.useRef(onTranscript);
|
|
19266
19317
|
reactExports.useEffect(() => {
|
|
19267
19318
|
onTranscriptRef.current = onTranscript;
|
|
@@ -19276,72 +19327,83 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19276
19327
|
const t = setTimeout(() => setNotice(null), 5e3);
|
|
19277
19328
|
return () => clearTimeout(t);
|
|
19278
19329
|
}, [notice]);
|
|
19279
|
-
const switchToLocal = reactExports.useCallback((reason) => {
|
|
19280
|
-
try {
|
|
19281
|
-
localStorage.setItem(ENGINE_KEY, "local");
|
|
19282
|
-
} catch {
|
|
19283
|
-
}
|
|
19284
|
-
setEngine("local");
|
|
19285
|
-
setNotice(reason);
|
|
19286
|
-
}, []);
|
|
19287
19330
|
const stopStream = reactExports.useCallback(() => {
|
|
19288
19331
|
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
19289
19332
|
streamRef.current = null;
|
|
19290
19333
|
}, []);
|
|
19291
|
-
const
|
|
19292
|
-
|
|
19293
|
-
if (!Ctor || recognitionRef.current) return;
|
|
19294
|
-
setError(null);
|
|
19295
|
-
setNotice(null);
|
|
19296
|
-
setInterim("");
|
|
19297
|
-
finalRef.current = "";
|
|
19298
|
-
const rec = new Ctor();
|
|
19299
|
-
rec.lang = navigator.language || "en-US";
|
|
19300
|
-
rec.continuous = true;
|
|
19301
|
-
rec.interimResults = true;
|
|
19302
|
-
rec.onresult = (event) => {
|
|
19303
|
-
let finalText = "";
|
|
19304
|
-
let interimText = "";
|
|
19305
|
-
for (let i2 = 0; i2 < event.results.length; i2++) {
|
|
19306
|
-
const res = event.results[i2];
|
|
19307
|
-
if (res.isFinal) finalText += res[0].transcript;
|
|
19308
|
-
else interimText += res[0].transcript;
|
|
19309
|
-
}
|
|
19310
|
-
finalRef.current = finalText;
|
|
19311
|
-
setInterim(interimText);
|
|
19312
|
-
};
|
|
19313
|
-
rec.onerror = (event) => {
|
|
19314
|
-
if (isEngineUnavailable(event.error) && LOCAL_SUPPORTED) {
|
|
19315
|
-
finalRef.current = "";
|
|
19316
|
-
switchToLocal("Voice service unavailable — switched to on-device. Press again.");
|
|
19317
|
-
} else if (event.error !== "no-speech" && event.error !== "aborted") {
|
|
19318
|
-
setError(event.error === "not-allowed" ? "Microphone access denied" : `Voice error: ${event.error}`);
|
|
19319
|
-
}
|
|
19320
|
-
};
|
|
19321
|
-
rec.onend = () => {
|
|
19322
|
-
const text2 = finalRef.current.trim();
|
|
19323
|
-
recognitionRef.current = null;
|
|
19324
|
-
setPhase("idle");
|
|
19325
|
-
setInterim("");
|
|
19326
|
-
if (text2) onTranscriptRef.current(text2);
|
|
19327
|
-
};
|
|
19328
|
-
recognitionRef.current = rec;
|
|
19334
|
+
const refreshDevices = reactExports.useCallback(async () => {
|
|
19335
|
+
if (!navigator.mediaDevices?.enumerateDevices) return;
|
|
19329
19336
|
try {
|
|
19330
|
-
|
|
19331
|
-
|
|
19332
|
-
} catch
|
|
19333
|
-
recognitionRef.current = null;
|
|
19334
|
-
setPhase("idle");
|
|
19335
|
-
setError("Could not start microphone");
|
|
19336
|
-
console.error("[voice] web speech start failed", err);
|
|
19337
|
+
const all = await navigator.mediaDevices.enumerateDevices();
|
|
19338
|
+
setDevices(all.filter((d) => d.kind === "audioinput"));
|
|
19339
|
+
} catch {
|
|
19337
19340
|
}
|
|
19338
|
-
}, [
|
|
19339
|
-
const
|
|
19341
|
+
}, []);
|
|
19342
|
+
const ensureDeviceLabels = reactExports.useCallback(async () => {
|
|
19343
|
+
if (!navigator.mediaDevices?.enumerateDevices) return;
|
|
19340
19344
|
try {
|
|
19341
|
-
|
|
19345
|
+
const all = await navigator.mediaDevices.enumerateDevices();
|
|
19346
|
+
const inputs = all.filter((d) => d.kind === "audioinput");
|
|
19347
|
+
if (inputs.length && inputs.every((d) => !d.label)) {
|
|
19348
|
+
const s = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
19349
|
+
s.getTracks().forEach((t) => t.stop());
|
|
19350
|
+
}
|
|
19342
19351
|
} catch {
|
|
19343
19352
|
}
|
|
19353
|
+
await refreshDevices();
|
|
19354
|
+
}, [refreshDevices]);
|
|
19355
|
+
const toggleDevicePicker = reactExports.useCallback(() => {
|
|
19356
|
+
setShowDevicePicker((open) => !open);
|
|
19357
|
+
}, []);
|
|
19358
|
+
reactExports.useEffect(() => {
|
|
19359
|
+
if (showDevicePicker) void ensureDeviceLabels();
|
|
19360
|
+
}, [showDevicePicker, ensureDeviceLabels]);
|
|
19361
|
+
const chooseDevice = reactExports.useCallback((id) => {
|
|
19362
|
+
setSelectedDeviceId(id);
|
|
19363
|
+
try {
|
|
19364
|
+
if (id) localStorage.setItem(DEVICE_KEY, id);
|
|
19365
|
+
else localStorage.removeItem(DEVICE_KEY);
|
|
19366
|
+
} catch {
|
|
19367
|
+
}
|
|
19368
|
+
setShowDevicePicker(false);
|
|
19369
|
+
}, []);
|
|
19370
|
+
const getStream = reactExports.useCallback(async () => {
|
|
19371
|
+
const id = selectedDeviceIdRef.current;
|
|
19372
|
+
try {
|
|
19373
|
+
return await navigator.mediaDevices.getUserMedia({
|
|
19374
|
+
audio: id ? { deviceId: { exact: id } } : true
|
|
19375
|
+
});
|
|
19376
|
+
} catch (err) {
|
|
19377
|
+
if (id && err?.name === "OverconstrainedError") {
|
|
19378
|
+
try {
|
|
19379
|
+
localStorage.removeItem(DEVICE_KEY);
|
|
19380
|
+
} catch {
|
|
19381
|
+
}
|
|
19382
|
+
setSelectedDeviceId(null);
|
|
19383
|
+
setNotice("Saved microphone unavailable — using system default.");
|
|
19384
|
+
return navigator.mediaDevices.getUserMedia({ audio: true });
|
|
19385
|
+
}
|
|
19386
|
+
throw err;
|
|
19387
|
+
}
|
|
19344
19388
|
}, []);
|
|
19389
|
+
reactExports.useEffect(() => {
|
|
19390
|
+
const md = navigator.mediaDevices;
|
|
19391
|
+
if (!md?.addEventListener) return;
|
|
19392
|
+
const onChange = () => {
|
|
19393
|
+
void refreshDevices();
|
|
19394
|
+
};
|
|
19395
|
+
md.addEventListener("devicechange", onChange);
|
|
19396
|
+
void refreshDevices();
|
|
19397
|
+
return () => md.removeEventListener("devicechange", onChange);
|
|
19398
|
+
}, [refreshDevices]);
|
|
19399
|
+
reactExports.useEffect(() => {
|
|
19400
|
+
if (!showDevicePicker) return;
|
|
19401
|
+
const onDocDown = (e) => {
|
|
19402
|
+
if (!wrapRef.current?.contains(e.target)) setShowDevicePicker(false);
|
|
19403
|
+
};
|
|
19404
|
+
document.addEventListener("mousedown", onDocDown);
|
|
19405
|
+
return () => document.removeEventListener("mousedown", onDocDown);
|
|
19406
|
+
}, [showDevicePicker]);
|
|
19345
19407
|
const handleModelProgress = reactExports.useCallback((p) => {
|
|
19346
19408
|
if (p.status === "progress" && typeof p.progress === "number") {
|
|
19347
19409
|
setModelPct(Math.min(100, Math.round(p.progress)));
|
|
@@ -19357,25 +19419,29 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19357
19419
|
return;
|
|
19358
19420
|
}
|
|
19359
19421
|
setPhase("transcribing");
|
|
19360
|
-
|
|
19422
|
+
setPartial("");
|
|
19361
19423
|
try {
|
|
19362
|
-
const text2 = await transcribeBlob(blob, handleModelProgress)
|
|
19424
|
+
const text2 = await transcribeBlob(blob, handleModelProgress, (t) => {
|
|
19425
|
+
setModelPct(null);
|
|
19426
|
+
setPartial(t);
|
|
19427
|
+
});
|
|
19363
19428
|
if (text2) onTranscriptRef.current(text2);
|
|
19429
|
+
else setNotice("No speech detected — check the mic source (▾).");
|
|
19364
19430
|
} catch (err) {
|
|
19365
19431
|
setError("On-device transcription failed");
|
|
19366
19432
|
console.error("[voice] local transcription failed", err);
|
|
19367
19433
|
} finally {
|
|
19368
19434
|
setPhase("idle");
|
|
19369
19435
|
setModelPct(null);
|
|
19436
|
+
setPartial("");
|
|
19370
19437
|
}
|
|
19371
19438
|
}, [stopStream, handleModelProgress]);
|
|
19372
19439
|
const startLocal = reactExports.useCallback(async () => {
|
|
19373
19440
|
setError(null);
|
|
19374
19441
|
setNotice(null);
|
|
19375
|
-
setInterim("");
|
|
19376
19442
|
cancelGestureRef.current = false;
|
|
19377
19443
|
try {
|
|
19378
|
-
const stream = await
|
|
19444
|
+
const stream = await getStream();
|
|
19379
19445
|
if (cancelGestureRef.current) {
|
|
19380
19446
|
stream.getTracks().forEach((t) => t.stop());
|
|
19381
19447
|
setPhase("idle");
|
|
@@ -19399,7 +19465,7 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19399
19465
|
setError(describeMicError(err));
|
|
19400
19466
|
console.error("[voice] getUserMedia failed", err);
|
|
19401
19467
|
}
|
|
19402
|
-
}, [runLocalTranscription, stopStream]);
|
|
19468
|
+
}, [runLocalTranscription, stopStream, getStream]);
|
|
19403
19469
|
const stopLocal = reactExports.useCallback(() => {
|
|
19404
19470
|
cancelGestureRef.current = true;
|
|
19405
19471
|
const rec = recorderRef.current;
|
|
@@ -19411,22 +19477,16 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19411
19477
|
}
|
|
19412
19478
|
}, []);
|
|
19413
19479
|
const handlePointerDown = (e) => {
|
|
19414
|
-
if (!
|
|
19480
|
+
if (!LOCAL_SUPPORTED || phase !== "idle") return;
|
|
19415
19481
|
e.preventDefault();
|
|
19416
19482
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
19417
|
-
|
|
19418
|
-
else if (LOCAL_SUPPORTED) void startLocal();
|
|
19483
|
+
void startLocal();
|
|
19419
19484
|
};
|
|
19420
19485
|
const handlePointerUp = (e) => {
|
|
19421
19486
|
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
19422
|
-
|
|
19423
|
-
else stopLocal();
|
|
19487
|
+
stopLocal();
|
|
19424
19488
|
};
|
|
19425
19489
|
reactExports.useEffect(() => () => {
|
|
19426
|
-
try {
|
|
19427
|
-
recognitionRef.current?.abort();
|
|
19428
|
-
} catch {
|
|
19429
|
-
}
|
|
19430
19490
|
try {
|
|
19431
19491
|
recorderRef.current?.stop();
|
|
19432
19492
|
} catch {
|
|
@@ -19435,21 +19495,34 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19435
19495
|
}, []);
|
|
19436
19496
|
const listening = phase === "listening";
|
|
19437
19497
|
const busy = phase === "transcribing";
|
|
19438
|
-
const title = !
|
|
19498
|
+
const title = !LOCAL_SUPPORTED ? "Voice typing not supported in this build" : listening ? "Listening… release to insert" : busy ? "Transcribing…" : "Hold to talk (on-device) — speech is typed into the terminal";
|
|
19439
19499
|
let chip = null;
|
|
19440
19500
|
if (error && phase === "idle") chip = { kind: "error", text: error };
|
|
19441
19501
|
else if (notice && phase === "idle") chip = { kind: "notice", text: notice };
|
|
19442
|
-
else if (listening) chip = { kind: "listening", text:
|
|
19443
|
-
else if (busy)
|
|
19502
|
+
else if (listening) chip = { kind: "listening", text: "Recording…" };
|
|
19503
|
+
else if (busy) {
|
|
19504
|
+
const preview = partial.length > 90 ? "…" + partial.slice(-90) : partial;
|
|
19505
|
+
chip = {
|
|
19506
|
+
kind: "busy",
|
|
19507
|
+
text: modelPct !== null ? `Downloading voice model… ${modelPct}%` : preview || "Transcribing…"
|
|
19508
|
+
};
|
|
19509
|
+
}
|
|
19444
19510
|
const floating = variant === "floating";
|
|
19445
19511
|
const btnClass = floating ? `voice-btn voice-btn-floating ${listening ? "listening" : ""} ${busy ? "busy" : ""}` : `titlebar-btn titlebar-icon-btn voice-btn ${listening ? "listening" : ""} ${busy ? "busy" : ""}`;
|
|
19446
|
-
|
|
19512
|
+
const realDevices = devices.filter(
|
|
19513
|
+
(d) => d.deviceId && d.deviceId !== "default" && d.deviceId !== "communications"
|
|
19514
|
+
);
|
|
19515
|
+
const rawDefault = devices.find((d) => d.deviceId === "default")?.label;
|
|
19516
|
+
const defaultLabel = rawDefault ? `System default · ${rawDefault.replace(/^Default\s*-\s*/i, "")}` : "System default";
|
|
19517
|
+
const activeLabel = selectedDeviceId ? realDevices.find((d) => d.deviceId === selectedDeviceId)?.label || "Selected microphone" : defaultLabel;
|
|
19518
|
+
const showSourcePicker = floating && LOCAL_SUPPORTED;
|
|
19519
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "voice-input-wrap", ref: wrapRef, children: [
|
|
19447
19520
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19448
19521
|
"button",
|
|
19449
19522
|
{
|
|
19450
19523
|
type: "button",
|
|
19451
19524
|
className: btnClass,
|
|
19452
|
-
disabled: !
|
|
19525
|
+
disabled: !LOCAL_SUPPORTED,
|
|
19453
19526
|
onPointerDown: handlePointerDown,
|
|
19454
19527
|
onPointerUp: handlePointerUp,
|
|
19455
19528
|
onPointerCancel: handlePointerUp,
|
|
@@ -19462,9 +19535,336 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19462
19535
|
]
|
|
19463
19536
|
}
|
|
19464
19537
|
),
|
|
19538
|
+
showSourcePicker && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19539
|
+
"button",
|
|
19540
|
+
{
|
|
19541
|
+
type: "button",
|
|
19542
|
+
className: "voice-source-btn",
|
|
19543
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
19544
|
+
onClick: toggleDevicePicker,
|
|
19545
|
+
title: `Mic source: ${activeLabel}`,
|
|
19546
|
+
"aria-label": "Choose microphone source",
|
|
19547
|
+
"aria-expanded": showDevicePicker,
|
|
19548
|
+
children: "▾"
|
|
19549
|
+
}
|
|
19550
|
+
),
|
|
19551
|
+
showSourcePicker && showDevicePicker && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "voice-source-menu", role: "menu", children: [
|
|
19552
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "voice-source-head", children: "Microphone source" }),
|
|
19553
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19554
|
+
"button",
|
|
19555
|
+
{
|
|
19556
|
+
type: "button",
|
|
19557
|
+
role: "menuitemradio",
|
|
19558
|
+
"aria-checked": selectedDeviceId === null,
|
|
19559
|
+
className: `voice-source-item ${selectedDeviceId === null ? "active" : ""}`,
|
|
19560
|
+
onClick: () => chooseDevice(null),
|
|
19561
|
+
children: [
|
|
19562
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-check", children: selectedDeviceId === null ? "✓" : "" }),
|
|
19563
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-label", children: defaultLabel })
|
|
19564
|
+
]
|
|
19565
|
+
}
|
|
19566
|
+
),
|
|
19567
|
+
realDevices.map((d, i2) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19568
|
+
"button",
|
|
19569
|
+
{
|
|
19570
|
+
type: "button",
|
|
19571
|
+
role: "menuitemradio",
|
|
19572
|
+
"aria-checked": selectedDeviceId === d.deviceId,
|
|
19573
|
+
className: `voice-source-item ${selectedDeviceId === d.deviceId ? "active" : ""}`,
|
|
19574
|
+
onClick: () => chooseDevice(d.deviceId),
|
|
19575
|
+
children: [
|
|
19576
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-check", children: selectedDeviceId === d.deviceId ? "✓" : "" }),
|
|
19577
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "voice-source-label", children: d.label || `Microphone ${i2 + 1}` })
|
|
19578
|
+
]
|
|
19579
|
+
},
|
|
19580
|
+
d.deviceId
|
|
19581
|
+
)),
|
|
19582
|
+
realDevices.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "voice-source-empty", children: "No microphones found" })
|
|
19583
|
+
] }),
|
|
19465
19584
|
chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
|
|
19466
19585
|
] });
|
|
19467
19586
|
}
|
|
19587
|
+
const ENGINE_KEY = "ctlsurf.tts.engine";
|
|
19588
|
+
const VOICE_KEY = "ctlsurf.tts.voiceURI";
|
|
19589
|
+
const RATE_KEY = "ctlsurf.tts.rate";
|
|
19590
|
+
const MAX_SPEAK_CHARS = 1600;
|
|
19591
|
+
const MAX_CHUNK_CHARS = 280;
|
|
19592
|
+
function cleanForSpeech(input) {
|
|
19593
|
+
let t = input;
|
|
19594
|
+
t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
|
|
19595
|
+
const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
|
|
19596
|
+
return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
|
|
19597
|
+
});
|
|
19598
|
+
t = t.replace(/```/g, " (code) ");
|
|
19599
|
+
t = t.replace(/`([^`]+)`/g, "$1");
|
|
19600
|
+
t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
19601
|
+
t = t.replace(/https?:\/\/\S+/g, "link");
|
|
19602
|
+
t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
|
|
19603
|
+
t = t.replace(/[*_#>]+/g, " ");
|
|
19604
|
+
t = t.replace(/^\s*[-•]\s+/gm, ", ");
|
|
19605
|
+
t = t.replace(/<[^>]+>/g, " ");
|
|
19606
|
+
t = t.replace(/\s+/g, " ").trim();
|
|
19607
|
+
if (t.length > MAX_SPEAK_CHARS) {
|
|
19608
|
+
t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
|
|
19609
|
+
}
|
|
19610
|
+
return t;
|
|
19611
|
+
}
|
|
19612
|
+
function splitIntoChunks(text2) {
|
|
19613
|
+
if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
|
|
19614
|
+
const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
|
|
19615
|
+
const chunks = [];
|
|
19616
|
+
let buf = "";
|
|
19617
|
+
for (const s of sentences) {
|
|
19618
|
+
if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
|
|
19619
|
+
chunks.push(buf.trim());
|
|
19620
|
+
buf = "";
|
|
19621
|
+
}
|
|
19622
|
+
if (s.length > MAX_CHUNK_CHARS) {
|
|
19623
|
+
for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
|
|
19624
|
+
} else {
|
|
19625
|
+
buf += s;
|
|
19626
|
+
}
|
|
19627
|
+
}
|
|
19628
|
+
if (buf.trim()) chunks.push(buf.trim());
|
|
19629
|
+
return chunks;
|
|
19630
|
+
}
|
|
19631
|
+
function getEngine() {
|
|
19632
|
+
try {
|
|
19633
|
+
return localStorage.getItem(ENGINE_KEY) === "neural" ? "neural" : "web";
|
|
19634
|
+
} catch {
|
|
19635
|
+
return "web";
|
|
19636
|
+
}
|
|
19637
|
+
}
|
|
19638
|
+
function setEngine(id) {
|
|
19639
|
+
try {
|
|
19640
|
+
localStorage.setItem(ENGINE_KEY, id);
|
|
19641
|
+
} catch {
|
|
19642
|
+
}
|
|
19643
|
+
}
|
|
19644
|
+
function getVoiceURI() {
|
|
19645
|
+
try {
|
|
19646
|
+
return localStorage.getItem(VOICE_KEY) || null;
|
|
19647
|
+
} catch {
|
|
19648
|
+
return null;
|
|
19649
|
+
}
|
|
19650
|
+
}
|
|
19651
|
+
function setVoiceURI(uri) {
|
|
19652
|
+
try {
|
|
19653
|
+
uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
|
|
19654
|
+
} catch {
|
|
19655
|
+
}
|
|
19656
|
+
}
|
|
19657
|
+
function getRate() {
|
|
19658
|
+
try {
|
|
19659
|
+
const n2 = Number(localStorage.getItem(RATE_KEY));
|
|
19660
|
+
return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
|
|
19661
|
+
} catch {
|
|
19662
|
+
return 1;
|
|
19663
|
+
}
|
|
19664
|
+
}
|
|
19665
|
+
function setRate(rate) {
|
|
19666
|
+
try {
|
|
19667
|
+
localStorage.setItem(RATE_KEY, String(rate));
|
|
19668
|
+
} catch {
|
|
19669
|
+
}
|
|
19670
|
+
}
|
|
19671
|
+
function listWebVoices() {
|
|
19672
|
+
if (typeof speechSynthesis === "undefined") return [];
|
|
19673
|
+
return speechSynthesis.getVoices();
|
|
19674
|
+
}
|
|
19675
|
+
const NEURAL_MODEL = "Xenova/mms-tts-eng";
|
|
19676
|
+
let synthPromise = null;
|
|
19677
|
+
function loadSynthesizer(onProgress) {
|
|
19678
|
+
if (!synthPromise) {
|
|
19679
|
+
synthPromise = (async () => {
|
|
19680
|
+
const { pipeline, env: env2 } = await __vitePreload(async () => {
|
|
19681
|
+
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
|
|
19682
|
+
return { pipeline: pipeline2, env: env3 };
|
|
19683
|
+
}, true ? [] : void 0, import.meta.url);
|
|
19684
|
+
env2.allowLocalModels = false;
|
|
19685
|
+
const common = { progress_callback: onProgress };
|
|
19686
|
+
const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
|
|
19687
|
+
if (hasWebGpu) {
|
|
19688
|
+
try {
|
|
19689
|
+
const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
|
|
19690
|
+
console.info("[tts] neural backend: webgpu");
|
|
19691
|
+
return s2;
|
|
19692
|
+
} catch (err) {
|
|
19693
|
+
console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
|
|
19694
|
+
}
|
|
19695
|
+
}
|
|
19696
|
+
const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
|
|
19697
|
+
console.info("[tts] neural backend: wasm");
|
|
19698
|
+
return s;
|
|
19699
|
+
})();
|
|
19700
|
+
synthPromise.catch(() => {
|
|
19701
|
+
synthPromise = null;
|
|
19702
|
+
});
|
|
19703
|
+
}
|
|
19704
|
+
return synthPromise;
|
|
19705
|
+
}
|
|
19706
|
+
class SpeechController {
|
|
19707
|
+
queue = [];
|
|
19708
|
+
draining = false;
|
|
19709
|
+
audioCtx = null;
|
|
19710
|
+
currentSource = null;
|
|
19711
|
+
generation = 0;
|
|
19712
|
+
// bumped by stop() to abort in-flight work
|
|
19713
|
+
active = false;
|
|
19714
|
+
onModelProgress = null;
|
|
19715
|
+
// Fires true while a reply is being spoken/queued, false when idle — drives
|
|
19716
|
+
// the visible Stop button.
|
|
19717
|
+
onActivityChange = null;
|
|
19718
|
+
onError = null;
|
|
19719
|
+
// Multiple UI surfaces (titlebar SpeakControls + floating read button) need
|
|
19720
|
+
// the speaking state at once, so beyond the single onActivityChange slot we
|
|
19721
|
+
// also keep a listener set.
|
|
19722
|
+
activityListeners = /* @__PURE__ */ new Set();
|
|
19723
|
+
get isActive() {
|
|
19724
|
+
return this.active;
|
|
19725
|
+
}
|
|
19726
|
+
// Subscribe to speaking-state changes. Fires immediately with the current
|
|
19727
|
+
// state so a late subscriber syncs. Returns an unsubscribe function.
|
|
19728
|
+
onActivity(fn) {
|
|
19729
|
+
this.activityListeners.add(fn);
|
|
19730
|
+
fn(this.active);
|
|
19731
|
+
return () => {
|
|
19732
|
+
this.activityListeners.delete(fn);
|
|
19733
|
+
};
|
|
19734
|
+
}
|
|
19735
|
+
// Prime/resume the AudioContext from a user gesture so neural playback isn't
|
|
19736
|
+
// blocked by the browser autoplay policy (the System engine is unaffected).
|
|
19737
|
+
unlock() {
|
|
19738
|
+
try {
|
|
19739
|
+
const ctx = this.ensureCtx();
|
|
19740
|
+
if (ctx.state === "suspended") void ctx.resume();
|
|
19741
|
+
} catch {
|
|
19742
|
+
}
|
|
19743
|
+
}
|
|
19744
|
+
// Start loading the neural model in the background (e.g. when the user picks
|
|
19745
|
+
// the neural engine) so the first utterance doesn't pay download/compile time.
|
|
19746
|
+
warmup() {
|
|
19747
|
+
if (getEngine() !== "neural") return;
|
|
19748
|
+
void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
|
|
19749
|
+
this.onModelProgress?.(null);
|
|
19750
|
+
});
|
|
19751
|
+
}
|
|
19752
|
+
enqueue(rawText) {
|
|
19753
|
+
const text2 = cleanForSpeech(rawText);
|
|
19754
|
+
if (!text2) return;
|
|
19755
|
+
this.queue.push(...splitIntoChunks(text2));
|
|
19756
|
+
void this.drain();
|
|
19757
|
+
}
|
|
19758
|
+
stop() {
|
|
19759
|
+
this.generation++;
|
|
19760
|
+
this.queue = [];
|
|
19761
|
+
this.draining = false;
|
|
19762
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
19763
|
+
try {
|
|
19764
|
+
speechSynthesis.cancel();
|
|
19765
|
+
} catch {
|
|
19766
|
+
}
|
|
19767
|
+
}
|
|
19768
|
+
if (this.currentSource) {
|
|
19769
|
+
try {
|
|
19770
|
+
this.currentSource.stop();
|
|
19771
|
+
} catch {
|
|
19772
|
+
}
|
|
19773
|
+
this.currentSource = null;
|
|
19774
|
+
}
|
|
19775
|
+
this.onModelProgress?.(null);
|
|
19776
|
+
this.setActive(false);
|
|
19777
|
+
}
|
|
19778
|
+
ensureCtx() {
|
|
19779
|
+
if (!this.audioCtx) {
|
|
19780
|
+
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
19781
|
+
this.audioCtx = new Ctx();
|
|
19782
|
+
}
|
|
19783
|
+
return this.audioCtx;
|
|
19784
|
+
}
|
|
19785
|
+
setActive(a) {
|
|
19786
|
+
if (this.active === a) return;
|
|
19787
|
+
this.active = a;
|
|
19788
|
+
this.onActivityChange?.(a);
|
|
19789
|
+
this.activityListeners.forEach((fn) => fn(a));
|
|
19790
|
+
}
|
|
19791
|
+
async drain() {
|
|
19792
|
+
if (this.draining) return;
|
|
19793
|
+
this.draining = true;
|
|
19794
|
+
const gen = this.generation;
|
|
19795
|
+
this.setActive(true);
|
|
19796
|
+
while (this.queue.length && gen === this.generation) {
|
|
19797
|
+
const chunk = this.queue.shift();
|
|
19798
|
+
try {
|
|
19799
|
+
if (getEngine() === "neural") await this.speakNeural(chunk, gen);
|
|
19800
|
+
else await this.speakWeb(chunk, gen);
|
|
19801
|
+
} catch (err) {
|
|
19802
|
+
console.error("[tts] speak failed", err);
|
|
19803
|
+
this.onModelProgress?.(null);
|
|
19804
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
19805
|
+
this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
|
|
19806
|
+
}
|
|
19807
|
+
}
|
|
19808
|
+
if (gen === this.generation) {
|
|
19809
|
+
this.draining = false;
|
|
19810
|
+
this.setActive(false);
|
|
19811
|
+
}
|
|
19812
|
+
}
|
|
19813
|
+
speakWeb(text2, gen) {
|
|
19814
|
+
return new Promise((resolve2) => {
|
|
19815
|
+
if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
|
|
19816
|
+
const u = new SpeechSynthesisUtterance(text2);
|
|
19817
|
+
u.rate = getRate();
|
|
19818
|
+
const wantUri = getVoiceURI();
|
|
19819
|
+
if (wantUri) {
|
|
19820
|
+
const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
|
|
19821
|
+
if (v2) u.voice = v2;
|
|
19822
|
+
}
|
|
19823
|
+
u.onend = () => resolve2();
|
|
19824
|
+
u.onerror = () => resolve2();
|
|
19825
|
+
speechSynthesis.speak(u);
|
|
19826
|
+
});
|
|
19827
|
+
}
|
|
19828
|
+
async speakNeural(text2, gen) {
|
|
19829
|
+
console.info("[tts] neural: loading model…");
|
|
19830
|
+
this.onModelProgress?.({ status: "loading" });
|
|
19831
|
+
const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
|
|
19832
|
+
if (gen !== this.generation) return;
|
|
19833
|
+
console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
|
|
19834
|
+
const out = await synth(text2);
|
|
19835
|
+
const raw = Array.isArray(out) ? out[0] : out;
|
|
19836
|
+
this.onModelProgress?.(null);
|
|
19837
|
+
if (gen !== this.generation) return;
|
|
19838
|
+
if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
|
|
19839
|
+
console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
|
|
19840
|
+
await this.playPcm(raw.audio, raw.sampling_rate, gen);
|
|
19841
|
+
}
|
|
19842
|
+
async playPcm(pcm, sampleRate, gen) {
|
|
19843
|
+
if (gen !== this.generation) return;
|
|
19844
|
+
const ctx = this.ensureCtx();
|
|
19845
|
+
if (ctx.state === "suspended") {
|
|
19846
|
+
try {
|
|
19847
|
+
await ctx.resume();
|
|
19848
|
+
} catch {
|
|
19849
|
+
}
|
|
19850
|
+
}
|
|
19851
|
+
if (gen !== this.generation) return;
|
|
19852
|
+
return new Promise((resolve2) => {
|
|
19853
|
+
const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
|
|
19854
|
+
buffer.getChannelData(0).set(pcm);
|
|
19855
|
+
const source = ctx.createBufferSource();
|
|
19856
|
+
source.buffer = buffer;
|
|
19857
|
+
source.connect(ctx.destination);
|
|
19858
|
+
source.onended = () => {
|
|
19859
|
+
if (this.currentSource === source) this.currentSource = null;
|
|
19860
|
+
resolve2();
|
|
19861
|
+
};
|
|
19862
|
+
this.currentSource = source;
|
|
19863
|
+
source.start();
|
|
19864
|
+
});
|
|
19865
|
+
}
|
|
19866
|
+
}
|
|
19867
|
+
const speech = new SpeechController();
|
|
19468
19868
|
const POS_KEY = "ctlsurf.floatingMicPos";
|
|
19469
19869
|
const EDGE = 20;
|
|
19470
19870
|
const TOP_MIN = 46;
|
|
@@ -19484,6 +19884,28 @@ function FloatingMic({ onTranscript, onHide }) {
|
|
|
19484
19884
|
const [pos, setPos] = reactExports.useState(loadPos);
|
|
19485
19885
|
const elRef = reactExports.useRef(null);
|
|
19486
19886
|
const dragRef = reactExports.useRef(null);
|
|
19887
|
+
const [speaking, setSpeaking] = reactExports.useState(false);
|
|
19888
|
+
const [note, setNote] = reactExports.useState(null);
|
|
19889
|
+
reactExports.useEffect(() => speech.onActivity(setSpeaking), []);
|
|
19890
|
+
reactExports.useEffect(() => {
|
|
19891
|
+
if (!note) return;
|
|
19892
|
+
const t = setTimeout(() => setNote(null), 3e3);
|
|
19893
|
+
return () => clearTimeout(t);
|
|
19894
|
+
}, [note]);
|
|
19895
|
+
const onReadAloud = reactExports.useCallback(async () => {
|
|
19896
|
+
if (speaking) {
|
|
19897
|
+
speech.stop();
|
|
19898
|
+
return;
|
|
19899
|
+
}
|
|
19900
|
+
speech.unlock();
|
|
19901
|
+
let text2 = null;
|
|
19902
|
+
try {
|
|
19903
|
+
text2 = await window.worker.getLastReply();
|
|
19904
|
+
} catch {
|
|
19905
|
+
}
|
|
19906
|
+
if (text2) speech.enqueue(text2);
|
|
19907
|
+
else setNote("No reply to read yet");
|
|
19908
|
+
}, [speaking]);
|
|
19487
19909
|
const clamp2 = reactExports.useCallback((x, y) => {
|
|
19488
19910
|
const el = elRef.current;
|
|
19489
19911
|
const w = el?.offsetWidth ?? 64;
|
|
@@ -19564,7 +19986,265 @@ function FloatingMic({ onTranscript, onHide }) {
|
|
|
19564
19986
|
]
|
|
19565
19987
|
}
|
|
19566
19988
|
),
|
|
19567
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript })
|
|
19989
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript }),
|
|
19990
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19991
|
+
"button",
|
|
19992
|
+
{
|
|
19993
|
+
type: "button",
|
|
19994
|
+
className: `floating-mic-read ${speaking ? "speaking" : ""}`,
|
|
19995
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
19996
|
+
onClick: onReadAloud,
|
|
19997
|
+
title: speaking ? "Stop reading" : "Read last agent reply aloud",
|
|
19998
|
+
"aria-label": speaking ? "Stop reading" : "Read last agent reply aloud",
|
|
19999
|
+
"aria-pressed": speaking,
|
|
20000
|
+
children: speaking ? /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20001
|
+
"svg",
|
|
20002
|
+
{
|
|
20003
|
+
viewBox: "0 0 24 24",
|
|
20004
|
+
width: "15",
|
|
20005
|
+
height: "15",
|
|
20006
|
+
fill: "none",
|
|
20007
|
+
stroke: "currentColor",
|
|
20008
|
+
strokeWidth: "2",
|
|
20009
|
+
strokeLinecap: "round",
|
|
20010
|
+
strokeLinejoin: "round",
|
|
20011
|
+
"aria-hidden": "true",
|
|
20012
|
+
children: [
|
|
20013
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
|
|
20014
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
20015
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
20016
|
+
]
|
|
20017
|
+
}
|
|
20018
|
+
)
|
|
20019
|
+
}
|
|
20020
|
+
),
|
|
20021
|
+
note && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "floating-mic-note", children: note })
|
|
20022
|
+
] });
|
|
20023
|
+
}
|
|
20024
|
+
const SAMPLE = "This is how spoken agent replies will sound.";
|
|
20025
|
+
function SpeakControls() {
|
|
20026
|
+
const [enabled, setEnabled] = reactExports.useState(false);
|
|
20027
|
+
const [engine, setEngineState] = reactExports.useState(getEngine);
|
|
20028
|
+
const [voices, setVoices] = reactExports.useState([]);
|
|
20029
|
+
const [voiceURI, setVoiceURIState] = reactExports.useState(getVoiceURI);
|
|
20030
|
+
const [rate, setRateState] = reactExports.useState(getRate);
|
|
20031
|
+
const [showMenu, setShowMenu] = reactExports.useState(false);
|
|
20032
|
+
const [modelPct, setModelPct] = reactExports.useState(null);
|
|
20033
|
+
const [speaking, setSpeaking] = reactExports.useState(false);
|
|
20034
|
+
const [error, setError] = reactExports.useState(null);
|
|
20035
|
+
const wrapRef = reactExports.useRef(null);
|
|
20036
|
+
reactExports.useEffect(() => {
|
|
20037
|
+
let alive = true;
|
|
20038
|
+
window.worker.getSpeakReplies().then((r) => {
|
|
20039
|
+
if (alive) setEnabled(!!r.enabled);
|
|
20040
|
+
}).catch(() => {
|
|
20041
|
+
});
|
|
20042
|
+
return () => {
|
|
20043
|
+
alive = false;
|
|
20044
|
+
};
|
|
20045
|
+
}, []);
|
|
20046
|
+
reactExports.useEffect(() => {
|
|
20047
|
+
const off = window.worker.onAgentMessage((text2) => speech.enqueue(text2));
|
|
20048
|
+
return off;
|
|
20049
|
+
}, []);
|
|
20050
|
+
reactExports.useEffect(() => {
|
|
20051
|
+
speech.onModelProgress = (p) => {
|
|
20052
|
+
if (p && p.status === "progress" && typeof p.progress === "number") {
|
|
20053
|
+
setModelPct(Math.min(100, Math.round(p.progress)));
|
|
20054
|
+
} else if (!p) {
|
|
20055
|
+
setModelPct(null);
|
|
20056
|
+
}
|
|
20057
|
+
};
|
|
20058
|
+
speech.onActivityChange = (a) => setSpeaking(a);
|
|
20059
|
+
speech.onError = (msg) => setError(msg);
|
|
20060
|
+
return () => {
|
|
20061
|
+
speech.onModelProgress = null;
|
|
20062
|
+
speech.onActivityChange = null;
|
|
20063
|
+
speech.onError = null;
|
|
20064
|
+
};
|
|
20065
|
+
}, []);
|
|
20066
|
+
reactExports.useEffect(() => {
|
|
20067
|
+
if (!error) return;
|
|
20068
|
+
const t = setTimeout(() => setError(null), 4e3);
|
|
20069
|
+
return () => clearTimeout(t);
|
|
20070
|
+
}, [error]);
|
|
20071
|
+
reactExports.useEffect(() => {
|
|
20072
|
+
const load = () => setVoices(listWebVoices());
|
|
20073
|
+
load();
|
|
20074
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
20075
|
+
speechSynthesis.addEventListener("voiceschanged", load);
|
|
20076
|
+
return () => speechSynthesis.removeEventListener("voiceschanged", load);
|
|
20077
|
+
}
|
|
20078
|
+
}, []);
|
|
20079
|
+
reactExports.useEffect(() => {
|
|
20080
|
+
if (!showMenu) return;
|
|
20081
|
+
const onDown = (e) => {
|
|
20082
|
+
if (!wrapRef.current?.contains(e.target)) setShowMenu(false);
|
|
20083
|
+
};
|
|
20084
|
+
document.addEventListener("mousedown", onDown);
|
|
20085
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
20086
|
+
}, [showMenu]);
|
|
20087
|
+
const toggle = reactExports.useCallback(async () => {
|
|
20088
|
+
const next = !enabled;
|
|
20089
|
+
setEnabled(next);
|
|
20090
|
+
if (next) speech.unlock();
|
|
20091
|
+
else speech.stop();
|
|
20092
|
+
try {
|
|
20093
|
+
await window.worker.setSpeakReplies(next);
|
|
20094
|
+
} catch {
|
|
20095
|
+
}
|
|
20096
|
+
}, [enabled]);
|
|
20097
|
+
const chooseEngine = reactExports.useCallback((id) => {
|
|
20098
|
+
setEngine(id);
|
|
20099
|
+
setEngineState(id);
|
|
20100
|
+
speech.stop();
|
|
20101
|
+
if (id === "neural") speech.warmup();
|
|
20102
|
+
}, []);
|
|
20103
|
+
const chooseVoice = reactExports.useCallback((uri) => {
|
|
20104
|
+
const v2 = uri || null;
|
|
20105
|
+
setVoiceURI(v2);
|
|
20106
|
+
setVoiceURIState(v2);
|
|
20107
|
+
}, []);
|
|
20108
|
+
const changeRate = reactExports.useCallback((r) => {
|
|
20109
|
+
setRate(r);
|
|
20110
|
+
setRateState(r);
|
|
20111
|
+
}, []);
|
|
20112
|
+
const testVoice = reactExports.useCallback(() => {
|
|
20113
|
+
speech.unlock();
|
|
20114
|
+
speech.stop();
|
|
20115
|
+
speech.enqueue(SAMPLE);
|
|
20116
|
+
}, []);
|
|
20117
|
+
const title = enabled ? "Spoken replies on — click to mute" : "Speak agent replies (off)";
|
|
20118
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-controls", ref: wrapRef, children: [
|
|
20119
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20120
|
+
"button",
|
|
20121
|
+
{
|
|
20122
|
+
type: "button",
|
|
20123
|
+
className: `titlebar-btn titlebar-icon-btn speak-btn ${enabled ? "active" : ""}`,
|
|
20124
|
+
onClick: toggle,
|
|
20125
|
+
title,
|
|
20126
|
+
"aria-label": "Toggle spoken agent replies",
|
|
20127
|
+
"aria-pressed": enabled,
|
|
20128
|
+
children: [
|
|
20129
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20130
|
+
"svg",
|
|
20131
|
+
{
|
|
20132
|
+
viewBox: "0 0 24 24",
|
|
20133
|
+
width: "13",
|
|
20134
|
+
height: "13",
|
|
20135
|
+
fill: "none",
|
|
20136
|
+
stroke: "currentColor",
|
|
20137
|
+
strokeWidth: "2",
|
|
20138
|
+
strokeLinecap: "round",
|
|
20139
|
+
strokeLinejoin: "round",
|
|
20140
|
+
"aria-hidden": "true",
|
|
20141
|
+
children: [
|
|
20142
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
|
|
20143
|
+
enabled ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
20144
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
20145
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
20146
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "23", y1: "9", x2: "17", y2: "15" })
|
|
20147
|
+
]
|
|
20148
|
+
}
|
|
20149
|
+
),
|
|
20150
|
+
modelPct !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "speak-pct", children: [
|
|
20151
|
+
modelPct,
|
|
20152
|
+
"%"
|
|
20153
|
+
] })
|
|
20154
|
+
]
|
|
20155
|
+
}
|
|
20156
|
+
),
|
|
20157
|
+
speaking && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20158
|
+
"button",
|
|
20159
|
+
{
|
|
20160
|
+
type: "button",
|
|
20161
|
+
className: "titlebar-btn titlebar-icon-btn speak-stop",
|
|
20162
|
+
onClick: () => speech.stop(),
|
|
20163
|
+
title: "Stop speaking",
|
|
20164
|
+
"aria-label": "Stop speaking",
|
|
20165
|
+
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" }) })
|
|
20166
|
+
}
|
|
20167
|
+
),
|
|
20168
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20169
|
+
"button",
|
|
20170
|
+
{
|
|
20171
|
+
type: "button",
|
|
20172
|
+
className: "titlebar-btn speak-caret",
|
|
20173
|
+
onClick: () => setShowMenu((v2) => !v2),
|
|
20174
|
+
title: "Voice options",
|
|
20175
|
+
"aria-label": "Voice options",
|
|
20176
|
+
"aria-expanded": showMenu,
|
|
20177
|
+
children: "▾"
|
|
20178
|
+
}
|
|
20179
|
+
),
|
|
20180
|
+
showMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu", role: "menu", children: [
|
|
20181
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-menu-head", children: "Engine" }),
|
|
20182
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20183
|
+
"button",
|
|
20184
|
+
{
|
|
20185
|
+
type: "button",
|
|
20186
|
+
className: `speak-menu-item ${engine === "web" ? "active" : ""}`,
|
|
20187
|
+
onClick: () => chooseEngine("web"),
|
|
20188
|
+
children: [
|
|
20189
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "speak-menu-check", children: engine === "web" ? "✓" : "" }),
|
|
20190
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "System voice (instant)" })
|
|
20191
|
+
]
|
|
20192
|
+
}
|
|
20193
|
+
),
|
|
20194
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20195
|
+
"button",
|
|
20196
|
+
{
|
|
20197
|
+
type: "button",
|
|
20198
|
+
className: `speak-menu-item ${engine === "neural" ? "active" : ""}`,
|
|
20199
|
+
onClick: () => chooseEngine("neural"),
|
|
20200
|
+
children: [
|
|
20201
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "speak-menu-check", children: engine === "neural" ? "✓" : "" }),
|
|
20202
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Neural voice (downloads)" })
|
|
20203
|
+
]
|
|
20204
|
+
}
|
|
20205
|
+
),
|
|
20206
|
+
engine === "web" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
20207
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-menu-head", children: "Voice" }),
|
|
20208
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20209
|
+
"select",
|
|
20210
|
+
{
|
|
20211
|
+
className: "speak-select",
|
|
20212
|
+
value: voiceURI || "",
|
|
20213
|
+
onChange: (e) => chooseVoice(e.target.value),
|
|
20214
|
+
children: [
|
|
20215
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "System default" }),
|
|
20216
|
+
voices.map((v2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: v2.voiceURI, children: [
|
|
20217
|
+
v2.name,
|
|
20218
|
+
" ",
|
|
20219
|
+
v2.lang ? `(${v2.lang})` : ""
|
|
20220
|
+
] }, v2.voiceURI))
|
|
20221
|
+
]
|
|
20222
|
+
}
|
|
20223
|
+
)
|
|
20224
|
+
] }),
|
|
20225
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu-head", children: [
|
|
20226
|
+
"Rate · ",
|
|
20227
|
+
rate.toFixed(1),
|
|
20228
|
+
"×"
|
|
20229
|
+
] }),
|
|
20230
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20231
|
+
"input",
|
|
20232
|
+
{
|
|
20233
|
+
className: "speak-rate",
|
|
20234
|
+
type: "range",
|
|
20235
|
+
min: 0.5,
|
|
20236
|
+
max: 2,
|
|
20237
|
+
step: 0.1,
|
|
20238
|
+
value: rate,
|
|
20239
|
+
onChange: (e) => changeRate(Number(e.target.value))
|
|
20240
|
+
}
|
|
20241
|
+
),
|
|
20242
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "speak-menu-row", children: [
|
|
20243
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "speak-menu-btn", onClick: testVoice, children: "Test" }),
|
|
20244
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "speak-menu-btn", onClick: () => speech.stop(), children: "Stop" })
|
|
20245
|
+
] })
|
|
20246
|
+
] }),
|
|
20247
|
+
error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-error", children: error })
|
|
19568
20248
|
] });
|
|
19569
20249
|
}
|
|
19570
20250
|
function CtlsurfPanel({ navigate }) {
|
|
@@ -207161,7 +207841,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
|
|
|
207161
207841
|
modeConfigurationDefault$2
|
|
207162
207842
|
);
|
|
207163
207843
|
function getMode$3() {
|
|
207164
|
-
return __vitePreload(() => import("./cssMode-
|
|
207844
|
+
return __vitePreload(() => import("./cssMode-BdYp0fp9.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
|
|
207165
207845
|
}
|
|
207166
207846
|
languages.onLanguage("less", () => {
|
|
207167
207847
|
getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
|
|
@@ -207266,7 +207946,7 @@ const razorLanguageService = registerHTMLLanguageService(
|
|
|
207266
207946
|
);
|
|
207267
207947
|
const razorDefaults = razorLanguageService.defaults;
|
|
207268
207948
|
function getMode$2() {
|
|
207269
|
-
return __vitePreload(() => import("./htmlMode-
|
|
207949
|
+
return __vitePreload(() => import("./htmlMode-CCm0aJ6C.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
|
|
207270
207950
|
}
|
|
207271
207951
|
function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
|
|
207272
207952
|
const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
|
|
@@ -207350,7 +208030,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
|
|
|
207350
208030
|
);
|
|
207351
208031
|
const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
|
|
207352
208032
|
function getMode$1() {
|
|
207353
|
-
return __vitePreload(() => import("./jsonMode-
|
|
208033
|
+
return __vitePreload(() => import("./jsonMode-BgCUHaFo.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
|
|
207354
208034
|
}
|
|
207355
208035
|
languages.register({
|
|
207356
208036
|
id: "json",
|
|
@@ -207596,7 +208276,7 @@ const getJavaScriptWorker = () => {
|
|
|
207596
208276
|
return getMode().then((mode) => mode.getJavaScriptWorker());
|
|
207597
208277
|
};
|
|
207598
208278
|
function getMode() {
|
|
207599
|
-
return __vitePreload(() => import("./tsMode-
|
|
208279
|
+
return __vitePreload(() => import("./tsMode-8FLRnobw.js"), true ? [] : void 0, import.meta.url);
|
|
207600
208280
|
}
|
|
207601
208281
|
languages.onLanguage("typescript", () => {
|
|
207602
208282
|
return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
|
|
@@ -207791,49 +208471,49 @@ registerLanguage({
|
|
|
207791
208471
|
extensions: [".ftl", ".ftlh", ".ftlx"],
|
|
207792
208472
|
aliases: ["FreeMarker2", "Apache FreeMarker2"],
|
|
207793
208473
|
loader: () => {
|
|
207794
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208474
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207795
208475
|
}
|
|
207796
208476
|
});
|
|
207797
208477
|
registerLanguage({
|
|
207798
208478
|
id: "freemarker2.tag-angle.interpolation-dollar",
|
|
207799
208479
|
aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
|
|
207800
208480
|
loader: () => {
|
|
207801
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208481
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
|
|
207802
208482
|
}
|
|
207803
208483
|
});
|
|
207804
208484
|
registerLanguage({
|
|
207805
208485
|
id: "freemarker2.tag-bracket.interpolation-dollar",
|
|
207806
208486
|
aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
|
|
207807
208487
|
loader: () => {
|
|
207808
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208488
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
|
|
207809
208489
|
}
|
|
207810
208490
|
});
|
|
207811
208491
|
registerLanguage({
|
|
207812
208492
|
id: "freemarker2.tag-angle.interpolation-bracket",
|
|
207813
208493
|
aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
|
|
207814
208494
|
loader: () => {
|
|
207815
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208495
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
|
|
207816
208496
|
}
|
|
207817
208497
|
});
|
|
207818
208498
|
registerLanguage({
|
|
207819
208499
|
id: "freemarker2.tag-bracket.interpolation-bracket",
|
|
207820
208500
|
aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
|
|
207821
208501
|
loader: () => {
|
|
207822
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208502
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
|
|
207823
208503
|
}
|
|
207824
208504
|
});
|
|
207825
208505
|
registerLanguage({
|
|
207826
208506
|
id: "freemarker2.tag-auto.interpolation-dollar",
|
|
207827
208507
|
aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
|
|
207828
208508
|
loader: () => {
|
|
207829
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208509
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
207830
208510
|
}
|
|
207831
208511
|
});
|
|
207832
208512
|
registerLanguage({
|
|
207833
208513
|
id: "freemarker2.tag-auto.interpolation-bracket",
|
|
207834
208514
|
aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
|
|
207835
208515
|
loader: () => {
|
|
207836
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208516
|
+
return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
|
|
207837
208517
|
}
|
|
207838
208518
|
});
|
|
207839
208519
|
registerLanguage({
|
|
@@ -207854,7 +208534,7 @@ registerLanguage({
|
|
|
207854
208534
|
extensions: [".handlebars", ".hbs"],
|
|
207855
208535
|
aliases: ["Handlebars", "handlebars", "hbs"],
|
|
207856
208536
|
mimetypes: ["text/x-handlebars-template"],
|
|
207857
|
-
loader: () => __vitePreload(() => import("./handlebars-
|
|
208537
|
+
loader: () => __vitePreload(() => import("./handlebars-EJs9W5Ub.js"), true ? [] : void 0, import.meta.url)
|
|
207858
208538
|
});
|
|
207859
208539
|
registerLanguage({
|
|
207860
208540
|
id: "hcl",
|
|
@@ -207867,7 +208547,7 @@ registerLanguage({
|
|
|
207867
208547
|
extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
|
|
207868
208548
|
aliases: ["HTML", "htm", "html", "xhtml"],
|
|
207869
208549
|
mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
|
|
207870
|
-
loader: () => __vitePreload(() => import("./html-
|
|
208550
|
+
loader: () => __vitePreload(() => import("./html-Ex4qy2lh.js"), true ? [] : void 0, import.meta.url)
|
|
207871
208551
|
});
|
|
207872
208552
|
registerLanguage({
|
|
207873
208553
|
id: "ini",
|
|
@@ -207890,7 +208570,7 @@ registerLanguage({
|
|
|
207890
208570
|
filenames: ["jakefile"],
|
|
207891
208571
|
aliases: ["JavaScript", "javascript", "js"],
|
|
207892
208572
|
mimetypes: ["text/javascript"],
|
|
207893
|
-
loader: () => __vitePreload(() => import("./javascript-
|
|
208573
|
+
loader: () => __vitePreload(() => import("./javascript-B2niCouI.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
|
|
207894
208574
|
});
|
|
207895
208575
|
registerLanguage({
|
|
207896
208576
|
id: "julia",
|
|
@@ -207929,7 +208609,7 @@ registerLanguage({
|
|
|
207929
208609
|
extensions: [".liquid", ".html.liquid"],
|
|
207930
208610
|
aliases: ["Liquid", "liquid"],
|
|
207931
208611
|
mimetypes: ["application/liquid"],
|
|
207932
|
-
loader: () => __vitePreload(() => import("./liquid-
|
|
208612
|
+
loader: () => __vitePreload(() => import("./liquid-DOQmSo4G.js"), true ? [] : void 0, import.meta.url)
|
|
207933
208613
|
});
|
|
207934
208614
|
registerLanguage({
|
|
207935
208615
|
id: "m3",
|
|
@@ -207947,7 +208627,7 @@ registerLanguage({
|
|
|
207947
208627
|
id: "mdx",
|
|
207948
208628
|
extensions: [".mdx"],
|
|
207949
208629
|
aliases: ["MDX", "mdx"],
|
|
207950
|
-
loader: () => __vitePreload(() => import("./mdx-
|
|
208630
|
+
loader: () => __vitePreload(() => import("./mdx-DuKdGNKE.js"), true ? [] : void 0, import.meta.url)
|
|
207951
208631
|
});
|
|
207952
208632
|
registerLanguage({
|
|
207953
208633
|
id: "mips",
|
|
@@ -208046,7 +208726,7 @@ registerLanguage({
|
|
|
208046
208726
|
extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
|
|
208047
208727
|
aliases: ["Python", "py"],
|
|
208048
208728
|
firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
|
|
208049
|
-
loader: () => __vitePreload(() => import("./python-
|
|
208729
|
+
loader: () => __vitePreload(() => import("./python-BUa4XFG-.js"), true ? [] : void 0, import.meta.url)
|
|
208050
208730
|
});
|
|
208051
208731
|
registerLanguage({
|
|
208052
208732
|
id: "qsharp",
|
|
@@ -208065,7 +208745,7 @@ registerLanguage({
|
|
|
208065
208745
|
extensions: [".cshtml"],
|
|
208066
208746
|
aliases: ["Razor", "razor"],
|
|
208067
208747
|
mimetypes: ["text/x-cshtml"],
|
|
208068
|
-
loader: () => __vitePreload(() => import("./razor-
|
|
208748
|
+
loader: () => __vitePreload(() => import("./razor-BUw8jho0.js"), true ? [] : void 0, import.meta.url)
|
|
208069
208749
|
});
|
|
208070
208750
|
registerLanguage({
|
|
208071
208751
|
id: "redis",
|
|
@@ -208198,7 +208878,7 @@ registerLanguage({
|
|
|
208198
208878
|
aliases: ["TypeScript", "ts", "typescript"],
|
|
208199
208879
|
mimetypes: ["text/typescript"],
|
|
208200
208880
|
loader: () => {
|
|
208201
|
-
return __vitePreload(() => import("./typescript-
|
|
208881
|
+
return __vitePreload(() => import("./typescript-rdwAjCi4.js"), true ? [] : void 0, import.meta.url);
|
|
208202
208882
|
}
|
|
208203
208883
|
});
|
|
208204
208884
|
registerLanguage({
|
|
@@ -208243,14 +208923,14 @@ registerLanguage({
|
|
|
208243
208923
|
firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
|
|
208244
208924
|
aliases: ["XML", "xml"],
|
|
208245
208925
|
mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
|
|
208246
|
-
loader: () => __vitePreload(() => import("./xml-
|
|
208926
|
+
loader: () => __vitePreload(() => import("./xml-HOzT59Qg.js"), true ? [] : void 0, import.meta.url)
|
|
208247
208927
|
});
|
|
208248
208928
|
registerLanguage({
|
|
208249
208929
|
id: "yaml",
|
|
208250
208930
|
extensions: [".yaml", ".yml"],
|
|
208251
208931
|
aliases: ["YAML", "yaml", "YML", "yml"],
|
|
208252
208932
|
mimetypes: ["application/x-yaml", "text/x-yaml"],
|
|
208253
|
-
loader: () => __vitePreload(() => import("./yaml-
|
|
208933
|
+
loader: () => __vitePreload(() => import("./yaml-B81JfLsW.js"), true ? [] : void 0, import.meta.url)
|
|
208254
208934
|
});
|
|
208255
208935
|
var __defProp = Object.defineProperty;
|
|
208256
208936
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -212672,15 +213352,19 @@ function App() {
|
|
|
212672
213352
|
const visiblePaneIds = findPaneIds(layout2);
|
|
212673
213353
|
reactExports.useEffect(() => {
|
|
212674
213354
|
async function init2() {
|
|
212675
|
-
|
|
212676
|
-
|
|
212677
|
-
|
|
212678
|
-
|
|
212679
|
-
|
|
212680
|
-
|
|
212681
|
-
|
|
212682
|
-
|
|
212683
|
-
|
|
213355
|
+
try {
|
|
213356
|
+
const list2 = await window.worker.listAgents();
|
|
213357
|
+
setAgents(list2);
|
|
213358
|
+
const status2 = await window.worker.getWorkerStatus();
|
|
213359
|
+
setWsStatus(status2);
|
|
213360
|
+
const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath());
|
|
213361
|
+
setCwd(initialCwd);
|
|
213362
|
+
cwdRef.current = initialCwd;
|
|
213363
|
+
window.worker.getProjectName().then(setProjectName).catch(() => {
|
|
213364
|
+
});
|
|
213365
|
+
} finally {
|
|
213366
|
+
window.__dismissSplash?.();
|
|
213367
|
+
}
|
|
212684
213368
|
}
|
|
212685
213369
|
init2();
|
|
212686
213370
|
}, []);
|
|
@@ -212737,8 +213421,9 @@ function App() {
|
|
|
212737
213421
|
const handleVoiceTranscript = reactExports.useCallback((text2) => {
|
|
212738
213422
|
const trimmed = text2.trim();
|
|
212739
213423
|
if (!trimmed) return;
|
|
212740
|
-
window.worker.writePty(activeTabId, trimmed
|
|
213424
|
+
window.worker.writePty(activeTabId, trimmed);
|
|
212741
213425
|
focusTerminal(activeTabId);
|
|
213426
|
+
setTimeout(() => window.worker.writePty(activeTabId, "\r"), 80);
|
|
212742
213427
|
}, [activeTabId]);
|
|
212743
213428
|
const cwdRef = reactExports.useRef(null);
|
|
212744
213429
|
const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
|
|
@@ -213030,6 +213715,7 @@ function App() {
|
|
|
213030
213715
|
)
|
|
213031
213716
|
}
|
|
213032
213717
|
),
|
|
213718
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpeakControls, {}),
|
|
213033
213719
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
|
|
213034
213720
|
agents.map((a) => {
|
|
213035
213721
|
const activeTab = tabs.find((t) => t.id === activeTabId);
|