@phenx-inc/ctlsurf 0.12.1 → 0.13.4
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/LICENSE +22 -0
- package/out/headless/index.mjs +33 -36
- package/out/headless/index.mjs.map +3 -3
- package/out/main/index.js +47 -40
- package/out/preload/index.js +1 -0
- package/out/renderer/assets/{cssMode-D7r66wvO.js → cssMode-CAEObGNt.js} +3 -3
- package/out/renderer/assets/{freemarker2-jSWJIjPG.js → freemarker2-D63hnnDq.js} +1 -1
- package/out/renderer/assets/{handlebars-BnFyz5RH.js → handlebars-UYo_IYpi.js} +1 -1
- package/out/renderer/assets/{html-Bl5UFavp.js → html-2dvYDi3g.js} +1 -1
- package/out/renderer/assets/{htmlMode-D4VByBT6.js → htmlMode-B6fxDDPF.js} +3 -3
- package/out/renderer/assets/{index-Cc_TqOCL.css → index-BEekQe3-.css} +232 -8
- package/out/renderer/assets/{index-DxhW1emk.js → index-CMb4AqkZ.js} +786 -473
- package/out/renderer/assets/{javascript-CMN1OmwG.js → javascript-8RyFDDdk.js} +2 -2
- package/out/renderer/assets/{jsonMode-CJ5AYSPV.js → jsonMode-CqspkWCb.js} +3 -3
- package/out/renderer/assets/{liquid-BGMIDTEm.js → liquid-DaEnAsYY.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DGifVYB5.js → lspLanguageFeatures-Czkp5sZb.js} +1 -1
- package/out/renderer/assets/{mdx-B2Z7R10Q.js → mdx-Q-qSZsFg.js} +1 -1
- package/out/renderer/assets/{python-LMDMSSdn.js → python-Sw0S0BJV.js} +1 -1
- package/out/renderer/assets/{razor-BZSrziPu.js → razor-C7zQmabz.js} +1 -1
- package/out/renderer/assets/{tsMode-DF9m6_3n.js → tsMode-BWdOxk6v.js} +1 -1
- package/out/renderer/assets/{typescript-rZeCMf0a.js → typescript-BrdIiKZf.js} +1 -1
- package/out/renderer/assets/{xml-fNSZ-7so.js → xml-DbKLNPOq.js} +1 -1
- package/out/renderer/assets/{yaml-1wxIuvzh.js → yaml-ehupJBsZ.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +2 -2
- package/src/main/bridge.ts +3 -2
- package/src/main/index.ts +15 -0
- package/src/main/orchestrator.ts +1 -1
- package/src/main/pty.ts +2 -5
- package/src/main/ticketStore.ts +28 -21
- package/src/main/timeTracker.ts +4 -1
- package/src/main/transcripts.ts +3 -2
- package/src/preload/index.ts +2 -0
- package/src/renderer/App.tsx +15 -2
- package/src/renderer/components/CtlsurfPanel.tsx +169 -35
- package/src/renderer/components/SettingsDialog.tsx +13 -0
- package/src/renderer/components/SpeakControls.tsx +5 -0
- package/src/renderer/components/TerminalPanel.tsx +155 -48
- package/src/renderer/components/TicketPanel.tsx +80 -24
- package/src/renderer/lib/speech.ts +2 -1
- package/src/renderer/styles.css +232 -8
|
@@ -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-CAEObGNt.js","./lspLanguageFeatures-Czkp5sZb.js","./htmlMode-B6fxDDPF.js","./jsonMode-CqspkWCb.js","./javascript-8RyFDDdk.js","./typescript-BrdIiKZf.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
|
}
|
|
@@ -18818,35 +18818,416 @@ function convertToEmailTable(raw) {
|
|
|
18818
18818
|
cols: rows[0].length
|
|
18819
18819
|
};
|
|
18820
18820
|
}
|
|
18821
|
-
function
|
|
18822
|
-
const
|
|
18823
|
-
|
|
18824
|
-
|
|
18821
|
+
const scriptRel = /* @__PURE__ */ (function detectScriptRel() {
|
|
18822
|
+
const relList = typeof document !== "undefined" && document.createElement("link").relList;
|
|
18823
|
+
return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
|
|
18824
|
+
})();
|
|
18825
|
+
const assetsURL = function(dep, importerUrl) {
|
|
18826
|
+
return new URL(dep, importerUrl).href;
|
|
18827
|
+
};
|
|
18828
|
+
const seen = {};
|
|
18829
|
+
const __vitePreload = function preload(baseModule, deps, importerUrl) {
|
|
18830
|
+
let promise = Promise.resolve();
|
|
18831
|
+
if (deps && deps.length > 0) {
|
|
18832
|
+
let allSettled2 = function(promises) {
|
|
18833
|
+
return Promise.all(
|
|
18834
|
+
promises.map(
|
|
18835
|
+
(p) => Promise.resolve(p).then(
|
|
18836
|
+
(value) => ({ status: "fulfilled", value }),
|
|
18837
|
+
(reason) => ({ status: "rejected", reason })
|
|
18838
|
+
)
|
|
18839
|
+
)
|
|
18840
|
+
);
|
|
18841
|
+
};
|
|
18842
|
+
const links = document.getElementsByTagName("link");
|
|
18843
|
+
const cspNonceMeta = document.querySelector(
|
|
18844
|
+
"meta[property=csp-nonce]"
|
|
18845
|
+
);
|
|
18846
|
+
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
|
|
18847
|
+
promise = allSettled2(
|
|
18848
|
+
deps.map((dep) => {
|
|
18849
|
+
dep = assetsURL(dep, importerUrl);
|
|
18850
|
+
if (dep in seen) return;
|
|
18851
|
+
seen[dep] = true;
|
|
18852
|
+
const isCss = dep.endsWith(".css");
|
|
18853
|
+
const cssSelector = isCss ? '[rel="stylesheet"]' : "";
|
|
18854
|
+
const isBaseRelative = !!importerUrl;
|
|
18855
|
+
if (isBaseRelative) {
|
|
18856
|
+
for (let i2 = links.length - 1; i2 >= 0; i2--) {
|
|
18857
|
+
const link22 = links[i2];
|
|
18858
|
+
if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
|
|
18859
|
+
return;
|
|
18860
|
+
}
|
|
18861
|
+
}
|
|
18862
|
+
} else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
|
|
18863
|
+
return;
|
|
18864
|
+
}
|
|
18865
|
+
const link2 = document.createElement("link");
|
|
18866
|
+
link2.rel = isCss ? "stylesheet" : scriptRel;
|
|
18867
|
+
if (!isCss) {
|
|
18868
|
+
link2.as = "script";
|
|
18869
|
+
}
|
|
18870
|
+
link2.crossOrigin = "";
|
|
18871
|
+
link2.href = dep;
|
|
18872
|
+
if (cspNonce) {
|
|
18873
|
+
link2.setAttribute("nonce", cspNonce);
|
|
18874
|
+
}
|
|
18875
|
+
document.head.appendChild(link2);
|
|
18876
|
+
if (isCss) {
|
|
18877
|
+
return new Promise((res, rej) => {
|
|
18878
|
+
link2.addEventListener("load", res);
|
|
18879
|
+
link2.addEventListener(
|
|
18880
|
+
"error",
|
|
18881
|
+
() => rej(new Error(`Unable to preload CSS for ${dep}`))
|
|
18882
|
+
);
|
|
18883
|
+
});
|
|
18884
|
+
}
|
|
18885
|
+
})
|
|
18886
|
+
);
|
|
18825
18887
|
}
|
|
18826
|
-
|
|
18827
|
-
new
|
|
18828
|
-
|
|
18829
|
-
})
|
|
18830
|
-
|
|
18888
|
+
function handlePreloadError(err) {
|
|
18889
|
+
const e = new Event("vite:preloadError", {
|
|
18890
|
+
cancelable: true
|
|
18891
|
+
});
|
|
18892
|
+
e.payload = err;
|
|
18893
|
+
window.dispatchEvent(e);
|
|
18894
|
+
if (!e.defaultPrevented) {
|
|
18895
|
+
throw err;
|
|
18896
|
+
}
|
|
18897
|
+
}
|
|
18898
|
+
return promise.then((res) => {
|
|
18899
|
+
for (const item of res || []) {
|
|
18900
|
+
if (item.status !== "rejected") continue;
|
|
18901
|
+
handlePreloadError(item.reason);
|
|
18902
|
+
}
|
|
18903
|
+
return baseModule().catch(handlePreloadError);
|
|
18904
|
+
});
|
|
18905
|
+
};
|
|
18906
|
+
const ENGINE_KEY = "ctlsurf.tts.engine";
|
|
18907
|
+
const VOICE_KEY = "ctlsurf.tts.voiceURI";
|
|
18908
|
+
const RATE_KEY = "ctlsurf.tts.rate";
|
|
18909
|
+
const MAX_SPEAK_CHARS = 1600;
|
|
18910
|
+
const MAX_CHUNK_CHARS = 280;
|
|
18911
|
+
function cleanForSpeech(input) {
|
|
18912
|
+
let t = input;
|
|
18913
|
+
t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
|
|
18914
|
+
const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
|
|
18915
|
+
return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
|
|
18916
|
+
});
|
|
18917
|
+
t = t.replace(/```/g, " (code) ");
|
|
18918
|
+
t = t.replace(/`([^`]+)`/g, "$1");
|
|
18919
|
+
t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
18920
|
+
t = t.replace(/https?:\/\/\S+/g, "link");
|
|
18921
|
+
t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
|
|
18922
|
+
t = t.replace(/[*_#>]+/g, " ");
|
|
18923
|
+
t = t.replace(/^\s*[-•]\s+/gm, ", ");
|
|
18924
|
+
t = t.replace(/<[^>]+>/g, " ");
|
|
18925
|
+
t = t.replace(/\s+/g, " ").trim();
|
|
18926
|
+
if (t.length > MAX_SPEAK_CHARS) {
|
|
18927
|
+
t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
|
|
18928
|
+
}
|
|
18929
|
+
return t;
|
|
18930
|
+
}
|
|
18931
|
+
function splitIntoChunks(text2) {
|
|
18932
|
+
if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
|
|
18933
|
+
const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
|
|
18934
|
+
const chunks = [];
|
|
18935
|
+
let buf = "";
|
|
18936
|
+
for (const s of sentences) {
|
|
18937
|
+
if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
|
|
18938
|
+
chunks.push(buf.trim());
|
|
18939
|
+
buf = "";
|
|
18940
|
+
}
|
|
18941
|
+
if (s.length > MAX_CHUNK_CHARS) {
|
|
18942
|
+
for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
|
|
18943
|
+
} else {
|
|
18944
|
+
buf += s;
|
|
18945
|
+
}
|
|
18946
|
+
}
|
|
18947
|
+
if (buf.trim()) chunks.push(buf.trim());
|
|
18948
|
+
return chunks;
|
|
18831
18949
|
}
|
|
18832
|
-
function
|
|
18833
|
-
|
|
18834
|
-
|
|
18950
|
+
function getEngine() {
|
|
18951
|
+
try {
|
|
18952
|
+
return localStorage.getItem(ENGINE_KEY) === "web" ? "web" : "neural";
|
|
18953
|
+
} catch {
|
|
18954
|
+
return "neural";
|
|
18955
|
+
}
|
|
18956
|
+
}
|
|
18957
|
+
function setEngine(id) {
|
|
18958
|
+
try {
|
|
18959
|
+
localStorage.setItem(ENGINE_KEY, id);
|
|
18960
|
+
} catch {
|
|
18961
|
+
}
|
|
18962
|
+
}
|
|
18963
|
+
function getVoiceURI() {
|
|
18964
|
+
try {
|
|
18965
|
+
return localStorage.getItem(VOICE_KEY) || null;
|
|
18966
|
+
} catch {
|
|
18967
|
+
return null;
|
|
18968
|
+
}
|
|
18969
|
+
}
|
|
18970
|
+
function setVoiceURI(uri) {
|
|
18971
|
+
try {
|
|
18972
|
+
uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
|
|
18973
|
+
} catch {
|
|
18974
|
+
}
|
|
18975
|
+
}
|
|
18976
|
+
function getRate() {
|
|
18977
|
+
try {
|
|
18978
|
+
const n2 = Number(localStorage.getItem(RATE_KEY));
|
|
18979
|
+
return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
|
|
18980
|
+
} catch {
|
|
18981
|
+
return 1;
|
|
18982
|
+
}
|
|
18983
|
+
}
|
|
18984
|
+
function setRate(rate) {
|
|
18985
|
+
try {
|
|
18986
|
+
localStorage.setItem(RATE_KEY, String(rate));
|
|
18987
|
+
} catch {
|
|
18988
|
+
}
|
|
18989
|
+
}
|
|
18990
|
+
function listWebVoices() {
|
|
18991
|
+
if (typeof speechSynthesis === "undefined") return [];
|
|
18992
|
+
return speechSynthesis.getVoices();
|
|
18993
|
+
}
|
|
18994
|
+
const NEURAL_MODEL = "Xenova/mms-tts-eng";
|
|
18995
|
+
let synthPromise = null;
|
|
18996
|
+
function loadSynthesizer(onProgress) {
|
|
18997
|
+
if (!synthPromise) {
|
|
18998
|
+
synthPromise = (async () => {
|
|
18999
|
+
const { pipeline, env: env2 } = await __vitePreload(async () => {
|
|
19000
|
+
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
|
|
19001
|
+
return { pipeline: pipeline2, env: env3 };
|
|
19002
|
+
}, true ? [] : void 0, import.meta.url);
|
|
19003
|
+
env2.allowLocalModels = false;
|
|
19004
|
+
const common = { progress_callback: onProgress };
|
|
19005
|
+
const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
|
|
19006
|
+
if (hasWebGpu) {
|
|
19007
|
+
try {
|
|
19008
|
+
const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
|
|
19009
|
+
console.info("[tts] neural backend: webgpu");
|
|
19010
|
+
return s2;
|
|
19011
|
+
} catch (err) {
|
|
19012
|
+
console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
|
|
19013
|
+
}
|
|
19014
|
+
}
|
|
19015
|
+
const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
|
|
19016
|
+
console.info("[tts] neural backend: wasm");
|
|
19017
|
+
return s;
|
|
19018
|
+
})();
|
|
19019
|
+
synthPromise.catch(() => {
|
|
19020
|
+
synthPromise = null;
|
|
19021
|
+
});
|
|
19022
|
+
}
|
|
19023
|
+
return synthPromise;
|
|
19024
|
+
}
|
|
19025
|
+
class SpeechController {
|
|
19026
|
+
queue = [];
|
|
19027
|
+
draining = false;
|
|
19028
|
+
audioCtx = null;
|
|
19029
|
+
currentSource = null;
|
|
19030
|
+
generation = 0;
|
|
19031
|
+
// bumped by stop() to abort in-flight work
|
|
19032
|
+
active = false;
|
|
19033
|
+
onModelProgress = null;
|
|
19034
|
+
// Fires true while a reply is being spoken/queued, false when idle — drives
|
|
19035
|
+
// the visible Stop button.
|
|
19036
|
+
onActivityChange = null;
|
|
19037
|
+
onError = null;
|
|
19038
|
+
// Multiple UI surfaces (titlebar SpeakControls + floating read button) need
|
|
19039
|
+
// the speaking state at once, so beyond the single onActivityChange slot we
|
|
19040
|
+
// also keep a listener set.
|
|
19041
|
+
activityListeners = /* @__PURE__ */ new Set();
|
|
19042
|
+
get isActive() {
|
|
19043
|
+
return this.active;
|
|
19044
|
+
}
|
|
19045
|
+
// Subscribe to speaking-state changes. Fires immediately with the current
|
|
19046
|
+
// state so a late subscriber syncs. Returns an unsubscribe function.
|
|
19047
|
+
onActivity(fn) {
|
|
19048
|
+
this.activityListeners.add(fn);
|
|
19049
|
+
fn(this.active);
|
|
19050
|
+
return () => {
|
|
19051
|
+
this.activityListeners.delete(fn);
|
|
19052
|
+
};
|
|
19053
|
+
}
|
|
19054
|
+
// Prime/resume the AudioContext from a user gesture so neural playback isn't
|
|
19055
|
+
// blocked by the browser autoplay policy (the System engine is unaffected).
|
|
19056
|
+
unlock() {
|
|
18835
19057
|
try {
|
|
18836
|
-
|
|
19058
|
+
const ctx = this.ensureCtx();
|
|
19059
|
+
if (ctx.state === "suspended") void ctx.resume();
|
|
18837
19060
|
} catch {
|
|
18838
19061
|
}
|
|
18839
|
-
win.location.href = uri;
|
|
18840
19062
|
}
|
|
19063
|
+
// Start loading the neural model in the background (e.g. when the user picks
|
|
19064
|
+
// the neural engine) so the first utterance doesn't pay download/compile time.
|
|
19065
|
+
warmup() {
|
|
19066
|
+
if (getEngine() !== "neural") return;
|
|
19067
|
+
void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
|
|
19068
|
+
this.onModelProgress?.(null);
|
|
19069
|
+
});
|
|
19070
|
+
}
|
|
19071
|
+
enqueue(rawText) {
|
|
19072
|
+
const text2 = cleanForSpeech(rawText);
|
|
19073
|
+
if (!text2) return;
|
|
19074
|
+
this.queue.push(...splitIntoChunks(text2));
|
|
19075
|
+
void this.drain();
|
|
19076
|
+
}
|
|
19077
|
+
stop() {
|
|
19078
|
+
this.generation++;
|
|
19079
|
+
this.queue = [];
|
|
19080
|
+
this.draining = false;
|
|
19081
|
+
if (typeof speechSynthesis !== "undefined") {
|
|
19082
|
+
try {
|
|
19083
|
+
speechSynthesis.cancel();
|
|
19084
|
+
} catch {
|
|
19085
|
+
}
|
|
19086
|
+
}
|
|
19087
|
+
if (this.currentSource) {
|
|
19088
|
+
try {
|
|
19089
|
+
this.currentSource.stop();
|
|
19090
|
+
} catch {
|
|
19091
|
+
}
|
|
19092
|
+
this.currentSource = null;
|
|
19093
|
+
}
|
|
19094
|
+
this.onModelProgress?.(null);
|
|
19095
|
+
this.setActive(false);
|
|
19096
|
+
}
|
|
19097
|
+
ensureCtx() {
|
|
19098
|
+
if (!this.audioCtx) {
|
|
19099
|
+
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
19100
|
+
this.audioCtx = new Ctx();
|
|
19101
|
+
}
|
|
19102
|
+
return this.audioCtx;
|
|
19103
|
+
}
|
|
19104
|
+
setActive(a) {
|
|
19105
|
+
if (this.active === a) return;
|
|
19106
|
+
this.active = a;
|
|
19107
|
+
this.onActivityChange?.(a);
|
|
19108
|
+
this.activityListeners.forEach((fn) => fn(a));
|
|
19109
|
+
}
|
|
19110
|
+
async drain() {
|
|
19111
|
+
if (this.draining) return;
|
|
19112
|
+
this.draining = true;
|
|
19113
|
+
const gen = this.generation;
|
|
19114
|
+
this.setActive(true);
|
|
19115
|
+
while (this.queue.length && gen === this.generation) {
|
|
19116
|
+
const chunk = this.queue.shift();
|
|
19117
|
+
try {
|
|
19118
|
+
if (getEngine() === "neural") await this.speakNeural(chunk, gen);
|
|
19119
|
+
else await this.speakWeb(chunk, gen);
|
|
19120
|
+
} catch (err) {
|
|
19121
|
+
console.error("[tts] speak failed", err);
|
|
19122
|
+
this.onModelProgress?.(null);
|
|
19123
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
19124
|
+
this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
|
|
19125
|
+
}
|
|
19126
|
+
}
|
|
19127
|
+
if (gen === this.generation) {
|
|
19128
|
+
this.draining = false;
|
|
19129
|
+
this.setActive(false);
|
|
19130
|
+
}
|
|
19131
|
+
}
|
|
19132
|
+
speakWeb(text2, gen) {
|
|
19133
|
+
return new Promise((resolve2) => {
|
|
19134
|
+
if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
|
|
19135
|
+
const u = new SpeechSynthesisUtterance(text2);
|
|
19136
|
+
u.rate = getRate();
|
|
19137
|
+
const wantUri = getVoiceURI();
|
|
19138
|
+
if (wantUri) {
|
|
19139
|
+
const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
|
|
19140
|
+
if (v2) u.voice = v2;
|
|
19141
|
+
}
|
|
19142
|
+
u.onend = () => resolve2();
|
|
19143
|
+
u.onerror = () => resolve2();
|
|
19144
|
+
speechSynthesis.speak(u);
|
|
19145
|
+
});
|
|
19146
|
+
}
|
|
19147
|
+
async speakNeural(text2, gen) {
|
|
19148
|
+
console.info("[tts] neural: loading model…");
|
|
19149
|
+
this.onModelProgress?.({ status: "loading" });
|
|
19150
|
+
const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
|
|
19151
|
+
if (gen !== this.generation) return;
|
|
19152
|
+
console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
|
|
19153
|
+
const out = await synth(text2);
|
|
19154
|
+
const raw = Array.isArray(out) ? out[0] : out;
|
|
19155
|
+
this.onModelProgress?.(null);
|
|
19156
|
+
if (gen !== this.generation) return;
|
|
19157
|
+
if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
|
|
19158
|
+
console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
|
|
19159
|
+
await this.playPcm(raw.audio, raw.sampling_rate, gen);
|
|
19160
|
+
}
|
|
19161
|
+
async playPcm(pcm, sampleRate, gen) {
|
|
19162
|
+
if (gen !== this.generation) return;
|
|
19163
|
+
const ctx = this.ensureCtx();
|
|
19164
|
+
if (ctx.state === "suspended") {
|
|
19165
|
+
try {
|
|
19166
|
+
await ctx.resume();
|
|
19167
|
+
} catch {
|
|
19168
|
+
}
|
|
19169
|
+
}
|
|
19170
|
+
if (gen !== this.generation) return;
|
|
19171
|
+
return new Promise((resolve2) => {
|
|
19172
|
+
const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
|
|
19173
|
+
buffer.getChannelData(0).set(pcm);
|
|
19174
|
+
const source = ctx.createBufferSource();
|
|
19175
|
+
source.buffer = buffer;
|
|
19176
|
+
source.connect(ctx.destination);
|
|
19177
|
+
source.onended = () => {
|
|
19178
|
+
if (this.currentSource === source) this.currentSource = null;
|
|
19179
|
+
resolve2();
|
|
19180
|
+
};
|
|
19181
|
+
this.currentSource = source;
|
|
19182
|
+
source.start();
|
|
19183
|
+
});
|
|
19184
|
+
}
|
|
19185
|
+
}
|
|
19186
|
+
const speech = new SpeechController();
|
|
19187
|
+
function showTerminalToast(message) {
|
|
19188
|
+
window.dispatchEvent(new CustomEvent("terminal-toast", { detail: { message } }));
|
|
19189
|
+
}
|
|
19190
|
+
function copyTextValue(text2) {
|
|
19191
|
+
if (!text2) return;
|
|
19192
|
+
void window.worker.copyText(text2);
|
|
19193
|
+
showTerminalToast("Copied to clipboard");
|
|
19194
|
+
}
|
|
19195
|
+
function convertTextToTable(text2) {
|
|
19196
|
+
const result = convertToEmailTable(text2);
|
|
19197
|
+
if (result) {
|
|
19198
|
+
window.worker.copyEmailTable(result.html, result.text);
|
|
19199
|
+
showTerminalToast(`Copied ${result.rows}×${result.cols} table to clipboard`);
|
|
19200
|
+
} else {
|
|
19201
|
+
showTerminalToast("No table found in selection");
|
|
19202
|
+
}
|
|
19203
|
+
}
|
|
19204
|
+
function speakTextValue(text2) {
|
|
19205
|
+
if (!text2.trim()) return;
|
|
19206
|
+
speech.unlock();
|
|
19207
|
+
speech.enqueue(text2);
|
|
19208
|
+
}
|
|
19209
|
+
function copySelectionPlain(terminal) {
|
|
19210
|
+
copyTextValue(terminal.getSelection());
|
|
19211
|
+
}
|
|
19212
|
+
function copySelectionAsEmailTable(terminal) {
|
|
19213
|
+
convertTextToTable(terminal.getSelection());
|
|
19214
|
+
}
|
|
19215
|
+
function speakSelection(terminal) {
|
|
19216
|
+
speakTextValue(terminal.getSelection());
|
|
19217
|
+
}
|
|
19218
|
+
function captureVisibleText(terminal) {
|
|
19219
|
+
const buf = terminal.buffer.active;
|
|
19220
|
+
const lines = [];
|
|
19221
|
+
for (let i2 = 0; i2 < terminal.rows; i2++) {
|
|
19222
|
+
const line = buf.getLine(buf.viewportY + i2);
|
|
19223
|
+
lines.push(line ? line.translateToString(true) : "");
|
|
19224
|
+
}
|
|
19225
|
+
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
19226
|
+
return lines.join("\n");
|
|
18841
19227
|
}
|
|
18842
19228
|
function handleTerminalLink(_event, uri) {
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
window.dispatchEvent(new CustomEvent("ctlsurf-open-url", { detail: { url: embedUrl } }));
|
|
18846
|
-
} else {
|
|
18847
|
-
openExternal(uri);
|
|
18848
|
-
}
|
|
18849
|
-
}).catch(() => openExternal(uri));
|
|
19229
|
+
const openInPanel = (url) => window.dispatchEvent(new CustomEvent("ctlsurf-open-url", { detail: { url } }));
|
|
19230
|
+
window.worker.getEmbedUrl(uri).then((embedUrl) => openInPanel(embedUrl || uri)).catch(() => openInPanel(uri));
|
|
18850
19231
|
}
|
|
18851
19232
|
const _terminals = /* @__PURE__ */ new Map();
|
|
18852
19233
|
function isAtBottom(terminal) {
|
|
@@ -18925,11 +19306,23 @@ function getOrCreateTerminal(tabId, onExit) {
|
|
|
18925
19306
|
if (e.key === "PageUp" || e.key === "PageDown" || e.key === "Home" || e.key === "End") {
|
|
18926
19307
|
markGesture(tabId);
|
|
18927
19308
|
}
|
|
18928
|
-
const
|
|
18929
|
-
if (
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
19309
|
+
const mod = (e.metaKey || e.ctrlKey) && e.shiftKey;
|
|
19310
|
+
if (mod && e.code === "KeyE") {
|
|
19311
|
+
e.preventDefault();
|
|
19312
|
+
copySelectionAsEmailTable(terminal);
|
|
19313
|
+
return false;
|
|
19314
|
+
}
|
|
19315
|
+
if (mod && e.code === "KeyC" && terminal.hasSelection()) {
|
|
19316
|
+
e.preventDefault();
|
|
19317
|
+
copySelectionPlain(terminal);
|
|
19318
|
+
return false;
|
|
19319
|
+
}
|
|
19320
|
+
if (mod && e.code === "KeyS" && terminal.getSelection().trim()) {
|
|
19321
|
+
e.preventDefault();
|
|
19322
|
+
speakSelection(terminal);
|
|
19323
|
+
return false;
|
|
19324
|
+
}
|
|
19325
|
+
return true;
|
|
18933
19326
|
});
|
|
18934
19327
|
const state = {
|
|
18935
19328
|
terminal,
|
|
@@ -18991,6 +19384,8 @@ function destroyTerminal(tabId) {
|
|
|
18991
19384
|
function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
|
|
18992
19385
|
const containerRef = reactExports.useRef(null);
|
|
18993
19386
|
const [menu, setMenu] = reactExports.useState(null);
|
|
19387
|
+
const [snapOpen, setSnapOpen] = reactExports.useState(false);
|
|
19388
|
+
const snapTextRef = reactExports.useRef("");
|
|
18994
19389
|
const [toast, setToast] = reactExports.useState(null);
|
|
18995
19390
|
const [followBottom, setFollowBottom] = reactExports.useState(true);
|
|
18996
19391
|
const jumpToLatest = () => {
|
|
@@ -19002,22 +19397,44 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
|
|
|
19002
19397
|
};
|
|
19003
19398
|
const handleContextMenu = (e) => {
|
|
19004
19399
|
const sel = _terminals.get(tabId)?.terminal.getSelection() ?? "";
|
|
19005
|
-
if (!
|
|
19400
|
+
if (!sel.trim()) return;
|
|
19006
19401
|
e.preventDefault();
|
|
19007
19402
|
setMenu({ x: e.clientX, y: e.clientY });
|
|
19008
19403
|
};
|
|
19404
|
+
const runCopy = () => {
|
|
19405
|
+
const term = _terminals.get(tabId)?.terminal;
|
|
19406
|
+
if (term) copySelectionPlain(term);
|
|
19407
|
+
setMenu(null);
|
|
19408
|
+
};
|
|
19009
19409
|
const runConvert = () => {
|
|
19010
19410
|
const term = _terminals.get(tabId)?.terminal;
|
|
19011
19411
|
if (term) copySelectionAsEmailTable(term);
|
|
19012
19412
|
setMenu(null);
|
|
19013
19413
|
};
|
|
19414
|
+
const runSpeak = () => {
|
|
19415
|
+
const term = _terminals.get(tabId)?.terminal;
|
|
19416
|
+
if (term) speakSelection(term);
|
|
19417
|
+
setMenu(null);
|
|
19418
|
+
};
|
|
19419
|
+
const openSnapshot = () => {
|
|
19420
|
+
const term = _terminals.get(tabId)?.terminal;
|
|
19421
|
+
snapTextRef.current = term ? captureVisibleText(term) : "";
|
|
19422
|
+
setSnapOpen(true);
|
|
19423
|
+
};
|
|
19424
|
+
const snapAction = (kind) => {
|
|
19425
|
+
const domSel = window.getSelection()?.toString() ?? "";
|
|
19426
|
+
const text2 = domSel.trim() ? domSel : snapTextRef.current;
|
|
19427
|
+
if (kind === "copy") copyTextValue(text2);
|
|
19428
|
+
else if (kind === "table") convertTextToTable(text2);
|
|
19429
|
+
else speakTextValue(text2);
|
|
19430
|
+
};
|
|
19014
19431
|
reactExports.useEffect(() => {
|
|
19015
|
-
const
|
|
19432
|
+
const onToast = (e) => {
|
|
19016
19433
|
const detail = e.detail;
|
|
19017
|
-
if (detail) setToast(detail.message);
|
|
19434
|
+
if (detail?.message) setToast(detail.message);
|
|
19018
19435
|
};
|
|
19019
|
-
window.addEventListener("
|
|
19020
|
-
return () => window.removeEventListener("
|
|
19436
|
+
window.addEventListener("terminal-toast", onToast);
|
|
19437
|
+
return () => window.removeEventListener("terminal-toast", onToast);
|
|
19021
19438
|
}, []);
|
|
19022
19439
|
reactExports.useEffect(() => {
|
|
19023
19440
|
if (!toast) return;
|
|
@@ -19141,100 +19558,75 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
|
|
|
19141
19558
|
title: "Jump to latest output",
|
|
19142
19559
|
children: "↓ Latest"
|
|
19143
19560
|
}
|
|
19144
|
-
)
|
|
19561
|
+
),
|
|
19562
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19563
|
+
"button",
|
|
19564
|
+
{
|
|
19565
|
+
type: "button",
|
|
19566
|
+
className: "term-util-btn",
|
|
19567
|
+
onMouseDown: (e) => {
|
|
19568
|
+
e.preventDefault();
|
|
19569
|
+
openSnapshot();
|
|
19570
|
+
},
|
|
19571
|
+
title: "Snapshot screen → copy / convert to table / read aloud",
|
|
19572
|
+
children: "⋮"
|
|
19573
|
+
}
|
|
19574
|
+
),
|
|
19575
|
+
snapOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot", children: [
|
|
19576
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot-head", children: [
|
|
19577
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Select text below (or select nothing to use everything), then pick an action." }),
|
|
19578
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19579
|
+
"button",
|
|
19580
|
+
{
|
|
19581
|
+
type: "button",
|
|
19582
|
+
className: "term-snapshot-x",
|
|
19583
|
+
onMouseDown: (e) => {
|
|
19584
|
+
e.preventDefault();
|
|
19585
|
+
setSnapOpen(false);
|
|
19586
|
+
},
|
|
19587
|
+
title: "Close",
|
|
19588
|
+
children: "✕"
|
|
19589
|
+
}
|
|
19590
|
+
)
|
|
19591
|
+
] }),
|
|
19592
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "term-snapshot-text", children: snapTextRef.current }),
|
|
19593
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot-actions", children: [
|
|
19594
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19595
|
+
e.preventDefault();
|
|
19596
|
+
snapAction("copy");
|
|
19597
|
+
}, children: "⧉ Copy" }),
|
|
19598
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19599
|
+
e.preventDefault();
|
|
19600
|
+
snapAction("table");
|
|
19601
|
+
}, children: "▦ Convert to table" }),
|
|
19602
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19603
|
+
e.preventDefault();
|
|
19604
|
+
snapAction("speak");
|
|
19605
|
+
}, children: "🔊 Read aloud" }),
|
|
19606
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "term-snapshot-done", onMouseDown: (e) => {
|
|
19607
|
+
e.preventDefault();
|
|
19608
|
+
setSnapOpen(false);
|
|
19609
|
+
}, children: "Close" })
|
|
19610
|
+
] })
|
|
19611
|
+
] })
|
|
19612
|
+
] }),
|
|
19613
|
+
menu && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-context-menu", style: { position: "fixed", left: menu.x, top: menu.y }, children: [
|
|
19614
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19615
|
+
e.preventDefault();
|
|
19616
|
+
runCopy();
|
|
19617
|
+
}, children: "Copy" }),
|
|
19618
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19619
|
+
e.preventDefault();
|
|
19620
|
+
runConvert();
|
|
19621
|
+
}, children: "Convert to table" }),
|
|
19622
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19623
|
+
e.preventDefault();
|
|
19624
|
+
runSpeak();
|
|
19625
|
+
}, children: "Read it" })
|
|
19145
19626
|
] }),
|
|
19146
|
-
menu && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "term-context-menu", style: { position: "fixed", left: menu.x, top: menu.y }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
|
|
19147
|
-
e.preventDefault();
|
|
19148
|
-
runConvert();
|
|
19149
|
-
}, children: "Copy as email table" }) }),
|
|
19150
19627
|
toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "term-toast", children: toast })
|
|
19151
19628
|
] });
|
|
19152
19629
|
}
|
|
19153
|
-
const scriptRel = /* @__PURE__ */ (function detectScriptRel() {
|
|
19154
|
-
const relList = typeof document !== "undefined" && document.createElement("link").relList;
|
|
19155
|
-
return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
|
|
19156
|
-
})();
|
|
19157
|
-
const assetsURL = function(dep, importerUrl) {
|
|
19158
|
-
return new URL(dep, importerUrl).href;
|
|
19159
|
-
};
|
|
19160
|
-
const seen = {};
|
|
19161
|
-
const __vitePreload = function preload(baseModule, deps, importerUrl) {
|
|
19162
|
-
let promise = Promise.resolve();
|
|
19163
|
-
if (deps && deps.length > 0) {
|
|
19164
|
-
let allSettled2 = function(promises) {
|
|
19165
|
-
return Promise.all(
|
|
19166
|
-
promises.map(
|
|
19167
|
-
(p) => Promise.resolve(p).then(
|
|
19168
|
-
(value) => ({ status: "fulfilled", value }),
|
|
19169
|
-
(reason) => ({ status: "rejected", reason })
|
|
19170
|
-
)
|
|
19171
|
-
)
|
|
19172
|
-
);
|
|
19173
|
-
};
|
|
19174
|
-
const links = document.getElementsByTagName("link");
|
|
19175
|
-
const cspNonceMeta = document.querySelector(
|
|
19176
|
-
"meta[property=csp-nonce]"
|
|
19177
|
-
);
|
|
19178
|
-
const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
|
|
19179
|
-
promise = allSettled2(
|
|
19180
|
-
deps.map((dep) => {
|
|
19181
|
-
dep = assetsURL(dep, importerUrl);
|
|
19182
|
-
if (dep in seen) return;
|
|
19183
|
-
seen[dep] = true;
|
|
19184
|
-
const isCss = dep.endsWith(".css");
|
|
19185
|
-
const cssSelector = isCss ? '[rel="stylesheet"]' : "";
|
|
19186
|
-
const isBaseRelative = !!importerUrl;
|
|
19187
|
-
if (isBaseRelative) {
|
|
19188
|
-
for (let i2 = links.length - 1; i2 >= 0; i2--) {
|
|
19189
|
-
const link22 = links[i2];
|
|
19190
|
-
if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
|
|
19191
|
-
return;
|
|
19192
|
-
}
|
|
19193
|
-
}
|
|
19194
|
-
} else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
|
|
19195
|
-
return;
|
|
19196
|
-
}
|
|
19197
|
-
const link2 = document.createElement("link");
|
|
19198
|
-
link2.rel = isCss ? "stylesheet" : scriptRel;
|
|
19199
|
-
if (!isCss) {
|
|
19200
|
-
link2.as = "script";
|
|
19201
|
-
}
|
|
19202
|
-
link2.crossOrigin = "";
|
|
19203
|
-
link2.href = dep;
|
|
19204
|
-
if (cspNonce) {
|
|
19205
|
-
link2.setAttribute("nonce", cspNonce);
|
|
19206
|
-
}
|
|
19207
|
-
document.head.appendChild(link2);
|
|
19208
|
-
if (isCss) {
|
|
19209
|
-
return new Promise((res, rej) => {
|
|
19210
|
-
link2.addEventListener("load", res);
|
|
19211
|
-
link2.addEventListener(
|
|
19212
|
-
"error",
|
|
19213
|
-
() => rej(new Error(`Unable to preload CSS for ${dep}`))
|
|
19214
|
-
);
|
|
19215
|
-
});
|
|
19216
|
-
}
|
|
19217
|
-
})
|
|
19218
|
-
);
|
|
19219
|
-
}
|
|
19220
|
-
function handlePreloadError(err) {
|
|
19221
|
-
const e = new Event("vite:preloadError", {
|
|
19222
|
-
cancelable: true
|
|
19223
|
-
});
|
|
19224
|
-
e.payload = err;
|
|
19225
|
-
window.dispatchEvent(e);
|
|
19226
|
-
if (!e.defaultPrevented) {
|
|
19227
|
-
throw err;
|
|
19228
|
-
}
|
|
19229
|
-
}
|
|
19230
|
-
return promise.then((res) => {
|
|
19231
|
-
for (const item of res || []) {
|
|
19232
|
-
if (item.status !== "rejected") continue;
|
|
19233
|
-
handlePreloadError(item.reason);
|
|
19234
|
-
}
|
|
19235
|
-
return baseModule().catch(handlePreloadError);
|
|
19236
|
-
});
|
|
19237
|
-
};
|
|
19238
19630
|
const MODEL = "Xenova/whisper-base";
|
|
19239
19631
|
const TARGET_SAMPLE_RATE = 16e3;
|
|
19240
19632
|
const SILENCE_RMS = 8e-3;
|
|
@@ -19658,287 +20050,6 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
|
|
|
19658
20050
|
chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
|
|
19659
20051
|
] });
|
|
19660
20052
|
}
|
|
19661
|
-
const ENGINE_KEY = "ctlsurf.tts.engine";
|
|
19662
|
-
const VOICE_KEY = "ctlsurf.tts.voiceURI";
|
|
19663
|
-
const RATE_KEY = "ctlsurf.tts.rate";
|
|
19664
|
-
const MAX_SPEAK_CHARS = 1600;
|
|
19665
|
-
const MAX_CHUNK_CHARS = 280;
|
|
19666
|
-
function cleanForSpeech(input) {
|
|
19667
|
-
let t = input;
|
|
19668
|
-
t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
|
|
19669
|
-
const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
|
|
19670
|
-
return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
|
|
19671
|
-
});
|
|
19672
|
-
t = t.replace(/```/g, " (code) ");
|
|
19673
|
-
t = t.replace(/`([^`]+)`/g, "$1");
|
|
19674
|
-
t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
19675
|
-
t = t.replace(/https?:\/\/\S+/g, "link");
|
|
19676
|
-
t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
|
|
19677
|
-
t = t.replace(/[*_#>]+/g, " ");
|
|
19678
|
-
t = t.replace(/^\s*[-•]\s+/gm, ", ");
|
|
19679
|
-
t = t.replace(/<[^>]+>/g, " ");
|
|
19680
|
-
t = t.replace(/\s+/g, " ").trim();
|
|
19681
|
-
if (t.length > MAX_SPEAK_CHARS) {
|
|
19682
|
-
t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
|
|
19683
|
-
}
|
|
19684
|
-
return t;
|
|
19685
|
-
}
|
|
19686
|
-
function splitIntoChunks(text2) {
|
|
19687
|
-
if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
|
|
19688
|
-
const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
|
|
19689
|
-
const chunks = [];
|
|
19690
|
-
let buf = "";
|
|
19691
|
-
for (const s of sentences) {
|
|
19692
|
-
if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
|
|
19693
|
-
chunks.push(buf.trim());
|
|
19694
|
-
buf = "";
|
|
19695
|
-
}
|
|
19696
|
-
if (s.length > MAX_CHUNK_CHARS) {
|
|
19697
|
-
for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
|
|
19698
|
-
} else {
|
|
19699
|
-
buf += s;
|
|
19700
|
-
}
|
|
19701
|
-
}
|
|
19702
|
-
if (buf.trim()) chunks.push(buf.trim());
|
|
19703
|
-
return chunks;
|
|
19704
|
-
}
|
|
19705
|
-
function getEngine() {
|
|
19706
|
-
try {
|
|
19707
|
-
return localStorage.getItem(ENGINE_KEY) === "neural" ? "neural" : "web";
|
|
19708
|
-
} catch {
|
|
19709
|
-
return "web";
|
|
19710
|
-
}
|
|
19711
|
-
}
|
|
19712
|
-
function setEngine(id) {
|
|
19713
|
-
try {
|
|
19714
|
-
localStorage.setItem(ENGINE_KEY, id);
|
|
19715
|
-
} catch {
|
|
19716
|
-
}
|
|
19717
|
-
}
|
|
19718
|
-
function getVoiceURI() {
|
|
19719
|
-
try {
|
|
19720
|
-
return localStorage.getItem(VOICE_KEY) || null;
|
|
19721
|
-
} catch {
|
|
19722
|
-
return null;
|
|
19723
|
-
}
|
|
19724
|
-
}
|
|
19725
|
-
function setVoiceURI(uri) {
|
|
19726
|
-
try {
|
|
19727
|
-
uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
|
|
19728
|
-
} catch {
|
|
19729
|
-
}
|
|
19730
|
-
}
|
|
19731
|
-
function getRate() {
|
|
19732
|
-
try {
|
|
19733
|
-
const n2 = Number(localStorage.getItem(RATE_KEY));
|
|
19734
|
-
return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
|
|
19735
|
-
} catch {
|
|
19736
|
-
return 1;
|
|
19737
|
-
}
|
|
19738
|
-
}
|
|
19739
|
-
function setRate(rate) {
|
|
19740
|
-
try {
|
|
19741
|
-
localStorage.setItem(RATE_KEY, String(rate));
|
|
19742
|
-
} catch {
|
|
19743
|
-
}
|
|
19744
|
-
}
|
|
19745
|
-
function listWebVoices() {
|
|
19746
|
-
if (typeof speechSynthesis === "undefined") return [];
|
|
19747
|
-
return speechSynthesis.getVoices();
|
|
19748
|
-
}
|
|
19749
|
-
const NEURAL_MODEL = "Xenova/mms-tts-eng";
|
|
19750
|
-
let synthPromise = null;
|
|
19751
|
-
function loadSynthesizer(onProgress) {
|
|
19752
|
-
if (!synthPromise) {
|
|
19753
|
-
synthPromise = (async () => {
|
|
19754
|
-
const { pipeline, env: env2 } = await __vitePreload(async () => {
|
|
19755
|
-
const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
|
|
19756
|
-
return { pipeline: pipeline2, env: env3 };
|
|
19757
|
-
}, true ? [] : void 0, import.meta.url);
|
|
19758
|
-
env2.allowLocalModels = false;
|
|
19759
|
-
const common = { progress_callback: onProgress };
|
|
19760
|
-
const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
|
|
19761
|
-
if (hasWebGpu) {
|
|
19762
|
-
try {
|
|
19763
|
-
const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
|
|
19764
|
-
console.info("[tts] neural backend: webgpu");
|
|
19765
|
-
return s2;
|
|
19766
|
-
} catch (err) {
|
|
19767
|
-
console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
|
|
19768
|
-
}
|
|
19769
|
-
}
|
|
19770
|
-
const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
|
|
19771
|
-
console.info("[tts] neural backend: wasm");
|
|
19772
|
-
return s;
|
|
19773
|
-
})();
|
|
19774
|
-
synthPromise.catch(() => {
|
|
19775
|
-
synthPromise = null;
|
|
19776
|
-
});
|
|
19777
|
-
}
|
|
19778
|
-
return synthPromise;
|
|
19779
|
-
}
|
|
19780
|
-
class SpeechController {
|
|
19781
|
-
queue = [];
|
|
19782
|
-
draining = false;
|
|
19783
|
-
audioCtx = null;
|
|
19784
|
-
currentSource = null;
|
|
19785
|
-
generation = 0;
|
|
19786
|
-
// bumped by stop() to abort in-flight work
|
|
19787
|
-
active = false;
|
|
19788
|
-
onModelProgress = null;
|
|
19789
|
-
// Fires true while a reply is being spoken/queued, false when idle — drives
|
|
19790
|
-
// the visible Stop button.
|
|
19791
|
-
onActivityChange = null;
|
|
19792
|
-
onError = null;
|
|
19793
|
-
// Multiple UI surfaces (titlebar SpeakControls + floating read button) need
|
|
19794
|
-
// the speaking state at once, so beyond the single onActivityChange slot we
|
|
19795
|
-
// also keep a listener set.
|
|
19796
|
-
activityListeners = /* @__PURE__ */ new Set();
|
|
19797
|
-
get isActive() {
|
|
19798
|
-
return this.active;
|
|
19799
|
-
}
|
|
19800
|
-
// Subscribe to speaking-state changes. Fires immediately with the current
|
|
19801
|
-
// state so a late subscriber syncs. Returns an unsubscribe function.
|
|
19802
|
-
onActivity(fn) {
|
|
19803
|
-
this.activityListeners.add(fn);
|
|
19804
|
-
fn(this.active);
|
|
19805
|
-
return () => {
|
|
19806
|
-
this.activityListeners.delete(fn);
|
|
19807
|
-
};
|
|
19808
|
-
}
|
|
19809
|
-
// Prime/resume the AudioContext from a user gesture so neural playback isn't
|
|
19810
|
-
// blocked by the browser autoplay policy (the System engine is unaffected).
|
|
19811
|
-
unlock() {
|
|
19812
|
-
try {
|
|
19813
|
-
const ctx = this.ensureCtx();
|
|
19814
|
-
if (ctx.state === "suspended") void ctx.resume();
|
|
19815
|
-
} catch {
|
|
19816
|
-
}
|
|
19817
|
-
}
|
|
19818
|
-
// Start loading the neural model in the background (e.g. when the user picks
|
|
19819
|
-
// the neural engine) so the first utterance doesn't pay download/compile time.
|
|
19820
|
-
warmup() {
|
|
19821
|
-
if (getEngine() !== "neural") return;
|
|
19822
|
-
void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
|
|
19823
|
-
this.onModelProgress?.(null);
|
|
19824
|
-
});
|
|
19825
|
-
}
|
|
19826
|
-
enqueue(rawText) {
|
|
19827
|
-
const text2 = cleanForSpeech(rawText);
|
|
19828
|
-
if (!text2) return;
|
|
19829
|
-
this.queue.push(...splitIntoChunks(text2));
|
|
19830
|
-
void this.drain();
|
|
19831
|
-
}
|
|
19832
|
-
stop() {
|
|
19833
|
-
this.generation++;
|
|
19834
|
-
this.queue = [];
|
|
19835
|
-
this.draining = false;
|
|
19836
|
-
if (typeof speechSynthesis !== "undefined") {
|
|
19837
|
-
try {
|
|
19838
|
-
speechSynthesis.cancel();
|
|
19839
|
-
} catch {
|
|
19840
|
-
}
|
|
19841
|
-
}
|
|
19842
|
-
if (this.currentSource) {
|
|
19843
|
-
try {
|
|
19844
|
-
this.currentSource.stop();
|
|
19845
|
-
} catch {
|
|
19846
|
-
}
|
|
19847
|
-
this.currentSource = null;
|
|
19848
|
-
}
|
|
19849
|
-
this.onModelProgress?.(null);
|
|
19850
|
-
this.setActive(false);
|
|
19851
|
-
}
|
|
19852
|
-
ensureCtx() {
|
|
19853
|
-
if (!this.audioCtx) {
|
|
19854
|
-
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
19855
|
-
this.audioCtx = new Ctx();
|
|
19856
|
-
}
|
|
19857
|
-
return this.audioCtx;
|
|
19858
|
-
}
|
|
19859
|
-
setActive(a) {
|
|
19860
|
-
if (this.active === a) return;
|
|
19861
|
-
this.active = a;
|
|
19862
|
-
this.onActivityChange?.(a);
|
|
19863
|
-
this.activityListeners.forEach((fn) => fn(a));
|
|
19864
|
-
}
|
|
19865
|
-
async drain() {
|
|
19866
|
-
if (this.draining) return;
|
|
19867
|
-
this.draining = true;
|
|
19868
|
-
const gen = this.generation;
|
|
19869
|
-
this.setActive(true);
|
|
19870
|
-
while (this.queue.length && gen === this.generation) {
|
|
19871
|
-
const chunk = this.queue.shift();
|
|
19872
|
-
try {
|
|
19873
|
-
if (getEngine() === "neural") await this.speakNeural(chunk, gen);
|
|
19874
|
-
else await this.speakWeb(chunk, gen);
|
|
19875
|
-
} catch (err) {
|
|
19876
|
-
console.error("[tts] speak failed", err);
|
|
19877
|
-
this.onModelProgress?.(null);
|
|
19878
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
19879
|
-
this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
|
|
19880
|
-
}
|
|
19881
|
-
}
|
|
19882
|
-
if (gen === this.generation) {
|
|
19883
|
-
this.draining = false;
|
|
19884
|
-
this.setActive(false);
|
|
19885
|
-
}
|
|
19886
|
-
}
|
|
19887
|
-
speakWeb(text2, gen) {
|
|
19888
|
-
return new Promise((resolve2) => {
|
|
19889
|
-
if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
|
|
19890
|
-
const u = new SpeechSynthesisUtterance(text2);
|
|
19891
|
-
u.rate = getRate();
|
|
19892
|
-
const wantUri = getVoiceURI();
|
|
19893
|
-
if (wantUri) {
|
|
19894
|
-
const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
|
|
19895
|
-
if (v2) u.voice = v2;
|
|
19896
|
-
}
|
|
19897
|
-
u.onend = () => resolve2();
|
|
19898
|
-
u.onerror = () => resolve2();
|
|
19899
|
-
speechSynthesis.speak(u);
|
|
19900
|
-
});
|
|
19901
|
-
}
|
|
19902
|
-
async speakNeural(text2, gen) {
|
|
19903
|
-
console.info("[tts] neural: loading model…");
|
|
19904
|
-
this.onModelProgress?.({ status: "loading" });
|
|
19905
|
-
const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
|
|
19906
|
-
if (gen !== this.generation) return;
|
|
19907
|
-
console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
|
|
19908
|
-
const out = await synth(text2);
|
|
19909
|
-
const raw = Array.isArray(out) ? out[0] : out;
|
|
19910
|
-
this.onModelProgress?.(null);
|
|
19911
|
-
if (gen !== this.generation) return;
|
|
19912
|
-
if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
|
|
19913
|
-
console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
|
|
19914
|
-
await this.playPcm(raw.audio, raw.sampling_rate, gen);
|
|
19915
|
-
}
|
|
19916
|
-
async playPcm(pcm, sampleRate, gen) {
|
|
19917
|
-
if (gen !== this.generation) return;
|
|
19918
|
-
const ctx = this.ensureCtx();
|
|
19919
|
-
if (ctx.state === "suspended") {
|
|
19920
|
-
try {
|
|
19921
|
-
await ctx.resume();
|
|
19922
|
-
} catch {
|
|
19923
|
-
}
|
|
19924
|
-
}
|
|
19925
|
-
if (gen !== this.generation) return;
|
|
19926
|
-
return new Promise((resolve2) => {
|
|
19927
|
-
const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
|
|
19928
|
-
buffer.getChannelData(0).set(pcm);
|
|
19929
|
-
const source = ctx.createBufferSource();
|
|
19930
|
-
source.buffer = buffer;
|
|
19931
|
-
source.connect(ctx.destination);
|
|
19932
|
-
source.onended = () => {
|
|
19933
|
-
if (this.currentSource === source) this.currentSource = null;
|
|
19934
|
-
resolve2();
|
|
19935
|
-
};
|
|
19936
|
-
this.currentSource = source;
|
|
19937
|
-
source.start();
|
|
19938
|
-
});
|
|
19939
|
-
}
|
|
19940
|
-
}
|
|
19941
|
-
const speech = new SpeechController();
|
|
19942
20053
|
const POS_KEY = "ctlsurf.floatingMicPos";
|
|
19943
20054
|
const EDGE = 20;
|
|
19944
20055
|
const TOP_MIN = 46;
|
|
@@ -20117,6 +20228,9 @@ function SpeakControls({ remote = false }) {
|
|
|
20117
20228
|
alive = false;
|
|
20118
20229
|
};
|
|
20119
20230
|
}, []);
|
|
20231
|
+
reactExports.useEffect(() => {
|
|
20232
|
+
speech.warmup();
|
|
20233
|
+
}, []);
|
|
20120
20234
|
reactExports.useEffect(() => {
|
|
20121
20235
|
const off = window.worker.onAgentMessage((text2) => speech.enqueue(text2));
|
|
20122
20236
|
return off;
|
|
@@ -20322,49 +20436,170 @@ function SpeakControls({ remote = false }) {
|
|
|
20322
20436
|
error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "speak-error", children: error })
|
|
20323
20437
|
] });
|
|
20324
20438
|
}
|
|
20439
|
+
function displayUrl(raw) {
|
|
20440
|
+
try {
|
|
20441
|
+
const u = new URL(raw);
|
|
20442
|
+
u.searchParams.delete("_code");
|
|
20443
|
+
u.searchParams.delete("embed");
|
|
20444
|
+
return u.toString();
|
|
20445
|
+
} catch {
|
|
20446
|
+
return raw;
|
|
20447
|
+
}
|
|
20448
|
+
}
|
|
20325
20449
|
function CtlsurfPanel({ navigate }) {
|
|
20326
20450
|
const webviewRef = reactExports.useRef(null);
|
|
20327
|
-
const [
|
|
20451
|
+
const [src, setSrc] = reactExports.useState(null);
|
|
20452
|
+
const [address, setAddress] = reactExports.useState("");
|
|
20453
|
+
const [loading, setLoading] = reactExports.useState(false);
|
|
20454
|
+
const [nav, setNav] = reactExports.useState({ back: false, forward: false });
|
|
20328
20455
|
const [key, setKey] = reactExports.useState(0);
|
|
20329
|
-
const
|
|
20456
|
+
const hasSrcRef = reactExports.useRef(false);
|
|
20457
|
+
const readyRef = reactExports.useRef(false);
|
|
20458
|
+
const pendingRef = reactExports.useRef(null);
|
|
20459
|
+
const resolve2 = reactExports.useCallback(async (raw) => {
|
|
20460
|
+
try {
|
|
20461
|
+
return await window.worker.getEmbedUrl(raw) || raw;
|
|
20462
|
+
} catch {
|
|
20463
|
+
return raw;
|
|
20464
|
+
}
|
|
20465
|
+
}, []);
|
|
20466
|
+
const load = reactExports.useCallback((url) => {
|
|
20467
|
+
if (!url) return;
|
|
20468
|
+
setAddress(displayUrl(url));
|
|
20469
|
+
if (!hasSrcRef.current) {
|
|
20470
|
+
hasSrcRef.current = true;
|
|
20471
|
+
setSrc(url);
|
|
20472
|
+
return;
|
|
20473
|
+
}
|
|
20474
|
+
const wv = webviewRef.current;
|
|
20475
|
+
if (wv && readyRef.current) wv.loadURL(url);
|
|
20476
|
+
else pendingRef.current = url;
|
|
20477
|
+
}, []);
|
|
20478
|
+
const loadProject = reactExports.useCallback(async () => {
|
|
20330
20479
|
try {
|
|
20331
20480
|
const info = await window.worker.getWebviewInfo();
|
|
20332
|
-
|
|
20481
|
+
load(info.pageUrl || info.frontendUrl);
|
|
20333
20482
|
} catch (err) {
|
|
20334
20483
|
console.error("[ctlsurf] Failed to get webview info:", err);
|
|
20335
|
-
|
|
20484
|
+
load("https://app.ctlsurf.com");
|
|
20336
20485
|
}
|
|
20337
|
-
};
|
|
20486
|
+
}, [load]);
|
|
20338
20487
|
reactExports.useEffect(() => {
|
|
20339
|
-
if (!navigate?.url)
|
|
20488
|
+
if (!navigate?.url) loadProject();
|
|
20340
20489
|
}, []);
|
|
20341
20490
|
reactExports.useEffect(() => {
|
|
20342
|
-
if (navigate?.url)
|
|
20343
|
-
setUrl(navigate.url);
|
|
20344
|
-
setKey((k2) => k2 + 1);
|
|
20345
|
-
}
|
|
20491
|
+
if (navigate?.url) resolve2(navigate.url).then(load);
|
|
20346
20492
|
}, [navigate?.n]);
|
|
20347
20493
|
reactExports.useEffect(() => {
|
|
20348
20494
|
const unsub = window.worker.onCwdChanged(() => {
|
|
20349
|
-
|
|
20495
|
+
hasSrcRef.current = false;
|
|
20496
|
+
readyRef.current = false;
|
|
20497
|
+
pendingRef.current = null;
|
|
20498
|
+
setSrc(null);
|
|
20350
20499
|
setKey((k2) => k2 + 1);
|
|
20351
|
-
|
|
20500
|
+
loadProject();
|
|
20352
20501
|
});
|
|
20353
20502
|
return unsub;
|
|
20354
|
-
}, []);
|
|
20355
|
-
|
|
20356
|
-
|
|
20357
|
-
|
|
20358
|
-
|
|
20359
|
-
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
|
|
20366
|
-
|
|
20367
|
-
|
|
20503
|
+
}, [loadProject]);
|
|
20504
|
+
reactExports.useEffect(() => {
|
|
20505
|
+
const wv = webviewRef.current;
|
|
20506
|
+
if (!wv) return;
|
|
20507
|
+
const syncNav = () => setNav({ back: wv.canGoBack?.() ?? false, forward: wv.canGoForward?.() ?? false });
|
|
20508
|
+
const onReady = () => {
|
|
20509
|
+
readyRef.current = true;
|
|
20510
|
+
if (pendingRef.current) {
|
|
20511
|
+
wv.loadURL(pendingRef.current);
|
|
20512
|
+
pendingRef.current = null;
|
|
20513
|
+
}
|
|
20514
|
+
syncNav();
|
|
20515
|
+
};
|
|
20516
|
+
const onNavigate = (e) => {
|
|
20517
|
+
const url = e.url;
|
|
20518
|
+
if (url) setAddress(displayUrl(url));
|
|
20519
|
+
syncNav();
|
|
20520
|
+
};
|
|
20521
|
+
const onStart = () => setLoading(true);
|
|
20522
|
+
const onStop = () => {
|
|
20523
|
+
setLoading(false);
|
|
20524
|
+
syncNav();
|
|
20525
|
+
};
|
|
20526
|
+
wv.addEventListener("dom-ready", onReady);
|
|
20527
|
+
wv.addEventListener("did-navigate", onNavigate);
|
|
20528
|
+
wv.addEventListener("did-navigate-in-page", onNavigate);
|
|
20529
|
+
wv.addEventListener("did-start-loading", onStart);
|
|
20530
|
+
wv.addEventListener("did-stop-loading", onStop);
|
|
20531
|
+
return () => {
|
|
20532
|
+
wv.removeEventListener("dom-ready", onReady);
|
|
20533
|
+
wv.removeEventListener("did-navigate", onNavigate);
|
|
20534
|
+
wv.removeEventListener("did-navigate-in-page", onNavigate);
|
|
20535
|
+
wv.removeEventListener("did-start-loading", onStart);
|
|
20536
|
+
wv.removeEventListener("did-stop-loading", onStop);
|
|
20537
|
+
};
|
|
20538
|
+
}, [src, key]);
|
|
20539
|
+
const onSubmit = (e) => {
|
|
20540
|
+
e.preventDefault();
|
|
20541
|
+
let target = address.trim();
|
|
20542
|
+
if (!target) return;
|
|
20543
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) target = "https://" + target;
|
|
20544
|
+
resolve2(target).then(load);
|
|
20545
|
+
};
|
|
20546
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ctlsurf-panel", children: [
|
|
20547
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ctlsurf-addressbar", children: [
|
|
20548
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20549
|
+
"button",
|
|
20550
|
+
{
|
|
20551
|
+
type: "button",
|
|
20552
|
+
className: "ctlsurf-nav-btn",
|
|
20553
|
+
title: "Back",
|
|
20554
|
+
disabled: !nav.back,
|
|
20555
|
+
onClick: () => webviewRef.current?.goBack?.(),
|
|
20556
|
+
children: "‹"
|
|
20557
|
+
}
|
|
20558
|
+
),
|
|
20559
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20560
|
+
"button",
|
|
20561
|
+
{
|
|
20562
|
+
type: "button",
|
|
20563
|
+
className: "ctlsurf-nav-btn",
|
|
20564
|
+
title: "Forward",
|
|
20565
|
+
disabled: !nav.forward,
|
|
20566
|
+
onClick: () => webviewRef.current?.goForward?.(),
|
|
20567
|
+
children: "›"
|
|
20568
|
+
}
|
|
20569
|
+
),
|
|
20570
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20571
|
+
"button",
|
|
20572
|
+
{
|
|
20573
|
+
type: "button",
|
|
20574
|
+
className: "ctlsurf-nav-btn",
|
|
20575
|
+
title: loading ? "Stop" : "Reload",
|
|
20576
|
+
onClick: () => loading ? webviewRef.current?.stop?.() : webviewRef.current?.reload?.(),
|
|
20577
|
+
children: loading ? "×" : "⟳"
|
|
20578
|
+
}
|
|
20579
|
+
),
|
|
20580
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("form", { className: "ctlsurf-address-form", onSubmit, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20581
|
+
"input",
|
|
20582
|
+
{
|
|
20583
|
+
className: "ctlsurf-address-input",
|
|
20584
|
+
value: address,
|
|
20585
|
+
spellCheck: false,
|
|
20586
|
+
placeholder: "Enter a URL",
|
|
20587
|
+
onChange: (e) => setAddress(e.target.value),
|
|
20588
|
+
onFocus: (e) => e.currentTarget.select()
|
|
20589
|
+
}
|
|
20590
|
+
) })
|
|
20591
|
+
] }),
|
|
20592
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ctlsurf-webview-host", children: src ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
20593
|
+
"webview",
|
|
20594
|
+
{
|
|
20595
|
+
ref: webviewRef,
|
|
20596
|
+
src,
|
|
20597
|
+
style: { width: "100%", height: "100%" },
|
|
20598
|
+
allowpopups: "true"
|
|
20599
|
+
},
|
|
20600
|
+
key
|
|
20601
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ctlsurf-webview-loading", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#565f89", fontSize: "13px" }, children: "Loading ctlsurf..." }) }) })
|
|
20602
|
+
] });
|
|
20368
20603
|
}
|
|
20369
20604
|
function _arrayLikeToArray(r, a) {
|
|
20370
20605
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -207916,7 +208151,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
|
|
|
207916
208151
|
modeConfigurationDefault$2
|
|
207917
208152
|
);
|
|
207918
208153
|
function getMode$3() {
|
|
207919
|
-
return __vitePreload(() => import("./cssMode-
|
|
208154
|
+
return __vitePreload(() => import("./cssMode-CAEObGNt.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
|
|
207920
208155
|
}
|
|
207921
208156
|
languages.onLanguage("less", () => {
|
|
207922
208157
|
getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
|
|
@@ -208021,7 +208256,7 @@ const razorLanguageService = registerHTMLLanguageService(
|
|
|
208021
208256
|
);
|
|
208022
208257
|
const razorDefaults = razorLanguageService.defaults;
|
|
208023
208258
|
function getMode$2() {
|
|
208024
|
-
return __vitePreload(() => import("./htmlMode-
|
|
208259
|
+
return __vitePreload(() => import("./htmlMode-B6fxDDPF.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
|
|
208025
208260
|
}
|
|
208026
208261
|
function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
|
|
208027
208262
|
const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
|
|
@@ -208105,7 +208340,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
|
|
|
208105
208340
|
);
|
|
208106
208341
|
const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
|
|
208107
208342
|
function getMode$1() {
|
|
208108
|
-
return __vitePreload(() => import("./jsonMode-
|
|
208343
|
+
return __vitePreload(() => import("./jsonMode-CqspkWCb.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
|
|
208109
208344
|
}
|
|
208110
208345
|
languages.register({
|
|
208111
208346
|
id: "json",
|
|
@@ -208351,7 +208586,7 @@ const getJavaScriptWorker = () => {
|
|
|
208351
208586
|
return getMode().then((mode) => mode.getJavaScriptWorker());
|
|
208352
208587
|
};
|
|
208353
208588
|
function getMode() {
|
|
208354
|
-
return __vitePreload(() => import("./tsMode-
|
|
208589
|
+
return __vitePreload(() => import("./tsMode-BWdOxk6v.js"), true ? [] : void 0, import.meta.url);
|
|
208355
208590
|
}
|
|
208356
208591
|
languages.onLanguage("typescript", () => {
|
|
208357
208592
|
return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
|
|
@@ -208546,49 +208781,49 @@ registerLanguage({
|
|
|
208546
208781
|
extensions: [".ftl", ".ftlh", ".ftlx"],
|
|
208547
208782
|
aliases: ["FreeMarker2", "Apache FreeMarker2"],
|
|
208548
208783
|
loader: () => {
|
|
208549
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208784
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
208550
208785
|
}
|
|
208551
208786
|
});
|
|
208552
208787
|
registerLanguage({
|
|
208553
208788
|
id: "freemarker2.tag-angle.interpolation-dollar",
|
|
208554
208789
|
aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
|
|
208555
208790
|
loader: () => {
|
|
208556
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208791
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
|
|
208557
208792
|
}
|
|
208558
208793
|
});
|
|
208559
208794
|
registerLanguage({
|
|
208560
208795
|
id: "freemarker2.tag-bracket.interpolation-dollar",
|
|
208561
208796
|
aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
|
|
208562
208797
|
loader: () => {
|
|
208563
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208798
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
|
|
208564
208799
|
}
|
|
208565
208800
|
});
|
|
208566
208801
|
registerLanguage({
|
|
208567
208802
|
id: "freemarker2.tag-angle.interpolation-bracket",
|
|
208568
208803
|
aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
|
|
208569
208804
|
loader: () => {
|
|
208570
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208805
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
|
|
208571
208806
|
}
|
|
208572
208807
|
});
|
|
208573
208808
|
registerLanguage({
|
|
208574
208809
|
id: "freemarker2.tag-bracket.interpolation-bracket",
|
|
208575
208810
|
aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
|
|
208576
208811
|
loader: () => {
|
|
208577
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208812
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
|
|
208578
208813
|
}
|
|
208579
208814
|
});
|
|
208580
208815
|
registerLanguage({
|
|
208581
208816
|
id: "freemarker2.tag-auto.interpolation-dollar",
|
|
208582
208817
|
aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
|
|
208583
208818
|
loader: () => {
|
|
208584
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208819
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
|
|
208585
208820
|
}
|
|
208586
208821
|
});
|
|
208587
208822
|
registerLanguage({
|
|
208588
208823
|
id: "freemarker2.tag-auto.interpolation-bracket",
|
|
208589
208824
|
aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
|
|
208590
208825
|
loader: () => {
|
|
208591
|
-
return __vitePreload(() => import("./freemarker2-
|
|
208826
|
+
return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
|
|
208592
208827
|
}
|
|
208593
208828
|
});
|
|
208594
208829
|
registerLanguage({
|
|
@@ -208609,7 +208844,7 @@ registerLanguage({
|
|
|
208609
208844
|
extensions: [".handlebars", ".hbs"],
|
|
208610
208845
|
aliases: ["Handlebars", "handlebars", "hbs"],
|
|
208611
208846
|
mimetypes: ["text/x-handlebars-template"],
|
|
208612
|
-
loader: () => __vitePreload(() => import("./handlebars-
|
|
208847
|
+
loader: () => __vitePreload(() => import("./handlebars-UYo_IYpi.js"), true ? [] : void 0, import.meta.url)
|
|
208613
208848
|
});
|
|
208614
208849
|
registerLanguage({
|
|
208615
208850
|
id: "hcl",
|
|
@@ -208622,7 +208857,7 @@ registerLanguage({
|
|
|
208622
208857
|
extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
|
|
208623
208858
|
aliases: ["HTML", "htm", "html", "xhtml"],
|
|
208624
208859
|
mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
|
|
208625
|
-
loader: () => __vitePreload(() => import("./html-
|
|
208860
|
+
loader: () => __vitePreload(() => import("./html-2dvYDi3g.js"), true ? [] : void 0, import.meta.url)
|
|
208626
208861
|
});
|
|
208627
208862
|
registerLanguage({
|
|
208628
208863
|
id: "ini",
|
|
@@ -208645,7 +208880,7 @@ registerLanguage({
|
|
|
208645
208880
|
filenames: ["jakefile"],
|
|
208646
208881
|
aliases: ["JavaScript", "javascript", "js"],
|
|
208647
208882
|
mimetypes: ["text/javascript"],
|
|
208648
|
-
loader: () => __vitePreload(() => import("./javascript-
|
|
208883
|
+
loader: () => __vitePreload(() => import("./javascript-8RyFDDdk.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
|
|
208649
208884
|
});
|
|
208650
208885
|
registerLanguage({
|
|
208651
208886
|
id: "julia",
|
|
@@ -208684,7 +208919,7 @@ registerLanguage({
|
|
|
208684
208919
|
extensions: [".liquid", ".html.liquid"],
|
|
208685
208920
|
aliases: ["Liquid", "liquid"],
|
|
208686
208921
|
mimetypes: ["application/liquid"],
|
|
208687
|
-
loader: () => __vitePreload(() => import("./liquid-
|
|
208922
|
+
loader: () => __vitePreload(() => import("./liquid-DaEnAsYY.js"), true ? [] : void 0, import.meta.url)
|
|
208688
208923
|
});
|
|
208689
208924
|
registerLanguage({
|
|
208690
208925
|
id: "m3",
|
|
@@ -208702,7 +208937,7 @@ registerLanguage({
|
|
|
208702
208937
|
id: "mdx",
|
|
208703
208938
|
extensions: [".mdx"],
|
|
208704
208939
|
aliases: ["MDX", "mdx"],
|
|
208705
|
-
loader: () => __vitePreload(() => import("./mdx-
|
|
208940
|
+
loader: () => __vitePreload(() => import("./mdx-Q-qSZsFg.js"), true ? [] : void 0, import.meta.url)
|
|
208706
208941
|
});
|
|
208707
208942
|
registerLanguage({
|
|
208708
208943
|
id: "mips",
|
|
@@ -208801,7 +209036,7 @@ registerLanguage({
|
|
|
208801
209036
|
extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
|
|
208802
209037
|
aliases: ["Python", "py"],
|
|
208803
209038
|
firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
|
|
208804
|
-
loader: () => __vitePreload(() => import("./python-
|
|
209039
|
+
loader: () => __vitePreload(() => import("./python-Sw0S0BJV.js"), true ? [] : void 0, import.meta.url)
|
|
208805
209040
|
});
|
|
208806
209041
|
registerLanguage({
|
|
208807
209042
|
id: "qsharp",
|
|
@@ -208820,7 +209055,7 @@ registerLanguage({
|
|
|
208820
209055
|
extensions: [".cshtml"],
|
|
208821
209056
|
aliases: ["Razor", "razor"],
|
|
208822
209057
|
mimetypes: ["text/x-cshtml"],
|
|
208823
|
-
loader: () => __vitePreload(() => import("./razor-
|
|
209058
|
+
loader: () => __vitePreload(() => import("./razor-C7zQmabz.js"), true ? [] : void 0, import.meta.url)
|
|
208824
209059
|
});
|
|
208825
209060
|
registerLanguage({
|
|
208826
209061
|
id: "redis",
|
|
@@ -208953,7 +209188,7 @@ registerLanguage({
|
|
|
208953
209188
|
aliases: ["TypeScript", "ts", "typescript"],
|
|
208954
209189
|
mimetypes: ["text/typescript"],
|
|
208955
209190
|
loader: () => {
|
|
208956
|
-
return __vitePreload(() => import("./typescript-
|
|
209191
|
+
return __vitePreload(() => import("./typescript-BrdIiKZf.js"), true ? [] : void 0, import.meta.url);
|
|
208957
209192
|
}
|
|
208958
209193
|
});
|
|
208959
209194
|
registerLanguage({
|
|
@@ -208998,14 +209233,14 @@ registerLanguage({
|
|
|
208998
209233
|
firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
|
|
208999
209234
|
aliases: ["XML", "xml"],
|
|
209000
209235
|
mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
|
|
209001
|
-
loader: () => __vitePreload(() => import("./xml-
|
|
209236
|
+
loader: () => __vitePreload(() => import("./xml-DbKLNPOq.js"), true ? [] : void 0, import.meta.url)
|
|
209002
209237
|
});
|
|
209003
209238
|
registerLanguage({
|
|
209004
209239
|
id: "yaml",
|
|
209005
209240
|
extensions: [".yaml", ".yml"],
|
|
209006
209241
|
aliases: ["YAML", "yaml", "YML", "yml"],
|
|
209007
209242
|
mimetypes: ["application/x-yaml", "text/x-yaml"],
|
|
209008
|
-
loader: () => __vitePreload(() => import("./yaml-
|
|
209243
|
+
loader: () => __vitePreload(() => import("./yaml-ehupJBsZ.js"), true ? [] : void 0, import.meta.url)
|
|
209009
209244
|
});
|
|
209010
209245
|
var __defProp = Object.defineProperty;
|
|
209011
209246
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -213297,6 +213532,7 @@ function SettingsDialog({ open, onClose }) {
|
|
|
213297
213532
|
const [trackTime, setTrackTime] = reactExports.useState(false);
|
|
213298
213533
|
const [idleTimeoutMin, setIdleTimeoutMin] = reactExports.useState(15);
|
|
213299
213534
|
const [saved, setSaved] = reactExports.useState(false);
|
|
213535
|
+
const [version, setVersion] = reactExports.useState("");
|
|
213300
213536
|
const loadProfiles = async () => {
|
|
213301
213537
|
const data = await window.worker.listProfiles();
|
|
213302
213538
|
setProfileList(data);
|
|
@@ -213307,6 +213543,8 @@ function SettingsDialog({ open, onClose }) {
|
|
|
213307
213543
|
loadProfiles();
|
|
213308
213544
|
setEditingId(null);
|
|
213309
213545
|
setSaved(false);
|
|
213546
|
+
window.worker.getVersion().then(setVersion).catch(() => {
|
|
213547
|
+
});
|
|
213310
213548
|
}
|
|
213311
213549
|
}, [open]);
|
|
213312
213550
|
const startEditing = (profile) => {
|
|
@@ -213477,7 +213715,16 @@ function SettingsDialog({ open, onClose }) {
|
|
|
213477
213715
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-secondary", onClick: onClose, children: "Close" })
|
|
213478
213716
|
] })
|
|
213479
213717
|
] })
|
|
213480
|
-
)
|
|
213718
|
+
),
|
|
213719
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-section settings-about", children: [
|
|
213720
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("h4", { children: "About" }),
|
|
213721
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "about-name", children: [
|
|
213722
|
+
"ctlsurf",
|
|
213723
|
+
version ? ` · v${version}` : ""
|
|
213724
|
+
] }),
|
|
213725
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "about-copyright", children: "© 2026 Phenx, Inc. All rights reserved." }),
|
|
213726
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "settings-hint", children: "Proprietary and confidential. No license to use, copy, modify, or distribute is granted except under a separate written agreement with Phenx, Inc." })
|
|
213727
|
+
] })
|
|
213481
213728
|
] }) });
|
|
213482
213729
|
}
|
|
213483
213730
|
const STATUS_OPTIONS = [
|
|
@@ -213518,6 +213765,47 @@ function TagIcon({ size: size2 = 16 }) {
|
|
|
213518
213765
|
}
|
|
213519
213766
|
);
|
|
213520
213767
|
}
|
|
213768
|
+
function SendIcon({ size: size2 = 15 }) {
|
|
213769
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
213770
|
+
"svg",
|
|
213771
|
+
{
|
|
213772
|
+
viewBox: "0 0 24 24",
|
|
213773
|
+
width: size2,
|
|
213774
|
+
height: size2,
|
|
213775
|
+
fill: "none",
|
|
213776
|
+
stroke: "currentColor",
|
|
213777
|
+
strokeWidth: "2",
|
|
213778
|
+
strokeLinecap: "round",
|
|
213779
|
+
strokeLinejoin: "round",
|
|
213780
|
+
"aria-hidden": "true",
|
|
213781
|
+
children: [
|
|
213782
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
213783
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
213784
|
+
]
|
|
213785
|
+
}
|
|
213786
|
+
);
|
|
213787
|
+
}
|
|
213788
|
+
function ticketPrompt(t, refs) {
|
|
213789
|
+
const lines = [
|
|
213790
|
+
"Please work on this ticket:",
|
|
213791
|
+
`Title: ${t.title || "Untitled"}`,
|
|
213792
|
+
`Priority: ${t.priority}`
|
|
213793
|
+
];
|
|
213794
|
+
const desc = t.description?.trim();
|
|
213795
|
+
if (desc) lines.push("", desc);
|
|
213796
|
+
lines.push(
|
|
213797
|
+
"",
|
|
213798
|
+
"ctlsurf references (use the ctlsurf MCP tools to read details or update status):",
|
|
213799
|
+
`- Ticket row ID: ${t.id}`
|
|
213800
|
+
);
|
|
213801
|
+
if (refs) {
|
|
213802
|
+
lines.push(
|
|
213803
|
+
`- Tickets datastore block ID: ${refs.blockId}`,
|
|
213804
|
+
`- Agent Datastore page ID: ${refs.pageId}`
|
|
213805
|
+
);
|
|
213806
|
+
}
|
|
213807
|
+
return lines.join("\n");
|
|
213808
|
+
}
|
|
213521
213809
|
function Segmented({ options, value, onChange }) {
|
|
213522
213810
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ticket-segmented", children: options.map((o) => {
|
|
213523
213811
|
const active = o.value === value;
|
|
@@ -213537,33 +213825,47 @@ function Segmented({ options, value, onChange }) {
|
|
|
213537
213825
|
);
|
|
213538
213826
|
}) });
|
|
213539
213827
|
}
|
|
213540
|
-
function TicketCard({ ticket, onClick }) {
|
|
213541
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
213542
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
213543
|
-
|
|
213544
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
213545
|
-
|
|
213546
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("
|
|
213547
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
213548
|
-
|
|
213549
|
-
|
|
213550
|
-
|
|
213551
|
-
|
|
213828
|
+
function TicketCard({ ticket, onClick, onSend }) {
|
|
213829
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ticket-card", children: [
|
|
213830
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ticket-card-open", onClick, title: "Edit ticket", children: [
|
|
213831
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-card-bar", style: { background: colorOf(STATUS_OPTIONS, ticket.status) } }),
|
|
213832
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ticket-card-main", children: [
|
|
213833
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-card-title", children: ticket.title || "Untitled" }),
|
|
213834
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ticket-card-meta", children: [
|
|
213835
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ticket-card-status", style: { color: colorOf(STATUS_OPTIONS, ticket.status) }, children: [
|
|
213836
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-seg-dot", style: { background: colorOf(STATUS_OPTIONS, ticket.status) } }),
|
|
213837
|
+
ticket.status
|
|
213838
|
+
] }),
|
|
213839
|
+
ticket.created && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-card-date", children: formatDate(ticket.created) })
|
|
213840
|
+
] })
|
|
213841
|
+
] }),
|
|
213842
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
213843
|
+
"span",
|
|
213844
|
+
{
|
|
213845
|
+
className: "ticket-card-pri",
|
|
213846
|
+
style: { color: colorOf(PRIORITY_OPTIONS, ticket.priority), borderColor: `${colorOf(PRIORITY_OPTIONS, ticket.priority)}66` },
|
|
213847
|
+
children: ticket.priority
|
|
213848
|
+
}
|
|
213849
|
+
)
|
|
213552
213850
|
] }),
|
|
213553
213851
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
213554
|
-
"
|
|
213852
|
+
"button",
|
|
213555
213853
|
{
|
|
213556
|
-
|
|
213557
|
-
|
|
213558
|
-
|
|
213854
|
+
type: "button",
|
|
213855
|
+
className: "ticket-card-send",
|
|
213856
|
+
onClick: onSend,
|
|
213857
|
+
title: "Send to coding terminal",
|
|
213858
|
+
"aria-label": "Send ticket to coding terminal",
|
|
213859
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(SendIcon, {})
|
|
213559
213860
|
}
|
|
213560
213861
|
)
|
|
213561
213862
|
] });
|
|
213562
213863
|
}
|
|
213563
|
-
function TicketPane() {
|
|
213864
|
+
function TicketPane({ onSendToTerminal }) {
|
|
213564
213865
|
const [project, setProject] = reactExports.useState(null);
|
|
213565
213866
|
const [view, setView] = reactExports.useState("list");
|
|
213566
213867
|
const [tickets, setTickets] = reactExports.useState([]);
|
|
213868
|
+
const [refs, setRefs] = reactExports.useState(null);
|
|
213567
213869
|
const [listLoading, setListLoading] = reactExports.useState(false);
|
|
213568
213870
|
const [listError, setListError] = reactExports.useState(null);
|
|
213569
213871
|
const [showDone, setShowDone] = reactExports.useState(false);
|
|
@@ -213578,13 +213880,17 @@ function TicketPane() {
|
|
|
213578
213880
|
setListError(null);
|
|
213579
213881
|
try {
|
|
213580
213882
|
const r = await window.worker.listTickets();
|
|
213581
|
-
if (r?.ok)
|
|
213582
|
-
|
|
213883
|
+
if (r?.ok) {
|
|
213884
|
+
setTickets(r.tickets || []);
|
|
213885
|
+
setRefs(r.blockId && r.pageId ? { blockId: r.blockId, pageId: r.pageId } : null);
|
|
213886
|
+
} else {
|
|
213583
213887
|
setTickets([]);
|
|
213888
|
+
setRefs(null);
|
|
213584
213889
|
setListError(r?.error || "Failed to load tickets");
|
|
213585
213890
|
}
|
|
213586
213891
|
} catch (err) {
|
|
213587
213892
|
setTickets([]);
|
|
213893
|
+
setRefs(null);
|
|
213588
213894
|
setListError(err?.message || "Failed to load tickets");
|
|
213589
213895
|
} finally {
|
|
213590
213896
|
setListLoading(false);
|
|
@@ -213684,7 +213990,7 @@ function TicketPane() {
|
|
|
213684
213990
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-empty-title", children: "No tickets yet" }),
|
|
213685
213991
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-empty-hint", children: "Log the first one for this project." })
|
|
213686
213992
|
] }),
|
|
213687
|
-
!listLoading && activeTickets.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TicketCard, { ticket: t, onClick: () => openEditForm(t) }, t.id)),
|
|
213993
|
+
!listLoading && activeTickets.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TicketCard, { ticket: t, onClick: () => openEditForm(t), onSend: () => onSendToTerminal(ticketPrompt(t, refs)) }, t.id)),
|
|
213688
213994
|
!listLoading && activeTickets.length === 0 && doneTickets.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ticket-list-msg", children: "Nothing open — all caught up." }),
|
|
213689
213995
|
!listLoading && doneTickets.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
213690
213996
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
@@ -213701,7 +214007,7 @@ function TicketPane() {
|
|
|
213701
214007
|
]
|
|
213702
214008
|
}
|
|
213703
214009
|
),
|
|
213704
|
-
showDone && doneTickets.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TicketCard, { ticket: t, onClick: () => openEditForm(t) }, t.id))
|
|
214010
|
+
showDone && doneTickets.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(TicketCard, { ticket: t, onClick: () => openEditForm(t), onSend: () => onSendToTerminal(ticketPrompt(t, refs)) }, t.id))
|
|
213705
214011
|
] })
|
|
213706
214012
|
] }),
|
|
213707
214013
|
tickets.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ticket-drawer-footer", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ticket-drawer-hint", children: doneTickets.length > 0 ? `${activeTickets.length} open · ${doneTickets.length} done` : `${tickets.length} ticket${tickets.length === 1 ? "" : "s"}` }) })
|
|
@@ -213897,6 +214203,13 @@ function App() {
|
|
|
213897
214203
|
focusTerminal(activeTabId);
|
|
213898
214204
|
setTimeout(() => window.worker.writePty(activeTabId, "\r"), 80);
|
|
213899
214205
|
}, [activeTabId]);
|
|
214206
|
+
const handleSendToTerminal = reactExports.useCallback((text2) => {
|
|
214207
|
+
const trimmed = text2.trim();
|
|
214208
|
+
if (!trimmed) return;
|
|
214209
|
+
window.worker.writePty(activeTabId, trimmed);
|
|
214210
|
+
focusTerminal(activeTabId);
|
|
214211
|
+
setTimeout(() => window.worker.writePty(activeTabId, "\r"), 120);
|
|
214212
|
+
}, [activeTabId]);
|
|
213900
214213
|
const cwdRef = reactExports.useRef(null);
|
|
213901
214214
|
const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
|
|
213902
214215
|
setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "active" } : t));
|
|
@@ -214109,7 +214422,7 @@ function App() {
|
|
|
214109
214422
|
] })
|
|
214110
214423
|
},
|
|
214111
214424
|
{ id: "ctlsurf", label: "ctlsurf", content: /* @__PURE__ */ jsxRuntimeExports.jsx(CtlsurfPanel, { navigate: ctlsurfNav }) },
|
|
214112
|
-
{ id: "tickets", label: "Tickets", content: /* @__PURE__ */ jsxRuntimeExports.jsx(TicketPane, {}) }
|
|
214425
|
+
{ id: "tickets", label: "Tickets", content: /* @__PURE__ */ jsxRuntimeExports.jsx(TicketPane, { onSendToTerminal: handleSendToTerminal }) }
|
|
214113
214426
|
];
|
|
214114
214427
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "app", children: [
|
|
214115
214428
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "titlebar", children: [
|