omnius 1.0.435 → 1.0.436
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/dist/index.js +468 -96
- package/dist/scripts/live-whisper.py +101 -46
- package/dist/scripts/transcribe-file.py +18 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -906,9 +906,9 @@ var init_model_broker = __esm({
|
|
|
906
906
|
// VRAM total/free comes from system-metrics; broker computes its own snapshot
|
|
907
907
|
]);
|
|
908
908
|
const snapshot = this.buildSnapshot();
|
|
909
|
+
await this.checkPressure(snapshot);
|
|
909
910
|
this._lastSnapshot = snapshot;
|
|
910
911
|
this.emit("snapshot", snapshot);
|
|
911
|
-
this.checkPressure(snapshot);
|
|
912
912
|
return snapshot;
|
|
913
913
|
}
|
|
914
914
|
/** Best-known current snapshot. */
|
|
@@ -42867,6 +42867,8 @@ import { join as join41, basename as basename7, extname as extname4, resolve as
|
|
|
42867
42867
|
import { homedir as homedir11, tmpdir as tmpdir6 } from "node:os";
|
|
42868
42868
|
function transcriptionPythonEnv(extra = {}) {
|
|
42869
42869
|
const env2 = { ...process.env, ...extra };
|
|
42870
|
+
if (!env2["OMNIUS_ASR_DEVICE"])
|
|
42871
|
+
env2["OMNIUS_ASR_DEVICE"] = "cuda";
|
|
42870
42872
|
applyMediaCudaDeviceFilterToEnv(env2, "asr");
|
|
42871
42873
|
return env2;
|
|
42872
42874
|
}
|
|
@@ -43379,17 +43381,18 @@ audio_file = ${JSON.stringify(filePath)}
|
|
|
43379
43381
|
model_name = ${JSON.stringify(model)}
|
|
43380
43382
|
try:
|
|
43381
43383
|
import whisper
|
|
43382
|
-
device = os.environ.get("OMNIUS_ASR_DEVICE", "
|
|
43384
|
+
device = os.environ.get("OMNIUS_ASR_DEVICE", "cuda").strip().lower() or "cuda"
|
|
43385
|
+
allow_cpu = os.environ.get("OMNIUS_ASR_ALLOW_CPU", "").strip().lower() in ("1", "true", "yes", "on")
|
|
43383
43386
|
if device == "auto":
|
|
43384
|
-
device = "
|
|
43385
|
-
|
|
43386
|
-
|
|
43387
|
-
|
|
43388
|
-
|
|
43389
|
-
|
|
43390
|
-
|
|
43387
|
+
device = "cuda"
|
|
43388
|
+
if device.startswith("cuda"):
|
|
43389
|
+
import torch
|
|
43390
|
+
if not torch.cuda.is_available() or torch.cuda.device_count() <= 0:
|
|
43391
|
+
raise RuntimeError(f"CUDA-only ASR requested but torch cannot use CUDA (torch.version.cuda={getattr(torch.version, 'cuda', None)}, device_count={torch.cuda.device_count()}). Install a CUDA-enabled PyTorch build for this device, or set OMNIUS_ASR_ALLOW_CPU=1 only for emergency fallback.")
|
|
43392
|
+
elif device == "cpu" and not allow_cpu:
|
|
43393
|
+
raise RuntimeError("CPU ASR refused. Set OMNIUS_ASR_ALLOW_CPU=1 only for explicit emergency fallback.")
|
|
43391
43394
|
m = whisper.load_model(model_name, device=device)
|
|
43392
|
-
result = m.transcribe(audio_file, fp16=(
|
|
43395
|
+
result = m.transcribe(audio_file, fp16=device.startswith("cuda"), condition_on_previous_text=False)
|
|
43393
43396
|
segments = []
|
|
43394
43397
|
for seg in result.get("segments", []) or []:
|
|
43395
43398
|
segments.append({"start": float(seg.get("start", 0)), "end": float(seg.get("end", seg.get("start", 0))), "text": str(seg.get("text", "")).strip()})
|
|
@@ -603816,6 +603819,10 @@ function asrConsensusEnabled() {
|
|
|
603816
603819
|
const consensusEnv = String(process.env["OMNIUS_ASR_CONSENSUS"] ?? "").trim().toLowerCase();
|
|
603817
603820
|
return !(consensusEnv === "0" || consensusEnv === "off" || consensusEnv === "false");
|
|
603818
603821
|
}
|
|
603822
|
+
function liveAsrUseTranscribeCli() {
|
|
603823
|
+
const value2 = String(process.env["OMNIUS_ASR_USE_TRANSCRIBE_CLI"] ?? "").trim().toLowerCase();
|
|
603824
|
+
return value2 === "1" || value2 === "true" || value2 === "yes" || value2 === "on";
|
|
603825
|
+
}
|
|
603819
603826
|
async function ensureVenvForTranscribeCli() {
|
|
603820
603827
|
const bin = process.platform === "win32" ? "Scripts" : "bin";
|
|
603821
603828
|
const exe = process.platform === "win32" ? "python.exe" : "python3";
|
|
@@ -603930,6 +603937,7 @@ var init_listen = __esm({
|
|
|
603930
603937
|
"use strict";
|
|
603931
603938
|
init_typed_node_events();
|
|
603932
603939
|
init_async_process();
|
|
603940
|
+
init_dist5();
|
|
603933
603941
|
MANAGED_TRANSCRIBE_CLI_DIR2 = join126(homedir40(), ".omnius", "runtimes", "asr", "transcribe-cli-node");
|
|
603934
603942
|
AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
603935
603943
|
".mp3",
|
|
@@ -603963,6 +603971,8 @@ var init_listen = __esm({
|
|
|
603963
603971
|
doa: null,
|
|
603964
603972
|
lastTranscript: "",
|
|
603965
603973
|
lastTranscriptAt: 0,
|
|
603974
|
+
lastPartialTranscript: "",
|
|
603975
|
+
lastPartialTranscriptAt: 0,
|
|
603966
603976
|
lastStatus: "",
|
|
603967
603977
|
updatedAt: 0
|
|
603968
603978
|
};
|
|
@@ -603977,6 +603987,7 @@ var init_listen = __esm({
|
|
|
603977
603987
|
scriptPath;
|
|
603978
603988
|
process = null;
|
|
603979
603989
|
_ready = false;
|
|
603990
|
+
registeredBrokerName = null;
|
|
603980
603991
|
get ready() {
|
|
603981
603992
|
return this._ready;
|
|
603982
603993
|
}
|
|
@@ -604051,6 +604062,7 @@ var init_listen = __esm({
|
|
|
604051
604062
|
this._ready = true;
|
|
604052
604063
|
clearTimeout(timeout2);
|
|
604053
604064
|
updateListenLiveState({ phase: "ready", lastStatus: "whisper model loaded" });
|
|
604065
|
+
this.registerBrokerModel(evt);
|
|
604054
604066
|
this.emit("ready");
|
|
604055
604067
|
resolve76();
|
|
604056
604068
|
break;
|
|
@@ -604097,6 +604109,7 @@ var init_listen = __esm({
|
|
|
604097
604109
|
reject(err);
|
|
604098
604110
|
});
|
|
604099
604111
|
onChildClose(this.process, (code8) => {
|
|
604112
|
+
this.unregisterBrokerModel();
|
|
604100
604113
|
if (!this._ready) {
|
|
604101
604114
|
clearTimeout(timeout2);
|
|
604102
604115
|
reject(
|
|
@@ -604114,6 +604127,7 @@ var init_listen = __esm({
|
|
|
604114
604127
|
}
|
|
604115
604128
|
/** Stop the worker. */
|
|
604116
604129
|
stop() {
|
|
604130
|
+
this.unregisterBrokerModel();
|
|
604117
604131
|
if (this.process) {
|
|
604118
604132
|
try {
|
|
604119
604133
|
this.process.stdin?.end();
|
|
@@ -604124,6 +604138,40 @@ var init_listen = __esm({
|
|
|
604124
604138
|
}
|
|
604125
604139
|
this._ready = false;
|
|
604126
604140
|
}
|
|
604141
|
+
registerBrokerModel(evt) {
|
|
604142
|
+
const device = String(evt["device"] ?? "");
|
|
604143
|
+
const cuda = evt["cuda"] === true || device.startsWith("cuda");
|
|
604144
|
+
const gpuIndexMatch = device.match(/^cuda:(\d+)/);
|
|
604145
|
+
const gpuIndex = gpuIndexMatch ? Number(gpuIndexMatch[1]) : null;
|
|
604146
|
+
const consensus = typeof evt["consensusModel"] === "string" && String(evt["consensusModel"]).trim() ? String(evt["consensusModel"]).trim() : "";
|
|
604147
|
+
const name10 = `whisper:${this.model}${consensus ? `+${consensus}` : ""}`;
|
|
604148
|
+
const vramMB = Math.max(0, Math.round(Number(evt["vramUsedMB"] ?? 0)));
|
|
604149
|
+
try {
|
|
604150
|
+
getModelBroker().registerLoaded({
|
|
604151
|
+
key: `whisper-cli:${name10}`,
|
|
604152
|
+
name: name10,
|
|
604153
|
+
domain: "asr",
|
|
604154
|
+
host: "whisper-cli",
|
|
604155
|
+
owner: "live-asr",
|
|
604156
|
+
vramMB: cuda ? vramMB : 0,
|
|
604157
|
+
ramMB: cuda ? 0 : vramMB,
|
|
604158
|
+
priority: 88,
|
|
604159
|
+
gpuIndex,
|
|
604160
|
+
gpuUuid: null
|
|
604161
|
+
});
|
|
604162
|
+
this.registeredBrokerName = name10;
|
|
604163
|
+
} catch {
|
|
604164
|
+
this.registeredBrokerName = null;
|
|
604165
|
+
}
|
|
604166
|
+
}
|
|
604167
|
+
unregisterBrokerModel() {
|
|
604168
|
+
if (!this.registeredBrokerName) return;
|
|
604169
|
+
try {
|
|
604170
|
+
getModelBroker().unregisterLoaded("whisper-cli", this.registeredBrokerName, "live-asr stopped");
|
|
604171
|
+
} catch {
|
|
604172
|
+
}
|
|
604173
|
+
this.registeredBrokerName = null;
|
|
604174
|
+
}
|
|
604127
604175
|
};
|
|
604128
604176
|
ListenEngine = class extends EventEmitter6 {
|
|
604129
604177
|
config;
|
|
@@ -604140,6 +604188,7 @@ var init_listen = __esm({
|
|
|
604140
604188
|
blinkTimer = null;
|
|
604141
604189
|
blinkState = false;
|
|
604142
604190
|
transcribeCliAvailable = null;
|
|
604191
|
+
owners = /* @__PURE__ */ new Set();
|
|
604143
604192
|
constructor(config) {
|
|
604144
604193
|
super();
|
|
604145
604194
|
this.config = {
|
|
@@ -604168,6 +604217,29 @@ var init_listen = __esm({
|
|
|
604168
604217
|
get pendingTranscript() {
|
|
604169
604218
|
return this.pendingText;
|
|
604170
604219
|
}
|
|
604220
|
+
hasOwner(owner) {
|
|
604221
|
+
return this.owners.has(owner);
|
|
604222
|
+
}
|
|
604223
|
+
ownerCount() {
|
|
604224
|
+
return this.owners.size;
|
|
604225
|
+
}
|
|
604226
|
+
async acquire(owner) {
|
|
604227
|
+
const clean5 = owner.trim() || "shared";
|
|
604228
|
+
this.owners.add(clean5);
|
|
604229
|
+
if (this.active) return `Live ASR already active (${[...this.owners].join(", ")}).`;
|
|
604230
|
+
const result = await this.start();
|
|
604231
|
+
if (!this.active) this.owners.delete(clean5);
|
|
604232
|
+
return result;
|
|
604233
|
+
}
|
|
604234
|
+
async release(owner) {
|
|
604235
|
+
const clean5 = owner.trim() || "shared";
|
|
604236
|
+
this.owners.delete(clean5);
|
|
604237
|
+
if (this.owners.size > 0) {
|
|
604238
|
+
return `Live ASR remains active (${[...this.owners].join(", ")}).`;
|
|
604239
|
+
}
|
|
604240
|
+
if (!this.active) return "Live ASR is not active.";
|
|
604241
|
+
return this.stop();
|
|
604242
|
+
}
|
|
604171
604243
|
setModel(model) {
|
|
604172
604244
|
this.config.model = model;
|
|
604173
604245
|
}
|
|
@@ -604318,19 +604390,21 @@ var init_listen = __esm({
|
|
|
604318
604390
|
updateListenLiveState({ phase: "error", lastStatus: "no mic capture tool (arecord/sox/ffmpeg)" });
|
|
604319
604391
|
return "No microphone capture tool found. Install arecord (Linux), sox (macOS), or ffmpeg.";
|
|
604320
604392
|
}
|
|
604321
|
-
const
|
|
604393
|
+
const consensusEnabled = asrConsensusEnabled();
|
|
604394
|
+
const useTranscribeCli = liveAsrUseTranscribeCli() && !consensusEnabled;
|
|
604395
|
+
const preferWhisperFallback = !useTranscribeCli;
|
|
604322
604396
|
if (preferWhisperFallback) {
|
|
604323
604397
|
this.emit(
|
|
604324
604398
|
"info",
|
|
604325
|
-
"ASR consensus enabled — using live-whisper.py
|
|
604399
|
+
consensusEnabled ? "ASR consensus enabled — using live-whisper.py with CUDA-only Whisper guard." : "Using live-whisper.py with CUDA-only Whisper guard."
|
|
604326
604400
|
);
|
|
604327
604401
|
updateListenLiveState({
|
|
604328
604402
|
backend: "openai-whisper",
|
|
604329
|
-
lastStatus: "starting consensus Whisper backend..."
|
|
604403
|
+
lastStatus: consensusEnabled ? "starting consensus Whisper backend..." : "starting CUDA Whisper backend..."
|
|
604330
604404
|
});
|
|
604331
604405
|
}
|
|
604332
|
-
let tc =
|
|
604333
|
-
if (
|
|
604406
|
+
let tc = useTranscribeCli ? await this.loadTranscribeCli() : null;
|
|
604407
|
+
if (useTranscribeCli && !tc) {
|
|
604334
604408
|
if (_bgInstallPromise) {
|
|
604335
604409
|
this.emit("info", "Waiting for transcribe-cli install...");
|
|
604336
604410
|
await _bgInstallPromise;
|
|
@@ -604413,7 +604487,7 @@ var init_listen = __esm({
|
|
|
604413
604487
|
if (!evt.text.trim()) return;
|
|
604414
604488
|
this.lastTranscriptTime = Date.now();
|
|
604415
604489
|
this.pendingText = evt.text.trim();
|
|
604416
|
-
updateListenLiveState({ lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime });
|
|
604490
|
+
updateListenLiveState(evt.isFinal ? { lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime } : { lastPartialTranscript: this.pendingText, lastPartialTranscriptAt: this.lastTranscriptTime });
|
|
604417
604491
|
this.emit("transcript", this.pendingText, evt.isFinal);
|
|
604418
604492
|
if (this.config.mode === "auto") this.resetSilenceTimer();
|
|
604419
604493
|
}
|
|
@@ -604471,7 +604545,7 @@ var init_listen = __esm({
|
|
|
604471
604545
|
if (!evt.text.trim()) return;
|
|
604472
604546
|
this.lastTranscriptTime = Date.now();
|
|
604473
604547
|
this.pendingText = evt.text.trim();
|
|
604474
|
-
updateListenLiveState({ lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime });
|
|
604548
|
+
updateListenLiveState(evt.isFinal ? { lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime } : { lastPartialTranscript: this.pendingText, lastPartialTranscriptAt: this.lastTranscriptTime });
|
|
604475
604549
|
this.emit("transcript", this.pendingText, evt.isFinal, { consensus: evt.consensus, doa: evt.doa });
|
|
604476
604550
|
if (this.config.mode === "auto") this.resetSilenceTimer();
|
|
604477
604551
|
});
|
|
@@ -604514,6 +604588,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
604514
604588
|
async stop() {
|
|
604515
604589
|
if (!this.active) return "Not listening.";
|
|
604516
604590
|
this.active = false;
|
|
604591
|
+
this.owners.clear();
|
|
604517
604592
|
this.blinkState = false;
|
|
604518
604593
|
updateListenLiveState({ phase: "idle", speechActive: false, micLevelDb: null, lastStatus: "stopped" });
|
|
604519
604594
|
if (this.blinkTimer) {
|
|
@@ -604609,13 +604684,14 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
604609
604684
|
*/
|
|
604610
604685
|
async createCallTranscriber() {
|
|
604611
604686
|
const requireConsensus = asrConsensusEnabled();
|
|
604612
|
-
|
|
604613
|
-
|
|
604687
|
+
const useTranscribeCli = liveAsrUseTranscribeCli() && !requireConsensus;
|
|
604688
|
+
let tc = useTranscribeCli ? await this.loadTranscribeCli() : null;
|
|
604689
|
+
if (useTranscribeCli && !tc && _bgInstallPromise) {
|
|
604614
604690
|
await _bgInstallPromise;
|
|
604615
604691
|
this.transcribeCliAvailable = null;
|
|
604616
604692
|
tc = await this.loadTranscribeCli();
|
|
604617
604693
|
}
|
|
604618
|
-
if (
|
|
604694
|
+
if (useTranscribeCli && !tc) {
|
|
604619
604695
|
try {
|
|
604620
604696
|
await ensureManagedTranscribeCliNode();
|
|
604621
604697
|
this.transcribeCliAvailable = null;
|
|
@@ -604623,7 +604699,7 @@ transcribe-cli error: ${transcribeCliError}` : "";
|
|
|
604623
604699
|
} catch {
|
|
604624
604700
|
}
|
|
604625
604701
|
}
|
|
604626
|
-
if (
|
|
604702
|
+
if (useTranscribeCli && tc?.TranscribeLive && await ensureVenvForTranscribeCli()) {
|
|
604627
604703
|
try {
|
|
604628
604704
|
const transcriber = new tc.TranscribeLive({
|
|
604629
604705
|
model: this.config.model,
|
|
@@ -605205,6 +605281,135 @@ function formatLiveResourceBudgetLines(width = 100) {
|
|
|
605205
605281
|
`└─ leases: ${truncateAnsi(leaseText, inner - 12)}`
|
|
605206
605282
|
];
|
|
605207
605283
|
}
|
|
605284
|
+
async function refreshLiveResourceModelSnapshot(minIntervalMs = 2500) {
|
|
605285
|
+
const now2 = Date.now();
|
|
605286
|
+
if (now2 - lastModelSnapshotRefreshAt < minIntervalMs) return;
|
|
605287
|
+
lastModelSnapshotRefreshAt = now2;
|
|
605288
|
+
await getModelBroker().pollOnce();
|
|
605289
|
+
}
|
|
605290
|
+
function formatLiveMenuResourceSummary() {
|
|
605291
|
+
const snap = getLiveResourceArbiter().snapshot();
|
|
605292
|
+
const broker = getModelBroker().snapshot();
|
|
605293
|
+
const totalGB = snap.totalMemMB / 1024;
|
|
605294
|
+
const usedGB = snap.usedMemMB / 1024;
|
|
605295
|
+
const vram = broker.vramMB;
|
|
605296
|
+
const vramText = vram ? `GPU/UMA ${mbToGb(vram.used)}/${mbToGb(vram.total)}GB` : "GPU/UMA unknown";
|
|
605297
|
+
const cuda = broker.vramPerDevice.length > 0 ? "CUDA on" : "CUDA off/unknown";
|
|
605298
|
+
return `RAM ${usedGB.toFixed(1)}/${totalGB.toFixed(1)}GB · ${vramText} · ${cuda} · models ${broker.loaded.length} loaded/${broker.inflight.length} loading`;
|
|
605299
|
+
}
|
|
605300
|
+
function formatLiveModelRuntimeLines(width = 100, opts = {}) {
|
|
605301
|
+
const broker = getModelBroker().snapshot();
|
|
605302
|
+
const inner = Math.max(24, width - 2);
|
|
605303
|
+
const loaded = broker.loaded ?? [];
|
|
605304
|
+
const inflight = broker.inflight ?? [];
|
|
605305
|
+
const modelsShown = Math.max(0, opts.maxModels ?? 4);
|
|
605306
|
+
const cudaAvailable = broker.vramPerDevice.length > 0;
|
|
605307
|
+
const gpuLine = formatGpuRuntimeLine(broker);
|
|
605308
|
+
const subsystemLine = formatSubsystemMemoryLine(loaded, inner);
|
|
605309
|
+
const unattributedLine = formatUnattributedMemoryLine(loaded, broker);
|
|
605310
|
+
const pollAge = broker.lastPollAt > 0 ? Math.max(0, Math.round((Date.now() - broker.lastPollAt) / 1e3)) : null;
|
|
605311
|
+
const lines = [
|
|
605312
|
+
colorText(" model runtime", 81),
|
|
605313
|
+
gpuLine,
|
|
605314
|
+
subsystemLine,
|
|
605315
|
+
unattributedLine,
|
|
605316
|
+
`├─ state: loaded=${loaded.length} loading=${inflight.length} slots=${broker.slots.inUse}/${broker.slots.capacity} queue=${broker.slots.queueDepth}/${broker.slots.queueCapacity}${pollAge !== null ? ` poll=${pollAge}s` : ""}`
|
|
605317
|
+
];
|
|
605318
|
+
if (loaded.length === 0 && inflight.length === 0) {
|
|
605319
|
+
lines.push(`└─ models: ${colorText("none tracked yet", 244)}${cudaAvailable ? "" : colorText(" (CUDA state unknown until broker/JTOP poll succeeds)", 244)}`);
|
|
605320
|
+
return lines;
|
|
605321
|
+
}
|
|
605322
|
+
const modelLines = loaded.sort((a2, b) => b.vramMB + b.ramMB - (a2.vramMB + a2.ramMB)).slice(0, modelsShown).map((model, index, shown) => {
|
|
605323
|
+
const last2 = index === shown.length - 1 && inflight.length === 0 && loaded.length <= modelsShown;
|
|
605324
|
+
return `${last2 ? "└" : "├"}─ ${formatLoadedModelRuntime(model, broker)}`;
|
|
605325
|
+
});
|
|
605326
|
+
lines.push(...modelLines.map((line) => truncateAnsi(line, inner)));
|
|
605327
|
+
if (loaded.length > modelsShown) {
|
|
605328
|
+
lines.push(`├─ ${colorText(`+${loaded.length - modelsShown} more loaded model(s)`, 244)}`);
|
|
605329
|
+
}
|
|
605330
|
+
if (inflight.length > 0) {
|
|
605331
|
+
const loading = inflight.slice(0, 3).map((entry) => {
|
|
605332
|
+
const age = Math.max(0, Math.round((Date.now() - entry.startedMs) / 1e3));
|
|
605333
|
+
return `${entry.key} owner=${entry.owner} ${age}s`;
|
|
605334
|
+
}).join("; ");
|
|
605335
|
+
lines.push(`└─ loading: ${truncateAnsi(loading, inner - 12)}`);
|
|
605336
|
+
}
|
|
605337
|
+
return lines;
|
|
605338
|
+
}
|
|
605339
|
+
function formatLiveResourceModelReport(width = 100) {
|
|
605340
|
+
return [
|
|
605341
|
+
...formatLiveResourceBudgetLines(width),
|
|
605342
|
+
...formatLiveModelRuntimeLines(width, { maxModels: 20 })
|
|
605343
|
+
].join("\n");
|
|
605344
|
+
}
|
|
605345
|
+
function formatGpuRuntimeLine(broker) {
|
|
605346
|
+
const devices = broker.vramPerDevice ?? [];
|
|
605347
|
+
if (devices.length === 0) {
|
|
605348
|
+
return `├─ accelerator: ${colorText("CUDA off/unknown", 208)} vram=unavailable`;
|
|
605349
|
+
}
|
|
605350
|
+
const parts = devices.map((device) => {
|
|
605351
|
+
const label = device.uuid === "jetson-uma" ? "jetson-uma" : `gpu${device.index}`;
|
|
605352
|
+
const pct = device.total > 0 ? device.used / device.total : 0;
|
|
605353
|
+
return `${label} ${pressureColor(pct, 0.78)(`${mbToGb(device.used)}/${mbToGb(device.total)}GB`)}`;
|
|
605354
|
+
});
|
|
605355
|
+
return `├─ accelerator: ${colorText("CUDA on", 82)} ${parts.join(" ")}`;
|
|
605356
|
+
}
|
|
605357
|
+
function formatSubsystemMemoryLine(loaded, width) {
|
|
605358
|
+
const totals = /* @__PURE__ */ new Map();
|
|
605359
|
+
for (const model of loaded) {
|
|
605360
|
+
const key = subsystemForModel(model);
|
|
605361
|
+
const current = totals.get(key) ?? { vram: 0, ram: 0, count: 0 };
|
|
605362
|
+
current.vram += Math.max(0, Number(model.vramMB ?? 0));
|
|
605363
|
+
current.ram += Math.max(0, Number(model.ramMB ?? 0));
|
|
605364
|
+
current.count += 1;
|
|
605365
|
+
totals.set(key, current);
|
|
605366
|
+
}
|
|
605367
|
+
if (totals.size === 0) return `├─ subsystems(tracked): ${colorText("no tracked model memory", 244)}`;
|
|
605368
|
+
const preferred = ["llm", "vision", "asr", "embedding", "image", "video", "audio", "other"];
|
|
605369
|
+
const parts = [...totals.entries()].sort((a2, b) => {
|
|
605370
|
+
const ai = preferred.indexOf(a2[0]);
|
|
605371
|
+
const bi = preferred.indexOf(b[0]);
|
|
605372
|
+
return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
|
|
605373
|
+
}).map(([name10, total]) => `${name10}=${mbToGb(total.vram)}Gv/${mbToGb(total.ram)}Gr`);
|
|
605374
|
+
return `├─ subsystems(tracked): ${truncateAnsi(parts.join(" "), width - 25)}`;
|
|
605375
|
+
}
|
|
605376
|
+
function formatUnattributedMemoryLine(loaded, broker) {
|
|
605377
|
+
const resource = getLiveResourceArbiter().snapshot();
|
|
605378
|
+
const trackedRam = loaded.reduce((sum, model) => sum + Math.max(0, Number(model.ramMB ?? 0)), 0);
|
|
605379
|
+
const trackedVram = loaded.reduce((sum, model) => sum + Math.max(0, Number(model.vramMB ?? 0)), 0);
|
|
605380
|
+
const ramGap = Math.max(0, resource.usedMemMB - trackedRam);
|
|
605381
|
+
const vramGap = broker.vramMB ? Math.max(0, broker.vramMB.used - trackedVram) : null;
|
|
605382
|
+
return `├─ unattributed/system: ram=${mbToGb(ramGap)}G${vramGap !== null ? ` vram/uma=${mbToGb(vramGap)}G` : ""} ${colorText("(OS, camera ffmpeg, CV libs, CUDA overhead, untracked subprocesses)", 244)}`;
|
|
605383
|
+
}
|
|
605384
|
+
function formatLoadedModelRuntime(model, broker) {
|
|
605385
|
+
const slots = broker.slots.byModel[model.name] ?? broker.slots.byModel[model.key] ?? null;
|
|
605386
|
+
const active = slots && slots.inUse > 0;
|
|
605387
|
+
const state = active ? colorText(`running x${slots.inUse}`, 82) : colorText("loaded idle", 244);
|
|
605388
|
+
const cuda = cudaStateForModel(model, broker.vramPerDevice.length > 0);
|
|
605389
|
+
const idle = Math.max(0, Math.round((Date.now() - model.lastUsedAt) / 1e3));
|
|
605390
|
+
const tps = slots && slots.samples > 0 ? ` ${slots.tokensPerSec.toFixed(1)}tok/s` : "";
|
|
605391
|
+
const ctx3 = model.numCtx ? ` ctx=${model.numCtx}` : "";
|
|
605392
|
+
return `${subsystemForModel(model)} ${model.host}:${model.name} ${state} ${cuda} vram=${model.vramMB}MB ram=${model.ramMB}MB idle=${idle}s${tps}${ctx3}`;
|
|
605393
|
+
}
|
|
605394
|
+
function cudaStateForModel(model, cudaAvailable) {
|
|
605395
|
+
if (model.gpuIndex !== null && model.gpuIndex !== void 0) return colorText(`cuda:on gpu${model.gpuIndex}`, 82);
|
|
605396
|
+
if (model.vramMB > 0) return colorText(cudaAvailable ? "cuda:on" : "gpu:on", 82);
|
|
605397
|
+
if (model.host === "external") return colorText("cuda:external", 244);
|
|
605398
|
+
return colorText(cudaAvailable ? "cuda:off/cpu" : "cuda:off", 208);
|
|
605399
|
+
}
|
|
605400
|
+
function subsystemForModel(model) {
|
|
605401
|
+
if (model.domain === "chat" || model.domain === "subagent") return "llm";
|
|
605402
|
+
if (model.domain === "vision" || model.host.includes("moondream")) return "vision";
|
|
605403
|
+
if (model.domain === "asr" || model.host.includes("whisper")) return "asr";
|
|
605404
|
+
if (model.domain === "embedding") return "embedding";
|
|
605405
|
+
if (model.domain === "image-gen") return "image";
|
|
605406
|
+
if (model.domain === "video-gen") return "video";
|
|
605407
|
+
if (model.domain === "tts" || model.domain === "music" || model.domain === "sound") return "audio";
|
|
605408
|
+
return "other";
|
|
605409
|
+
}
|
|
605410
|
+
function mbToGb(mb) {
|
|
605411
|
+
return (Math.max(0, Number(mb) || 0) / 1024).toFixed(1);
|
|
605412
|
+
}
|
|
605208
605413
|
function readBudget() {
|
|
605209
605414
|
const raw = String(process.env["OMNIUS_LIVE_RESOURCE_BUDGET"] ?? "").trim().toLowerCase();
|
|
605210
605415
|
if (raw === "low" || raw === "conserve" || raw === "conservative" || raw === "battery") return "conserve";
|
|
@@ -605240,10 +605445,11 @@ function truncateAnsi(text2, max) {
|
|
|
605240
605445
|
if (plain.length <= max) return text2;
|
|
605241
605446
|
return `${plain.slice(0, Math.max(0, max - 3))}...`;
|
|
605242
605447
|
}
|
|
605243
|
-
var LiveResourceArbiter, singleton;
|
|
605448
|
+
var LiveResourceArbiter, singleton, lastModelSnapshotRefreshAt;
|
|
605244
605449
|
var init_live_resource_arbiter = __esm({
|
|
605245
605450
|
"packages/cli/src/tui/live-resource-arbiter.ts"() {
|
|
605246
605451
|
"use strict";
|
|
605452
|
+
init_dist5();
|
|
605247
605453
|
LiveResourceArbiter = class {
|
|
605248
605454
|
leases = /* @__PURE__ */ new Map();
|
|
605249
605455
|
seq = 0;
|
|
@@ -605400,6 +605606,7 @@ var init_live_resource_arbiter = __esm({
|
|
|
605400
605606
|
}
|
|
605401
605607
|
};
|
|
605402
605608
|
singleton = null;
|
|
605609
|
+
lastModelSnapshotRefreshAt = 0;
|
|
605403
605610
|
}
|
|
605404
605611
|
});
|
|
605405
605612
|
|
|
@@ -605645,6 +605852,18 @@ function truncateCell(value2, width) {
|
|
|
605645
605852
|
function stripDashboardAnsi(value2) {
|
|
605646
605853
|
return value2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
605647
605854
|
}
|
|
605855
|
+
function dashboardAnsi(code8, text2) {
|
|
605856
|
+
return `\x1B[${code8}m${text2}\x1B[0m`;
|
|
605857
|
+
}
|
|
605858
|
+
function dashboardDim(text2) {
|
|
605859
|
+
return dashboardAnsi("38;5;244", text2);
|
|
605860
|
+
}
|
|
605861
|
+
function dashboardSoundColor(score) {
|
|
605862
|
+
if (score === void 0) return "38;5;81";
|
|
605863
|
+
if (score >= 0.75) return "38;5;82";
|
|
605864
|
+
if (score >= 0.5) return "38;5;220";
|
|
605865
|
+
return "38;5;208";
|
|
605866
|
+
}
|
|
605648
605867
|
function dashboardVisibleWidth(value2) {
|
|
605649
605868
|
return stripDashboardAnsi(value2).length;
|
|
605650
605869
|
}
|
|
@@ -605893,13 +606112,6 @@ function readPcm16WavActivity(filePath, bins = 36) {
|
|
|
605893
606112
|
return void 0;
|
|
605894
606113
|
}
|
|
605895
606114
|
}
|
|
605896
|
-
function extractAsrBackend(output) {
|
|
605897
|
-
const match = String(output ?? "").match(/^Backend:\s*(.+)$/m);
|
|
605898
|
-
if (!match?.[1]) return void 0;
|
|
605899
|
-
const backend = match[1].trim();
|
|
605900
|
-
if (/managed openai-whisper|transcribe-cli|whisper/i.test(backend)) return backend;
|
|
605901
|
-
return backend.slice(0, 80);
|
|
605902
|
-
}
|
|
605903
606115
|
function parseLiveAudioIntakeDecision(value2) {
|
|
605904
606116
|
const parsed = parseJsonObjectFromText(value2);
|
|
605905
606117
|
if (!parsed) return null;
|
|
@@ -606293,6 +606505,23 @@ function formatAudioAnalysisDashboardSummary(value2) {
|
|
|
606293
606505
|
}
|
|
606294
606506
|
return `sound: ${trimOneLine(text2.replace(/\{[\s\S]*$/, ""), 140) || trimOneLine(text2, 140)}`;
|
|
606295
606507
|
}
|
|
606508
|
+
function formatAudioAnalysisDashboardSummaryColored(value2) {
|
|
606509
|
+
const text2 = String(value2 ?? "").trim();
|
|
606510
|
+
if (!text2) return "";
|
|
606511
|
+
const parsed = parseJsonObjectFromText(text2);
|
|
606512
|
+
const classifications = Array.isArray(parsed?.["classifications"]) ? parsed["classifications"] : [];
|
|
606513
|
+
if (classifications.length > 0) {
|
|
606514
|
+
const parts = classifications.slice(0, 4).map((entry) => {
|
|
606515
|
+
const label = trimOneLine(entry["class"] ?? entry["label"] ?? entry["name"] ?? "sound", 44);
|
|
606516
|
+
const score = boundedConfidence(entry["score"] ?? entry["confidence"]);
|
|
606517
|
+
const coloredLabel = dashboardAnsi(dashboardSoundColor(score), label || "sound");
|
|
606518
|
+
return `${coloredLabel}${score !== void 0 ? ` ${dashboardDim(score.toFixed(2))}` : ""}`;
|
|
606519
|
+
});
|
|
606520
|
+
return `${dashboardAnsi("38;5;45", "sounds heard")}: ${parts.join(dashboardDim("; "))}`;
|
|
606521
|
+
}
|
|
606522
|
+
const summary = trimOneLine(text2.replace(/\{[\s\S]*$/, ""), 140) || trimOneLine(text2, 140);
|
|
606523
|
+
return `${dashboardAnsi("38;5;45", "sound")}: ${dashboardAnsi("38;5;250", summary)}`;
|
|
606524
|
+
}
|
|
606296
606525
|
function formatCameraRotationDashboard(camera) {
|
|
606297
606526
|
const orientation = camera.orientation;
|
|
606298
606527
|
const rotation = orientation?.rotation ?? camera.rotation ?? 0;
|
|
@@ -606378,6 +606607,9 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
606378
606607
|
for (const budgetLine of formatLiveResourceBudgetLines(width - 2)) {
|
|
606379
606608
|
lines.push(`│${truncateAnsiCell(budgetLine, width - 2)}│`);
|
|
606380
606609
|
}
|
|
606610
|
+
for (const modelLine of formatLiveModelRuntimeLines(width - 2, { maxModels: 4 })) {
|
|
606611
|
+
lines.push(`│${truncateAnsiCell(modelLine, width - 2)}│`);
|
|
606612
|
+
}
|
|
606381
606613
|
const enabledStreams = Object.entries(snapshot.config.cameraStreams ?? {}).filter(([, stream]) => stream.enabled !== false);
|
|
606382
606614
|
const selectedResolution = snapshot.config.selectedCamera ? formatLiveCameraResolution(snapshot.config.cameraStreams?.[snapshot.config.selectedCamera]?.resolution) : "none";
|
|
606383
606615
|
lines.push(`│${truncateCell(` systems: camera-streams=${enabledStreams.length}/${snapshot.devices.video.length} selected-res=${selectedResolution} yolo=${snapshot.config.inferEnabled ? "on" : "off"} clip=${snapshot.config.clipEnabled ? "on" : "off"} asr=${snapshot.config.asrEnabled ? "on" : "off"} sounds=${snapshot.config.audioAnalysisEnabled ? "on" : "off"}`, width - 2)}│`);
|
|
@@ -606423,13 +606655,16 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
606423
606655
|
pushDashboardWrapped(lines, detail, width);
|
|
606424
606656
|
}
|
|
606425
606657
|
});
|
|
606426
|
-
|
|
606658
|
+
const dashboardAsr = getListenLiveState();
|
|
606659
|
+
const showDashboardAudio = Boolean(snapshot.audio) || dashboardAsr.phase !== "idle";
|
|
606660
|
+
if (showDashboardAudio) {
|
|
606661
|
+
const audioSnapshot = snapshot.audio;
|
|
606427
606662
|
lines.push(`├${horizontal}┤`);
|
|
606428
606663
|
lines.push(`│${truncateCell(" audio monitor", width - 2)}│`);
|
|
606429
|
-
const transcriptFailure = isAudioFailureText(
|
|
606430
|
-
const transcript = transcriptFailure ?
|
|
606431
|
-
const audioError = sanitizeLiveAudioError([
|
|
606432
|
-
const asr =
|
|
606664
|
+
const transcriptFailure = audioSnapshot && isAudioFailureText(audioSnapshot.transcript) ? audioSnapshot.transcript : "";
|
|
606665
|
+
const transcript = audioSnapshot && !transcriptFailure ? audioSnapshot.transcript : "";
|
|
606666
|
+
const audioError = sanitizeLiveAudioError([audioSnapshot?.error, transcriptFailure].filter(Boolean).join(" | "));
|
|
606667
|
+
const asr = dashboardAsr;
|
|
606433
606668
|
const asrPhaseLabel = {
|
|
606434
606669
|
bootstrapping: "bootstrapping deps…",
|
|
606435
606670
|
"loading-model": "loading model…",
|
|
@@ -606440,21 +606675,26 @@ function formatLiveDashboardFromSnapshot(snapshot, opts = {}) {
|
|
|
606440
606675
|
};
|
|
606441
606676
|
const asrMicBits = asr.micLevelDb !== null ? ` mic=${asr.micLevelDb.toFixed(1)}dB${asr.speechActive ? " ●speech" : ""}${asr.doa !== null ? ` dir=${asr.doa}°` : ""}` : "";
|
|
606442
606677
|
const asrLine = asr.phase !== "idle" ? `│ ├─ whisper: ${[asr.backend, asr.model].filter(Boolean).join(" ") || "starting"} — ${asrPhaseLabel[asr.phase] ?? asr.phase}${asrMicBits}` : "";
|
|
606443
|
-
const
|
|
606444
|
-
const
|
|
606678
|
+
const asrFinalAge = asr.lastTranscriptAt > 0 ? Math.max(0, Math.round((now2 - asr.lastTranscriptAt) / 1e3)) : null;
|
|
606679
|
+
const asrPartialAge = asr.lastPartialTranscriptAt > 0 ? Math.max(0, Math.round((now2 - asr.lastPartialTranscriptAt) / 1e3)) : null;
|
|
606680
|
+
const showPartial = asr.lastPartialTranscript && (!asr.lastTranscriptAt || asr.lastPartialTranscriptAt >= asr.lastTranscriptAt);
|
|
606681
|
+
const asrHeardLine = asr.phase !== "idle" && (showPartial || asr.lastTranscript) ? showPartial ? `│ ├─ hearing${asrPartialAge !== null ? ` ${asrPartialAge}s ago` : ""}: ${trimOneLine(asr.lastPartialTranscript, 120)}` : `│ ├─ heard${asrFinalAge !== null ? ` ${asrFinalAge}s ago` : ""}: ${trimOneLine(asr.lastTranscript, 120)}` : "";
|
|
606445
606682
|
const asrStatusLine = (asr.phase === "bootstrapping" || asr.phase === "loading-model" || asr.phase === "error") && asr.lastStatus ? `│ ├─ whisper status: ${trimOneLine(asr.lastStatus, 120)}` : "";
|
|
606683
|
+
const coloredSoundSummary = audioSnapshot?.analysis ? formatAudioAnalysisDashboardSummaryColored(audioSnapshot.analysis) : "";
|
|
606446
606684
|
const audioBits = [
|
|
606447
|
-
`├─ input: ${
|
|
606685
|
+
`├─ input: ${audioSnapshot?.input || snapshot.config.selectedAudioInput || "none"}`,
|
|
606448
606686
|
asrLine,
|
|
606449
606687
|
asrStatusLine,
|
|
606450
606688
|
asrHeardLine,
|
|
606451
|
-
|
|
606689
|
+
audioSnapshot?.activity ? `│ ├─ activity: ${audioSnapshot.activity.waveform} rms=${audioSnapshot.activity.rmsDb.toFixed(1)}dB active=${Math.round(audioSnapshot.activity.activeRatio * 100)}%` : "",
|
|
606452
606690
|
audioError ? `│ ├─ error: ${trimOneLine(audioError, 140)}` : "",
|
|
606453
|
-
transcript ? `│ ├─ asr${
|
|
606454
|
-
|
|
606455
|
-
snapshot.audio.analysis ? `│ └─ ${formatAudioAnalysisDashboardSummary(snapshot.audio.analysis)}` : ""
|
|
606691
|
+
transcript ? `│ ├─ asr${audioSnapshot?.asrBackend ? ` (${audioSnapshot.asrBackend})` : ""}: ${trimOneLine(transcript, 140)}` : "",
|
|
606692
|
+
audioSnapshot?.intake ? `│ ├─ intake: ${audioSnapshot.intake.intendedForAgent ? "for-agent" : "ambient"} conf=${audioSnapshot.intake.confidence.toFixed(2)} action=${audioSnapshot.intake.action}` : ""
|
|
606456
606693
|
].filter(Boolean);
|
|
606457
606694
|
for (const item of audioBits.slice(0, 9)) pushDashboardWrapped(lines, item, width);
|
|
606695
|
+
if (coloredSoundSummary) {
|
|
606696
|
+
lines.push(`│${truncateAnsiCell(` └─ ${coloredSoundSummary}`, width - 2)}│`);
|
|
606697
|
+
}
|
|
606458
606698
|
}
|
|
606459
606699
|
lines.push(`╰${horizontal}╯`);
|
|
606460
606700
|
return lines;
|
|
@@ -606847,6 +607087,17 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
606847
607087
|
lines.push(`summary: ${cameraSnapshotSummary(camera)}`);
|
|
606848
607088
|
}
|
|
606849
607089
|
}
|
|
607090
|
+
const liveAsr = getListenLiveState();
|
|
607091
|
+
const liveAsrFinalAge = liveAsr.lastTranscriptAt > 0 ? now2 - liveAsr.lastTranscriptAt : Number.POSITIVE_INFINITY;
|
|
607092
|
+
const liveAsrPartialAge = liveAsr.lastPartialTranscriptAt > 0 ? now2 - liveAsr.lastPartialTranscriptAt : Number.POSITIVE_INFINITY;
|
|
607093
|
+
const liveAsrText = liveAsr.lastTranscript && liveAsrFinalAge < maxAgeMs ? liveAsr.lastTranscript : liveAsr.lastPartialTranscript && liveAsrPartialAge < Math.min(maxAgeMs, 3e4) ? liveAsr.lastPartialTranscript : "";
|
|
607094
|
+
if (liveAsrText) {
|
|
607095
|
+
const label = liveAsr.lastTranscript && liveAsrFinalAge < maxAgeMs ? "Live streaming ASR" : "Live streaming ASR partial";
|
|
607096
|
+
lines.push("");
|
|
607097
|
+
lines.push("## LIVE STREAMING ASR");
|
|
607098
|
+
lines.push(`${label}${liveAsr.backend ? ` (${liveAsr.backend} ${liveAsr.model})` : ""}:`);
|
|
607099
|
+
lines.push(cleanPreview(liveAsrText, 1200));
|
|
607100
|
+
}
|
|
606850
607101
|
if (snapshot.audio) {
|
|
606851
607102
|
const transcriptFailure = isAudioFailureText(snapshot.audio.transcript) ? snapshot.audio.transcript : "";
|
|
606852
607103
|
const transcript = transcriptFailure ? "" : snapshot.audio.transcript;
|
|
@@ -606881,6 +607132,7 @@ function formatLiveSensorContextFromSnapshot(snapshot, opts = {}) {
|
|
|
606881
607132
|
lines.push(`intake_reason: ${snapshot.audio.intake.reason}`);
|
|
606882
607133
|
}
|
|
606883
607134
|
if (snapshot.audio.analysis) {
|
|
607135
|
+
lines.push(`Live sound summary: ${formatAudioAnalysisDashboardSummary(snapshot.audio.analysis)}`);
|
|
606884
607136
|
lines.push("Live sound analysis:");
|
|
606885
607137
|
lines.push(cleanPreview(snapshot.audio.analysis, 1600));
|
|
606886
607138
|
}
|
|
@@ -607210,6 +607462,7 @@ function formatLiveStatus(snapshot) {
|
|
|
607210
607462
|
`Camera orientation: ${cfg.selectedCamera ? describeCameraOrientation(cfg.cameraOrientation?.[cfg.selectedCamera]) : "none"}`,
|
|
607211
607463
|
`Audio input: ${cfg.selectedAudioInput || "none"}`,
|
|
607212
607464
|
`Audio output: ${cfg.selectedAudioOutput || "none"}`,
|
|
607465
|
+
`Resources: ${formatLiveMenuResourceSummary()}`,
|
|
607213
607466
|
`Snapshot: ${liveSnapshotPath(snapshot.repoRoot)}`
|
|
607214
607467
|
];
|
|
607215
607468
|
if (snapshot.video?.updatedAt) {
|
|
@@ -607265,6 +607518,10 @@ var init_live_sensors = __esm({
|
|
|
607265
607518
|
config: this.config,
|
|
607266
607519
|
devices: this.devices
|
|
607267
607520
|
};
|
|
607521
|
+
try {
|
|
607522
|
+
getModelBroker().startPolling(2500);
|
|
607523
|
+
} catch {
|
|
607524
|
+
}
|
|
607268
607525
|
this.ensureLoops();
|
|
607269
607526
|
}
|
|
607270
607527
|
repoRoot;
|
|
@@ -608486,27 +608743,20 @@ ${output}`).join("\n\n");
|
|
|
608486
608743
|
audioErrors.push(`No live input signal detected from selected input ${capture.device}; keeping the explicit selection.`);
|
|
608487
608744
|
}
|
|
608488
608745
|
if (this.config.asrEnabled) {
|
|
608489
|
-
|
|
608490
|
-
|
|
608491
|
-
|
|
608492
|
-
|
|
608493
|
-
|
|
608494
|
-
|
|
608495
|
-
|
|
608496
|
-
|
|
608497
|
-
|
|
608498
|
-
|
|
608499
|
-
|
|
608500
|
-
|
|
608501
|
-
|
|
608502
|
-
|
|
608503
|
-
transcript = cleanPreview(typeof asr.llmContent === "string" ? asr.llmContent : "", 1600);
|
|
608504
|
-
asrBackend = extractAsrBackend(asr.output) ?? "whisper";
|
|
608505
|
-
} else {
|
|
608506
|
-
audioErrors.push(`ASR unavailable: ${asr.error || asr.output || "unknown error"}`);
|
|
608507
|
-
}
|
|
608508
|
-
} catch (err) {
|
|
608509
|
-
audioErrors.push(`ASR unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
608746
|
+
const liveAsr = getListenLiveState();
|
|
608747
|
+
const now2 = Date.now();
|
|
608748
|
+
const finalAge = liveAsr.lastTranscriptAt > 0 ? now2 - liveAsr.lastTranscriptAt : Number.POSITIVE_INFINITY;
|
|
608749
|
+
const partialAge = liveAsr.lastPartialTranscriptAt > 0 ? now2 - liveAsr.lastPartialTranscriptAt : Number.POSITIVE_INFINITY;
|
|
608750
|
+
if (liveAsr.lastTranscript && finalAge < 45e3) {
|
|
608751
|
+
transcript = cleanPreview(liveAsr.lastTranscript, 1600);
|
|
608752
|
+
asrBackend = [liveAsr.backend, liveAsr.model, "streaming"].filter(Boolean).join(" ");
|
|
608753
|
+
} else if (liveAsr.lastPartialTranscript && partialAge < 2e4) {
|
|
608754
|
+
transcript = cleanPreview(liveAsr.lastPartialTranscript, 1600);
|
|
608755
|
+
asrBackend = [liveAsr.backend, liveAsr.model, "streaming-partial"].filter(Boolean).join(" ");
|
|
608756
|
+
} else if (liveAsr.phase === "idle") {
|
|
608757
|
+
audioErrors.push("Streaming ASR is not running. Start /live run or /listen to enable CUDA Whisper streaming.");
|
|
608758
|
+
} else if (liveAsr.lastStatus) {
|
|
608759
|
+
audioErrors.push(`Streaming ASR status: ${liveAsr.lastStatus}`);
|
|
608510
608760
|
}
|
|
608511
608761
|
}
|
|
608512
608762
|
if (this.config.audioAnalysisEnabled) {
|
|
@@ -608528,6 +608778,13 @@ ${output}`).join("\n\n");
|
|
|
608528
608778
|
analysis = parts.filter(Boolean).join("\n");
|
|
608529
608779
|
}
|
|
608530
608780
|
if (transcript) {
|
|
608781
|
+
asrLease = this.resourceArbiter.claim({
|
|
608782
|
+
owner: "live-asr",
|
|
608783
|
+
priority: 88,
|
|
608784
|
+
ttlMs: 18e3,
|
|
608785
|
+
reason: "live streaming ASR transcript accepted",
|
|
608786
|
+
suppress: ["vision"]
|
|
608787
|
+
});
|
|
608531
608788
|
intake = await classifyLiveAudioIntake(transcript, this.intakeAgentConfig);
|
|
608532
608789
|
keepAsrLease = true;
|
|
608533
608790
|
asrLease?.extend(
|
|
@@ -608735,17 +608992,17 @@ ${analysis}` : ""
|
|
|
608735
608992
|
this.videoTimer.unref?.();
|
|
608736
608993
|
}
|
|
608737
608994
|
scheduleAudioLoop(delayMs) {
|
|
608738
|
-
const wantsAudio = this.config.audioEnabled &&
|
|
608995
|
+
const wantsAudio = this.config.audioEnabled && this.config.audioAnalysisEnabled;
|
|
608739
608996
|
if (!wantsAudio || this.audioTimer) return;
|
|
608740
608997
|
this.audioTimer = setTimeout(async () => {
|
|
608741
608998
|
this.audioTimer = null;
|
|
608742
|
-
const stillWantsAudio = this.config.audioEnabled &&
|
|
608999
|
+
const stillWantsAudio = this.config.audioEnabled && this.config.audioAnalysisEnabled;
|
|
608743
609000
|
if (!stillWantsAudio) return;
|
|
608744
609001
|
try {
|
|
608745
609002
|
await this.sampleAudioNow();
|
|
608746
609003
|
} catch {
|
|
608747
609004
|
} finally {
|
|
608748
|
-
const wantsNext = this.config.audioEnabled &&
|
|
609005
|
+
const wantsNext = this.config.audioEnabled && this.config.audioAnalysisEnabled;
|
|
608749
609006
|
if (wantsNext) {
|
|
608750
609007
|
this.scheduleAudioLoop(this.config.audioIntervalMs);
|
|
608751
609008
|
}
|
|
@@ -608783,7 +609040,7 @@ ${analysis}` : ""
|
|
|
608783
609040
|
clearTimeout(this.previewTickTimer);
|
|
608784
609041
|
this.previewTickTimer = null;
|
|
608785
609042
|
}
|
|
608786
|
-
const wantsAudio = this.config.audioEnabled &&
|
|
609043
|
+
const wantsAudio = this.config.audioEnabled && this.config.audioAnalysisEnabled;
|
|
608787
609044
|
if (wantsAudio && !this.audioTimer) {
|
|
608788
609045
|
this.scheduleAudioLoop(0);
|
|
608789
609046
|
} else if (!wantsAudio && this.audioTimer) {
|
|
@@ -616949,7 +617206,13 @@ function renderVoiceChatCoalescedRow(row, opts) {
|
|
|
616949
617206
|
return;
|
|
616950
617207
|
}
|
|
616951
617208
|
if (_voiceChatCoalesce) {
|
|
616952
|
-
|
|
617209
|
+
const replacePrefix = opts?.replaceLastPrefix;
|
|
617210
|
+
const lastIndex = _voiceChatCoalesce.rows.length - 1;
|
|
617211
|
+
if (replacePrefix && lastIndex >= 0 && _voiceChatCoalesce.rows[lastIndex]?.startsWith(replacePrefix)) {
|
|
617212
|
+
_voiceChatCoalesce.rows[lastIndex] = row;
|
|
617213
|
+
} else {
|
|
617214
|
+
_voiceChatCoalesce.rows.push(row);
|
|
617215
|
+
}
|
|
616953
617216
|
host.refreshDynamicBlocks();
|
|
616954
617217
|
return;
|
|
616955
617218
|
}
|
|
@@ -659689,6 +659952,19 @@ function attachLiveSinks(ctx3, manager, agentEnabled = false, speechEnabledOverr
|
|
|
659689
659952
|
manager.setAgentActionSink(void 0);
|
|
659690
659953
|
}
|
|
659691
659954
|
}
|
|
659955
|
+
async function ensureLiveStreamingAsr(ctx3, manager) {
|
|
659956
|
+
if (!manager.getConfig().asrEnabled) return;
|
|
659957
|
+
if (!ctx3.listenStart) return;
|
|
659958
|
+
ctx3.listenSetMode?.("auto");
|
|
659959
|
+
const msg = await ctx3.listenStart("live");
|
|
659960
|
+
if (!/already active/i.test(msg)) renderInfo(`Live ASR: ${msg}`);
|
|
659961
|
+
}
|
|
659962
|
+
async function releaseLiveStreamingAsr(ctx3) {
|
|
659963
|
+
try {
|
|
659964
|
+
await ctx3.listenRelease?.("live");
|
|
659965
|
+
} catch {
|
|
659966
|
+
}
|
|
659967
|
+
}
|
|
659692
659968
|
async function startLiveVoicechat(ctx3, manager) {
|
|
659693
659969
|
const speechEnabled = Boolean(ctx3.voiceSpeak);
|
|
659694
659970
|
attachLiveSinks(ctx3, manager, false, speechEnabled);
|
|
@@ -659920,6 +660196,7 @@ async function handleLiveCommand(ctx3, arg) {
|
|
|
659920
660196
|
if (sub2 === "stop" || sub2 === "off" || sub2 === "disable") {
|
|
659921
660197
|
manager.stopAll();
|
|
659922
660198
|
manager.setAgentActionSink(void 0);
|
|
660199
|
+
await releaseLiveStreamingAsr(ctx3);
|
|
659923
660200
|
if (ctx3.isVoiceChatActive?.()) {
|
|
659924
660201
|
try {
|
|
659925
660202
|
await ctx3.voiceChatStop?.();
|
|
@@ -659938,6 +660215,7 @@ async function handleLiveCommand(ctx3, arg) {
|
|
|
659938
660215
|
}
|
|
659939
660216
|
await ensureLiveInventory(manager);
|
|
659940
660217
|
renderInfo(manager.startRunMode({ agent: effectiveAgent, lowLatency: true, speech: effectiveAgent && Boolean(ctx3.voiceSpeak) }));
|
|
660218
|
+
await ensureLiveStreamingAsr(ctx3, manager);
|
|
659941
660219
|
return;
|
|
659942
660220
|
}
|
|
659943
660221
|
if (sub2 === "chat" || sub2 === "voicechat" || sub2 === "voice") {
|
|
@@ -660037,10 +660315,13 @@ async function handleLiveCommand(ctx3, arg) {
|
|
|
660037
660315
|
}
|
|
660038
660316
|
if (sub2 === "asr") {
|
|
660039
660317
|
const cfg = manager.getConfig();
|
|
660318
|
+
const enabled2 = setBool(value2, !cfg.asrEnabled);
|
|
660040
660319
|
manager.configure({
|
|
660041
660320
|
audioEnabled: true,
|
|
660042
|
-
asrEnabled:
|
|
660321
|
+
asrEnabled: enabled2
|
|
660043
660322
|
});
|
|
660323
|
+
if (enabled2) await ensureLiveStreamingAsr(ctx3, manager);
|
|
660324
|
+
else await releaseLiveStreamingAsr(ctx3);
|
|
660044
660325
|
renderInfo(formatLiveStatus(manager.getSnapshot()));
|
|
660045
660326
|
return;
|
|
660046
660327
|
}
|
|
@@ -660328,6 +660609,7 @@ async function handleCameraCommand(ctx3, arg) {
|
|
|
660328
660609
|
async function showLiveMenu(ctx3, manager) {
|
|
660329
660610
|
await ensureLiveInventory(manager);
|
|
660330
660611
|
while (true) {
|
|
660612
|
+
await refreshLiveResourceModelSnapshot().catch(() => void 0);
|
|
660331
660613
|
const snapshot = manager.getSnapshot();
|
|
660332
660614
|
const cfg = manager.getConfig();
|
|
660333
660615
|
const devices = snapshot.devices;
|
|
@@ -660339,6 +660621,15 @@ async function showLiveMenu(ctx3, manager) {
|
|
|
660339
660621
|
` video ${liveEnabled(cfg.videoEnabled)} · infer ${liveEnabled(cfg.inferEnabled)} · clip ${liveEnabled(cfg.clipEnabled)} · audio ${liveEnabled(cfg.audioEnabled)} · asr ${liveEnabled(cfg.asrEnabled)} · sounds ${liveEnabled(cfg.audioAnalysisEnabled)}`
|
|
660340
660622
|
)
|
|
660341
660623
|
},
|
|
660624
|
+
{
|
|
660625
|
+
key: "info:resources",
|
|
660626
|
+
label: selectColors.dim(` ${formatLiveMenuResourceSummary()}`)
|
|
660627
|
+
},
|
|
660628
|
+
{
|
|
660629
|
+
key: "resources",
|
|
660630
|
+
label: "Show Resource + Model State",
|
|
660631
|
+
detail: "RAM/VRAM by subsystem, CUDA per loaded model, active slots, inflight loads"
|
|
660632
|
+
},
|
|
660342
660633
|
{
|
|
660343
660634
|
key: "run-live",
|
|
660344
660635
|
label: "Start Live Run",
|
|
@@ -660476,6 +660767,7 @@ async function showLiveMenu(ctx3, manager) {
|
|
|
660476
660767
|
case "run-live":
|
|
660477
660768
|
attachLiveSinks(ctx3, manager, false);
|
|
660478
660769
|
renderInfo(manager.startRunMode({ agent: false, lowLatency: true, speech: false }));
|
|
660770
|
+
await ensureLiveStreamingAsr(ctx3, manager);
|
|
660479
660771
|
continue;
|
|
660480
660772
|
case "run-agent":
|
|
660481
660773
|
attachLiveSinks(ctx3, manager, true);
|
|
@@ -660483,6 +660775,7 @@ async function showLiveMenu(ctx3, manager) {
|
|
|
660483
660775
|
renderWarning("Live PFC agent review is unavailable in this context; starting visual/audio feedback only.");
|
|
660484
660776
|
}
|
|
660485
660777
|
renderInfo(manager.startRunMode({ agent: Boolean(ctx3.startBackgroundPrompt), lowLatency: true, speech: Boolean(ctx3.startBackgroundPrompt && ctx3.voiceSpeak) }));
|
|
660778
|
+
await ensureLiveStreamingAsr(ctx3, manager);
|
|
660486
660779
|
continue;
|
|
660487
660780
|
case "run-chat":
|
|
660488
660781
|
await startLiveVoicechat(ctx3, manager);
|
|
@@ -660501,6 +660794,8 @@ async function showLiveMenu(ctx3, manager) {
|
|
|
660501
660794
|
continue;
|
|
660502
660795
|
case "toggle-asr":
|
|
660503
660796
|
manager.configure({ audioEnabled: true, asrEnabled: !cfg.asrEnabled });
|
|
660797
|
+
if (!cfg.asrEnabled) await ensureLiveStreamingAsr(ctx3, manager);
|
|
660798
|
+
else await releaseLiveStreamingAsr(ctx3);
|
|
660504
660799
|
continue;
|
|
660505
660800
|
case "toggle-sounds":
|
|
660506
660801
|
manager.configure({ audioEnabled: true, audioAnalysisEnabled: !cfg.audioAnalysisEnabled });
|
|
@@ -660561,6 +660856,10 @@ Previewing microphone activity...`);
|
|
|
660561
660856
|
renderWarning("Live transcript UI is unavailable in this context.");
|
|
660562
660857
|
}
|
|
660563
660858
|
continue;
|
|
660859
|
+
case "resources":
|
|
660860
|
+
await refreshLiveResourceModelSnapshot(0).catch(() => void 0);
|
|
660861
|
+
renderInfo(formatLiveResourceModelReport(110));
|
|
660862
|
+
continue;
|
|
660564
660863
|
case "refresh":
|
|
660565
660864
|
renderInfo(formatLiveInventory(await manager.refreshDevices()));
|
|
660566
660865
|
continue;
|
|
@@ -660570,6 +660869,7 @@ Previewing microphone activity...`);
|
|
|
660570
660869
|
case "stop":
|
|
660571
660870
|
manager.stopAll();
|
|
660572
660871
|
manager.setAgentActionSink(void 0);
|
|
660872
|
+
await releaseLiveStreamingAsr(ctx3);
|
|
660573
660873
|
if (ctx3.isVoiceChatActive?.()) {
|
|
660574
660874
|
try {
|
|
660575
660875
|
await ctx3.voiceChatStop?.();
|
|
@@ -665644,6 +665944,7 @@ var init_commands = __esm({
|
|
|
665644
665944
|
init_generative_progress();
|
|
665645
665945
|
init_cad_model_viewer();
|
|
665646
665946
|
init_live_sensors();
|
|
665947
|
+
init_live_resource_arbiter();
|
|
665647
665948
|
init_command_registry();
|
|
665648
665949
|
init_hf_token_prompt();
|
|
665649
665950
|
init_dist5();
|
|
@@ -699583,9 +699884,15 @@ Rules:
|
|
|
699583
699884
|
this._retryMicTimer = null;
|
|
699584
699885
|
if (!this.active) return;
|
|
699585
699886
|
try {
|
|
699586
|
-
|
|
699587
|
-
|
|
699588
|
-
|
|
699887
|
+
if (typeof this.listen.release === "function" && typeof this.listen.acquire === "function") {
|
|
699888
|
+
await this.listen.release("voicechat").catch(() => {
|
|
699889
|
+
});
|
|
699890
|
+
await this.listen.acquire("voicechat");
|
|
699891
|
+
} else {
|
|
699892
|
+
await this.listen.stop().catch(() => {
|
|
699893
|
+
});
|
|
699894
|
+
await this.listen.start();
|
|
699895
|
+
}
|
|
699589
699896
|
if (this.verbose) this.onStatus("Mic auto-recovered — LISTENING");
|
|
699590
699897
|
} catch {
|
|
699591
699898
|
}
|
|
@@ -699595,7 +699902,11 @@ Rules:
|
|
|
699595
699902
|
this.listen.on("transcript", this._onTranscript);
|
|
699596
699903
|
this.listen.on("error", this._onError);
|
|
699597
699904
|
try {
|
|
699598
|
-
|
|
699905
|
+
if (typeof this.listen.acquire === "function") {
|
|
699906
|
+
await this.listen.acquire("voicechat");
|
|
699907
|
+
} else {
|
|
699908
|
+
await this.listen.start();
|
|
699909
|
+
}
|
|
699599
699910
|
this.setState("LISTENING");
|
|
699600
699911
|
if (this.verbose) this.onStatus("Mic active — LISTENING for speech...");
|
|
699601
699912
|
} catch (err) {
|
|
@@ -699622,15 +699933,21 @@ Rules:
|
|
|
699622
699933
|
this.finalizeSegment();
|
|
699623
699934
|
}
|
|
699624
699935
|
if (this._onTranscript) {
|
|
699625
|
-
this.listen.
|
|
699936
|
+
if (typeof this.listen.off === "function") this.listen.off("transcript", this._onTranscript);
|
|
699937
|
+
else this.listen.removeAllListeners("transcript");
|
|
699626
699938
|
this._onTranscript = null;
|
|
699627
699939
|
}
|
|
699628
699940
|
if (this._onError) {
|
|
699629
|
-
this.listen.
|
|
699941
|
+
if (typeof this.listen.off === "function") this.listen.off("error", this._onError);
|
|
699942
|
+
else this.listen.removeAllListeners("error");
|
|
699630
699943
|
this._onError = null;
|
|
699631
699944
|
}
|
|
699632
699945
|
try {
|
|
699633
|
-
|
|
699946
|
+
if (typeof this.listen.release === "function") {
|
|
699947
|
+
await this.listen.release("voicechat");
|
|
699948
|
+
} else {
|
|
699949
|
+
await this.listen.stop();
|
|
699950
|
+
}
|
|
699634
699951
|
} catch {
|
|
699635
699952
|
}
|
|
699636
699953
|
this.releaseLiveResources();
|
|
@@ -738730,8 +739047,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
738730
739047
|
// Listen mode (transcribe-cli integration + live voice session)
|
|
738731
739048
|
async listenToggle() {
|
|
738732
739049
|
const engine = getListenEngine();
|
|
738733
|
-
if (engine.isActive) {
|
|
738734
|
-
const text2 = await engine.
|
|
739050
|
+
if (engine.isActive && engine.hasOwner("listen")) {
|
|
739051
|
+
const text2 = await engine.release("listen");
|
|
738735
739052
|
statusBar.setRecording(false);
|
|
738736
739053
|
if (voiceSession?.isActive) {
|
|
738737
739054
|
const sessionMsg = await voiceSession.stop();
|
|
@@ -738774,7 +739091,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
738774
739091
|
engine.on("info", (msg2) => {
|
|
738775
739092
|
writeContent(() => renderInfo(msg2));
|
|
738776
739093
|
});
|
|
738777
|
-
const msg = await engine.
|
|
739094
|
+
const msg = await engine.acquire("listen");
|
|
738778
739095
|
if (voiceEngine.enabled && voiceEngine.ready) {
|
|
738779
739096
|
try {
|
|
738780
739097
|
voiceSession = new VoiceSession();
|
|
@@ -738803,6 +739120,39 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
738803
739120
|
}
|
|
738804
739121
|
return msg;
|
|
738805
739122
|
},
|
|
739123
|
+
async listenStart(owner = "live") {
|
|
739124
|
+
const engine = getListenEngine();
|
|
739125
|
+
const available = await engine.isAvailable();
|
|
739126
|
+
if (!available) {
|
|
739127
|
+
return "ASR runtime unavailable. Omnius attempted managed Whisper setup; check ~/.omnius/runtimes/asr and CUDA PyTorch.";
|
|
739128
|
+
}
|
|
739129
|
+
const uiEngine = engine;
|
|
739130
|
+
if (!uiEngine.__omniusLiveAsrUiWired) {
|
|
739131
|
+
uiEngine.__omniusLiveAsrUiWired = true;
|
|
739132
|
+
engine.on("recording", (active) => {
|
|
739133
|
+
statusBar.setRecording(active);
|
|
739134
|
+
});
|
|
739135
|
+
engine.on("level", (evt) => {
|
|
739136
|
+
statusBar.setMicActivity(evt.speechActive, evt.levelDb);
|
|
739137
|
+
});
|
|
739138
|
+
engine.on("error", (err) => {
|
|
739139
|
+
writeContent(() => renderWarning(`Live ASR error: ${err.message}`));
|
|
739140
|
+
});
|
|
739141
|
+
engine.on("info", (msg) => {
|
|
739142
|
+
if (/CUDA|Whisper|consensus|ASR|VAD|torch/i.test(msg)) {
|
|
739143
|
+
writeContent(() => renderInfo(`[asr] ${msg}`));
|
|
739144
|
+
}
|
|
739145
|
+
});
|
|
739146
|
+
}
|
|
739147
|
+
return engine.acquire(owner);
|
|
739148
|
+
},
|
|
739149
|
+
async listenRelease(owner = "live") {
|
|
739150
|
+
const engine = getListenEngine();
|
|
739151
|
+
return engine.release(owner);
|
|
739152
|
+
},
|
|
739153
|
+
listenIsActive() {
|
|
739154
|
+
return getListenEngine().isActive;
|
|
739155
|
+
},
|
|
738806
739156
|
async listenSetModel(model) {
|
|
738807
739157
|
const engine = getListenEngine();
|
|
738808
739158
|
engine.setModel(model);
|
|
@@ -738815,8 +739165,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
738815
739165
|
},
|
|
738816
739166
|
async listenStop() {
|
|
738817
739167
|
const engine = getListenEngine();
|
|
738818
|
-
const text2 = await engine.stop();
|
|
738819
|
-
statusBar.setRecording(false);
|
|
739168
|
+
const text2 = engine.hasOwner("listen") ? await engine.release("listen") : engine.ownerCount() > 0 ? "Shared ASR is still running for /live, /voicechat, or /call." : await engine.stop();
|
|
739169
|
+
if (engine.ownerCount() === 0) statusBar.setRecording(false);
|
|
738820
739170
|
if (voiceSession?.isActive) {
|
|
738821
739171
|
const runtime = voiceSession.runtime;
|
|
738822
739172
|
await voiceSession.stop();
|
|
@@ -739128,19 +739478,22 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
739128
739478
|
writeContent(() => renderInfo(voiceMsg));
|
|
739129
739479
|
}
|
|
739130
739480
|
const { VoiceChatSession: VoiceChatSession2 } = await Promise.resolve().then(() => (init_voicechat(), voicechat_exports));
|
|
739131
|
-
const
|
|
739132
|
-
const
|
|
739133
|
-
|
|
739134
|
-
|
|
739135
|
-
|
|
739136
|
-
|
|
739137
|
-
|
|
739138
|
-
|
|
739139
|
-
|
|
739140
|
-
|
|
739141
|
-
|
|
739142
|
-
|
|
739143
|
-
|
|
739481
|
+
const listenEng = getListenEngine();
|
|
739482
|
+
const voicechatUiEngine = listenEng;
|
|
739483
|
+
if (!voicechatUiEngine.__omniusVoiceChatAsrUiWired) {
|
|
739484
|
+
voicechatUiEngine.__omniusVoiceChatAsrUiWired = true;
|
|
739485
|
+
listenEng.on("recording", (active) => {
|
|
739486
|
+
statusBar.setRecording(active);
|
|
739487
|
+
});
|
|
739488
|
+
listenEng.on("level", (evt) => {
|
|
739489
|
+
statusBar.setMicActivity(evt.speechActive, evt.levelDb);
|
|
739490
|
+
});
|
|
739491
|
+
listenEng.on("info", (msg) => {
|
|
739492
|
+
if (/consensus rejected|loading whisper|whisper model loaded|Whisper VAD|no accepted speech text|very short utterance/i.test(msg)) {
|
|
739493
|
+
writeContent(() => renderInfo(`[voicechat/asr] ${msg}`));
|
|
739494
|
+
}
|
|
739495
|
+
});
|
|
739496
|
+
}
|
|
739144
739497
|
const summaryRunner = {
|
|
739145
739498
|
injectUserMessage(content) {
|
|
739146
739499
|
if (activeTask?.runner) {
|
|
@@ -739148,6 +739501,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
739148
739501
|
}
|
|
739149
739502
|
}
|
|
739150
739503
|
};
|
|
739504
|
+
const voicechatPartialPrefix = `\x1B[38;5;244m[hearing]\x1B[0m`;
|
|
739151
739505
|
_voiceChatSession2 = new VoiceChatSession2({
|
|
739152
739506
|
voice: voiceEngine,
|
|
739153
739507
|
listen: listenEng,
|
|
@@ -739305,11 +739659,20 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
739305
739659
|
},
|
|
739306
739660
|
onUserSpeech(text2) {
|
|
739307
739661
|
writeContent(
|
|
739308
|
-
() => renderVoiceChatCoalescedRow(`\x1B[38;5;45m[you]\x1B[0m ${text2}
|
|
739662
|
+
() => renderVoiceChatCoalescedRow(`\x1B[38;5;45m[you]\x1B[0m ${text2}`, {
|
|
739663
|
+
replaceLastPrefix: voicechatPartialPrefix
|
|
739664
|
+
})
|
|
739309
739665
|
);
|
|
739310
739666
|
},
|
|
739311
|
-
|
|
739312
|
-
|
|
739667
|
+
onPartialTranscript(text2) {
|
|
739668
|
+
const cleaned = text2.trim();
|
|
739669
|
+
if (!cleaned) return;
|
|
739670
|
+
writeContent(
|
|
739671
|
+
() => renderVoiceChatCoalescedRow(
|
|
739672
|
+
`${voicechatPartialPrefix} ${truncateByLines(cleaned, 2, 240)}`,
|
|
739673
|
+
{ replaceLastPrefix: voicechatPartialPrefix }
|
|
739674
|
+
)
|
|
739675
|
+
);
|
|
739313
739676
|
},
|
|
739314
739677
|
onAgentSpeech(text2) {
|
|
739315
739678
|
writeContent(
|
|
@@ -739322,6 +739685,15 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
739322
739685
|
onStateChange(_state4) {
|
|
739323
739686
|
}
|
|
739324
739687
|
});
|
|
739688
|
+
_voiceChatSession2.on("consensusFiltered", (evt) => {
|
|
739689
|
+
const text2 = String(evt?.text ?? "").trim();
|
|
739690
|
+
writeContent(
|
|
739691
|
+
() => renderVoiceChatCoalescedRow(
|
|
739692
|
+
`\x1B[38;5;244m[filtered]\x1B[0m no ASR consensus${text2 ? `: ${truncateByLines(text2, 1, 180)}` : ""}`,
|
|
739693
|
+
{ replaceLastPrefix: voicechatPartialPrefix }
|
|
739694
|
+
)
|
|
739695
|
+
);
|
|
739696
|
+
});
|
|
739325
739697
|
await _voiceChatSession2.start();
|
|
739326
739698
|
},
|
|
739327
739699
|
async voiceChatStop() {
|
|
@@ -85,6 +85,33 @@ def emit_transcript(text: str, is_final: bool = False, doa=None, consensus=None)
|
|
|
85
85
|
emit(event)
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def emit_consensus_rejected(primary: str, secondary: str, reason: str):
|
|
89
|
+
emit({
|
|
90
|
+
"type": "consensus_rejected",
|
|
91
|
+
"primary": primary[:200],
|
|
92
|
+
"secondary": (secondary or "")[:200],
|
|
93
|
+
"reason": reason,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cuda_memory_reserved_mb(device: str) -> int:
|
|
98
|
+
if not str(device).startswith("cuda"):
|
|
99
|
+
return 0
|
|
100
|
+
try:
|
|
101
|
+
import torch
|
|
102
|
+
raw = str(device).split(":", 1)[1] if ":" in str(device) else ""
|
|
103
|
+
index = int(raw) if raw.strip().isdigit() else torch.cuda.current_device()
|
|
104
|
+
try:
|
|
105
|
+
torch.cuda.synchronize(index)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
reserved = int(torch.cuda.memory_reserved(index))
|
|
109
|
+
allocated = int(torch.cuda.memory_allocated(index))
|
|
110
|
+
return max(0, round(max(reserved, allocated) / (1024 * 1024)))
|
|
111
|
+
except Exception:
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
88
115
|
def host_cuda_hardware_present() -> bool:
|
|
89
116
|
"""Best-effort CUDA hardware hint, including Jetson/Orin nodes."""
|
|
90
117
|
for path in (
|
|
@@ -100,6 +127,32 @@ def host_cuda_hardware_present() -> bool:
|
|
|
100
127
|
return bool(visible and visible not in ("none", "void", "no"))
|
|
101
128
|
|
|
102
129
|
|
|
130
|
+
def asr_cpu_allowed() -> bool:
|
|
131
|
+
raw = os.environ.get("OMNIUS_ASR_ALLOW_CPU", "").strip().lower()
|
|
132
|
+
return raw in ("1", "true", "yes", "on")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def jetson_host_hint() -> str:
|
|
136
|
+
try:
|
|
137
|
+
model = Path("/proc/device-tree/model").read_text(errors="ignore").replace("\x00", " ").strip()
|
|
138
|
+
except Exception:
|
|
139
|
+
model = ""
|
|
140
|
+
if model and any(token in model.lower() for token in ("jetson", "orin", "tegra")):
|
|
141
|
+
return (
|
|
142
|
+
f" Detected {model}. Install a JetPack/L4T-compatible CUDA PyTorch wheel in the Omnius ASR venv; "
|
|
143
|
+
"the generic pip torch wheel is often CPU-only or CUDA-incompatible on Jetson."
|
|
144
|
+
)
|
|
145
|
+
return ""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def cuda_required_error(reason: str):
|
|
149
|
+
emit_error(
|
|
150
|
+
f"CUDA-only ASR is enabled and Whisper cannot use CUDA: {reason}."
|
|
151
|
+
f"{jetson_host_hint()} Set OMNIUS_ASR_ALLOW_CPU=1 only for explicit emergency CPU fallback."
|
|
152
|
+
)
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
103
156
|
def select_whisper_device() -> str:
|
|
104
157
|
"""Force Whisper onto CUDA whenever PyTorch can see a CUDA device.
|
|
105
158
|
|
|
@@ -110,9 +163,8 @@ def select_whisper_device() -> str:
|
|
|
110
163
|
try:
|
|
111
164
|
import torch
|
|
112
165
|
except Exception as e:
|
|
113
|
-
if host_cuda_hardware_present():
|
|
114
|
-
|
|
115
|
-
sys.exit(1)
|
|
166
|
+
if host_cuda_hardware_present() or not asr_cpu_allowed():
|
|
167
|
+
cuda_required_error(f"PyTorch could not be imported ({e})")
|
|
116
168
|
emit_status(f"PyTorch unavailable for CUDA probe ({e}); using CPU because CUDA is not available to Whisper.")
|
|
117
169
|
return "cpu"
|
|
118
170
|
|
|
@@ -120,17 +172,15 @@ def select_whisper_device() -> str:
|
|
|
120
172
|
cuda_count = int(torch.cuda.device_count())
|
|
121
173
|
cuda_available = bool(torch.cuda.is_available() and cuda_count > 0)
|
|
122
174
|
except Exception as e:
|
|
123
|
-
if host_cuda_hardware_present():
|
|
124
|
-
|
|
125
|
-
sys.exit(1)
|
|
175
|
+
if host_cuda_hardware_present() or not asr_cpu_allowed():
|
|
176
|
+
cuda_required_error(f"PyTorch CUDA probe failed ({e})")
|
|
126
177
|
emit_status(f"CUDA probe failed ({e}); using CPU because CUDA is not available to Whisper.")
|
|
127
178
|
return "cpu"
|
|
128
179
|
|
|
129
180
|
if not cuda_available:
|
|
130
181
|
cuda_version = getattr(getattr(torch, "version", None), "cuda", None)
|
|
131
|
-
if host_cuda_hardware_present():
|
|
132
|
-
|
|
133
|
-
sys.exit(1)
|
|
182
|
+
if host_cuda_hardware_present() or not asr_cpu_allowed():
|
|
183
|
+
cuda_required_error(f"PyTorch reports CUDA unavailable (torch.version.cuda={cuda_version}, device_count={cuda_count})")
|
|
134
184
|
emit_status(f"CUDA unavailable to PyTorch (torch.version.cuda={cuda_version}, device_count={cuda_count}); using CPU fallback.")
|
|
135
185
|
return "cpu"
|
|
136
186
|
|
|
@@ -151,7 +201,7 @@ def select_whisper_device() -> str:
|
|
|
151
201
|
emit_error(f"CUDA is available but cuda:{device_index} could not be selected ({e}); refusing CPU fallback.")
|
|
152
202
|
sys.exit(1)
|
|
153
203
|
|
|
154
|
-
emit_status(f"CUDA available; forcing Whisper onto cuda:{device_index} ({name}).")
|
|
204
|
+
emit_status(f"CUDA available; forcing Whisper onto cuda:{device_index} ({name}); torch={getattr(torch, '__version__', 'unknown')} cuda={getattr(getattr(torch, 'version', None), 'cuda', None)}.")
|
|
155
205
|
return f"cuda:{device_index}"
|
|
156
206
|
|
|
157
207
|
# ---------------------------------------------------------------------------
|
|
@@ -442,9 +492,15 @@ def main():
|
|
|
442
492
|
if tuning is not None:
|
|
443
493
|
emit_status("ReSpeaker DSP active: hardware VAD, echo cancellation, DOA.")
|
|
444
494
|
|
|
445
|
-
emit({"type": "ready"})
|
|
446
|
-
|
|
447
495
|
fp16 = str(device).startswith("cuda")
|
|
496
|
+
emit({
|
|
497
|
+
"type": "ready",
|
|
498
|
+
"model": args.model,
|
|
499
|
+
"device": str(device),
|
|
500
|
+
"cuda": bool(fp16),
|
|
501
|
+
"consensusModel": consensus_name if consensus_model is not None else None,
|
|
502
|
+
"vramUsedMB": cuda_memory_reserved_mb(str(device)),
|
|
503
|
+
})
|
|
448
504
|
|
|
449
505
|
def run_transcribe(m, samples):
|
|
450
506
|
return m.transcribe(
|
|
@@ -525,47 +581,46 @@ def main():
|
|
|
525
581
|
return
|
|
526
582
|
samples = amplify(samples)
|
|
527
583
|
try:
|
|
528
|
-
|
|
529
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
530
|
-
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
531
|
-
primary_future = pool.submit(run_transcribe, model, samples)
|
|
532
|
-
secondary_future = pool.submit(run_transcribe, consensus_model, samples)
|
|
533
|
-
result = primary_future.result()
|
|
534
|
-
secondary = secondary_future.result()
|
|
535
|
-
else:
|
|
536
|
-
result = run_transcribe(model, samples)
|
|
537
|
-
secondary = None
|
|
584
|
+
result = run_transcribe(model, samples)
|
|
538
585
|
|
|
539
586
|
text = _segment_filtered_text(result)
|
|
540
587
|
if not text:
|
|
541
588
|
emit_status("Whisper final produced no accepted speech text.")
|
|
542
589
|
return
|
|
543
|
-
if secondary is not None:
|
|
544
|
-
secondary_text = _segment_filtered_text(secondary)
|
|
545
|
-
lang_a = str(result.get("language", "")).lower()
|
|
546
|
-
lang_b = str(secondary.get("language", "")).lower()
|
|
547
|
-
lang_disagree = bool(lang_a and lang_b and lang_a != lang_b and not args.language)
|
|
548
|
-
if lang_disagree or not _texts_agree(text, secondary_text, args.consensus_threshold):
|
|
549
|
-
emit({
|
|
550
|
-
"type": "consensus_rejected",
|
|
551
|
-
"primary": text[:200],
|
|
552
|
-
"secondary": (secondary_text or "")[:200],
|
|
553
|
-
"reason": "language_mismatch" if lang_disagree else "low_similarity",
|
|
554
|
-
})
|
|
555
|
-
return
|
|
556
590
|
if text == last_final_text:
|
|
557
591
|
return
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
592
|
+
doa = tuning.doa() if tuning else None
|
|
593
|
+
if consensus_model is None:
|
|
594
|
+
last_final_text = text
|
|
595
|
+
emit_transcript(text, is_final=True, doa=doa, consensus=None)
|
|
596
|
+
return
|
|
597
|
+
|
|
598
|
+
# Fast UX path: surface the primary model's finalized text
|
|
599
|
+
# immediately as a non-final transcript. The secondary model then
|
|
600
|
+
# validates whether it is safe to accept as a final utterance.
|
|
601
|
+
emit_transcript(text, is_final=False, doa=doa, consensus=False)
|
|
602
|
+
|
|
603
|
+
def validate_consensus(primary_text, primary_result, audio_samples, primary_doa):
|
|
604
|
+
nonlocal last_final_text
|
|
605
|
+
try:
|
|
606
|
+
secondary = run_transcribe(consensus_model, audio_samples)
|
|
607
|
+
secondary_text = _segment_filtered_text(secondary)
|
|
608
|
+
lang_a = str(primary_result.get("language", "")).lower()
|
|
609
|
+
lang_b = str(secondary.get("language", "")).lower()
|
|
610
|
+
lang_disagree = bool(lang_a and lang_b and lang_a != lang_b and not args.language)
|
|
611
|
+
if lang_disagree or not _texts_agree(primary_text, secondary_text, args.consensus_threshold):
|
|
612
|
+
emit_consensus_rejected(primary_text, secondary_text, "language_mismatch" if lang_disagree else "low_similarity")
|
|
613
|
+
return
|
|
614
|
+
last_final_text = primary_text
|
|
615
|
+
emit_transcript(primary_text, is_final=True, doa=primary_doa, consensus=True)
|
|
616
|
+
except Exception as e:
|
|
617
|
+
emit_error(f"Consensus validation error: {e}")
|
|
618
|
+
|
|
619
|
+
threading.Thread(
|
|
620
|
+
target=validate_consensus,
|
|
621
|
+
args=(text, result, samples.copy(), doa),
|
|
622
|
+
daemon=True,
|
|
623
|
+
).start()
|
|
569
624
|
except Exception as e:
|
|
570
625
|
emit_error(f"Transcription error: {e}")
|
|
571
626
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Transcribe an audio file using openai-whisper (
|
|
3
|
+
Transcribe an audio file using openai-whisper (CUDA-only by default).
|
|
4
4
|
|
|
5
5
|
stdin: JSON {"path": "...", "model": "tiny|base|small|medium|large-v3"}
|
|
6
6
|
stdout: JSON {"text": "...", "duration": null, "segments": []}
|
|
7
7
|
or {"error": "<kind>", "message": "..."}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
(e.g. a GT 1030 with sm_61 against a sm_75+ torch build) — those mismatches
|
|
11
|
-
print noisy CUDA warnings and then crash. CPU is slower but always works.
|
|
9
|
+
ASR is GPU-first: CPU fallback is refused unless OMNIUS_ASR_ALLOW_CPU=1.
|
|
12
10
|
"""
|
|
13
11
|
import sys, os, json, warnings
|
|
14
12
|
|
|
15
|
-
# Quiet CUDA warnings from torch even though we won't use the GPU.
|
|
16
13
|
warnings.filterwarnings("ignore")
|
|
17
|
-
os.environ.setdefault("CUDA_VISIBLE_DEVICES", "") # hide all GPUs
|
|
18
14
|
os.environ.setdefault("PYTHONWARNINGS", "ignore")
|
|
19
15
|
|
|
20
16
|
try:
|
|
@@ -39,8 +35,22 @@ def main() -> None:
|
|
|
39
35
|
|
|
40
36
|
model_name = data.get("model") or "tiny"
|
|
41
37
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
device = os.environ.get("OMNIUS_ASR_DEVICE", "cuda").strip().lower() or "cuda"
|
|
39
|
+
allow_cpu = os.environ.get("OMNIUS_ASR_ALLOW_CPU", "").strip().lower() in ("1", "true", "yes", "on")
|
|
40
|
+
if device == "auto":
|
|
41
|
+
device = "cuda"
|
|
42
|
+
if device.startswith("cuda"):
|
|
43
|
+
import torch
|
|
44
|
+
if not torch.cuda.is_available() or torch.cuda.device_count() <= 0:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
f"CUDA-only ASR requested but torch cannot use CUDA "
|
|
47
|
+
f"(torch.version.cuda={getattr(torch.version, 'cuda', None)}, device_count={torch.cuda.device_count()}). "
|
|
48
|
+
"Install a CUDA-enabled PyTorch build for this device, or set OMNIUS_ASR_ALLOW_CPU=1 only for emergency fallback."
|
|
49
|
+
)
|
|
50
|
+
elif device == "cpu" and not allow_cpu:
|
|
51
|
+
raise RuntimeError("CPU ASR refused. Set OMNIUS_ASR_ALLOW_CPU=1 only for explicit emergency fallback.")
|
|
52
|
+
model = whisper.load_model(model_name, device=device)
|
|
53
|
+
result = model.transcribe(path, fp16=device.startswith("cuda"), condition_on_previous_text=False)
|
|
44
54
|
except Exception as e:
|
|
45
55
|
print(json.dumps({"error": "whisper_failed", "message": str(e)}))
|
|
46
56
|
return
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.436",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omnius",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.436",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED