@appkit/llamacpp-cli 1.10.1 → 1.12.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/CHANGELOG.md +10 -0
- package/README.md +356 -3
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin/config.d.ts +10 -0
- package/dist/commands/admin/config.d.ts.map +1 -0
- package/dist/commands/admin/config.js +100 -0
- package/dist/commands/admin/config.js.map +1 -0
- package/dist/commands/admin/logs.d.ts +10 -0
- package/dist/commands/admin/logs.d.ts.map +1 -0
- package/dist/commands/admin/logs.js +114 -0
- package/dist/commands/admin/logs.js.map +1 -0
- package/dist/commands/admin/restart.d.ts +2 -0
- package/dist/commands/admin/restart.d.ts.map +1 -0
- package/dist/commands/admin/restart.js +29 -0
- package/dist/commands/admin/restart.js.map +1 -0
- package/dist/commands/admin/start.d.ts +2 -0
- package/dist/commands/admin/start.d.ts.map +1 -0
- package/dist/commands/admin/start.js +30 -0
- package/dist/commands/admin/start.js.map +1 -0
- package/dist/commands/admin/status.d.ts +2 -0
- package/dist/commands/admin/status.d.ts.map +1 -0
- package/dist/commands/admin/status.js +82 -0
- package/dist/commands/admin/status.js.map +1 -0
- package/dist/commands/admin/stop.d.ts +2 -0
- package/dist/commands/admin/stop.d.ts.map +1 -0
- package/dist/commands/admin/stop.js +21 -0
- package/dist/commands/admin/stop.js.map +1 -0
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +22 -0
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/server-show.js +1 -1
- package/dist/commands/server-show.js.map +1 -1
- package/dist/lib/admin-manager.d.ts +111 -0
- package/dist/lib/admin-manager.d.ts.map +1 -0
- package/dist/lib/admin-manager.js +413 -0
- package/dist/lib/admin-manager.js.map +1 -0
- package/dist/lib/admin-server.d.ts +148 -0
- package/dist/lib/admin-server.d.ts.map +1 -0
- package/dist/lib/admin-server.js +1161 -0
- package/dist/lib/admin-server.js.map +1 -0
- package/dist/lib/download-job-manager.d.ts +64 -0
- package/dist/lib/download-job-manager.d.ts.map +1 -0
- package/dist/lib/download-job-manager.js +164 -0
- package/dist/lib/download-job-manager.js.map +1 -0
- package/dist/tui/MultiServerMonitorApp.js +2 -2
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/types/admin-config.d.ts +19 -0
- package/dist/types/admin-config.d.ts.map +1 -0
- package/dist/types/admin-config.js +3 -0
- package/dist/types/admin-config.js.map +1 -0
- package/dist/utils/format-utils.d.ts +6 -0
- package/dist/utils/format-utils.d.ts.map +1 -1
- package/dist/utils/format-utils.js +18 -0
- package/dist/utils/format-utils.js.map +1 -1
- package/dist/utils/log-parser.d.ts +9 -0
- package/dist/utils/log-parser.d.ts.map +1 -1
- package/dist/utils/log-parser.js +11 -0
- package/dist/utils/log-parser.js.map +1 -1
- package/docs/images/web-ui-servers.png +0 -0
- package/package.json +1 -1
- package/src/cli.ts +100 -0
- package/src/commands/admin/config.ts +121 -0
- package/src/commands/admin/logs.ts +91 -0
- package/src/commands/admin/restart.ts +26 -0
- package/src/commands/admin/start.ts +27 -0
- package/src/commands/admin/status.ts +84 -0
- package/src/commands/admin/stop.ts +16 -0
- package/src/commands/logs.ts +24 -0
- package/src/commands/server-show.ts +2 -2
- package/src/lib/admin-manager.ts +435 -0
- package/src/lib/admin-server.ts +1243 -0
- package/src/lib/download-job-manager.ts +213 -0
- package/src/tui/MultiServerMonitorApp.ts +3 -3
- package/src/types/admin-config.ts +25 -0
- package/src/utils/format-utils.ts +18 -0
- package/src/utils/log-parser.ts +13 -0
- package/web/README.md +429 -0
- package/web/eslint.config.js +23 -0
- package/web/index.html +13 -0
- package/web/llamacpp-web-dist.tar.gz +0 -0
- package/web/package-lock.json +4017 -0
- package/web/package.json +38 -0
- package/web/postcss.config.js +6 -0
- package/web/public/vite.svg +1 -0
- package/web/src/App.css +42 -0
- package/web/src/App.tsx +86 -0
- package/web/src/assets/react.svg +1 -0
- package/web/src/components/ApiKeyPrompt.tsx +71 -0
- package/web/src/components/CreateServerModal.tsx +372 -0
- package/web/src/components/DownloadProgress.tsx +123 -0
- package/web/src/components/Nav.tsx +89 -0
- package/web/src/components/RouterConfigModal.tsx +240 -0
- package/web/src/components/SearchModal.tsx +306 -0
- package/web/src/components/ServerConfigModal.tsx +291 -0
- package/web/src/hooks/useApi.ts +259 -0
- package/web/src/index.css +42 -0
- package/web/src/lib/api.ts +226 -0
- package/web/src/main.tsx +10 -0
- package/web/src/pages/Dashboard.tsx +103 -0
- package/web/src/pages/Models.tsx +258 -0
- package/web/src/pages/Router.tsx +270 -0
- package/web/src/pages/RouterLogs.tsx +201 -0
- package/web/src/pages/ServerLogs.tsx +553 -0
- package/web/src/pages/Servers.tsx +358 -0
- package/web/src/types/api.ts +140 -0
- package/web/tailwind.config.js +31 -0
- package/web/tsconfig.app.json +28 -0
- package/web/tsconfig.json +7 -0
- package/web/tsconfig.node.json +26 -0
- package/web/vite.config.ts +25 -0
- package/MONITORING-ACCURACY-FIX.md +0 -199
- package/PER-PROCESS-METRICS.md +0 -190
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { modelDownloader, DownloadProgress } from './model-downloader';
|
|
3
|
+
import { stateManager } from './state-manager';
|
|
4
|
+
|
|
5
|
+
export type DownloadJobStatus = 'pending' | 'downloading' | 'completed' | 'failed' | 'cancelled';
|
|
6
|
+
|
|
7
|
+
export interface DownloadJob {
|
|
8
|
+
id: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
filename: string;
|
|
11
|
+
status: DownloadJobStatus;
|
|
12
|
+
progress: {
|
|
13
|
+
downloaded: number;
|
|
14
|
+
total: number;
|
|
15
|
+
percentage: number;
|
|
16
|
+
speed: string;
|
|
17
|
+
} | null;
|
|
18
|
+
error?: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
completedAt?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface InternalJob extends DownloadJob {
|
|
24
|
+
abortController: AbortController;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Manages download jobs with progress tracking and cancellation support
|
|
29
|
+
*/
|
|
30
|
+
class DownloadJobManager {
|
|
31
|
+
private jobs: Map<string, InternalJob> = new Map();
|
|
32
|
+
private jobCounter = 0;
|
|
33
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
// Auto-cleanup completed/failed jobs after 5 minutes
|
|
37
|
+
this.cleanupInterval = setInterval(() => this.cleanupOldJobs(), 60000);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new download job
|
|
42
|
+
*/
|
|
43
|
+
createJob(repo: string, filename: string): string {
|
|
44
|
+
const id = `download-${Date.now()}-${++this.jobCounter}`;
|
|
45
|
+
const abortController = new AbortController();
|
|
46
|
+
|
|
47
|
+
const job: InternalJob = {
|
|
48
|
+
id,
|
|
49
|
+
repo,
|
|
50
|
+
filename,
|
|
51
|
+
status: 'pending',
|
|
52
|
+
progress: null,
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
abortController,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.jobs.set(id, job);
|
|
58
|
+
|
|
59
|
+
// Start download asynchronously
|
|
60
|
+
this.startDownload(job);
|
|
61
|
+
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get a job by ID
|
|
67
|
+
*/
|
|
68
|
+
getJob(id: string): DownloadJob | null {
|
|
69
|
+
const job = this.jobs.get(id);
|
|
70
|
+
if (!job) return null;
|
|
71
|
+
|
|
72
|
+
// Return public job info (without abortController)
|
|
73
|
+
return this.toPublicJob(job);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* List all jobs
|
|
78
|
+
*/
|
|
79
|
+
listJobs(): DownloadJob[] {
|
|
80
|
+
return Array.from(this.jobs.values()).map(job => this.toPublicJob(job));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Cancel a download job
|
|
85
|
+
*/
|
|
86
|
+
cancelJob(id: string): boolean {
|
|
87
|
+
const job = this.jobs.get(id);
|
|
88
|
+
if (!job) return false;
|
|
89
|
+
|
|
90
|
+
if (job.status === 'pending' || job.status === 'downloading') {
|
|
91
|
+
job.abortController.abort();
|
|
92
|
+
job.status = 'cancelled';
|
|
93
|
+
job.completedAt = new Date().toISOString();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Delete a job from the list
|
|
102
|
+
*/
|
|
103
|
+
deleteJob(id: string): boolean {
|
|
104
|
+
const job = this.jobs.get(id);
|
|
105
|
+
if (!job) return false;
|
|
106
|
+
|
|
107
|
+
// Cancel if still running
|
|
108
|
+
if (job.status === 'pending' || job.status === 'downloading') {
|
|
109
|
+
job.abortController.abort();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.jobs.delete(id);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Start the download process for a job
|
|
118
|
+
*/
|
|
119
|
+
private async startDownload(job: InternalJob): Promise<void> {
|
|
120
|
+
job.status = 'downloading';
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const modelsDir = await stateManager.getModelsDirectory();
|
|
124
|
+
|
|
125
|
+
await modelDownloader.downloadModel(
|
|
126
|
+
job.repo,
|
|
127
|
+
job.filename,
|
|
128
|
+
(progress: DownloadProgress) => {
|
|
129
|
+
job.progress = {
|
|
130
|
+
downloaded: progress.downloaded,
|
|
131
|
+
total: progress.total,
|
|
132
|
+
percentage: progress.percentage,
|
|
133
|
+
speed: progress.speed,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
modelsDir,
|
|
137
|
+
{
|
|
138
|
+
silent: true,
|
|
139
|
+
signal: job.abortController.signal,
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Only mark as completed if not cancelled
|
|
144
|
+
if (job.status === 'downloading') {
|
|
145
|
+
job.status = 'completed';
|
|
146
|
+
job.completedAt = new Date().toISOString();
|
|
147
|
+
// Ensure progress shows 100%
|
|
148
|
+
if (job.progress) {
|
|
149
|
+
job.progress.percentage = 100;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// Check if this was a cancellation (status may have been set by cancelJob)
|
|
154
|
+
const currentStatus = job.status as DownloadJobStatus;
|
|
155
|
+
if (currentStatus === 'cancelled') {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const message = (error as Error).message;
|
|
160
|
+
if (message.includes('cancelled') || message.includes('interrupted')) {
|
|
161
|
+
job.status = 'cancelled';
|
|
162
|
+
} else {
|
|
163
|
+
job.status = 'failed';
|
|
164
|
+
job.error = message;
|
|
165
|
+
}
|
|
166
|
+
job.completedAt = new Date().toISOString();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Convert internal job to public job (strips internal fields)
|
|
172
|
+
*/
|
|
173
|
+
private toPublicJob(job: InternalJob): DownloadJob {
|
|
174
|
+
const { abortController, ...publicJob } = job;
|
|
175
|
+
return publicJob;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Clean up old completed/failed jobs
|
|
180
|
+
*/
|
|
181
|
+
private cleanupOldJobs(): void {
|
|
182
|
+
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
|
|
183
|
+
|
|
184
|
+
for (const [id, job] of this.jobs.entries()) {
|
|
185
|
+
if (
|
|
186
|
+
job.completedAt &&
|
|
187
|
+
new Date(job.completedAt).getTime() < fiveMinutesAgo
|
|
188
|
+
) {
|
|
189
|
+
this.jobs.delete(id);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Cleanup on shutdown
|
|
196
|
+
*/
|
|
197
|
+
shutdown(): void {
|
|
198
|
+
if (this.cleanupInterval) {
|
|
199
|
+
clearInterval(this.cleanupInterval);
|
|
200
|
+
this.cleanupInterval = null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Cancel all active downloads
|
|
204
|
+
for (const job of this.jobs.values()) {
|
|
205
|
+
if (job.status === 'pending' || job.status === 'downloading') {
|
|
206
|
+
job.abortController.abort();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Export singleton instance
|
|
213
|
+
export const downloadJobManager = new DownloadJobManager();
|
|
@@ -16,7 +16,7 @@ import { portManager } from '../lib/port-manager.js';
|
|
|
16
16
|
import { configGenerator, ServerOptions } from '../lib/config-generator.js';
|
|
17
17
|
import { ModelInfo } from '../types/model-info.js';
|
|
18
18
|
import { getLogsDir, getLaunchAgentsDir, ensureDir, parseMetalMemoryFromLog } from '../utils/file-utils.js';
|
|
19
|
-
import { formatBytes } from '../utils/format-utils.js';
|
|
19
|
+
import { formatBytes, formatContextSize } from '../utils/format-utils.js';
|
|
20
20
|
import { isPortInUse } from '../utils/process-utils.js';
|
|
21
21
|
|
|
22
22
|
type ViewMode = 'list' | 'detail';
|
|
@@ -41,7 +41,7 @@ export async function createMultiServerMonitorUI(
|
|
|
41
41
|
onModels?: (controls: MonitorUIControls) => void,
|
|
42
42
|
onFirstRender?: () => void
|
|
43
43
|
): Promise<MonitorUIControls> {
|
|
44
|
-
let updateInterval =
|
|
44
|
+
let updateInterval = 5000;
|
|
45
45
|
let intervalId: NodeJS.Timeout | null = null;
|
|
46
46
|
let viewMode: ViewMode = directJumpIndex !== undefined ? 'detail' : 'list';
|
|
47
47
|
let selectedServerIndex = directJumpIndex ?? 0;
|
|
@@ -467,7 +467,7 @@ export async function createMultiServerMonitorUI(
|
|
|
467
467
|
|
|
468
468
|
content += `Model: ${server.modelName}`;
|
|
469
469
|
if (data.server.contextSize) {
|
|
470
|
-
content += ` Context: ${data.server.contextSize}
|
|
470
|
+
content += ` Context: ${formatContextSize(data.server.contextSize)}/slot`;
|
|
471
471
|
}
|
|
472
472
|
content += '\n';
|
|
473
473
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AdminStatus = 'running' | 'stopped' | 'crashed';
|
|
2
|
+
|
|
3
|
+
export interface AdminConfig {
|
|
4
|
+
id: 'admin';
|
|
5
|
+
port: number;
|
|
6
|
+
host: string;
|
|
7
|
+
apiKey: string; // Auto-generated on first start
|
|
8
|
+
|
|
9
|
+
// State tracking
|
|
10
|
+
status: AdminStatus;
|
|
11
|
+
pid?: number;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
lastStarted?: string;
|
|
14
|
+
lastStopped?: string;
|
|
15
|
+
|
|
16
|
+
// launchctl metadata
|
|
17
|
+
plistPath: string;
|
|
18
|
+
label: 'com.llama.admin';
|
|
19
|
+
stdoutPath: string;
|
|
20
|
+
stderrPath: string;
|
|
21
|
+
|
|
22
|
+
// Admin settings
|
|
23
|
+
requestTimeout: number; // ms for API requests (default: 30000)
|
|
24
|
+
verbose: boolean; // Enable verbose logging to file (default: false)
|
|
25
|
+
}
|
|
@@ -78,3 +78,21 @@ export function truncate(str: string, maxLength: number): string {
|
|
|
78
78
|
export function pad(str: string, length: number, char = ' '): string {
|
|
79
79
|
return str.padEnd(length, char);
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format context size to human-readable format
|
|
84
|
+
* Uses "k" suffix for clean multiples of 1024 (e.g., 32768 → "32k")
|
|
85
|
+
* Falls back to full number with "tokens" for non-standard sizes
|
|
86
|
+
*/
|
|
87
|
+
export function formatContextSize(tokens: number): string {
|
|
88
|
+
// Check if it's a clean multiple of 1024
|
|
89
|
+
if (tokens % 1024 === 0) {
|
|
90
|
+
const k = tokens / 1024;
|
|
91
|
+
// Only use "k" format for reasonable sizes (1k to 1024k i.e., up to 1M)
|
|
92
|
+
if (k >= 1 && k <= 1024) {
|
|
93
|
+
return `${k}k`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// For non-standard sizes or very large values, show full number
|
|
97
|
+
return `${tokens.toLocaleString()} tokens`;
|
|
98
|
+
}
|
package/src/utils/log-parser.ts
CHANGED
|
@@ -18,6 +18,19 @@ export class LogParser {
|
|
|
18
18
|
private buffer: string[] = [];
|
|
19
19
|
private isBuffering = false;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Health check endpoints to filter out by default
|
|
23
|
+
* These are polled frequently by the TUI and generate excessive log noise
|
|
24
|
+
*/
|
|
25
|
+
private static readonly HEALTH_CHECK_ENDPOINTS = ['/health', '/slots', '/props'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a log line represents a health check request
|
|
29
|
+
*/
|
|
30
|
+
isHealthCheckRequest(line: string): boolean {
|
|
31
|
+
return LogParser.HEALTH_CHECK_ENDPOINTS.some(ep => line.includes(`GET ${ep} `));
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
/**
|
|
22
35
|
* Check if line is a request status line (contains method/endpoint/status, no JSON)
|
|
23
36
|
* Handles both old and new formats:
|