@phenx-inc/ctlsurf 0.7.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.
Files changed (33) hide show
  1. package/out/headless/index.mjs +26 -10
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +31 -9
  4. package/out/preload/index.js +8 -0
  5. package/out/renderer/assets/{cssMode-eTXVdAkZ.js → cssMode-BQN8v2ok.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-B5BKaiK4.js → freemarker2-DbxGYYVp.js} +1 -1
  7. package/out/renderer/assets/{handlebars-BIdLd2wU.js → handlebars-3auU1CAd.js} +1 -1
  8. package/out/renderer/assets/{html-BXL4cnLS.js → html-D8xFiRmI.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-46N3XG2c.js → htmlMode-M3MApZ4n.js} +3 -3
  10. package/out/renderer/assets/{index-dRvutfbl.js → index---H6cxNl.js} +696 -33
  11. package/out/renderer/assets/{index-Cf-RsxoC.css → index-B-iM7dFC.css} +195 -0
  12. package/out/renderer/assets/{javascript-n_iZZzDX.js → javascript-BO_ViZM5.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-DXDczSNu.js → jsonMode-CKp2zvZu.js} +3 -3
  14. package/out/renderer/assets/{liquid-B1QweUh7.js → liquid-C1eHcrht.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-DqzMqkRk.js → lspLanguageFeatures-CHWJx_Tl.js} +1 -1
  16. package/out/renderer/assets/{mdx-BCv8lm5e.js → mdx-Qqdtk7fL.js} +1 -1
  17. package/out/renderer/assets/{python-BLNzYwDv.js → python-DKu7rNbs.js} +1 -1
  18. package/out/renderer/assets/{razor-CvAww8bG.js → razor-BOMpCo6z.js} +1 -1
  19. package/out/renderer/assets/{tsMode-C7m6Kr5E.js → tsMode-yAjlPR-D.js} +1 -1
  20. package/out/renderer/assets/{typescript-DhPw4VVg.js → typescript-BiJRCUcL.js} +1 -1
  21. package/out/renderer/assets/{xml-B0WLFJ2U.js → xml-D4PvYeQq.js} +1 -1
  22. package/out/renderer/assets/{yaml-BWyn9Wd7.js → yaml-BeHVkmnS.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/index.ts +7 -0
  26. package/src/main/orchestrator.ts +38 -9
  27. package/src/preload/index.ts +11 -0
  28. package/src/renderer/App.tsx +5 -0
  29. package/src/renderer/components/SpeakControls.tsx +235 -0
  30. package/src/renderer/components/VoiceInput.tsx +159 -3
  31. package/src/renderer/lib/localWhisper.ts +48 -4
  32. package/src/renderer/lib/speech.ts +299 -0
  33. package/src/renderer/styles.css +195 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-eTXVdAkZ.js","./lspLanguageFeatures-DqzMqkRk.js","./htmlMode-46N3XG2c.js","./jsonMode-DXDczSNu.js","./javascript-n_iZZzDX.js","./typescript-DhPw4VVg.js"])))=>i.map(i=>d[i]);
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
- return (text2 || "").trim();
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";
@@ -19246,12 +19283,20 @@ function describeMicError(err) {
19246
19283
  return "Could not start microphone";
19247
19284
  }
19248
19285
  function VoiceInput({ onTranscript, variant = "titlebar" }) {
19249
- const [engine, setEngine] = reactExports.useState(loadInitialEngine);
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, variant = "titlebar" }) {
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
- setEngine("local");
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, variant = "titlebar" }) {
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, variant = "titlebar" }) {
19375
19494
  setInterim("");
19376
19495
  cancelGestureRef.current = false;
19377
19496
  try {
19378
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
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, variant = "titlebar" }) {
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;
@@ -19443,7 +19562,14 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19443
19562
  else if (busy) chip = { kind: "busy", text: modelPct !== null ? `Downloading voice model… ${modelPct}%` : "Transcribing…" };
19444
19563
  const floating = variant === "floating";
19445
19564
  const btnClass = floating ? `voice-btn voice-btn-floating ${listening ? "listening" : ""} ${busy ? "busy" : ""}` : `titlebar-btn titlebar-icon-btn voice-btn ${listening ? "listening" : ""} ${busy ? "busy" : ""}`;
19446
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "voice-input-wrap", children: [
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: [
19447
19573
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
19448
19574
  "button",
19449
19575
  {
@@ -19462,6 +19588,52 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19462
19588
  ]
19463
19589
  }
19464
19590
  ),
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
+ ] }),
19465
19637
  chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
19466
19638
  ] });
19467
19639
  }
