ocb-cli 1.0.1 → 1.0.3
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/proxy.js +184 -36
- package/package.json +1 -1
- package/src/proxy.ts +186 -36
package/dist/proxy.js
CHANGED
|
@@ -19,7 +19,9 @@ async function fetchModelsFromOpenCode() {
|
|
|
19
19
|
if (data.all) {
|
|
20
20
|
providers = {};
|
|
21
21
|
availableModels = [];
|
|
22
|
-
|
|
22
|
+
const providerList = Array.isArray(data.all) ? data.all : Object.values(data.all);
|
|
23
|
+
for (const providerData of providerList) {
|
|
24
|
+
const providerID = providerData.id || providerData.providerID;
|
|
23
25
|
const p = providerData;
|
|
24
26
|
providers[providerID] = { name: p.name, source: p.source };
|
|
25
27
|
if (p.models) {
|
|
@@ -115,6 +117,9 @@ app.use((req, res, next) => {
|
|
|
115
117
|
next();
|
|
116
118
|
});
|
|
117
119
|
app.get("/", (req, res) => res.send(generateHTML()));
|
|
120
|
+
app.get("/health", (req, res) => {
|
|
121
|
+
res.json({ status: "ok", timestamp: Date.now(), models: availableModels.length, providers: Object.keys(providers).length });
|
|
122
|
+
});
|
|
118
123
|
app.get("/api/status", (req, res) => {
|
|
119
124
|
res.json({
|
|
120
125
|
proxyPort: PROXY_PORT,
|
|
@@ -198,58 +203,166 @@ function generateHTML() {
|
|
|
198
203
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
199
204
|
<title>OCB - OpenCode Bridge</title>
|
|
200
205
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
201
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
206
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
202
207
|
<style>
|
|
203
|
-
* {
|
|
204
|
-
body { font-family: 'Inter', -apple-system, sans-serif; background: #
|
|
208
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
209
|
+
body { font-family: 'Inter', -apple-system, sans-serif; background: #0f0f11; color: #fafafa; min-height: 100vh; }
|
|
205
210
|
::-webkit-scrollbar { width: 6px; }
|
|
206
211
|
::-webkit-scrollbar-track { background: transparent; }
|
|
207
212
|
::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 3px; }
|
|
213
|
+
.tab-active { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: white; }
|
|
214
|
+
.model-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px -5px rgba(99, 102, 241, 0.3); }
|
|
208
215
|
</style>
|
|
209
216
|
</head>
|
|
210
217
|
<body>
|
|
211
218
|
<div class="min-h-screen flex flex-col">
|
|
212
|
-
<header class="bg-
|
|
219
|
+
<header class="bg-[#18181b] border-b border-zinc-800 px-6 py-4">
|
|
213
220
|
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
|
214
|
-
<div class="flex items-center gap-
|
|
215
|
-
<div class="w-
|
|
221
|
+
<div class="flex items-center gap-4">
|
|
222
|
+
<div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center text-2xl shadow-lg shadow-indigo-500/20">⚡</div>
|
|
216
223
|
<div>
|
|
217
|
-
<h1 class="text-
|
|
218
|
-
<p class="text-xs text-zinc-500">OpenCode Bridge</p>
|
|
224
|
+
<h1 class="text-2xl font-bold bg-gradient-to-r from-white to-zinc-400 bg-clip-text text-transparent">OCB</h1>
|
|
225
|
+
<p class="text-xs text-zinc-500 font-medium">OpenCode Bridge</p>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="ml-8 flex items-center gap-2 px-4 py-2 bg-zinc-900 rounded-xl border border-zinc-800">
|
|
228
|
+
<div id="healthDot" class="w-2.5 h-2.5 bg-green-500 rounded-full animate-pulse"></div>
|
|
229
|
+
<span id="connectionStatus" class="text-sm text-zinc-300">Connected</span>
|
|
219
230
|
</div>
|
|
220
231
|
</div>
|
|
221
232
|
<div class="flex items-center gap-3">
|
|
222
|
-
<div class="
|
|
223
|
-
<
|
|
224
|
-
<
|
|
233
|
+
<div class="text-right mr-4">
|
|
234
|
+
<p class="text-xs text-zinc-500">Current Model</p>
|
|
235
|
+
<p id="currentModelDisplay" class="text-sm font-medium text-white">Loading...</p>
|
|
225
236
|
</div>
|
|
226
|
-
<button onclick="refreshModels()" class="
|
|
227
|
-
|
|
237
|
+
<button onclick="refreshModels()" class="p-2.5 bg-zinc-800 hover:bg-zinc-700 rounded-xl transition text-zinc-300 hover:text-white" title="Refresh Models">
|
|
238
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
|
|
239
|
+
</button>
|
|
228
240
|
</div>
|
|
229
241
|
</div>
|
|
230
242
|
</header>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
243
|
+
|
|
244
|
+
<nav class="bg-[#18181b] border-b border-zinc-800 px-6">
|
|
245
|
+
<div class="max-w-7xl mx-auto flex gap-1">
|
|
246
|
+
<button onclick="switchTab('models')" id="tab-models" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all tab-active">Models</button>
|
|
247
|
+
<button onclick="switchTab('status')" id="tab-status" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all text-zinc-400 hover:bg-zinc-800">Status</button>
|
|
248
|
+
<button onclick="switchTab('settings')" id="tab-settings" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all text-zinc-400 hover:bg-zinc-800">Settings</button>
|
|
249
|
+
</div>
|
|
250
|
+
</nav>
|
|
251
|
+
|
|
252
|
+
<main class="flex-1 max-w-7xl mx-auto w-full p-6">
|
|
253
|
+
<div id="models-view" class="flex gap-6 h-[calc(100vh-220px)]">
|
|
254
|
+
<aside class="w-72 bg-[#18181b] rounded-2xl border border-zinc-800 flex flex-col overflow-hidden">
|
|
255
|
+
<div class="p-4 border-b border-zinc-800">
|
|
256
|
+
<div class="relative">
|
|
257
|
+
<input type="text" id="searchInput" placeholder="Search models..." oninput="filterData()" class="w-full px-4 py-2.5 bg-zinc-900 border border-zinc-700 rounded-xl text-sm text-white placeholder-zinc-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20">
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div id="providerList" class="flex-1 overflow-y-auto p-2"></div>
|
|
261
|
+
</aside>
|
|
262
|
+
<section class="flex-1 overflow-y-auto">
|
|
263
|
+
<div class="flex items-center justify-between mb-4">
|
|
264
|
+
<h2 id="sectionTitle" class="text-lg font-semibold text-white">All Models</h2>
|
|
265
|
+
<span id="modelCount" class="text-sm text-zinc-500"></span>
|
|
266
|
+
</div>
|
|
267
|
+
<div id="modelGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
|
|
268
|
+
</section>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div id="status-view" class="hidden space-y-6">
|
|
272
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
273
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
274
|
+
<div class="flex items-center gap-3 mb-4">
|
|
275
|
+
<div class="w-10 h-10 bg-indigo-500/20 rounded-xl flex items-center justify-center text-indigo-400">📊</div>
|
|
276
|
+
<div>
|
|
277
|
+
<p class="text-xs text-zinc-500">Total Requests</p>
|
|
278
|
+
<p id="totalRequests" class="text-2xl font-bold text-white">0</p>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
283
|
+
<div class="flex items-center gap-3 mb-4">
|
|
284
|
+
<div class="w-10 h-10 bg-purple-500/20 rounded-xl flex items-center justify-center text-purple-400">🎯</div>
|
|
285
|
+
<div>
|
|
286
|
+
<p class="text-xs text-zinc-500">Total Tokens</p>
|
|
287
|
+
<p id="totalTokens" class="text-2xl font-bold text-white">0</p>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
292
|
+
<div class="flex items-center gap-3 mb-4">
|
|
293
|
+
<div class="w-10 h-10 bg-green-500/20 rounded-xl flex items-center justify-center text-green-400">🔗</div>
|
|
294
|
+
<div>
|
|
295
|
+
<p class="text-xs text-zinc-500">Session</p>
|
|
296
|
+
<p id="sessionStatus" class="text-lg font-semibold text-green-400">Active</p>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
302
|
+
<h3 class="text-lg font-semibold text-white mb-4">Model Usage</h3>
|
|
303
|
+
<div class="space-y-3">
|
|
304
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
305
|
+
<span class="text-zinc-400">Active Model</span>
|
|
306
|
+
<span id="activeModelName" class="text-white font-medium">-</span>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
309
|
+
<span class="text-zinc-400">Available Models</span>
|
|
310
|
+
<span id="totalModelsDisplay" class="text-white font-medium">-</span>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
313
|
+
<span class="text-zinc-400">Providers</span>
|
|
314
|
+
<span id="totalProviders" class="text-white font-medium">-</span>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="flex justify-between items-center py-2">
|
|
317
|
+
<span class="text-zinc-400">OpenCode Server</span>
|
|
318
|
+
<span class="text-green-400 font-medium">localhost:4096</span>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="flex gap-4">
|
|
323
|
+
<button onclick="resetSession()" class="px-6 py-3 bg-zinc-800 hover:bg-zinc-700 rounded-xl text-white font-medium transition">Reset Session</button>
|
|
324
|
+
<button onclick="resetStats()" class="px-6 py-3 bg-zinc-800 hover:bg-zinc-700 rounded-xl text-white font-medium transition">Reset Stats</button>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<div id="settings-view" class="hidden space-y-6">
|
|
329
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
330
|
+
<h3 class="text-lg font-semibold text-white mb-4">Claude Code Configuration</h3>
|
|
331
|
+
<p class="text-zinc-400 text-sm mb-4">Add this to your Claude Code settings to use OCB:</p>
|
|
332
|
+
<div class="bg-zinc-950 rounded-xl p-4 font-mono text-sm text-zinc-300 overflow-x-auto">
|
|
333
|
+
<pre id="configJson">{
|
|
334
|
+
"env": {
|
|
335
|
+
"ANTHROPIC_BASE_URL": "http://localhost:8300/v1",
|
|
336
|
+
"ANTHROPIC_AUTH_TOKEN": "test",
|
|
337
|
+
"ANTHROPIC_MODEL": "minimax-m2.5-free"
|
|
338
|
+
}
|
|
339
|
+
}</pre>
|
|
340
|
+
</div>
|
|
341
|
+
<button onclick="copyConfig()" class="mt-4 px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 rounded-xl text-white font-medium transition flex items-center gap-2">
|
|
342
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
|
343
|
+
Copy Config
|
|
344
|
+
</button>
|
|
235
345
|
</div>
|
|
236
|
-
<div
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
346
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
347
|
+
<h3 class="text-lg font-semibold text-white mb-4">API Endpoints</h3>
|
|
348
|
+
<div class="space-y-2 font-mono text-sm">
|
|
349
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Health</span><span class="text-indigo-400">http://localhost:8300/health</span></div>
|
|
350
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Status</span><span class="text-indigo-400">http://localhost:8300/api/status</span></div>
|
|
351
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Models</span><span class="text-indigo-400">http://localhost:8300/api/models</span></div>
|
|
352
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Set Model</span><span class="text-indigo-400">POST /api/model</span></div>
|
|
353
|
+
<div class="flex justify-between py-2"><span class="text-zinc-400">Messages</span><span class="text-indigo-400">http://localhost:8300/v1/messages</span></div>
|
|
354
|
+
</div>
|
|
242
355
|
</div>
|
|
243
|
-
|
|
244
|
-
</section>
|
|
356
|
+
</div>
|
|
245
357
|
</main>
|
|
246
|
-
|
|
358
|
+
|
|
359
|
+
<footer class="bg-[#18181b] border-t border-zinc-800 px-6 py-3">
|
|
247
360
|
<div class="max-w-7xl mx-auto flex items-center justify-between text-sm text-zinc-500">
|
|
248
361
|
<div class="flex items-center gap-6">
|
|
249
|
-
<span>
|
|
250
|
-
<span>
|
|
362
|
+
<span>OCB v1.0.1</span>
|
|
363
|
+
<span>Proxy Port: <span class="text-white">8300</span></span>
|
|
251
364
|
</div>
|
|
252
|
-
<span
|
|
365
|
+
<span>OpenCode Bridge for Claude Code</span>
|
|
253
366
|
</div>
|
|
254
367
|
</footer>
|
|
255
368
|
</div>
|
|
@@ -259,6 +372,31 @@ function generateHTML() {
|
|
|
259
372
|
let currentProvider = 'all';
|
|
260
373
|
let currentModelId = null;
|
|
261
374
|
|
|
375
|
+
function switchTab(tab) {
|
|
376
|
+
document.querySelectorAll('[id^="tab-"]').forEach(el => {
|
|
377
|
+
el.classList.remove('tab-active');
|
|
378
|
+
el.classList.add('text-zinc-400', 'hover:bg-zinc-800');
|
|
379
|
+
});
|
|
380
|
+
document.getElementById('tab-' + tab).classList.add('tab-active');
|
|
381
|
+
document.getElementById('tab-' + tab).classList.remove('text-zinc-400', 'hover:bg-zinc-800');
|
|
382
|
+
|
|
383
|
+
document.getElementById('models-view').classList.add('hidden');
|
|
384
|
+
document.getElementById('status-view').classList.add('hidden');
|
|
385
|
+
document.getElementById('settings-view').classList.add('hidden');
|
|
386
|
+
document.getElementById(tab + '-view').classList.remove('hidden');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function copyConfig() {
|
|
390
|
+
const config = document.getElementById('configJson').textContent;
|
|
391
|
+
navigator.clipboard.writeText(config);
|
|
392
|
+
alert('Config copied to clipboard!');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function resetStats() {
|
|
396
|
+
await fetch('/api/reset-stats', { method: 'POST' });
|
|
397
|
+
loadStatus();
|
|
398
|
+
}
|
|
399
|
+
|
|
262
400
|
async function loadModels() {
|
|
263
401
|
const res = await fetch('/api/models');
|
|
264
402
|
const data = await res.json();
|
|
@@ -276,9 +414,9 @@ function generateHTML() {
|
|
|
276
414
|
if (searchTerm) {
|
|
277
415
|
providersToShow = providersToShow.filter(([name, models]) => name.toLowerCase().includes(searchTerm) || models.some(m => m.name.toLowerCase().includes(searchTerm)));
|
|
278
416
|
}
|
|
279
|
-
let html = '<div onclick="selectProvider('all')" class="px-3 py-2 rounded-
|
|
417
|
+
let html = '<div onclick="selectProvider('all')" class="px-3 py-2.5 rounded-xl cursor-pointer flex items-center justify-between text-sm mb-1 ' + (currentProvider === 'all' ? 'bg-indigo-600 text-white' : 'text-zinc-400 hover:bg-zinc-800') + '"><span>🏠 All Models</span><span class="text-xs opacity-60 bg-zinc-800 px-2 py-0.5 rounded-full">' + allModels.length + '</span></div>';
|
|
280
418
|
for (const [provider, models] of providersToShow) {
|
|
281
|
-
html += '<div onclick="selectProvider(\\'' + provider + '\\')" class="px-3 py-2 rounded-
|
|
419
|
+
html += '<div onclick="selectProvider(\\'' + provider.replace(/'/g, "\\\\'") + '\\')" class="px-3 py-2.5 rounded-xl cursor-pointer flex items-center justify-between text-sm mb-1 ' + (currentProvider === provider ? 'bg-indigo-600 text-white' : 'text-zinc-400 hover:bg-zinc-800') + '"><span class="truncate">' + provider + '</span><span class="text-xs opacity-60 bg-zinc-800 px-2 py-0.5 rounded-full">' + models.length + '</span></div>';
|
|
282
420
|
}
|
|
283
421
|
container.innerHTML = html;
|
|
284
422
|
}
|
|
@@ -302,14 +440,14 @@ function generateHTML() {
|
|
|
302
440
|
container.innerHTML = '<div class="col-span-full text-center text-zinc-500 py-12">No models found</div>';
|
|
303
441
|
return;
|
|
304
442
|
}
|
|
305
|
-
container.innerHTML = filtered.map(m => '<div onclick="selectModel(\\'' + m.id + '\\')" class="p-
|
|
443
|
+
container.innerHTML = filtered.slice(0, 100).map(m => '<div onclick="selectModel(\\'' + m.id.replace(/'/g, "\\\\'") + '\\')" class="model-card p-5 rounded-2xl border cursor-pointer transition-all ' + (m.id === currentModelId ? 'bg-indigo-600/20 border-indigo-500 shadow-lg shadow-indigo-500/20' : 'bg-[#18181b] border-zinc-800 hover:border-zinc-700') + '"><div class="flex items-start justify-between mb-3"><h3 class="font-semibold text-white truncate text-base">' + m.name + '</h3>' + (m.id === currentModelId ? '<span class="text-xs bg-indigo-500 text-white px-3 py-1 rounded-full font-medium">Active</span>' : '') + '</div><p class="text-xs text-zinc-500 font-mono truncate mb-3">' + m.id + '</p>' + (m.cost ? '<div class="flex items-center gap-2 text-xs text-zinc-400"><span class="bg-zinc-800 px-2 py-1 rounded">$' + m.cost.input + '/M</span><span>→</span><span class="bg-zinc-800 px-2 py-1 rounded">$' + m.cost.output + '/M</span></div>' : '<div class="text-xs text-zinc-600">Free</div>') + '</div>').join('');
|
|
306
444
|
}
|
|
307
445
|
|
|
308
446
|
function filterData() {
|
|
309
447
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
310
448
|
if (searchTerm) {
|
|
311
449
|
const filtered = allModels.filter(m => m.name.toLowerCase().includes(searchTerm) || m.id.toLowerCase().includes(searchTerm));
|
|
312
|
-
document.getElementById('sectionTitle').textContent = 'Search Results';
|
|
450
|
+
document.getElementById('sectionTitle').textContent = 'Search Results (' + filtered.length + ')';
|
|
313
451
|
renderModels(filtered);
|
|
314
452
|
renderProviders();
|
|
315
453
|
} else {
|
|
@@ -324,6 +462,7 @@ function generateHTML() {
|
|
|
324
462
|
currentModelId = modelId;
|
|
325
463
|
loadStatus();
|
|
326
464
|
renderModels(currentProvider === 'all' ? allModels : (groupedModels[currentProvider] || []));
|
|
465
|
+
document.getElementById('configJson').textContent = JSON.stringify({ env: { ANTHROPIC_BASE_URL: "http://localhost:8300/v1", ANTHROPIC_AUTH_TOKEN: "test", ANTHROPIC_MODEL: modelId } }, null, 2);
|
|
327
466
|
}
|
|
328
467
|
}
|
|
329
468
|
|
|
@@ -332,10 +471,15 @@ function generateHTML() {
|
|
|
332
471
|
const data = await res.json();
|
|
333
472
|
const model = allModels.find(m => m.id === data.currentModel);
|
|
334
473
|
document.getElementById('currentModelDisplay').textContent = model?.name || data.currentModel;
|
|
335
|
-
document.getElementById('
|
|
474
|
+
document.getElementById('healthDot').className = 'w-2.5 h-2.5 rounded-full animate-pulse ' + (data.sessionId === 'active' ? 'bg-green-500' : 'bg-red-500');
|
|
475
|
+
document.getElementById('connectionStatus').textContent = data.sessionId === 'active' ? 'Connected' : 'Disconnected';
|
|
336
476
|
document.getElementById('totalRequests').textContent = data.totalRequests.toLocaleString();
|
|
337
477
|
document.getElementById('totalTokens').textContent = data.totalTokensUsed.toLocaleString();
|
|
338
|
-
document.getElementById('
|
|
478
|
+
document.getElementById('sessionStatus').textContent = data.sessionId === 'active' ? 'Active' : 'Inactive';
|
|
479
|
+
document.getElementById('sessionStatus').className = 'text-lg font-semibold ' + (data.sessionId === 'active' ? 'text-green-400' : 'text-red-400');
|
|
480
|
+
document.getElementById('activeModelName').textContent = model?.name || data.currentModel;
|
|
481
|
+
document.getElementById('totalModelsDisplay').textContent = allModels.length + ' models';
|
|
482
|
+
document.getElementById('totalProviders').textContent = Object.keys(groupedModels).length + ' providers';
|
|
339
483
|
currentModelId = data.currentModel;
|
|
340
484
|
}
|
|
341
485
|
|
|
@@ -349,6 +493,10 @@ function generateHTML() {
|
|
|
349
493
|
loadModels();
|
|
350
494
|
}
|
|
351
495
|
|
|
496
|
+
function updateStats() {
|
|
497
|
+
document.getElementById('totalModelsDisplay').textContent = allModels.length + ' models from ' + Object.keys(groupedModels).length + ' providers';
|
|
498
|
+
}
|
|
499
|
+
|
|
352
500
|
loadModels();
|
|
353
501
|
loadStatus();
|
|
354
502
|
setInterval(loadStatus, 3000);
|
package/package.json
CHANGED
package/src/proxy.ts
CHANGED
|
@@ -34,7 +34,10 @@ async function fetchModelsFromOpenCode() {
|
|
|
34
34
|
providers = {};
|
|
35
35
|
availableModels = [];
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const providerList = Array.isArray(data.all) ? data.all : Object.values(data.all);
|
|
38
|
+
|
|
39
|
+
for (const providerData of providerList) {
|
|
40
|
+
const providerID = providerData.id || providerData.providerID;
|
|
38
41
|
const p = providerData as { name: string; source: string; models: Record<string, any> };
|
|
39
42
|
providers[providerID] = { name: p.name, source: p.source };
|
|
40
43
|
|
|
@@ -143,6 +146,10 @@ app.use((req, res, next) => {
|
|
|
143
146
|
|
|
144
147
|
app.get("/", (req, res) => res.send(generateHTML()));
|
|
145
148
|
|
|
149
|
+
app.get("/health", (req, res) => {
|
|
150
|
+
res.json({ status: "ok", timestamp: Date.now(), models: availableModels.length, providers: Object.keys(providers).length });
|
|
151
|
+
});
|
|
152
|
+
|
|
146
153
|
app.get("/api/status", (req, res) => {
|
|
147
154
|
res.json({
|
|
148
155
|
proxyPort: PROXY_PORT,
|
|
@@ -232,58 +239,166 @@ function generateHTML() {
|
|
|
232
239
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
233
240
|
<title>OCB - OpenCode Bridge</title>
|
|
234
241
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
235
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
242
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
236
243
|
<style>
|
|
237
|
-
* {
|
|
238
|
-
body { font-family: 'Inter', -apple-system, sans-serif; background: #
|
|
244
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
245
|
+
body { font-family: 'Inter', -apple-system, sans-serif; background: #0f0f11; color: #fafafa; min-height: 100vh; }
|
|
239
246
|
::-webkit-scrollbar { width: 6px; }
|
|
240
247
|
::-webkit-scrollbar-track { background: transparent; }
|
|
241
248
|
::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 3px; }
|
|
249
|
+
.tab-active { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: white; }
|
|
250
|
+
.model-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px -5px rgba(99, 102, 241, 0.3); }
|
|
242
251
|
</style>
|
|
243
252
|
</head>
|
|
244
253
|
<body>
|
|
245
254
|
<div class="min-h-screen flex flex-col">
|
|
246
|
-
<header class="bg-
|
|
255
|
+
<header class="bg-[#18181b] border-b border-zinc-800 px-6 py-4">
|
|
247
256
|
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
|
248
|
-
<div class="flex items-center gap-
|
|
249
|
-
<div class="w-
|
|
257
|
+
<div class="flex items-center gap-4">
|
|
258
|
+
<div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center text-2xl shadow-lg shadow-indigo-500/20">⚡</div>
|
|
250
259
|
<div>
|
|
251
|
-
<h1 class="text-
|
|
252
|
-
<p class="text-xs text-zinc-500">OpenCode Bridge</p>
|
|
260
|
+
<h1 class="text-2xl font-bold bg-gradient-to-r from-white to-zinc-400 bg-clip-text text-transparent">OCB</h1>
|
|
261
|
+
<p class="text-xs text-zinc-500 font-medium">OpenCode Bridge</p>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="ml-8 flex items-center gap-2 px-4 py-2 bg-zinc-900 rounded-xl border border-zinc-800">
|
|
264
|
+
<div id="healthDot" class="w-2.5 h-2.5 bg-green-500 rounded-full animate-pulse"></div>
|
|
265
|
+
<span id="connectionStatus" class="text-sm text-zinc-300">Connected</span>
|
|
253
266
|
</div>
|
|
254
267
|
</div>
|
|
255
268
|
<div class="flex items-center gap-3">
|
|
256
|
-
<div class="
|
|
257
|
-
<
|
|
258
|
-
<
|
|
269
|
+
<div class="text-right mr-4">
|
|
270
|
+
<p class="text-xs text-zinc-500">Current Model</p>
|
|
271
|
+
<p id="currentModelDisplay" class="text-sm font-medium text-white">Loading...</p>
|
|
259
272
|
</div>
|
|
260
|
-
<button onclick="refreshModels()" class="
|
|
261
|
-
|
|
273
|
+
<button onclick="refreshModels()" class="p-2.5 bg-zinc-800 hover:bg-zinc-700 rounded-xl transition text-zinc-300 hover:text-white" title="Refresh Models">
|
|
274
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
|
|
275
|
+
</button>
|
|
262
276
|
</div>
|
|
263
277
|
</div>
|
|
264
278
|
</header>
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
|
|
280
|
+
<nav class="bg-[#18181b] border-b border-zinc-800 px-6">
|
|
281
|
+
<div class="max-w-7xl mx-auto flex gap-1">
|
|
282
|
+
<button onclick="switchTab('models')" id="tab-models" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all tab-active">Models</button>
|
|
283
|
+
<button onclick="switchTab('status')" id="tab-status" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all text-zinc-400 hover:bg-zinc-800">Status</button>
|
|
284
|
+
<button onclick="switchTab('settings')" id="tab-settings" class="px-5 py-3 text-sm font-medium rounded-t-xl transition-all text-zinc-400 hover:bg-zinc-800">Settings</button>
|
|
285
|
+
</div>
|
|
286
|
+
</nav>
|
|
287
|
+
|
|
288
|
+
<main class="flex-1 max-w-7xl mx-auto w-full p-6">
|
|
289
|
+
<div id="models-view" class="flex gap-6 h-[calc(100vh-220px)]">
|
|
290
|
+
<aside class="w-72 bg-[#18181b] rounded-2xl border border-zinc-800 flex flex-col overflow-hidden">
|
|
291
|
+
<div class="p-4 border-b border-zinc-800">
|
|
292
|
+
<div class="relative">
|
|
293
|
+
<input type="text" id="searchInput" placeholder="Search models..." oninput="filterData()" class="w-full px-4 py-2.5 bg-zinc-900 border border-zinc-700 rounded-xl text-sm text-white placeholder-zinc-500 focus:outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20">
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
<div id="providerList" class="flex-1 overflow-y-auto p-2"></div>
|
|
297
|
+
</aside>
|
|
298
|
+
<section class="flex-1 overflow-y-auto">
|
|
299
|
+
<div class="flex items-center justify-between mb-4">
|
|
300
|
+
<h2 id="sectionTitle" class="text-lg font-semibold text-white">All Models</h2>
|
|
301
|
+
<span id="modelCount" class="text-sm text-zinc-500"></span>
|
|
302
|
+
</div>
|
|
303
|
+
<div id="modelGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
|
|
304
|
+
</section>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div id="status-view" class="hidden space-y-6">
|
|
308
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
309
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
310
|
+
<div class="flex items-center gap-3 mb-4">
|
|
311
|
+
<div class="w-10 h-10 bg-indigo-500/20 rounded-xl flex items-center justify-center text-indigo-400">📊</div>
|
|
312
|
+
<div>
|
|
313
|
+
<p class="text-xs text-zinc-500">Total Requests</p>
|
|
314
|
+
<p id="totalRequests" class="text-2xl font-bold text-white">0</p>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
319
|
+
<div class="flex items-center gap-3 mb-4">
|
|
320
|
+
<div class="w-10 h-10 bg-purple-500/20 rounded-xl flex items-center justify-center text-purple-400">🎯</div>
|
|
321
|
+
<div>
|
|
322
|
+
<p class="text-xs text-zinc-500">Total Tokens</p>
|
|
323
|
+
<p id="totalTokens" class="text-2xl font-bold text-white">0</p>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
328
|
+
<div class="flex items-center gap-3 mb-4">
|
|
329
|
+
<div class="w-10 h-10 bg-green-500/20 rounded-xl flex items-center justify-center text-green-400">🔗</div>
|
|
330
|
+
<div>
|
|
331
|
+
<p class="text-xs text-zinc-500">Session</p>
|
|
332
|
+
<p id="sessionStatus" class="text-lg font-semibold text-green-400">Active</p>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
338
|
+
<h3 class="text-lg font-semibold text-white mb-4">Model Usage</h3>
|
|
339
|
+
<div class="space-y-3">
|
|
340
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
341
|
+
<span class="text-zinc-400">Active Model</span>
|
|
342
|
+
<span id="activeModelName" class="text-white font-medium">-</span>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
345
|
+
<span class="text-zinc-400">Available Models</span>
|
|
346
|
+
<span id="totalModelsDisplay" class="text-white font-medium">-</span>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="flex justify-between items-center py-2 border-b border-zinc-800">
|
|
349
|
+
<span class="text-zinc-400">Providers</span>
|
|
350
|
+
<span id="totalProviders" class="text-white font-medium">-</span>
|
|
351
|
+
</div>
|
|
352
|
+
<div class="flex justify-between items-center py-2">
|
|
353
|
+
<span class="text-zinc-400">OpenCode Server</span>
|
|
354
|
+
<span class="text-green-400 font-medium">localhost:4096</span>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div class="flex gap-4">
|
|
359
|
+
<button onclick="resetSession()" class="px-6 py-3 bg-zinc-800 hover:bg-zinc-700 rounded-xl text-white font-medium transition">Reset Session</button>
|
|
360
|
+
<button onclick="resetStats()" class="px-6 py-3 bg-zinc-800 hover:bg-zinc-700 rounded-xl text-white font-medium transition">Reset Stats</button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div id="settings-view" class="hidden space-y-6">
|
|
365
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
366
|
+
<h3 class="text-lg font-semibold text-white mb-4">Claude Code Configuration</h3>
|
|
367
|
+
<p class="text-zinc-400 text-sm mb-4">Add this to your Claude Code settings to use OCB:</p>
|
|
368
|
+
<div class="bg-zinc-950 rounded-xl p-4 font-mono text-sm text-zinc-300 overflow-x-auto">
|
|
369
|
+
<pre id="configJson">{
|
|
370
|
+
"env": {
|
|
371
|
+
"ANTHROPIC_BASE_URL": "http://localhost:8300/v1",
|
|
372
|
+
"ANTHROPIC_AUTH_TOKEN": "test",
|
|
373
|
+
"ANTHROPIC_MODEL": "minimax-m2.5-free"
|
|
374
|
+
}
|
|
375
|
+
}</pre>
|
|
376
|
+
</div>
|
|
377
|
+
<button onclick="copyConfig()" class="mt-4 px-6 py-2.5 bg-indigo-600 hover:bg-indigo-500 rounded-xl text-white font-medium transition flex items-center gap-2">
|
|
378
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
|
379
|
+
Copy Config
|
|
380
|
+
</button>
|
|
269
381
|
</div>
|
|
270
|
-
<div
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
382
|
+
<div class="bg-[#18181b] rounded-2xl border border-zinc-800 p-6">
|
|
383
|
+
<h3 class="text-lg font-semibold text-white mb-4">API Endpoints</h3>
|
|
384
|
+
<div class="space-y-2 font-mono text-sm">
|
|
385
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Health</span><span class="text-indigo-400">http://localhost:8300/health</span></div>
|
|
386
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Status</span><span class="text-indigo-400">http://localhost:8300/api/status</span></div>
|
|
387
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Models</span><span class="text-indigo-400">http://localhost:8300/api/models</span></div>
|
|
388
|
+
<div class="flex justify-between py-2 border-b border-zinc-800"><span class="text-zinc-400">Set Model</span><span class="text-indigo-400">POST /api/model</span></div>
|
|
389
|
+
<div class="flex justify-between py-2"><span class="text-zinc-400">Messages</span><span class="text-indigo-400">http://localhost:8300/v1/messages</span></div>
|
|
390
|
+
</div>
|
|
276
391
|
</div>
|
|
277
|
-
|
|
278
|
-
</section>
|
|
392
|
+
</div>
|
|
279
393
|
</main>
|
|
280
|
-
|
|
394
|
+
|
|
395
|
+
<footer class="bg-[#18181b] border-t border-zinc-800 px-6 py-3">
|
|
281
396
|
<div class="max-w-7xl mx-auto flex items-center justify-between text-sm text-zinc-500">
|
|
282
397
|
<div class="flex items-center gap-6">
|
|
283
|
-
<span>
|
|
284
|
-
<span>
|
|
398
|
+
<span>OCB v1.0.1</span>
|
|
399
|
+
<span>Proxy Port: <span class="text-white">8300</span></span>
|
|
285
400
|
</div>
|
|
286
|
-
<span
|
|
401
|
+
<span>OpenCode Bridge for Claude Code</span>
|
|
287
402
|
</div>
|
|
288
403
|
</footer>
|
|
289
404
|
</div>
|
|
@@ -293,6 +408,31 @@ function generateHTML() {
|
|
|
293
408
|
let currentProvider = 'all';
|
|
294
409
|
let currentModelId = null;
|
|
295
410
|
|
|
411
|
+
function switchTab(tab) {
|
|
412
|
+
document.querySelectorAll('[id^="tab-"]').forEach(el => {
|
|
413
|
+
el.classList.remove('tab-active');
|
|
414
|
+
el.classList.add('text-zinc-400', 'hover:bg-zinc-800');
|
|
415
|
+
});
|
|
416
|
+
document.getElementById('tab-' + tab).classList.add('tab-active');
|
|
417
|
+
document.getElementById('tab-' + tab).classList.remove('text-zinc-400', 'hover:bg-zinc-800');
|
|
418
|
+
|
|
419
|
+
document.getElementById('models-view').classList.add('hidden');
|
|
420
|
+
document.getElementById('status-view').classList.add('hidden');
|
|
421
|
+
document.getElementById('settings-view').classList.add('hidden');
|
|
422
|
+
document.getElementById(tab + '-view').classList.remove('hidden');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function copyConfig() {
|
|
426
|
+
const config = document.getElementById('configJson').textContent;
|
|
427
|
+
navigator.clipboard.writeText(config);
|
|
428
|
+
alert('Config copied to clipboard!');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function resetStats() {
|
|
432
|
+
await fetch('/api/reset-stats', { method: 'POST' });
|
|
433
|
+
loadStatus();
|
|
434
|
+
}
|
|
435
|
+
|
|
296
436
|
async function loadModels() {
|
|
297
437
|
const res = await fetch('/api/models');
|
|
298
438
|
const data = await res.json();
|
|
@@ -310,9 +450,9 @@ function generateHTML() {
|
|
|
310
450
|
if (searchTerm) {
|
|
311
451
|
providersToShow = providersToShow.filter(([name, models]) => name.toLowerCase().includes(searchTerm) || models.some(m => m.name.toLowerCase().includes(searchTerm)));
|
|
312
452
|
}
|
|
313
|
-
let html = '<div onclick="selectProvider('all')" class="px-3 py-2 rounded-
|
|
453
|
+
let html = '<div onclick="selectProvider('all')" class="px-3 py-2.5 rounded-xl cursor-pointer flex items-center justify-between text-sm mb-1 ' + (currentProvider === 'all' ? 'bg-indigo-600 text-white' : 'text-zinc-400 hover:bg-zinc-800') + '"><span>🏠 All Models</span><span class="text-xs opacity-60 bg-zinc-800 px-2 py-0.5 rounded-full">' + allModels.length + '</span></div>';
|
|
314
454
|
for (const [provider, models] of providersToShow) {
|
|
315
|
-
html += '<div onclick="selectProvider(\\'' + provider + '\\')" class="px-3 py-2 rounded-
|
|
455
|
+
html += '<div onclick="selectProvider(\\'' + provider.replace(/'/g, "\\\\'") + '\\')" class="px-3 py-2.5 rounded-xl cursor-pointer flex items-center justify-between text-sm mb-1 ' + (currentProvider === provider ? 'bg-indigo-600 text-white' : 'text-zinc-400 hover:bg-zinc-800') + '"><span class="truncate">' + provider + '</span><span class="text-xs opacity-60 bg-zinc-800 px-2 py-0.5 rounded-full">' + models.length + '</span></div>';
|
|
316
456
|
}
|
|
317
457
|
container.innerHTML = html;
|
|
318
458
|
}
|
|
@@ -336,14 +476,14 @@ function generateHTML() {
|
|
|
336
476
|
container.innerHTML = '<div class="col-span-full text-center text-zinc-500 py-12">No models found</div>';
|
|
337
477
|
return;
|
|
338
478
|
}
|
|
339
|
-
container.innerHTML = filtered.map(m => '<div onclick="selectModel(\\'' + m.id + '\\')" class="p-
|
|
479
|
+
container.innerHTML = filtered.slice(0, 100).map(m => '<div onclick="selectModel(\\'' + m.id.replace(/'/g, "\\\\'") + '\\')" class="model-card p-5 rounded-2xl border cursor-pointer transition-all ' + (m.id === currentModelId ? 'bg-indigo-600/20 border-indigo-500 shadow-lg shadow-indigo-500/20' : 'bg-[#18181b] border-zinc-800 hover:border-zinc-700') + '"><div class="flex items-start justify-between mb-3"><h3 class="font-semibold text-white truncate text-base">' + m.name + '</h3>' + (m.id === currentModelId ? '<span class="text-xs bg-indigo-500 text-white px-3 py-1 rounded-full font-medium">Active</span>' : '') + '</div><p class="text-xs text-zinc-500 font-mono truncate mb-3">' + m.id + '</p>' + (m.cost ? '<div class="flex items-center gap-2 text-xs text-zinc-400"><span class="bg-zinc-800 px-2 py-1 rounded">$' + m.cost.input + '/M</span><span>→</span><span class="bg-zinc-800 px-2 py-1 rounded">$' + m.cost.output + '/M</span></div>' : '<div class="text-xs text-zinc-600">Free</div>') + '</div>').join('');
|
|
340
480
|
}
|
|
341
481
|
|
|
342
482
|
function filterData() {
|
|
343
483
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
344
484
|
if (searchTerm) {
|
|
345
485
|
const filtered = allModels.filter(m => m.name.toLowerCase().includes(searchTerm) || m.id.toLowerCase().includes(searchTerm));
|
|
346
|
-
document.getElementById('sectionTitle').textContent = 'Search Results';
|
|
486
|
+
document.getElementById('sectionTitle').textContent = 'Search Results (' + filtered.length + ')';
|
|
347
487
|
renderModels(filtered);
|
|
348
488
|
renderProviders();
|
|
349
489
|
} else {
|
|
@@ -358,6 +498,7 @@ function generateHTML() {
|
|
|
358
498
|
currentModelId = modelId;
|
|
359
499
|
loadStatus();
|
|
360
500
|
renderModels(currentProvider === 'all' ? allModels : (groupedModels[currentProvider] || []));
|
|
501
|
+
document.getElementById('configJson').textContent = JSON.stringify({ env: { ANTHROPIC_BASE_URL: "http://localhost:8300/v1", ANTHROPIC_AUTH_TOKEN: "test", ANTHROPIC_MODEL: modelId } }, null, 2);
|
|
361
502
|
}
|
|
362
503
|
}
|
|
363
504
|
|
|
@@ -366,10 +507,15 @@ function generateHTML() {
|
|
|
366
507
|
const data = await res.json();
|
|
367
508
|
const model = allModels.find(m => m.id === data.currentModel);
|
|
368
509
|
document.getElementById('currentModelDisplay').textContent = model?.name || data.currentModel;
|
|
369
|
-
document.getElementById('
|
|
510
|
+
document.getElementById('healthDot').className = 'w-2.5 h-2.5 rounded-full animate-pulse ' + (data.sessionId === 'active' ? 'bg-green-500' : 'bg-red-500');
|
|
511
|
+
document.getElementById('connectionStatus').textContent = data.sessionId === 'active' ? 'Connected' : 'Disconnected';
|
|
370
512
|
document.getElementById('totalRequests').textContent = data.totalRequests.toLocaleString();
|
|
371
513
|
document.getElementById('totalTokens').textContent = data.totalTokensUsed.toLocaleString();
|
|
372
|
-
document.getElementById('
|
|
514
|
+
document.getElementById('sessionStatus').textContent = data.sessionId === 'active' ? 'Active' : 'Inactive';
|
|
515
|
+
document.getElementById('sessionStatus').className = 'text-lg font-semibold ' + (data.sessionId === 'active' ? 'text-green-400' : 'text-red-400');
|
|
516
|
+
document.getElementById('activeModelName').textContent = model?.name || data.currentModel;
|
|
517
|
+
document.getElementById('totalModelsDisplay').textContent = allModels.length + ' models';
|
|
518
|
+
document.getElementById('totalProviders').textContent = Object.keys(groupedModels).length + ' providers';
|
|
373
519
|
currentModelId = data.currentModel;
|
|
374
520
|
}
|
|
375
521
|
|
|
@@ -383,6 +529,10 @@ function generateHTML() {
|
|
|
383
529
|
loadModels();
|
|
384
530
|
}
|
|
385
531
|
|
|
532
|
+
function updateStats() {
|
|
533
|
+
document.getElementById('totalModelsDisplay').textContent = allModels.length + ' models from ' + Object.keys(groupedModels).length + ' providers';
|
|
534
|
+
}
|
|
535
|
+
|
|
386
536
|
loadModels();
|
|
387
537
|
loadStatus();
|
|
388
538
|
setInterval(loadStatus, 3000);
|