@appkit/llamacpp-cli 1.12.0 → 1.13.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/README.md +294 -168
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/launch/claude.d.ts +6 -0
- package/dist/commands/launch/claude.d.ts.map +1 -0
- package/dist/commands/launch/claude.js +277 -0
- package/dist/commands/launch/claude.js.map +1 -0
- package/dist/lib/integration-checker.d.ts +26 -0
- package/dist/lib/integration-checker.d.ts.map +1 -0
- package/dist/lib/integration-checker.js +77 -0
- package/dist/lib/integration-checker.js.map +1 -0
- package/dist/lib/router-manager.d.ts +4 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +10 -0
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts +13 -0
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +267 -7
- package/dist/lib/router-server.js.map +1 -1
- package/dist/types/integration-config.d.ts +28 -0
- package/dist/types/integration-config.d.ts.map +1 -0
- package/dist/types/integration-config.js +3 -0
- package/dist/types/integration-config.js.map +1 -0
- package/package.json +10 -2
- package/web/dist/assets/index-Bin89Lwr.css +1 -0
- package/web/dist/assets/index-CVmonw3T.js +17 -0
- package/web/{index.html → dist/index.html} +2 -1
- package/.versionrc.json +0 -16
- package/CHANGELOG.md +0 -213
- package/docs/images/.gitkeep +0 -1
- package/docs/images/web-ui-servers.png +0 -0
- package/src/cli.ts +0 -523
- package/src/commands/admin/config.ts +0 -121
- package/src/commands/admin/logs.ts +0 -91
- package/src/commands/admin/restart.ts +0 -26
- package/src/commands/admin/start.ts +0 -27
- package/src/commands/admin/status.ts +0 -84
- package/src/commands/admin/stop.ts +0 -16
- package/src/commands/config-global.ts +0 -38
- package/src/commands/config.ts +0 -323
- package/src/commands/create.ts +0 -183
- package/src/commands/delete.ts +0 -74
- package/src/commands/list.ts +0 -37
- package/src/commands/logs-all.ts +0 -251
- package/src/commands/logs.ts +0 -345
- package/src/commands/monitor.ts +0 -110
- package/src/commands/ps.ts +0 -84
- package/src/commands/pull.ts +0 -44
- package/src/commands/rm.ts +0 -107
- package/src/commands/router/config.ts +0 -116
- package/src/commands/router/logs.ts +0 -256
- package/src/commands/router/restart.ts +0 -36
- package/src/commands/router/start.ts +0 -60
- package/src/commands/router/status.ts +0 -119
- package/src/commands/router/stop.ts +0 -33
- package/src/commands/run.ts +0 -233
- package/src/commands/search.ts +0 -107
- package/src/commands/server-show.ts +0 -161
- package/src/commands/show.ts +0 -207
- package/src/commands/start.ts +0 -101
- package/src/commands/stop.ts +0 -39
- package/src/commands/tui.ts +0 -25
- package/src/lib/admin-manager.ts +0 -435
- package/src/lib/admin-server.ts +0 -1243
- package/src/lib/config-generator.ts +0 -130
- package/src/lib/download-job-manager.ts +0 -213
- package/src/lib/history-manager.ts +0 -172
- package/src/lib/launchctl-manager.ts +0 -225
- package/src/lib/metrics-aggregator.ts +0 -257
- package/src/lib/model-downloader.ts +0 -328
- package/src/lib/model-scanner.ts +0 -157
- package/src/lib/model-search.ts +0 -114
- package/src/lib/models-dir-setup.ts +0 -46
- package/src/lib/port-manager.ts +0 -80
- package/src/lib/router-logger.ts +0 -201
- package/src/lib/router-manager.ts +0 -414
- package/src/lib/router-server.ts +0 -538
- package/src/lib/state-manager.ts +0 -206
- package/src/lib/status-checker.ts +0 -113
- package/src/lib/system-collector.ts +0 -315
- package/src/tui/ConfigApp.ts +0 -1085
- package/src/tui/HistoricalMonitorApp.ts +0 -587
- package/src/tui/ModelsApp.ts +0 -368
- package/src/tui/MonitorApp.ts +0 -386
- package/src/tui/MultiServerMonitorApp.ts +0 -1833
- package/src/tui/RootNavigator.ts +0 -74
- package/src/tui/SearchApp.ts +0 -511
- package/src/tui/SplashScreen.ts +0 -149
- package/src/types/admin-config.ts +0 -25
- package/src/types/global-config.ts +0 -26
- package/src/types/history-types.ts +0 -39
- package/src/types/model-info.ts +0 -8
- package/src/types/monitor-types.ts +0 -162
- package/src/types/router-config.ts +0 -25
- package/src/types/server-config.ts +0 -46
- package/src/utils/downsample-utils.ts +0 -128
- package/src/utils/file-utils.ts +0 -146
- package/src/utils/format-utils.ts +0 -98
- package/src/utils/log-parser.ts +0 -284
- package/src/utils/log-utils.ts +0 -178
- package/src/utils/process-utils.ts +0 -316
- package/src/utils/prompt-utils.ts +0 -47
- package/test-load.sh +0 -100
- package/tsconfig.json +0 -20
- package/web/eslint.config.js +0 -23
- package/web/llamacpp-web-dist.tar.gz +0 -0
- package/web/package-lock.json +0 -4017
- package/web/package.json +0 -38
- package/web/postcss.config.js +0 -6
- package/web/src/App.css +0 -42
- package/web/src/App.tsx +0 -86
- package/web/src/assets/react.svg +0 -1
- package/web/src/components/ApiKeyPrompt.tsx +0 -71
- package/web/src/components/CreateServerModal.tsx +0 -372
- package/web/src/components/DownloadProgress.tsx +0 -123
- package/web/src/components/Nav.tsx +0 -89
- package/web/src/components/RouterConfigModal.tsx +0 -240
- package/web/src/components/SearchModal.tsx +0 -306
- package/web/src/components/ServerConfigModal.tsx +0 -291
- package/web/src/hooks/useApi.ts +0 -259
- package/web/src/index.css +0 -42
- package/web/src/lib/api.ts +0 -226
- package/web/src/main.tsx +0 -10
- package/web/src/pages/Dashboard.tsx +0 -103
- package/web/src/pages/Models.tsx +0 -258
- package/web/src/pages/Router.tsx +0 -270
- package/web/src/pages/RouterLogs.tsx +0 -201
- package/web/src/pages/ServerLogs.tsx +0 -553
- package/web/src/pages/Servers.tsx +0 -358
- package/web/src/types/api.ts +0 -140
- package/web/tailwind.config.js +0 -31
- package/web/tsconfig.app.json +0 -28
- package/web/tsconfig.json +0 -7
- package/web/tsconfig.node.json +0 -26
- package/web/vite.config.ts +0 -25
- /package/web/{public → dist}/vite.svg +0 -0
package/web/src/pages/Models.tsx
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react';
|
|
2
|
-
import { useModels, useDeleteModel, useDownloadJobs } from '../hooks/useApi';
|
|
3
|
-
import { HardDrive, Server, Trash2, Clock, Loader2, Download } from 'lucide-react';
|
|
4
|
-
import { SearchModal } from '../components/SearchModal';
|
|
5
|
-
import { DownloadProgress } from '../components/DownloadProgress';
|
|
6
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
7
|
-
|
|
8
|
-
interface ModelsProps {
|
|
9
|
-
searchQuery?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type ModelFilter = 'all' | 'active' | 'inactive';
|
|
13
|
-
|
|
14
|
-
export function Models({ searchQuery = '' }: ModelsProps) {
|
|
15
|
-
const queryClient = useQueryClient();
|
|
16
|
-
const { data: modelsData, isLoading } = useModels();
|
|
17
|
-
const deleteModel = useDeleteModel();
|
|
18
|
-
const { data: jobsData } = useDownloadJobs(true);
|
|
19
|
-
|
|
20
|
-
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
|
21
|
-
const [showSearchModal, setShowSearchModal] = useState(false);
|
|
22
|
-
const [filter, setFilter] = useState<ModelFilter>('all');
|
|
23
|
-
|
|
24
|
-
const hasActiveDownloads = (jobsData?.jobs || []).some(
|
|
25
|
-
job => job.status === 'pending' || job.status === 'downloading'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const handleDownloadComplete = () => {
|
|
29
|
-
queryClient.invalidateQueries({ queryKey: ['models'] });
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const models = modelsData?.models || [];
|
|
33
|
-
|
|
34
|
-
const filteredModels = useMemo(() => {
|
|
35
|
-
let filtered = models;
|
|
36
|
-
|
|
37
|
-
// Apply search filter
|
|
38
|
-
if (searchQuery) {
|
|
39
|
-
const query = searchQuery.toLowerCase();
|
|
40
|
-
filtered = filtered.filter(model =>
|
|
41
|
-
model.filename.toLowerCase().includes(query)
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Apply status filter
|
|
46
|
-
if (filter === 'active') {
|
|
47
|
-
filtered = filtered.filter(model => model.serversUsing > 0);
|
|
48
|
-
} else if (filter === 'inactive') {
|
|
49
|
-
filtered = filtered.filter(model => model.serversUsing === 0);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return filtered;
|
|
53
|
-
}, [models, searchQuery, filter]);
|
|
54
|
-
|
|
55
|
-
const handleDelete = async (name: string, serversUsing: number) => {
|
|
56
|
-
if (serversUsing > 0) {
|
|
57
|
-
if (!confirm(`Model "${name}" is used by ${serversUsing} server(s). Delete the model AND all associated servers?`)) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
if (!confirm(`Delete model "${name}"? This cannot be undone.`)) return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
setActionLoading(name);
|
|
65
|
-
try {
|
|
66
|
-
await deleteModel.mutateAsync({ name, cascade: serversUsing > 0 });
|
|
67
|
-
} finally {
|
|
68
|
-
setActionLoading(null);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const formatSize = (bytes: number) => {
|
|
73
|
-
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
74
|
-
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
75
|
-
return `${(bytes / 1e3).toFixed(1)} KB`;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const formatDate = (dateStr: string | Date) => {
|
|
79
|
-
const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr;
|
|
80
|
-
const now = new Date();
|
|
81
|
-
const diffMs = now.getTime() - date.getTime();
|
|
82
|
-
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
83
|
-
|
|
84
|
-
if (diffDays === 0) return 'Today';
|
|
85
|
-
if (diffDays === 1) return 'Yesterday';
|
|
86
|
-
if (diffDays < 7) return `${diffDays}d ago`;
|
|
87
|
-
if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
|
|
88
|
-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
if (isLoading) {
|
|
92
|
-
return (
|
|
93
|
-
<div className="max-w-7xl mx-auto px-6 py-12">
|
|
94
|
-
<p className="text-neutral-500 text-center">Loading...</p>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const activeModels = models.filter(m => m.serversUsing > 0);
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<div className="max-w-7xl mx-auto px-6 py-8">
|
|
103
|
-
{/* Header */}
|
|
104
|
-
<div className="flex items-center justify-between mb-6">
|
|
105
|
-
<div>
|
|
106
|
-
<h1 className="text-2xl font-semibold text-neutral-900 tracking-tight">Models</h1>
|
|
107
|
-
<p className="text-sm text-neutral-600 mt-1">
|
|
108
|
-
{models.length} model{models.length !== 1 ? 's' : ''} available
|
|
109
|
-
{activeModels.length > 0 && ` • ${activeModels.length} active`}
|
|
110
|
-
</p>
|
|
111
|
-
</div>
|
|
112
|
-
<button
|
|
113
|
-
onClick={() => setShowSearchModal(true)}
|
|
114
|
-
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-800 rounded-md transition-colors cursor-pointer"
|
|
115
|
-
>
|
|
116
|
-
<Download className="w-4 h-4" />
|
|
117
|
-
Pull Model
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{/* Filter Buttons */}
|
|
122
|
-
<div className="flex items-center gap-2 mb-6">
|
|
123
|
-
<button
|
|
124
|
-
onClick={() => setFilter('all')}
|
|
125
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-full border transition-all cursor-pointer ${
|
|
126
|
-
filter === 'all'
|
|
127
|
-
? 'bg-neutral-900 text-white border-neutral-900'
|
|
128
|
-
: 'bg-white text-neutral-600 border-neutral-200 hover:border-neutral-300'
|
|
129
|
-
}`}
|
|
130
|
-
>
|
|
131
|
-
All
|
|
132
|
-
</button>
|
|
133
|
-
<button
|
|
134
|
-
onClick={() => setFilter('active')}
|
|
135
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-full border transition-all cursor-pointer ${
|
|
136
|
-
filter === 'active'
|
|
137
|
-
? 'bg-neutral-900 text-white border-neutral-900'
|
|
138
|
-
: 'bg-white text-neutral-600 border-neutral-200 hover:border-neutral-300'
|
|
139
|
-
}`}
|
|
140
|
-
>
|
|
141
|
-
Active
|
|
142
|
-
</button>
|
|
143
|
-
<button
|
|
144
|
-
onClick={() => setFilter('inactive')}
|
|
145
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-full border transition-all cursor-pointer ${
|
|
146
|
-
filter === 'inactive'
|
|
147
|
-
? 'bg-neutral-900 text-white border-neutral-900'
|
|
148
|
-
: 'bg-white text-neutral-600 border-neutral-200 hover:border-neutral-300'
|
|
149
|
-
}`}
|
|
150
|
-
>
|
|
151
|
-
Inactive
|
|
152
|
-
</button>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
{filteredModels.length === 0 ? (
|
|
156
|
-
<div className="text-center py-16 bg-white border border-neutral-200 rounded-lg">
|
|
157
|
-
{searchQuery ? (
|
|
158
|
-
<>
|
|
159
|
-
<p className="text-neutral-600 text-base mb-2">No models matching "{searchQuery}"</p>
|
|
160
|
-
<p className="text-sm text-neutral-500">Try a different search term</p>
|
|
161
|
-
</>
|
|
162
|
-
) : filter !== 'all' && models.length > 0 ? (
|
|
163
|
-
<>
|
|
164
|
-
<p className="text-neutral-600 text-base mb-2">No {filter} models</p>
|
|
165
|
-
<p className="text-sm text-neutral-500">Try a different filter</p>
|
|
166
|
-
</>
|
|
167
|
-
) : (
|
|
168
|
-
<>
|
|
169
|
-
<p className="text-neutral-600 text-base mb-2">No models found</p>
|
|
170
|
-
<p className="text-sm text-neutral-500 mb-6">
|
|
171
|
-
Download models from Hugging Face to get started
|
|
172
|
-
</p>
|
|
173
|
-
<button
|
|
174
|
-
onClick={() => setShowSearchModal(true)}
|
|
175
|
-
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-800 rounded-md transition-colors cursor-pointer"
|
|
176
|
-
>
|
|
177
|
-
<Download className="w-4 h-4" />
|
|
178
|
-
Pull Model
|
|
179
|
-
</button>
|
|
180
|
-
</>
|
|
181
|
-
)}
|
|
182
|
-
</div>
|
|
183
|
-
) : (
|
|
184
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
185
|
-
{filteredModels.map((model) => (
|
|
186
|
-
<div
|
|
187
|
-
key={model.filename}
|
|
188
|
-
className="group bg-white border border-neutral-200 rounded-lg p-5 hover:border-neutral-300 hover:shadow-sm transition-all"
|
|
189
|
-
>
|
|
190
|
-
<div className="flex items-start justify-between mb-4">
|
|
191
|
-
<div className="flex-1 min-w-0">
|
|
192
|
-
<h3 className="text-base font-semibold text-neutral-900 truncate mb-2">
|
|
193
|
-
{model.filename.replace('.gguf', '')}
|
|
194
|
-
</h3>
|
|
195
|
-
<div className="flex flex-wrap gap-1.5">
|
|
196
|
-
{model.serversUsing > 0 && (
|
|
197
|
-
<span className="inline-flex items-center px-2 py-0.5 rounded-md border border-green-200/50 bg-green-50 text-xs font-medium text-green-700">
|
|
198
|
-
active
|
|
199
|
-
</span>
|
|
200
|
-
)}
|
|
201
|
-
<span className="inline-flex items-center px-2 py-0.5 rounded-md border border-neutral-200 bg-neutral-50 text-xs font-medium text-neutral-600">
|
|
202
|
-
gguf
|
|
203
|
-
</span>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
{/* Stats */}
|
|
209
|
-
<div className="space-y-2 mb-4">
|
|
210
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
211
|
-
<HardDrive className="w-3.5 h-3.5 text-neutral-400" />
|
|
212
|
-
<span>{formatSize(model.size)}</span>
|
|
213
|
-
</div>
|
|
214
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
215
|
-
<Server className="w-3.5 h-3.5 text-neutral-400" />
|
|
216
|
-
<span>{model.serversUsing} server{model.serversUsing !== 1 ? 's' : ''}</span>
|
|
217
|
-
</div>
|
|
218
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
219
|
-
<Clock className="w-3.5 h-3.5 text-neutral-400" />
|
|
220
|
-
<span>{formatDate(model.modified)}</span>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{/* Delete Action */}
|
|
225
|
-
<button
|
|
226
|
-
onClick={() => handleDelete(model.filename, model.serversUsing)}
|
|
227
|
-
disabled={actionLoading === model.filename}
|
|
228
|
-
className="w-full flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors opacity-0 group-hover:opacity-100 disabled:opacity-100 cursor-pointer disabled:cursor-wait"
|
|
229
|
-
>
|
|
230
|
-
{actionLoading === model.filename ? (
|
|
231
|
-
<>
|
|
232
|
-
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
233
|
-
Deleting
|
|
234
|
-
</>
|
|
235
|
-
) : (
|
|
236
|
-
<>
|
|
237
|
-
<Trash2 className="w-3.5 h-3.5" />
|
|
238
|
-
Delete
|
|
239
|
-
</>
|
|
240
|
-
)}
|
|
241
|
-
</button>
|
|
242
|
-
</div>
|
|
243
|
-
))}
|
|
244
|
-
</div>
|
|
245
|
-
)}
|
|
246
|
-
|
|
247
|
-
{/* Search Modal */}
|
|
248
|
-
<SearchModal
|
|
249
|
-
isOpen={showSearchModal}
|
|
250
|
-
onClose={() => setShowSearchModal(false)}
|
|
251
|
-
onDownloadComplete={handleDownloadComplete}
|
|
252
|
-
/>
|
|
253
|
-
|
|
254
|
-
{/* Download Progress Indicator */}
|
|
255
|
-
{hasActiveDownloads && <DownloadProgress />}
|
|
256
|
-
</div>
|
|
257
|
-
);
|
|
258
|
-
}
|
package/web/src/pages/Router.tsx
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate } from 'react-router-dom';
|
|
3
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
4
|
-
import {
|
|
5
|
-
useRouter,
|
|
6
|
-
useStartRouter,
|
|
7
|
-
useStopRouter,
|
|
8
|
-
useRestartRouter,
|
|
9
|
-
} from '../hooks/useApi';
|
|
10
|
-
import {
|
|
11
|
-
Play,
|
|
12
|
-
Square,
|
|
13
|
-
RotateCw,
|
|
14
|
-
Loader2,
|
|
15
|
-
Activity,
|
|
16
|
-
Shuffle,
|
|
17
|
-
FileText,
|
|
18
|
-
Settings,
|
|
19
|
-
} from 'lucide-react';
|
|
20
|
-
import { RouterConfigModal } from '../components/RouterConfigModal';
|
|
21
|
-
|
|
22
|
-
export function Router() {
|
|
23
|
-
const navigate = useNavigate();
|
|
24
|
-
const queryClient = useQueryClient();
|
|
25
|
-
const { data: routerData, isLoading } = useRouter();
|
|
26
|
-
const startRouter = useStartRouter();
|
|
27
|
-
const stopRouter = useStopRouter();
|
|
28
|
-
const restartRouter = useRestartRouter();
|
|
29
|
-
|
|
30
|
-
const [actionLoading, setActionLoading] = useState<'start' | 'stop' | 'restart' | null>(null);
|
|
31
|
-
const [showConfigModal, setShowConfigModal] = useState(false);
|
|
32
|
-
|
|
33
|
-
const handleStart = async () => {
|
|
34
|
-
setActionLoading('start');
|
|
35
|
-
try {
|
|
36
|
-
await startRouter.mutateAsync();
|
|
37
|
-
await queryClient.refetchQueries({ queryKey: ['router'] });
|
|
38
|
-
} finally {
|
|
39
|
-
setActionLoading(null);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const handleStop = async () => {
|
|
44
|
-
setActionLoading('stop');
|
|
45
|
-
try {
|
|
46
|
-
await stopRouter.mutateAsync();
|
|
47
|
-
await queryClient.refetchQueries({ queryKey: ['router'] });
|
|
48
|
-
} finally {
|
|
49
|
-
setActionLoading(null);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const handleRestart = async () => {
|
|
54
|
-
setActionLoading('restart');
|
|
55
|
-
try {
|
|
56
|
-
await restartRouter.mutateAsync();
|
|
57
|
-
await queryClient.refetchQueries({ queryKey: ['router'] });
|
|
58
|
-
} finally {
|
|
59
|
-
setActionLoading(null);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Only show loading on initial load, not on refetches
|
|
64
|
-
if (isLoading && !routerData) {
|
|
65
|
-
return (
|
|
66
|
-
<div className="max-w-7xl mx-auto px-6 py-12">
|
|
67
|
-
<p className="text-neutral-500 text-center">Loading...</p>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const router = routerData;
|
|
73
|
-
const isRunning = router?.isRunning || false;
|
|
74
|
-
const isNotConfigured = router?.status === 'not_configured';
|
|
75
|
-
|
|
76
|
-
const renderStatusBadge = () => {
|
|
77
|
-
if (actionLoading) {
|
|
78
|
-
return (
|
|
79
|
-
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-neutral-100 text-neutral-700">
|
|
80
|
-
<Loader2 className="w-3 h-3 animate-spin" />
|
|
81
|
-
{actionLoading === 'start' && 'Starting'}
|
|
82
|
-
{actionLoading === 'stop' && 'Stopping'}
|
|
83
|
-
{actionLoading === 'restart' && 'Restarting'}
|
|
84
|
-
</span>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (isNotConfigured) {
|
|
89
|
-
return (
|
|
90
|
-
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-neutral-100 text-neutral-600">
|
|
91
|
-
<span className="w-1.5 h-1.5 rounded-full bg-neutral-400"></span>
|
|
92
|
-
Not Configured
|
|
93
|
-
</span>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (isRunning) {
|
|
98
|
-
return (
|
|
99
|
-
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-green-50 text-green-700 border border-green-200/50">
|
|
100
|
-
<span className="w-1.5 h-1.5 rounded-full bg-green-500"></span>
|
|
101
|
-
Running
|
|
102
|
-
</span>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium bg-neutral-100 text-neutral-600">
|
|
108
|
-
<span className="w-1.5 h-1.5 rounded-full bg-neutral-400"></span>
|
|
109
|
-
Stopped
|
|
110
|
-
</span>
|
|
111
|
-
);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<div className="max-w-7xl mx-auto px-6 py-8">
|
|
116
|
-
{/* Header */}
|
|
117
|
-
<div className="flex items-center justify-between mb-8">
|
|
118
|
-
<div>
|
|
119
|
-
<h1 className="text-2xl font-semibold text-neutral-900 tracking-tight">Router</h1>
|
|
120
|
-
<p className="text-sm text-neutral-600 mt-1">
|
|
121
|
-
Unified API endpoint for model routing
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
{/* Router Card */}
|
|
127
|
-
<div className="bg-white border border-neutral-200 rounded-lg p-5 hover:border-neutral-300 hover:shadow-sm transition-all">
|
|
128
|
-
<div className="flex items-start justify-between mb-4">
|
|
129
|
-
<div className="flex items-center gap-3">
|
|
130
|
-
<div className="w-10 h-10 rounded-lg bg-blue-50 flex items-center justify-center">
|
|
131
|
-
<Shuffle className="w-5 h-5 text-blue-600" />
|
|
132
|
-
</div>
|
|
133
|
-
<div className="flex-1 min-w-0">
|
|
134
|
-
<h3 className="text-base font-semibold text-neutral-900 mb-1">Router Service</h3>
|
|
135
|
-
<p className="text-sm text-neutral-500">
|
|
136
|
-
{isNotConfigured
|
|
137
|
-
? 'Not yet configured'
|
|
138
|
-
: `localhost:${router?.config?.port || 'N/A'}`}
|
|
139
|
-
</p>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
{renderStatusBadge()}
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
{/* Configuration Details */}
|
|
146
|
-
{!isNotConfigured && router?.config && (
|
|
147
|
-
<div className="space-y-2 mb-4">
|
|
148
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
149
|
-
<span className="text-neutral-400">Host:</span>
|
|
150
|
-
<span>{router.config.host}</span>
|
|
151
|
-
</div>
|
|
152
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
153
|
-
<span className="text-neutral-400">Request Timeout:</span>
|
|
154
|
-
<span>{(router.config.requestTimeout / 1000).toFixed(0)}s</span>
|
|
155
|
-
</div>
|
|
156
|
-
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
|
157
|
-
<span className="text-neutral-400">Verbose Logs:</span>
|
|
158
|
-
<span>{router.config.verbose ? 'Enabled' : 'Disabled'}</span>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
)}
|
|
162
|
-
|
|
163
|
-
{/* Available Models */}
|
|
164
|
-
{!isNotConfigured && router && (
|
|
165
|
-
<div className="mb-4 pt-4 border-t border-neutral-200">
|
|
166
|
-
<div className="flex items-center gap-2 mb-3">
|
|
167
|
-
<Activity className="w-3.5 h-3.5 text-neutral-400" />
|
|
168
|
-
<h4 className="text-xs font-semibold text-neutral-900">Available Models</h4>
|
|
169
|
-
</div>
|
|
170
|
-
{router.availableModels.length > 0 ? (
|
|
171
|
-
<div className="flex flex-wrap gap-2">
|
|
172
|
-
{router.availableModels.map((model) => (
|
|
173
|
-
<span
|
|
174
|
-
key={model}
|
|
175
|
-
className="inline-flex items-center px-2 py-1 text-xs font-medium text-neutral-700 bg-neutral-100 rounded-md"
|
|
176
|
-
>
|
|
177
|
-
{model.replace('.gguf', '')}
|
|
178
|
-
</span>
|
|
179
|
-
))}
|
|
180
|
-
</div>
|
|
181
|
-
) : (
|
|
182
|
-
<p className="text-xs text-neutral-500">
|
|
183
|
-
No models available. Start some servers to enable routing.
|
|
184
|
-
</p>
|
|
185
|
-
)}
|
|
186
|
-
</div>
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
{/* Not Configured Message */}
|
|
190
|
-
{isNotConfigured && (
|
|
191
|
-
<div className="mb-4 pt-4 border-t border-neutral-200">
|
|
192
|
-
<p className="text-sm text-neutral-600 mb-3">
|
|
193
|
-
Click "Start" to configure and launch the router service. The router will
|
|
194
|
-
automatically discover and route requests to running servers.
|
|
195
|
-
</p>
|
|
196
|
-
</div>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
{/* Actions */}
|
|
200
|
-
<div className="flex items-center gap-1 opacity-100 transition-opacity pt-4 border-t border-neutral-200">
|
|
201
|
-
{!isNotConfigured && isRunning && (
|
|
202
|
-
<button
|
|
203
|
-
onClick={() => navigate('/router/logs')}
|
|
204
|
-
disabled={actionLoading !== null}
|
|
205
|
-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50 rounded-md transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-wait"
|
|
206
|
-
title="View Logs"
|
|
207
|
-
>
|
|
208
|
-
<FileText className="w-3.5 h-3.5" />
|
|
209
|
-
Logs
|
|
210
|
-
</button>
|
|
211
|
-
)}
|
|
212
|
-
|
|
213
|
-
{!isNotConfigured && (
|
|
214
|
-
<button
|
|
215
|
-
onClick={() => setShowConfigModal(true)}
|
|
216
|
-
disabled={actionLoading !== null}
|
|
217
|
-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50 rounded-md transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-wait"
|
|
218
|
-
title="Config"
|
|
219
|
-
>
|
|
220
|
-
<Settings className="w-3.5 h-3.5" />
|
|
221
|
-
Config
|
|
222
|
-
</button>
|
|
223
|
-
)}
|
|
224
|
-
|
|
225
|
-
{!isNotConfigured && isRunning && (
|
|
226
|
-
<>
|
|
227
|
-
<button
|
|
228
|
-
onClick={handleRestart}
|
|
229
|
-
disabled={actionLoading !== null}
|
|
230
|
-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50 rounded-md transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-wait"
|
|
231
|
-
title="Restart"
|
|
232
|
-
>
|
|
233
|
-
<RotateCw className="w-3.5 h-3.5" />
|
|
234
|
-
Restart
|
|
235
|
-
</button>
|
|
236
|
-
<button
|
|
237
|
-
onClick={handleStop}
|
|
238
|
-
disabled={actionLoading !== null}
|
|
239
|
-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-wait"
|
|
240
|
-
title="Stop"
|
|
241
|
-
>
|
|
242
|
-
<Square className="w-3.5 h-3.5" />
|
|
243
|
-
Stop
|
|
244
|
-
</button>
|
|
245
|
-
</>
|
|
246
|
-
)}
|
|
247
|
-
|
|
248
|
-
{(isNotConfigured || !isRunning) && (
|
|
249
|
-
<button
|
|
250
|
-
onClick={handleStart}
|
|
251
|
-
disabled={actionLoading !== null}
|
|
252
|
-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium text-neutral-600 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-wait"
|
|
253
|
-
title="Start"
|
|
254
|
-
>
|
|
255
|
-
<Play className="w-3.5 h-3.5" />
|
|
256
|
-
Start
|
|
257
|
-
</button>
|
|
258
|
-
)}
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
|
|
262
|
-
{/* Config Modal */}
|
|
263
|
-
<RouterConfigModal
|
|
264
|
-
router={router || null}
|
|
265
|
-
isOpen={showConfigModal}
|
|
266
|
-
onClose={() => setShowConfigModal(false)}
|
|
267
|
-
/>
|
|
268
|
-
</div>
|
|
269
|
-
);
|
|
270
|
-
}
|