@agentprojectcontext/apx 1.45.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/host/daemon/stt-venv.js +100 -0
- package/src/host/daemon/whisper-server.js +4 -1
- package/src/interfaces/web/dist/assets/{index-BAKk7d_M.css → index-CilEtMjV.css} +1 -1
- package/src/interfaces/web/dist/assets/{index-D7px5xcy.js → index-oOjZZktw.js} +2 -2
- package/src/interfaces/web/dist/assets/{index-D7px5xcy.js.map → index-oOjZZktw.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +5 -5
package/package.json
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Dedicated Python virtualenv for the STT engines.
|
|
2
|
+
//
|
|
3
|
+
// We never install into the system or user-site Python — mlx-whisper drags in
|
|
4
|
+
// torch/scipy/numba and would pollute (or clash with) the user's other
|
|
5
|
+
// projects. Instead APX owns ~/.apx/runtime/whisper-venv: create it, install
|
|
6
|
+
// faster-whisper / mlx-whisper into it, and spawn whisper-server.py with its
|
|
7
|
+
// interpreter. "Reset the engine" = delete the folder and recreate it.
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
|
|
13
|
+
export const VENV_DIR = path.join(os.homedir(), ".apx", "runtime", "whisper-venv");
|
|
14
|
+
|
|
15
|
+
/** Path to the venv's python (…/bin/python on POSIX, …/Scripts on Windows). */
|
|
16
|
+
export function venvPython() {
|
|
17
|
+
return process.platform === "win32"
|
|
18
|
+
? path.join(VENV_DIR, "Scripts", "python.exe")
|
|
19
|
+
: path.join(VENV_DIR, "bin", "python");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True once the venv has a usable interpreter. */
|
|
23
|
+
export function venvExists() {
|
|
24
|
+
try { return fs.existsSync(venvPython()); } catch { return false; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Interpreter whisper-server.py should run under: the venv if it exists, else
|
|
29
|
+
* the system python3 (legacy path — faster-whisper from the user-site).
|
|
30
|
+
*/
|
|
31
|
+
export function pythonForWhisper() {
|
|
32
|
+
return venvExists() ? venvPython() : "python3";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function run(cmd, args, onLine) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
let proc;
|
|
38
|
+
try {
|
|
39
|
+
proc = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
40
|
+
} catch (e) {
|
|
41
|
+
onLine?.(`spawn failed: ${e.message}`);
|
|
42
|
+
return resolve({ ok: false, code: -1 });
|
|
43
|
+
}
|
|
44
|
+
const pump = (chunk) => {
|
|
45
|
+
for (const line of chunk.toString().split(/\r?\n/)) {
|
|
46
|
+
if (line.trim()) onLine?.(line);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
proc.stdout.on("data", pump);
|
|
50
|
+
proc.stderr.on("data", pump);
|
|
51
|
+
proc.on("exit", (code) => resolve({ ok: code === 0, code }));
|
|
52
|
+
proc.on("error", (e) => { onLine?.(e.message); resolve({ ok: false, code: -1 }); });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Create the venv (idempotent). Streams progress lines to onLine. */
|
|
57
|
+
export async function ensureVenv(onLine) {
|
|
58
|
+
if (venvExists()) return { ok: true, created: false };
|
|
59
|
+
fs.mkdirSync(path.dirname(VENV_DIR), { recursive: true });
|
|
60
|
+
onLine?.(`creating venv at ${VENV_DIR}…`);
|
|
61
|
+
const r = await run("python3", ["-m", "venv", VENV_DIR], onLine);
|
|
62
|
+
if (!r.ok || !venvExists()) return { ok: false, error: "venv creation failed" };
|
|
63
|
+
// Upgrade pip once so wheel resolution is fast/modern.
|
|
64
|
+
await run(venvPython(), ["-m", "pip", "install", "--upgrade", "pip", "wheel"], onLine);
|
|
65
|
+
return { ok: true, created: true };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Is a module importable inside the venv? */
|
|
69
|
+
export function venvHasModule(mod) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
if (!venvExists()) return resolve(false);
|
|
72
|
+
const p = spawn(venvPython(), ["-c", `import ${mod}`], { stdio: "ignore" });
|
|
73
|
+
p.on("exit", (code) => resolve(code === 0));
|
|
74
|
+
p.on("error", () => resolve(false));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** pip install <pkgs> into the venv, streaming progress. Creates the venv first. */
|
|
79
|
+
export async function pipInstall(pkgs, onLine) {
|
|
80
|
+
const ensured = await ensureVenv(onLine);
|
|
81
|
+
if (!ensured.ok) return ensured;
|
|
82
|
+
const list = Array.isArray(pkgs) ? pkgs : [pkgs];
|
|
83
|
+
onLine?.(`installing ${list.join(", ")}…`);
|
|
84
|
+
const r = await run(venvPython(), ["-m", "pip", "install", "--upgrade", ...list], onLine);
|
|
85
|
+
return { ok: r.ok, code: r.code };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Delete the whole venv (so the engine can be reinstalled clean). */
|
|
89
|
+
export async function removeVenv(onLine) {
|
|
90
|
+
if (!fs.existsSync(VENV_DIR)) return { ok: true, removed: false };
|
|
91
|
+
onLine?.(`removing ${VENV_DIR}…`);
|
|
92
|
+
await fs.promises.rm(VENV_DIR, { recursive: true, force: true });
|
|
93
|
+
return { ok: !fs.existsSync(VENV_DIR), removed: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Engine → the pip package(s) it needs in the venv. */
|
|
97
|
+
export const ENGINE_PACKAGES = {
|
|
98
|
+
faster: ["faster-whisper"],
|
|
99
|
+
mlx: ["mlx-whisper"],
|
|
100
|
+
};
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DEFAULT_LOCAL,
|
|
17
17
|
getConfig,
|
|
18
18
|
} from "#core/voice/transcription.js";
|
|
19
|
+
import { pythonForWhisper } from "./stt-venv.js";
|
|
19
20
|
|
|
20
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
22
|
const __dirname = path.dirname(__filename);
|
|
@@ -126,7 +127,9 @@ async function _spawnWhisper(opts, model, backend, retried) {
|
|
|
126
127
|
"--idle-minutes", String(opts.idle_minutes ?? DEFAULT_LOCAL.idle_minutes),
|
|
127
128
|
];
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
// Prefer APX's dedicated venv interpreter (isolated mlx/faster-whisper);
|
|
131
|
+
// fall back to system python3 for the legacy user-site install.
|
|
132
|
+
const proc = spawn(pythonForWhisper(), args, {
|
|
130
133
|
stdio: ["ignore", "pipe", "inherit"],
|
|
131
134
|
detached: false,
|
|
132
135
|
});
|