@@ -19567,6 +19739,496 @@ function FloatingMic({ onTranscript, onHide }) {
19567
19739
  /* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript })
19568
19740
  ] });
19569
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 })
20230
+ ] });
20231
+ }
19570
20232
  function CtlsurfPanel({ navigate }) {
19571
20233
  const webviewRef = reactExports.useRef(null);
19572
20234
  const [url, setUrl] = reactExports.useState(null);
@@ -207161,7 +207823,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
207161
207823
  modeConfigurationDefault$2
207162
207824
  );
207163
207825
  function getMode$3() {
207164
- return __vitePreload(() => import("./cssMode-eTXVdAkZ.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207826
+ return __vitePreload(() => import("./cssMode-BQN8v2ok.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207165
207827
  }
207166
207828
  languages.onLanguage("less", () => {
207167
207829
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -207266,7 +207928,7 @@ const razorLanguageService = registerHTMLLanguageService(
207266
207928
  );
207267
207929
  const razorDefaults = razorLanguageService.defaults;
207268
207930
  function getMode$2() {
207269
- return __vitePreload(() => import("./htmlMode-46N3XG2c.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207931
+ return __vitePreload(() => import("./htmlMode-M3MApZ4n.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207270
207932
  }
207271
207933
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
207272
207934
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -207350,7 +208012,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
207350
208012
  );
207351
208013
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
207352
208014
  function getMode$1() {
207353
- return __vitePreload(() => import("./jsonMode-DXDczSNu.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208015
+ return __vitePreload(() => import("./jsonMode-CKp2zvZu.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
207354
208016
  }
207355
208017
  languages.register({
207356
208018
  id: "json",
@@ -207596,7 +208258,7 @@ const getJavaScriptWorker = () => {
207596
208258
  return getMode().then((mode) => mode.getJavaScriptWorker());
207597
208259
  };
207598
208260
  function getMode() {
207599
- return __vitePreload(() => import("./tsMode-C7m6Kr5E.js"), true ? [] : void 0, import.meta.url);
208261
+ return __vitePreload(() => import("./tsMode-yAjlPR-D.js"), true ? [] : void 0, import.meta.url);
207600
208262
  }
207601
208263
  languages.onLanguage("typescript", () => {
207602
208264
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -207791,49 +208453,49 @@ registerLanguage({
207791
208453
  extensions: [".ftl", ".ftlh", ".ftlx"],
207792
208454
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
207793
208455
  loader: () => {
207794
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208456
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207795
208457
  }
207796
208458
  });
207797
208459
  registerLanguage({
207798
208460
  id: "freemarker2.tag-angle.interpolation-dollar",
207799
208461
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
207800
208462
  loader: () => {
207801
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208463
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207802
208464
  }
207803
208465
  });
207804
208466
  registerLanguage({
207805
208467
  id: "freemarker2.tag-bracket.interpolation-dollar",
207806
208468
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
207807
208469
  loader: () => {
207808
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208470
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207809
208471
  }
207810
208472
  });
207811
208473
  registerLanguage({
207812
208474
  id: "freemarker2.tag-angle.interpolation-bracket",
207813
208475
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
207814
208476
  loader: () => {
207815
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208477
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207816
208478
  }
207817
208479
  });
207818
208480
  registerLanguage({
207819
208481
  id: "freemarker2.tag-bracket.interpolation-bracket",
207820
208482
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
207821
208483
  loader: () => {
207822
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208484
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207823
208485
  }
207824
208486
  });
207825
208487
  registerLanguage({
207826
208488
  id: "freemarker2.tag-auto.interpolation-dollar",
207827
208489
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
207828
208490
  loader: () => {
207829
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208491
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207830
208492
  }
207831
208493
  });
207832
208494
  registerLanguage({
207833
208495
  id: "freemarker2.tag-auto.interpolation-bracket",
207834
208496
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
207835
208497
  loader: () => {
207836
- return __vitePreload(() => import("./freemarker2-B5BKaiK4.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208498
+ return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207837
208499
  }
207838
208500
  });
207839
208501
  registerLanguage({
@@ -207854,7 +208516,7 @@ registerLanguage({
207854
208516
  extensions: [".handlebars", ".hbs"],
207855
208517
  aliases: ["Handlebars", "handlebars", "hbs"],
207856
208518
  mimetypes: ["text/x-handlebars-template"],
207857
- loader: () => __vitePreload(() => import("./handlebars-BIdLd2wU.js"), true ? [] : void 0, import.meta.url)
208519
+ loader: () => __vitePreload(() => import("./handlebars-3auU1CAd.js"), true ? [] : void 0, import.meta.url)
207858
208520
  });
207859
208521
  registerLanguage({
207860
208522
  id: "hcl",
@@ -207867,7 +208529,7 @@ registerLanguage({
207867
208529
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
207868
208530
  aliases: ["HTML", "htm", "html", "xhtml"],
207869
208531
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
207870
- loader: () => __vitePreload(() => import("./html-BXL4cnLS.js"), true ? [] : void 0, import.meta.url)
208532
+ loader: () => __vitePreload(() => import("./html-D8xFiRmI.js"), true ? [] : void 0, import.meta.url)
207871
208533
  });
207872
208534
  registerLanguage({
207873
208535
  id: "ini",
@@ -207890,7 +208552,7 @@ registerLanguage({
207890
208552
  filenames: ["jakefile"],
207891
208553
  aliases: ["JavaScript", "javascript", "js"],
207892
208554
  mimetypes: ["text/javascript"],
207893
- loader: () => __vitePreload(() => import("./javascript-n_iZZzDX.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208555
+ loader: () => __vitePreload(() => import("./javascript-BO_ViZM5.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207894
208556
  });
207895
208557
  registerLanguage({
207896
208558
  id: "julia",
@@ -207929,7 +208591,7 @@ registerLanguage({
207929
208591
  extensions: [".liquid", ".html.liquid"],
207930
208592
  aliases: ["Liquid", "liquid"],
207931
208593
  mimetypes: ["application/liquid"],
207932
- loader: () => __vitePreload(() => import("./liquid-B1QweUh7.js"), true ? [] : void 0, import.meta.url)
208594
+ loader: () => __vitePreload(() => import("./liquid-C1eHcrht.js"), true ? [] : void 0, import.meta.url)
207933
208595
  });
207934
208596
  registerLanguage({
207935
208597
  id: "m3",
@@ -207947,7 +208609,7 @@ registerLanguage({
207947
208609
  id: "mdx",
207948
208610
  extensions: [".mdx"],
207949
208611
  aliases: ["MDX", "mdx"],
207950
- loader: () => __vitePreload(() => import("./mdx-BCv8lm5e.js"), true ? [] : void 0, import.meta.url)
208612
+ loader: () => __vitePreload(() => import("./mdx-Qqdtk7fL.js"), true ? [] : void 0, import.meta.url)
207951
208613
  });
207952
208614
  registerLanguage({
207953
208615
  id: "mips",
@@ -208046,7 +208708,7 @@ registerLanguage({
208046
208708
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
208047
208709
  aliases: ["Python", "py"],
208048
208710
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
208049
- loader: () => __vitePreload(() => import("./python-BLNzYwDv.js"), true ? [] : void 0, import.meta.url)
208711
+ loader: () => __vitePreload(() => import("./python-DKu7rNbs.js"), true ? [] : void 0, import.meta.url)
208050
208712
  });
208051
208713
  registerLanguage({
208052
208714
  id: "qsharp",
@@ -208065,7 +208727,7 @@ registerLanguage({
208065
208727
  extensions: [".cshtml"],
208066
208728
  aliases: ["Razor", "razor"],
208067
208729
  mimetypes: ["text/x-cshtml"],
208068
- loader: () => __vitePreload(() => import("./razor-CvAww8bG.js"), true ? [] : void 0, import.meta.url)
208730
+ loader: () => __vitePreload(() => import("./razor-BOMpCo6z.js"), true ? [] : void 0, import.meta.url)
208069
208731
  });
208070
208732
  registerLanguage({
208071
208733
  id: "redis",
@@ -208198,7 +208860,7 @@ registerLanguage({
208198
208860
  aliases: ["TypeScript", "ts", "typescript"],
208199
208861
  mimetypes: ["text/typescript"],
208200
208862
  loader: () => {
208201
- return __vitePreload(() => import("./typescript-DhPw4VVg.js"), true ? [] : void 0, import.meta.url);
208863
+ return __vitePreload(() => import("./typescript-BiJRCUcL.js"), true ? [] : void 0, import.meta.url);
208202
208864
  }
208203
208865
  });
208204
208866
  registerLanguage({
@@ -208243,14 +208905,14 @@ registerLanguage({
208243
208905
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
208244
208906
  aliases: ["XML", "xml"],
208245
208907
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
208246
- loader: () => __vitePreload(() => import("./xml-B0WLFJ2U.js"), true ? [] : void 0, import.meta.url)
208908
+ loader: () => __vitePreload(() => import("./xml-D4PvYeQq.js"), true ? [] : void 0, import.meta.url)
208247
208909
  });
208248
208910
  registerLanguage({
208249
208911
  id: "yaml",
208250
208912
  extensions: [".yaml", ".yml"],
208251
208913
  aliases: ["YAML", "yaml", "YML", "yml"],
208252
208914
  mimetypes: ["application/x-yaml", "text/x-yaml"],
208253
- loader: () => __vitePreload(() => import("./yaml-BWyn9Wd7.js"), true ? [] : void 0, import.meta.url)
208915
+ loader: () => __vitePreload(() => import("./yaml-BeHVkmnS.js"), true ? [] : void 0, import.meta.url)
208254
208916
  });
208255
208917
  var __defProp = Object.defineProperty;
208256
208918
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213030,6 +213692,7 @@ function App() {
213030
213692
  )
213031
213693
  }
213032
213694
  ),
213695
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SpeakControls, {}),
213033
213696
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
213034
213697
  agents.map((a) => {
213035
213698
  const activeTab = tabs.find((t) => t.id === activeTabId);