-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.ts
More file actions
103 lines (90 loc) · 3.29 KB
/
models.ts
File metadata and controls
103 lines (90 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { execFile, spawn } from 'child_process';
import { findBirda } from './runner';
import type { InstalledModel, AvailableModel } from '$shared/types';
interface BirdaJsonEnvelope {
spec_version: string;
timestamp: string;
event: string;
payload: Record<string, unknown>;
}
async function runBirdaJson(args: string[]): Promise<BirdaJsonEnvelope> {
const birdaPath = await findBirda();
return new Promise((resolve, reject) => {
execFile(birdaPath, args, { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
if (err) {
reject(new Error(`birda command failed: ${stderr || err.message}`));
return;
}
try {
resolve(JSON.parse(stdout) as BirdaJsonEnvelope);
} catch {
reject(new Error(`Failed to parse birda output as JSON: ${stdout.slice(0, 200)}`));
}
});
});
}
export async function listModels(): Promise<InstalledModel[]> {
const envelope = await runBirdaJson(['--output-mode', 'json', 'models', 'list']);
const payload = envelope.payload as { models?: InstalledModel[] };
return payload.models ?? [];
}
export async function listAvailable(): Promise<AvailableModel[]> {
const envelope = await runBirdaJson(['--output-mode', 'json', 'models', 'list-available']);
const payload = envelope.payload as { models?: AvailableModel[] };
return payload.models ?? [];
}
export async function installModel(name: string, onProgress?: (line: string) => void): Promise<string> {
const birdaPath = await findBirda();
return new Promise((resolve, reject) => {
const proc = spawn(birdaPath, ['models', 'install', name], {
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
let stdoutRemainder = '';
let stderrRemainder = '';
const reportProgress = (chunk: string, remainder: string): string => {
if (!onProgress) return remainder + chunk;
const text = remainder + chunk;
const parts = text.split('\n');
const newRemainder = parts.pop() ?? '';
for (const line of parts) {
const trimmed = line.trim();
if (trimmed) onProgress(trimmed);
}
return newRemainder;
};
proc.stdout.on('data', (data: Buffer) => {
const text = data.toString();
stdout += text;
stdoutRemainder = reportProgress(text, stdoutRemainder);
});
proc.stderr.on('data', (data: Buffer) => {
const text = data.toString();
stderr += text;
stderrRemainder = reportProgress(text, stderrRemainder);
});
// Auto-accept the license prompt; the GUI shows its own acceptance dialog
// before calling this function.
proc.stdin.write('accept\n');
proc.stdin.end();
proc.on('close', (code) => {
if (onProgress) {
if (stdoutRemainder.trim()) onProgress(stdoutRemainder.trim());
if (stderrRemainder.trim()) onProgress(stderrRemainder.trim());
}
if (code !== 0) {
reject(new Error(`Model install failed: ${stderr || stdout}`));
} else {
resolve(stdout);
}
});
proc.on('error', (err) => {
reject(new Error(`Model install failed: ${err.message}`));
});
});
}
export async function modelInfo(name: string): Promise<unknown> {
const envelope = await runBirdaJson(['--output-mode', 'json', 'models', 'info', name]);
return envelope.payload;
}