@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.
- package/out/headless/index.mjs +26 -10
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +31 -9
- package/out/preload/index.js +8 -0
- package/out/renderer/assets/{cssMode-eTXVdAkZ.js → cssMode-BQN8v2ok.js} +3 -3
- package/out/renderer/assets/{freemarker2-B5BKaiK4.js → freemarker2-DbxGYYVp.js} +1 -1
- package/out/renderer/assets/{handlebars-BIdLd2wU.js → handlebars-3auU1CAd.js} +1 -1
- package/out/renderer/assets/{html-BXL4cnLS.js → html-D8xFiRmI.js} +1 -1
- package/out/renderer/assets/{htmlMode-46N3XG2c.js → htmlMode-M3MApZ4n.js} +3 -3
- package/out/renderer/assets/{index-dRvutfbl.js → index---H6cxNl.js} +696 -33
- package/out/renderer/assets/{index-Cf-RsxoC.css → index-B-iM7dFC.css} +195 -0
- package/out/renderer/assets/{javascript-n_iZZzDX.js → javascript-BO_ViZM5.js} +2 -2
- package/out/renderer/assets/{jsonMode-DXDczSNu.js → jsonMode-CKp2zvZu.js} +3 -3
- package/out/renderer/assets/{liquid-B1QweUh7.js → liquid-C1eHcrht.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DqzMqkRk.js → lspLanguageFeatures-CHWJx_Tl.js} +1 -1
- package/out/renderer/assets/{mdx-BCv8lm5e.js → mdx-Qqdtk7fL.js} +1 -1
- package/out/renderer/assets/{python-BLNzYwDv.js → python-DKu7rNbs.js} +1 -1
- package/out/renderer/assets/{razor-CvAww8bG.js → razor-BOMpCo6z.js} +1 -1
- package/out/renderer/assets/{tsMode-C7m6Kr5E.js → tsMode-yAjlPR-D.js} +1 -1
- package/out/renderer/assets/{typescript-DhPw4VVg.js → typescript-BiJRCUcL.js} +1 -1
- package/out/renderer/assets/{xml-B0WLFJ2U.js → xml-D4PvYeQq.js} +1 -1
- package/out/renderer/assets/{yaml-BWyn9Wd7.js → yaml-BeHVkmnS.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.ts +7 -0
- package/src/main/orchestrator.ts +38 -9
- package/src/preload/index.ts +11 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/components/SpeakControls.tsx +235 -0
- package/src/renderer/components/VoiceInput.tsx +159 -3
- package/src/renderer/lib/localWhisper.ts +48 -4
- package/src/renderer/lib/speech.ts +299 -0
- 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-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BQN8v2ok.js","./lspLanguageFeatures-CHWJx_Tl.js","./htmlMode-M3MApZ4n.js","./jsonMode-CKp2zvZu.js","./javascript-BO_ViZM5.js","./typescript-BiJRCUcL.js"])))=>i.map(i=>d[i]);
|
|
2
2
|
function getDefaultExportFromCjs(x) {
|
|
3
3
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
4
|
}
|
|
@@ -19163,6 +19163,26 @@ const __vitePreload = function preload(baseModule, deps, importerUrl) {
|
|
|
19163
19163
|
};
|
|
19164
19164
|
const MODEL = "Xenova/whisper-base";
|
|
19165
19165
|
const TARGET_SAMPLE_RATE = 16e3;
|
|
19166
|
+
const SILENCE_RMS = 8e-3;
|
|
19167
|
+
const LOW_CONFIDENCE_RMS = 0.02;
|
|
19168
|
+
const FILLER_PHRASES = /* @__PURE__ */ new Set([
|
|
19169
|
+
"you",
|
|
19170
|
+
"thank you",
|
|
19171
|
+
"thank you very much",
|
|
19172
|
+
"thank you for watching",
|
|
19173
|
+
"thanks for watching",
|
|
19174
|
+
"please subscribe"
|
|
19175
|
+
]);
|
|
19176
|
+
function computeRms(pcm) {
|
|
19177
|
+
if (pcm.length === 0) return 0;
|
|
19178
|
+
let sum2 = 0;
|
|
19179
|
+
for (let i2 = 0; i2 < pcm.length; i2++) sum2 += pcm[i2] * pcm[i2];
|
|
19180
|
+
return Math.sqrt(sum2 / pcm.length);
|
|
19181
|
+
}
|
|
19182
|
+
function isFillerOnly(text2) {
|
|
19183
|
+
const norm = text2.toLowerCase().replace(/[.!?,…"']/g, "").replace(/\s+/g, " ").trim();
|
|
19184
|
+
return FILLER_PHRASES.has(norm);
|
|
19185
|
+
}
|
|
19166
19186
|
let transcriberPromise = null;
|
|
19167
19187
|
async function loadTranscriber(onProgress) {
|
|
19168
19188
|
if (!transcriberPromise) {
|
|
@@ -19213,25 +19233,42 @@ async function blobToPcm16k(blob) {
|
|
|
19213
19233
|
return rendered.getChannelData(0);
|
|
19214
19234
|
}
|
|
19215
19235
|
async function transcribeBlob(blob, onProgress) {
|
|
19216
|
-
const transcriber = await loadTranscriber(onProgress);
|
|
19217
19236
|
const pcm = await blobToPcm16k(blob);
|
|
19218
19237
|
if (!pcm) return "";
|
|
19238
|
+
const rms = computeRms(pcm);
|
|
19239
|
+
if (rms < SILENCE_RMS) {
|
|
19240
|
+
console.info(`[voice] near-silent clip (rms=${rms.toFixed(4)}); skipping transcription`);
|
|
19241
|
+
return "";
|
|
19242
|
+
}
|
|
19243
|
+
const transcriber = await loadTranscriber(onProgress);
|
|
19219
19244
|
const result = await transcriber(pcm);
|
|
19220
|
-
const text2 = Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text;
|
|
19221
|
-
|
|
19245
|
+
const text2 = (Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text || "").trim();
|
|
19246
|
+
if (text2 && rms < LOW_CONFIDENCE_RMS && isFillerOnly(text2)) {
|
|
19247
|
+
console.info(`[voice] dropping filler-only output "${text2}" from quiet clip (rms=${rms.toFixed(4)})`);
|
|
19248
|
+
return "";
|
|
19249
|
+
}
|
|
19250
|
+
return text2;
|
|
19222
19251
|
}
|
|
19223
19252
|
function getRecognitionCtor() {
|
|
19224
19253
|
const w = window;
|
|
19225
19254
|
return w.SpeechRecognition || w.webkitSpeechRecognition || null;
|
|
19226
19255
|
}
|
|
19227
|
-
const ENGINE_KEY = "ctlsurf.voiceEngine";
|
|
19256
|
+
const ENGINE_KEY$1 = "ctlsurf.voiceEngine";
|
|
19257
|
+
const DEVICE_KEY = "ctlsurf.voiceDeviceId";
|
|
19258
|
+
function loadDeviceId() {
|
|
19259
|
+
try {
|
|
19260
|
+
return localStorage.getItem(DEVICE_KEY) || null;
|
|
19261
|
+
} catch {
|
|
19262
|
+
return null;
|
|
19263
|
+
}
|
|
19264
|
+
}
|
|
19228
19265
|
const WEB_SPEECH_SUPPORTED = getRecognitionCtor() !== null;
|
|
19229
19266
|
const LOCAL_SUPPORTED = typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" && typeof OfflineAudioContext !== "undefined";
|
|
19230
19267
|
const ANY_SUPPORTED = WEB_SPEECH_SUPPORTED || LOCAL_SUPPORTED;
|
|
19231
19268
|
function loadInitialEngine() {
|
|
19232
19269
|
if (!WEB_SPEECH_SUPPORTED && LOCAL_SUPPORTED) return "local";
|
|
19233
19270
|
try {
|
|
19234
|
-
if (localStorage.getItem(ENGINE_KEY) === "local" && LOCAL_SUPPORTED) return "local";
|
|
19271
|
+
if (localStorage.getItem(ENGINE_KEY$1) === "local" && LOCAL_SUPPORTED) return "local";
|
|
19235
19272
|
} catch {
|
|
19236
19273
|
}
|
|
19237
19274
|
return WEB_SPEECH_SUPPORTED ? "web-speech" : "local";
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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);
|