@kevin0181/rcodex 0.0.2 → 0.0.4
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 +3 -3
- package/dist/commands/doctor.js +11 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +8 -7
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/migrate.js +6 -6
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/switch.js +4 -4
- package/dist/commands/switch.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +12 -21
- package/dist/commands/sync.js.map +1 -1
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +15 -6
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/ui.d.ts.map +1 -1
- package/dist/gateway/ui.js +267 -208
- package/dist/gateway/ui.js.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/logger.js +4 -4
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.cjs +9 -17
package/dist/gateway/ui.js
CHANGED
|
@@ -18,7 +18,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
18
18
|
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
19
19
|
text-rendering:geometricPrecision;-webkit-text-size-adjust:100%}
|
|
20
20
|
|
|
21
|
-
/*
|
|
21
|
+
/* Header */
|
|
22
22
|
.hdr{height:46px;display:flex;align-items:center;justify-content:space-between;
|
|
23
23
|
padding:0 14px 0 10px;background:rgba(13,13,18,.98);border-bottom:1px solid var(--b1);
|
|
24
24
|
position:relative;z-index:100;flex-shrink:0;gap:8px}
|
|
@@ -40,10 +40,10 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
40
40
|
box-shadow:0 0 5px var(--gr);animation:blink 2s infinite}
|
|
41
41
|
@keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
|
|
42
42
|
|
|
43
|
-
/*
|
|
43
|
+
/* Layout */
|
|
44
44
|
.layout{display:flex;flex:1;overflow:hidden;position:relative}
|
|
45
45
|
|
|
46
|
-
/*
|
|
46
|
+
/* Sidebar shell */
|
|
47
47
|
.sb{position:absolute;left:0;top:0;bottom:0;width:280px;
|
|
48
48
|
background:var(--s1);border-right:1px solid var(--b1);
|
|
49
49
|
display:flex;flex-direction:column;z-index:60;
|
|
@@ -62,13 +62,13 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
62
62
|
.sb-x:hover{background:var(--s2);color:var(--tx)}
|
|
63
63
|
.sb-body{flex:1;overflow-y:auto}
|
|
64
64
|
|
|
65
|
-
/*
|
|
65
|
+
/* Scrollbars */
|
|
66
66
|
.sb-body::-webkit-scrollbar,.mn-body::-webkit-scrollbar{width:5px}
|
|
67
67
|
.sb-body::-webkit-scrollbar-track,.mn-body::-webkit-scrollbar-track{background:transparent}
|
|
68
68
|
.sb-body::-webkit-scrollbar-thumb,.mn-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:3px}
|
|
69
69
|
.sb-body::-webkit-scrollbar-thumb:hover,.mn-body::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.28)}
|
|
70
70
|
|
|
71
|
-
/*
|
|
71
|
+
/* Home nav items */
|
|
72
72
|
.nav-item{display:flex;align-items:center;gap:11px;padding:11px 16px;
|
|
73
73
|
cursor:pointer;transition:background .12s;user-select:none}
|
|
74
74
|
.nav-item:hover{background:var(--s2)}
|
|
@@ -82,7 +82,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
82
82
|
background:rgba(99,102,241,.15);color:var(--bl2);border:1px solid rgba(99,102,241,.2)}
|
|
83
83
|
.sb-sep{height:1px;background:var(--b1);margin:4px 0}
|
|
84
84
|
|
|
85
|
-
/*
|
|
85
|
+
/* Provider type list */
|
|
86
86
|
.ptype{display:flex;align-items:center;gap:10px;padding:10px 14px;
|
|
87
87
|
cursor:pointer;transition:background .12s;border-radius:0}
|
|
88
88
|
.ptype:hover{background:var(--s2)}
|
|
@@ -96,7 +96,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
96
96
|
cursor:pointer;transition:all .15s;white-space:nowrap;flex-shrink:0}
|
|
97
97
|
.add-btn:hover{background:rgba(99,102,241,.22);border-color:var(--bl)}
|
|
98
98
|
|
|
99
|
-
/*
|
|
99
|
+
/* Connected accounts */
|
|
100
100
|
.sb-section{font-size:9px;font-weight:700;text-transform:uppercase;
|
|
101
101
|
letter-spacing:.1em;color:var(--mu);padding:12px 16px 6px}
|
|
102
102
|
.acc-item{display:flex;align-items:center;gap:10px;padding:9px 14px;
|
|
@@ -119,7 +119,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
119
119
|
justify-content:center;transition:all .12s}
|
|
120
120
|
.del-btn:hover{background:rgba(239,68,68,.1);color:var(--rd)}
|
|
121
121
|
|
|
122
|
-
/*
|
|
122
|
+
/* Auth method cards */
|
|
123
123
|
.auth-cards{display:flex;flex-direction:column;gap:8px;padding:12px 14px}
|
|
124
124
|
.auth-card{border:1px solid var(--b1);border-radius:11px;padding:12px 14px;
|
|
125
125
|
cursor:pointer;transition:all .15s;background:var(--s2)}
|
|
@@ -132,7 +132,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
132
132
|
border-radius:8px;padding:8px 11px;font-size:10px;color:#fcd34d;
|
|
133
133
|
margin:0 14px 10px;line-height:1.5}
|
|
134
134
|
|
|
135
|
-
/*
|
|
135
|
+
/* Auth form */
|
|
136
136
|
.auth-form{padding:12px 14px;display:flex;flex-direction:column;gap:8px}
|
|
137
137
|
.form-label{font-size:10px;color:var(--mu)}
|
|
138
138
|
.form-input{width:100%;padding:8px 11px;border-radius:8px;background:var(--bg);
|
|
@@ -148,7 +148,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
148
148
|
color:#fff;font-size:11px;font-weight:600;cursor:pointer}
|
|
149
149
|
.form-submit:hover{opacity:.9}
|
|
150
150
|
|
|
151
|
-
/*
|
|
151
|
+
/* Canvas */
|
|
152
152
|
.ws{position:relative;flex:1;overflow:hidden;cursor:default;user-select:none;
|
|
153
153
|
background-color:var(--bg);
|
|
154
154
|
background-image:radial-gradient(circle,var(--b1) 1px,transparent 1px);
|
|
@@ -162,8 +162,8 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
162
162
|
animation:sdash .5s linear infinite}
|
|
163
163
|
@keyframes sdash{to{stroke-dashoffset:-22}}
|
|
164
164
|
|
|
165
|
-
/*
|
|
166
|
-
.nd{position:absolute;width:
|
|
165
|
+
/* Canvas nodes */
|
|
166
|
+
.nd{position:absolute;width:260px;background:var(--s1);border:1px solid var(--b1);
|
|
167
167
|
border-radius:13px;box-shadow:0 6px 24px rgba(0,0,0,.5);
|
|
168
168
|
transition:border-color .2s,box-shadow .2s}
|
|
169
169
|
.nd:hover{border-color:var(--b2)}
|
|
@@ -174,7 +174,9 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
174
174
|
.nh:active{cursor:grabbing}
|
|
175
175
|
.nic{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;
|
|
176
176
|
justify-content:center;font-size:13px;flex-shrink:0}
|
|
177
|
-
.nn{font-size:11px;font-weight:600;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
177
|
+
.nn{font-size:11px;font-weight:600;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
178
|
+
.acct-badge{font-size:9px;font-weight:800;color:var(--bl2);background:rgba(99,102,241,.16);
|
|
179
|
+
border:1px solid rgba(99,102,241,.28);border-radius:5px;padding:1px 5px;line-height:1.4;flex-shrink:0}
|
|
178
180
|
.bk{font-size:9px;padding:2px 6px;border-radius:5px;font-weight:600;white-space:nowrap;flex-shrink:0}
|
|
179
181
|
.bk-on{background:rgba(34,197,94,.12);color:var(--gr);border:1px solid rgba(34,197,94,.2)}
|
|
180
182
|
.bk-off{background:rgba(96,96,128,.1);color:var(--mu);border:1px solid var(--b1)}
|
|
@@ -192,7 +194,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
192
194
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.65;
|
|
193
195
|
letter-spacing:.01em}
|
|
194
196
|
|
|
195
|
-
/*
|
|
197
|
+
/* Ports */
|
|
196
198
|
.po{position:absolute;right:-11px;top:50%;transform:translateY(-50%);
|
|
197
199
|
width:22px;height:22px;border-radius:50%;background:var(--s1);
|
|
198
200
|
border:2px solid var(--b2);cursor:crosshair;z-index:15;
|
|
@@ -210,7 +212,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
210
212
|
.pi.live{border-color:var(--gr)}.pi.live::after{background:var(--gr)}
|
|
211
213
|
.pi.acc{border-color:var(--bl);box-shadow:0 0 0 4px rgba(99,102,241,.2)}.pi.acc::after{background:var(--bl)}
|
|
212
214
|
|
|
213
|
-
/*
|
|
215
|
+
/* OUT node */
|
|
214
216
|
.out-node{width:240px}
|
|
215
217
|
.out-ic{width:26px;height:26px;border-radius:7px;
|
|
216
218
|
background:linear-gradient(135deg,#6366f1,#8b5cf6);
|
|
@@ -233,7 +235,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
233
235
|
.oi-x:hover{color:var(--rd)}
|
|
234
236
|
.out-empty{font-size:10px;color:var(--mu);text-align:center;padding:12px 0;line-height:1.8}
|
|
235
237
|
|
|
236
|
-
/*
|
|
238
|
+
/* Zoom */
|
|
237
239
|
.zbar{position:absolute;bottom:18px;right:18px;display:flex;align-items:center;gap:3px;
|
|
238
240
|
background:var(--s1);border:1px solid var(--b1);border-radius:8px;padding:3px 5px;
|
|
239
241
|
z-index:50;box-shadow:0 4px 16px rgba(0,0,0,.35)}
|
|
@@ -245,7 +247,7 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
245
247
|
.hint{position:absolute;bottom:18px;left:18px;font-size:10px;color:var(--mu);
|
|
246
248
|
pointer-events:none;z-index:50;line-height:1.8}
|
|
247
249
|
|
|
248
|
-
/*
|
|
250
|
+
/* Monitor node */
|
|
249
251
|
.mn{position:absolute;width:620px;background:var(--s1);border:1px solid var(--b1);
|
|
250
252
|
border-radius:13px;box-shadow:0 8px 40px rgba(0,0,0,.65);z-index:20;min-width:320px;min-height:160px}
|
|
251
253
|
.mn-rs-e{position:absolute;right:-4px;top:12px;bottom:12px;width:8px;cursor:ew-resize;z-index:21}
|
|
@@ -321,21 +323,21 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
321
323
|
.mn-empty{display:flex;align-items:center;justify-content:center;height:100%;
|
|
322
324
|
font-size:11px;color:var(--mu)}
|
|
323
325
|
|
|
324
|
-
/*
|
|
326
|
+
/* Mode switcher */
|
|
325
327
|
.mode-sw{display:flex;align-items:center;background:var(--s2);border:1px solid var(--b1);border-radius:7px;padding:2px;gap:1px}
|
|
326
328
|
.ms-btn{padding:3px 10px;border-radius:5px;border:none;font-size:10px;font-weight:600;cursor:pointer;
|
|
327
329
|
transition:all .15s;color:var(--mu);background:transparent;white-space:nowrap}
|
|
328
330
|
.ms-btn.active{background:var(--bl);color:#fff}
|
|
329
331
|
.ms-btn:not(.active):hover{color:var(--tx);background:rgba(255,255,255,.06)}
|
|
330
332
|
|
|
331
|
-
/*
|
|
333
|
+
/* OUT node bypass warning */
|
|
332
334
|
.out-bypass{font-size:9px;color:#fcd34d;background:rgba(245,158,11,.1);
|
|
333
335
|
border-top:1px solid rgba(245,158,11,.25);padding:5px 10px;text-align:center;
|
|
334
336
|
border-radius:0 0 12px 12px;letter-spacing:.01em}
|
|
335
337
|
.nd.bypassed{border-color:rgba(245,158,11,.5)!important;
|
|
336
338
|
box-shadow:0 0 0 1px rgba(245,158,11,.1),0 6px 24px rgba(0,0,0,.5)!important}
|
|
337
339
|
|
|
338
|
-
/*
|
|
340
|
+
/* Toast */
|
|
339
341
|
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);
|
|
340
342
|
background:var(--s1);border:1px solid var(--b1);border-radius:8px;padding:8px 16px;
|
|
341
343
|
font-size:11px;box-shadow:0 8px 32px rgba(0,0,0,.45);z-index:300;
|
|
@@ -355,9 +357,9 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
355
357
|
</svg>
|
|
356
358
|
</button>
|
|
357
359
|
<div class="logo">
|
|
358
|
-
<div class="logo-ic"
|
|
360
|
+
<div class="logo-ic">R</div>
|
|
359
361
|
<span class="logo-txt">rcodex Gateway</span>
|
|
360
|
-
<span class="logo-sep">
|
|
362
|
+
<span class="logo-sep"> / </span>
|
|
361
363
|
<span class="logo-port">:${port}</span>
|
|
362
364
|
</div>
|
|
363
365
|
</div>
|
|
@@ -389,79 +391,96 @@ html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
|
389
391
|
|
|
390
392
|
<div class="layout">
|
|
391
393
|
|
|
392
|
-
<!--
|
|
394
|
+
<!-- Sidebar -->
|
|
393
395
|
<div class="sb" id="sb">
|
|
394
396
|
<div class="sb-hdr">
|
|
395
|
-
<button class="sb-back" id="sb-back" onclick="sbGoBack()" style="display:none"
|
|
397
|
+
<button class="sb-back" id="sb-back" onclick="sbGoBack()" style="display:none"><</button>
|
|
396
398
|
<span class="sb-title" id="sb-title">Menu</span>
|
|
397
|
-
<button class="sb-x" onclick="toggleSb()"
|
|
399
|
+
<button class="sb-x" onclick="toggleSb()">x</button>
|
|
398
400
|
</div>
|
|
399
401
|
<div class="sb-body" id="sb-body"></div>
|
|
400
402
|
</div>
|
|
401
403
|
|
|
402
|
-
<!--
|
|
404
|
+
<!-- Canvas -->
|
|
403
405
|
<div class="ws" id="ws">
|
|
404
406
|
<svg id="svgl"></svg>
|
|
405
407
|
<div id="world"></div>
|
|
406
408
|
<div class="zbar">
|
|
407
|
-
<button class="zbtn" onclick="zoomStep(-1)"
|
|
409
|
+
<button class="zbtn" onclick="zoomStep(-1)">-</button>
|
|
408
410
|
<div class="zpct" id="zpct">100%</div>
|
|
409
411
|
<button class="zbtn" onclick="zoomStep(1)">+</button>
|
|
410
|
-
<button class="zbtn" onclick="fitAll()" style="font-size:12px"
|
|
412
|
+
<button class="zbtn" onclick="fitAll()" style="font-size:12px">fit</button>
|
|
411
413
|
</div>
|
|
412
|
-
<div class="hint">Scroll to zoom
|
|
414
|
+
<div class="hint">Scroll to zoom / Drag to pan / Drag ports to connect</div>
|
|
413
415
|
</div>
|
|
414
416
|
|
|
415
417
|
</div>
|
|
416
418
|
|
|
417
419
|
<script>
|
|
418
|
-
//
|
|
419
|
-
const PDEFS = [
|
|
420
|
-
{id:'anthropic',name:'Claude',sub:'Anthropic',icon:'
|
|
421
|
-
methods:[
|
|
422
|
-
{id:'oauth',icon:'
|
|
423
|
-
{id:'apikey',icon:'
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
{id:'
|
|
429
|
-
{id:'
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const IBGS={anthropic:'rgba(249,115,22,.15)',openai:'rgba(16,163,127,.15)',google:'rgba(66,133,244,.15)',ollama:'rgba(168,85,247,.15)',antigravity:'rgba(52,211,153,.15)',copilot:'rgba(31,111,235,.15)'};
|
|
452
|
-
const IMG_ICONS={
|
|
420
|
+
// Provider definitions
|
|
421
|
+
const PDEFS = [
|
|
422
|
+
{id:'anthropic',name:'Claude',sub:'Anthropic',icon:'C',ibg:'rgba(249,115,22,.15)',color:'#f97316',
|
|
423
|
+
methods:[
|
|
424
|
+
{id:'oauth',icon:'Auth',name:'Login with Claude Code',desc:'OAuth login uses your Claude Pro/Max subscription',warn:null},
|
|
425
|
+
{id:'apikey',icon:'Key',name:'API Key',desc:'Use Anthropic API key from console.anthropic.com',warn:null},
|
|
426
|
+
]},
|
|
427
|
+
{id:'openai',name:'ChatGPT / Codex',sub:'OpenAI',icon:'O',ibg:'rgba(16,163,127,.15)',color:'#10a37f',
|
|
428
|
+
methods:[
|
|
429
|
+
{id:'oauth',icon:'Auth',name:'Login with ChatGPT',desc:'OAuth login uses your ChatGPT subscription',warn:null},
|
|
430
|
+
{id:'apikey',icon:'Key',name:'API Key',desc:'Use OpenAI API key from platform.openai.com',warn:null},
|
|
431
|
+
{id:'session',icon:'Cookie',name:'Session Token',desc:'Use chatgpt.com browser cookie (unofficial)',warn:'Unofficial; may break. Against ToS.'},
|
|
432
|
+
]},
|
|
433
|
+
{id:'google',name:'Gemini',sub:'Google',icon:'G',ibg:'rgba(66,133,244,.15)',color:'#4285f4',
|
|
434
|
+
methods:[
|
|
435
|
+
{id:'apikey',icon:'Key',name:'API Key',desc:'Use Google AI Studio key from aistudio.google.com',warn:null},
|
|
436
|
+
]},
|
|
437
|
+
{id:'ollama',name:'Ollama',sub:'Local models',icon:'L',ibg:'rgba(168,85,247,.15)',color:'#a855f7',
|
|
438
|
+
methods:[
|
|
439
|
+
{id:'local',icon:'Local',name:'Connect Local',desc:'Use locally running Ollama (localhost:11434)',warn:null},
|
|
440
|
+
]},
|
|
441
|
+
{id:'antigravity',name:'Antigravity',sub:'Google (Daily)',icon:'A',ibg:'rgba(52,211,153,.15)',color:'#34d399',
|
|
442
|
+
methods:[
|
|
443
|
+
{id:'oauth',icon:'Auth',name:'Login with Google',desc:'OAuth login uses your Google Cloud / Gemini Code Assist account',warn:null},
|
|
444
|
+
]},
|
|
445
|
+
{id:'copilot',name:'Copilot',sub:'GitHub',icon:'P',ibg:'rgba(31,111,235,.15)',color:'#2f81f7',
|
|
446
|
+
methods:[
|
|
447
|
+
{id:'oauth',icon:'Auth',name:'Login with GitHub',desc:'OAuth device login uses your GitHub Copilot subscription',warn:null},
|
|
448
|
+
]},
|
|
449
|
+
];
|
|
450
|
+
const COL={anthropic:'#f97316',openai:'#10a37f',google:'#4285f4',ollama:'#a855f7',antigravity:'#34d399',copilot:'#2f81f7'};
|
|
451
|
+
const ICONS={anthropic:'C',openai:'O',google:'G',ollama:'L',antigravity:'A',copilot:'P'};
|
|
452
|
+
const IBGS={anthropic:'rgba(249,115,22,.15)',openai:'rgba(16,163,127,.15)',google:'rgba(66,133,244,.15)',ollama:'rgba(168,85,247,.15)',antigravity:'rgba(52,211,153,.15)',copilot:'rgba(31,111,235,.15)'};const IMG_ICONS={
|
|
453
453
|
anthropic:'data:image/png;base64,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',
|
|
454
454
|
openai:'data:image/png;base64,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',
|
|
455
455
|
google:'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5OjcBCgoKDQwNGg8PGjclHyU3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3N//AABEIAJQAlAMBEQACEQEDEQH/xAAbAAEAAQUBAAAAAAAAAAAAAAAABgECAwQHBf/EADQQAAICAQIDBAcIAwEAAAAAAAABAgMEBRESITEGIkFRE0JhcYGR0QcUFSMyUrHBYnKhQ//EABoBAQADAQEBAAAAAAAAAAAAAAADBAUBAgb/xAAvEQEAAwACAAQDBwQDAQAAAAAAAQIDBBEFEiExEyJBMlFhgZHB0UJxsfEjofAU/9oADAMBAAIRAxEAPwDuIAAAAAAAADyO1efLTtCyciufBbw8Fcl1UpckyLa/kpMwu+HYRvyaUmO495/tCFdhdXyY65HHycm2yrJi47WTcu/1T59OjKvHvbz9TLf8Y4mf/wA/npWImv3R9HTC++TAAAAAAAAAAAAAAAAAABAftJ1DjsxdOg9+D86fv5qP9lLlW9Yq+l8Bw6i20/2j/M/sh2JdPFyaciv9dM1OPvT3IKekxLa2rGlJpPtPo7Th3wysarIqe9dsFKL9jRqRPcPgr0mlppPvDMHkAAAAAAAAAAAAAAAAYsnIrxaLLrpcNdcXKT8kjkzER3L1Slr2itfeXGtTzZ6lqORmW772z3Sfqx8F8tjMmZvaZl93hlXDKuUfRhiialUel3Qfs/1NWYlmn2y79Per38YN818H/JdrHUPmPEqR8T4kfVLzrNAAAAAAAAAAAAAAAAEH+0LWNorSseXOW072n0XhH+/kVeRf+iG/4LxfWeRb8v3lBkiKlO25pdkii5nmzttW9pebZp2dVlU/qrfOP7l4ov0x80dMjk3i0dOs4eTVl4tWRRLirsjxRZUtWaz1LMZjgAAAAAAAAAAAAAA8ntFrNWj4DuklK6XKqt+tL6LxI9NIpXta4fFtytPLHt9ZcqunZkX2XXyc7bJOUpPq2U6xMz3L67utKxWvpEKKJbzzUtdV6RoZZsvbZU0M82XrqkvY3XVgX/csqe2NbLuyfSuX0Z45fEm9fPX3hUrtHm6l0JPcx1hUAAAAAAAAAAAANLVdSo0zFlkZMu6uUYrrJ+SPF7xSO5TYYX3v5Kf6cv1bPv1XMnk5L5vlGC6QXkih5p0t3L6nHOnGz8lP9tRQ5FrOiHXZXY0cs2ZtsoaOWbL12Wtl2lGfpotk+XMs1qp3um/Y/tNxqGm6hZ+Z0otl63+Lfn5eZj+IcGa/8ucen1j7vxW+NyYn5L+6aLoY6+qAAAAAAAAAAeZrOsY2l08Vz4rX+ipPvS+i9pBtyKYx6+6xx+Lfe3Vfb6y55qmdk6pk+nypb/sgukF5IzZ0trbuz6HKmfHp5Kf7anAW8qodNlGjTxoztd1kuRpZZs3XZjky/SjP01Y3It1qqXusbJYhDNlre566RzKadl+2PouDD1ebcFyryX4eyX1+fmYvO8M770xj+8fw0eNzf6dP1TyElOKlFpxfNNeJgtRcAAAAAACy22FUHOycYRXWUnsjza1aR3aeodiJtPUQjWrdqOFSq02PFLp6aS5L3LxMjkeK1+zj6/i1OP4d382v6Inc7L7ZW3TlOyX6pSfNmbF7XnuZ9Wl560jy19oY3Av4x2p6bLJI1sKs/Xdika+NGdpswzZpZ0Ur6dsM2XKVVbXYmyesIZst3JOkfam51xQdQPa0DtNnaK1XF+mxd+dE30/1fh/BR5fAy5Hr7W+/+Vnj8q+Pp7x9zomi9ocDWIpY1yjb402cpr4ePvR85yOHtx5+ePT7/o18eTnrHyz6vX3KqwAANPI1LEx21bdHiXWMeb+RT28Q42EzF7x3H0+v6Js+Ppp9mHk5naJvdYlPP91n0Rj7+PR7Y1/Of4Xc/D/rpLwsy/IzJcWTbKfkn0XwMjXl67z3pbv/AB+i/SlMo6pHTVlX7DlZctoxThsXMlTTVhmjVwZ+uzBNm3hDO02YJs18YUr6ME2aWcIJuwyZaqime2Nk0PEyoz04oHDc6KMBFuMlKMnGSe6a5NM5MRPpJ7esJHpXbPVMBRhe1mVLlta++l/t9dzN38Kx19a/LP4e36LmXO1p6T6wlmn9uNIyUo3uzFn5Wx3T9zW//djI18K5Gf2fm/t/Er+fPxt7+iQ42TRlUq7Gtruql0nXJST+KM+9bUny3jqVytq2jus9wjeXo+XC2c4wdkXJtOL5s+K5fhPKrpa1Y80T93v+37tnLl5zWImemhKqUJcMouL8mtmZU1tWerR1P4rHxImO4WOB7qjm7HOOxNRBe7XtRfyUdNGpaa2DP10atjNnCWfpdrzZsYyrWswyNHOXiZYpFurz2sJoFrPTih1wAAAKb8gN/A0bUtQaeJhXTi/X4eGPzfIrbcrHL7doTZ4a6fZhOuzfZnMwcCVeVlOqydjnwVS3S5JdfPkYPM5+eundK9xEfVq8Xi3zp1afVLmZS+xXY9V8eG6uM1/kiLXDPWOtK9vVbWr7S8nM0JbOWLPZ/sn0+Zh8jwOvvhPX4T/KevIt/U8PJoson6O2DjLyZj2xvlby6R1Ja/fs0rYljNT0lp3RNPGVDSWlajXwupXa00a+N0FpYpGnlZ4YpF2kiwsRItPYozrgBR8lucEg0TslqGq8Nli+6475qy2POS9kfrsZ3J8Txx9I+afw/lcw4emvrPpCc6V2S0rTeGfofvFy/wDS/vPf2LojD38Q229O+o+6Gplw8s/p3P4vcjHZFFaXAAAADDkY9WTBwugpR/gi1xprXy3jsRnVdHsxU7K97KfPbnH3/UwuTwLYT5q+tf8ACO8S8K6B5yspaQ0roGnjdTvDTsia2N1a0NeSNTK7wxyRoZ2GNot1kW7EsSKHobGDhZGoZMcbDqdtsvVXgvN+SI9dqZU8956h6zztpby1juXRezvY/F0zhvzFHJy1zTa7kPcv7f8Aw+b5niWm/wAtPSv/AHLZ4/Crn629ZSfYzF5UAAAAAAACjW4Eb1zRVFSyMSPd6zrXh7UZPK4fk+fP84/hX1y7juEWugRZWZ94aNsTUxuq2hqTRq43QSxSNLK7naxou0sLeEnrZ1uaTpWRq2XHGxY8+s5vpBebPG/JphTz2S4421t5aup6HouLo2KqcaO8nzsta7035v6Hy3J5OnIv5ry3scK416q9IrpgAAAAAAAAAAARLtLpPoN8vHj+VJ9+K9V+fuMzkcfyT56+0qPJy6+aEVuR6yuzLtK1czTxur2a7NPK6PtQvUu722MHCvz8uvGxocVlj2Xkva/JE1t651m1vaE2dLaWitfeXVND0mjSMNY9C3k+dljXOcvM+d5G9t7+az6DDGuNfLD0SBMAAAAAAAAAAAABZbCNkHCaTjJbNPxRyY7jqXJjv0lzrX9PlpubKrrVLvVyfivL4GfbP4duvoxOVl8O/X0eHb1LeVlC0teRo5XRdqb+ZdpoduldjtFWm4ayL4bZV63lv6kfBfUo8rkTrbyx7Q+i4PG+FTzW95SMqrwAAAAAAAAAAAAAAB5HaXTPxHTbIwjvdX36vf4r4kWtPPVV5ePxc5iPePZy+xkOfo+ZtZgky5SyLt7/AGL0r8S1T0lsN8fG2nLfpKXqr+/gWLazFfRoeHYfG17n2r/6HTktkis+lVAAAAAAAAAAAAAAAAH0A5d2ywPuGs2cC2qvXpYfH9S+f8orXr5bPl/Ecvhbz90+qPtrx6ElbM2bOr9kdO/DdFphKO11v5tu/Xd+HwWyJe+31vAw+DhET7z6y9oLoAAAAAAAAAAAAAAAAARX7Q8L0+jwyorv41ib5eq+T/7s/gRax6dsnxjLzYeeP6ZQfs7hfiOtYmO1vBzUpr/Fc3/G3xPFPdg8LL429afj6/k7ClsWH2ioAAAAAAAAAAAAAAAAAA19Qxq8zDuxr03XbBxlt1OTHcI9s66ZzS3tKI9g9MopiszJTnKyqbpjxNbKO/Xp15EWUessjwrjUpe9/rHomxM2wAAAAAAAAB//2Q==',
|
|
456
456
|
ollama:'data:image/png;base64,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',
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
};
|
|
458
|
+
const SVG_ICONS={
|
|
459
|
+
providers:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M12 3l7.5 4.3v8.5L12 20l-7.5-4.2V7.3L12 3z" stroke="currentColor" stroke-width="2"/><path d="M12 8v8M8.2 10.2l7.6 4.4M15.8 10.2l-7.6 4.4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
|
|
460
|
+
monitor:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M4 19V5m5 14v-8m5 8V8m5 11V3" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>',
|
|
461
|
+
terminal:'<svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M4 7l5 5-5 5m8 0h8" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
462
|
+
refresh:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M20 6v5h-5M4 18v-5h5M18.2 9A7 7 0 0 0 6.3 6.8M5.8 15A7 7 0 0 0 17.7 17.2" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
463
|
+
close:'<svg width="12" height="12" viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>',
|
|
464
|
+
login:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M10 17l5-5-5-5M15 12H3M14 4h4a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3h-4" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
465
|
+
key:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none"><circle cx="7.5" cy="14.5" r="3.5" stroke="currentColor" stroke-width="2"/><path d="M10 12l8-8m-1 1l3 3m-6 0l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
466
|
+
cookie:'<svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M20 13.5A8 8 0 1 1 10.5 4a3 3 0 0 0 3 3 3 3 0 0 0 3 3 3 3 0 0 0 3.5 3.5z" stroke="currentColor" stroke-width="2"/><path d="M8 10h.01M12 16h.01M9 17h.01" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
|
467
|
+
};
|
|
468
|
+
const PROVIDER_SVG={
|
|
469
|
+
anthropic:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 4l7 16h-3l-1.4-3.4H9.4L8 20H5l7-16zm1.6 10L12 9.8 10.4 14h3.2z" fill="currentColor"/></svg>',
|
|
470
|
+
openai:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 3.5a4.2 4.2 0 0 1 4 2.9 4.2 4.2 0 0 1 3.2 6.3 4.2 4.2 0 0 1-4 5.9 4.2 4.2 0 0 1-6.4.2 4.2 4.2 0 0 1-3.9-6.1A4.2 4.2 0 0 1 8.8 6.4 4.2 4.2 0 0 1 12 3.5z" stroke="currentColor" stroke-width="1.8"/><path d="M8.8 6.4l6.4 3.7v7.8M19.2 12.7l-6.4 3.7-6.8-3.9M8.8 18.8V11l6.8-3.9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
471
|
+
google:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M20 12.2c0-.7-.1-1.4-.2-2H12v3.7h4.5a3.9 3.9 0 0 1-1.7 2.5v2h2.8c1.6-1.5 2.4-3.6 2.4-6.2z" fill="currentColor"/><path d="M12 20c2.2 0 4.1-.7 5.5-2l-2.8-2a5 5 0 0 1-7.4-2.6H4.4v2.1A8 8 0 0 0 12 20zM7.3 13.4a5 5 0 0 1 0-2.8V8.5H4.4a8 8 0 0 0 0 7l2.9-2.1zM12 7a4.4 4.4 0 0 1 3.1 1.2l2.4-2.4A8 8 0 0 0 4.4 8.5l2.9 2.1A4.8 4.8 0 0 1 12 7z" fill="currentColor"/></svg>',
|
|
472
|
+
ollama:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M7 12c0-4 2-7 5-7s5 3 5 7v7H7v-7z" stroke="currentColor" stroke-width="2"/><path d="M9 11h.01M15 11h.01M10 16h4" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>',
|
|
473
|
+
antigravity:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M12 3l2.4 6.5L21 12l-6.6 2.5L12 21l-2.4-6.5L3 12l6.6-2.5L12 3z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>',
|
|
474
|
+
copilot:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M7 10V8a5 5 0 0 1 10 0v2M5 11h14v6a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4v-6z" stroke="currentColor" stroke-width="2"/><path d="M9 15h.01M15 15h.01" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
|
475
|
+
};
|
|
476
|
+
function providerImg(provider,size){
|
|
477
|
+
size=size||18;
|
|
478
|
+
const src=IMG_ICONS[provider];
|
|
479
|
+
const fallback=PROVIDER_SVG[provider]||ICONS[provider]||'?';
|
|
480
|
+
return src?\`<img src="\${src}" style="width:\${size}px;height:\${size}px;object-fit:contain;border-radius:2px">\`:\`<span style="width:\${size}px;height:\${size}px;display:inline-flex;align-items:center;justify-content:center">\${fallback}</span>\`;
|
|
481
|
+
}
|
|
463
482
|
|
|
464
|
-
//
|
|
483
|
+
// App state
|
|
465
484
|
let ST = { accounts:[], ollamaRunning:false, ollamaModels:[], ollamaBaseUrl:'http://localhost:11434' };
|
|
466
485
|
let sbOpen = false;
|
|
467
486
|
let sbScreen = 'home'; // 'home' | 'providers' | 'add-type' | 'add-method' | 'oauth-device'
|
|
@@ -486,28 +505,47 @@ let lastReqData = null;
|
|
|
486
505
|
let codexMode = 'rcodex';
|
|
487
506
|
|
|
488
507
|
// Canvas state (localStorage) ??keyed by slotId (not accountId)
|
|
489
|
-
const LS_POS = 'rcodex-pos-v4';
|
|
490
|
-
const LS_CANVAS = 'rcodex-canvas-v4';
|
|
491
|
-
const LS_SLOTS = 'rcodex-slots-v1';
|
|
492
|
-
|
|
493
|
-
function
|
|
494
|
-
function
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
let
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
508
|
+
const LS_POS = 'rcodex-pos-v4';
|
|
509
|
+
const LS_CANVAS = 'rcodex-canvas-v4';
|
|
510
|
+
const LS_SLOTS = 'rcodex-slots-v1';
|
|
511
|
+
const LS_HIDDEN = 'rcodex-hidden-slots-v1';
|
|
512
|
+
function loadPos(){ try{return JSON.parse(localStorage.getItem(LS_POS)||'{}')}catch{return {}} }
|
|
513
|
+
function loadCanvas(){ try{return new Set(JSON.parse(localStorage.getItem(LS_CANVAS)||'[]'))}catch{return new Set()} }
|
|
514
|
+
function loadSlots(){ try{return JSON.parse(localStorage.getItem(LS_SLOTS)||'{}')}catch{return {}} }
|
|
515
|
+
function loadHidden(){ try{return new Set(JSON.parse(localStorage.getItem(LS_HIDDEN)||'[]'))}catch{return new Set()} }
|
|
516
|
+
let NP = loadPos();
|
|
517
|
+
let onCanvas = loadCanvas(); // Set of slotIds (+ 'out', 'monitor')
|
|
518
|
+
let nodeSlots = loadSlots(); // {[slotId]: {accountId, model}}
|
|
519
|
+
let hiddenSlots = loadHidden();
|
|
520
|
+
|
|
521
|
+
function saveLS(){
|
|
522
|
+
localStorage.setItem(LS_POS, JSON.stringify(NP));
|
|
523
|
+
localStorage.setItem(LS_CANVAS, JSON.stringify([...onCanvas]));
|
|
524
|
+
localStorage.setItem(LS_SLOTS, JSON.stringify(nodeSlots));
|
|
525
|
+
localStorage.setItem(LS_HIDDEN, JSON.stringify([...hiddenSlots]));
|
|
526
|
+
}
|
|
527
|
+
function accountNo(acc){
|
|
528
|
+
const same=(ST.accounts||[]).filter(a=>a.provider===acc.provider&&a.label===acc.label);
|
|
529
|
+
const idx=same.findIndex(a=>a.id===acc.id);
|
|
530
|
+
return same.length>1&&idx>=0?idx+1:null;
|
|
531
|
+
}
|
|
532
|
+
function accountName(acc){
|
|
533
|
+
const n=accountNo(acc);
|
|
534
|
+
return (acc.label||acc.provider)+(n?' #'+n:'');
|
|
535
|
+
}
|
|
536
|
+
function methodIcon(id){
|
|
537
|
+
if(id==='oauth')return SVG_ICONS.login;
|
|
538
|
+
if(id==='apikey')return SVG_ICONS.key;
|
|
539
|
+
if(id==='session')return SVG_ICONS.cookie;
|
|
540
|
+
return PROVIDER_SVG.ollama;
|
|
541
|
+
}
|
|
504
542
|
if(!NP.out) NP.out={x:520,y:280};
|
|
505
543
|
|
|
506
544
|
// Viewport
|
|
507
545
|
let vp={x:60,y:40,s:1};
|
|
508
546
|
let panD=null,nodeD=null,connD=null;
|
|
509
547
|
|
|
510
|
-
//
|
|
548
|
+
// Sidebar navigation
|
|
511
549
|
function toggleSb(){
|
|
512
550
|
sbOpen=!sbOpen;
|
|
513
551
|
document.getElementById('sb').classList.toggle('open',sbOpen);
|
|
@@ -535,15 +573,15 @@ function renderSb(){
|
|
|
535
573
|
body.innerHTML=\`
|
|
536
574
|
<div style="padding:8px 0">
|
|
537
575
|
<div class="nav-item" onclick="sbGoTo('providers')">
|
|
538
|
-
<div class="nav-ic" style="background:rgba(99,102,241,.15)"
|
|
576
|
+
<div class="nav-ic" style="background:rgba(99,102,241,.15)">\${SVG_ICONS.providers}</div>
|
|
539
577
|
<div class="nav-info">
|
|
540
578
|
<div class="nav-name">Providers</div>
|
|
541
579
|
<div class="nav-sub">Manage AI provider accounts</div>
|
|
542
580
|
</div>
|
|
543
|
-
<span class="nav-arr"
|
|
581
|
+
<span class="nav-arr">></span>
|
|
544
582
|
</div>
|
|
545
583
|
<div class="nav-item" style="opacity:.4;pointer-events:none">
|
|
546
|
-
<div class="nav-ic" style="background:rgba(96,96,128,.1)"
|
|
584
|
+
<div class="nav-ic" style="background:rgba(96,96,128,.1)">i</div>
|
|
547
585
|
<div class="nav-info">
|
|
548
586
|
<div class="nav-name">Settings</div>
|
|
549
587
|
<div class="nav-sub">Gateway configuration</div>
|
|
@@ -551,8 +589,8 @@ function renderSb(){
|
|
|
551
589
|
<span class="nav-badge">Soon</span>
|
|
552
590
|
</div>
|
|
553
591
|
<div class="nav-item" style="opacity:.4;pointer-events:none">
|
|
554
|
-
<div class="nav-ic" style="background:rgba(96,96,128,.1)"
|
|
555
|
-
<div class="nav-info">
|
|
592
|
+
<div class="nav-ic" style="background:rgba(96,96,128,.1)">\${SVG_ICONS.monitor}</div>
|
|
593
|
+
<div class="nav-info">
|
|
556
594
|
<div class="nav-name">Monitor</div>
|
|
557
595
|
<div class="nav-sub">Request logs & metrics</div>
|
|
558
596
|
</div>
|
|
@@ -587,7 +625,7 @@ function renderSb(){
|
|
|
587
625
|
\${methods.map(m=>\`
|
|
588
626
|
<div class="auth-card" onclick="sbGoToMethod('\${m.id}')">
|
|
589
627
|
<div class="auth-card-hdr">
|
|
590
|
-
<span class="auth-card-ic">\${m.
|
|
628
|
+
<span class="auth-card-ic">\${methodIcon(m.id)}</span>
|
|
591
629
|
<span class="auth-card-name">\${m.name}</span>
|
|
592
630
|
</div>
|
|
593
631
|
<div class="auth-card-sub">\${m.desc}</div>
|
|
@@ -597,7 +635,7 @@ function renderSb(){
|
|
|
597
635
|
else if(sbScreen==='add-method'){
|
|
598
636
|
const m=sbAddingMethod;
|
|
599
637
|
title.textContent=m.name;
|
|
600
|
-
let warn=m.warn?\`<div class="auth-warn"
|
|
638
|
+
let warn=m.warn?\`<div class="auth-warn">\${m.warn}</div>\`:'';
|
|
601
639
|
|
|
602
640
|
if(m.id==='oauth'){
|
|
603
641
|
body.innerHTML=\`\${warn}
|
|
@@ -606,7 +644,7 @@ function renderSb(){
|
|
|
606
644
|
<input class="form-input" id="f-label" placeholder="\${sbAddingDef.name} Account" value="\${sbAddingDef.name}"/>
|
|
607
645
|
<div class="form-actions">
|
|
608
646
|
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
609
|
-
<button class="form-submit" onclick="doOAuth()"
|
|
647
|
+
<button class="form-submit" onclick="doOAuth()">\${SVG_ICONS.login} Open Login</button>
|
|
610
648
|
</div>
|
|
611
649
|
</div>\`;
|
|
612
650
|
}
|
|
@@ -631,13 +669,13 @@ function renderSb(){
|
|
|
631
669
|
const site=sbAddingDef.id==='anthropic'?'claude.ai':'chatgpt.com';
|
|
632
670
|
body.innerHTML=\`\${warn}
|
|
633
671
|
<div style="padding:0 14px 10px;font-size:10px;color:var(--mu);line-height:1.6">
|
|
634
|
-
Open DevTools
|
|
672
|
+
Open DevTools > Application > Cookies > \${site} > copy session token.
|
|
635
673
|
</div>
|
|
636
674
|
<div class="auth-form">
|
|
637
675
|
<div class="form-label">Account label</div>
|
|
638
676
|
<input class="form-input" id="f-label" placeholder="\${sbAddingDef.name} Account" value="\${sbAddingDef.name}"/>
|
|
639
677
|
<div class="form-label">Session Token</div>
|
|
640
|
-
<input class="form-input" id="f-ses" type="password" placeholder="Paste token
|
|
678
|
+
<input class="form-input" id="f-ses" type="password" placeholder="Paste token"/>
|
|
641
679
|
<div class="form-actions">
|
|
642
680
|
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
643
681
|
<button class="form-submit" onclick="doSession()">Connect</button>
|
|
@@ -702,11 +740,11 @@ function renderConnectedAccounts(){
|
|
|
702
740
|
<div style="display:flex;align-items:center;gap:10px">
|
|
703
741
|
<div class="acc-ic" style="background:\${IBGS[a.provider]||'rgba(96,96,128,.1)'}">\${providerImg(a.provider,16)}</div>
|
|
704
742
|
<div class="acc-info">
|
|
705
|
-
<div class="acc-name">\${a
|
|
706
|
-
<div class="acc-sub">\${methodLabel[a.method]||a.method}\${activeSlots?'
|
|
743
|
+
<div class="acc-name">\${accountName(a)}</div>
|
|
744
|
+
<div class="acc-sub">\${methodLabel[a.method]||a.method}\${activeSlots?' / <span style="color:var(--gr)">active</span> '+activeSlots+' active':canvasCount?' / '+canvasCount+' on canvas':''}</div>
|
|
707
745
|
\${sub?\`<div class="acc-sub" style="opacity:.6;margin-top:1px;font-size:9px">\${sub}</div>\`:''}
|
|
708
746
|
</div>
|
|
709
|
-
<button class="del-btn" onclick="deleteAccount('\${a.id}')" title="Delete"
|
|
747
|
+
<button class="del-btn" onclick="deleteAccount('\${a.id}')" title="Delete">\${SVG_ICONS.close}</button>
|
|
710
748
|
</div>
|
|
711
749
|
<div style="display:flex;gap:6px;padding:0 0 2px 40px">
|
|
712
750
|
\${modelPicker}
|
|
@@ -716,7 +754,7 @@ function renderConnectedAccounts(){
|
|
|
716
754
|
}).join('');
|
|
717
755
|
if(ST.ollamaRunning&&!ST.accounts.find(a=>a.provider==='ollama')){
|
|
718
756
|
html+=\`<div style="padding:8px 16px;font-size:10px;color:var(--mu)">
|
|
719
|
-
Ollama is running
|
|
757
|
+
Ollama is running <button onclick="sbGoToAdd('ollama')" style="background:none;border:none;color:var(--bl2);cursor:pointer;font-size:10px">add it as an account</button>
|
|
720
758
|
</div>\`;
|
|
721
759
|
}
|
|
722
760
|
return html;
|
|
@@ -734,14 +772,14 @@ function sbGoToMethod(methodId){
|
|
|
734
772
|
renderSb();
|
|
735
773
|
}
|
|
736
774
|
|
|
737
|
-
//
|
|
775
|
+
// Auth actions
|
|
738
776
|
async function doApiKey(){
|
|
739
777
|
const label=document.getElementById('f-label')?.value.trim()||sbAddingDef.name;
|
|
740
778
|
const key=document.getElementById('f-key')?.value.trim();
|
|
741
779
|
if(!key){toast('API key is required',true);return}
|
|
742
780
|
const r=await api('POST','/api/accounts',{provider:sbAddingDef.id,label,method:'apikey',apiKey:key});
|
|
743
781
|
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
744
|
-
toast(
|
|
782
|
+
toast(label+' connected');
|
|
745
783
|
sbScreen='providers';
|
|
746
784
|
await fetchStatus();
|
|
747
785
|
renderSb();
|
|
@@ -753,7 +791,7 @@ async function doSession(){
|
|
|
753
791
|
const method=sbAddingDef.id==='anthropic'?'oauth-unofficial':'oauth-unofficial';
|
|
754
792
|
const r=await api('POST','/api/accounts',{provider:sbAddingDef.id,label,method,sessionToken:ses});
|
|
755
793
|
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
756
|
-
toast(
|
|
794
|
+
toast(label+' connected');
|
|
757
795
|
sbScreen='providers';
|
|
758
796
|
await fetchStatus();
|
|
759
797
|
renderSb();
|
|
@@ -777,7 +815,7 @@ async function doOAuth(){
|
|
|
777
815
|
pollNewAccount(0,ST.accounts.length);
|
|
778
816
|
return;
|
|
779
817
|
}else{
|
|
780
|
-
toast('Complete login in the opened window
|
|
818
|
+
toast('Complete login in the opened window');
|
|
781
819
|
}
|
|
782
820
|
sbScreen='providers';
|
|
783
821
|
renderSb();
|
|
@@ -786,7 +824,7 @@ async function doOAuth(){
|
|
|
786
824
|
async function copyOAuthCode(){
|
|
787
825
|
if(!sbOAuthDevice?.userCode)return;
|
|
788
826
|
try{await navigator.clipboard?.writeText(sbOAuthDevice.userCode);toast('Code copied');}
|
|
789
|
-
catch{toast('Copy failed
|
|
827
|
+
catch{toast('Copy failed - select the code manually',true);}
|
|
790
828
|
}
|
|
791
829
|
function openOAuthDevicePage(){
|
|
792
830
|
if(sbOAuthDevice?.authUrl)window.open(sbOAuthDevice.authUrl,'_blank','width=600,height=700');
|
|
@@ -797,13 +835,13 @@ async function doLocal(){
|
|
|
797
835
|
await api('POST','/api/ollama/config',{baseUrl:url});
|
|
798
836
|
const r=await api('POST','/api/accounts',{provider:'ollama',label,method:'local'});
|
|
799
837
|
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
800
|
-
toast(
|
|
838
|
+
toast(label+' added');
|
|
801
839
|
sbScreen='providers';
|
|
802
840
|
await fetchStatus();
|
|
803
841
|
renderSb();
|
|
804
842
|
}
|
|
805
843
|
function pollNewAccount(n,prevCount){
|
|
806
|
-
if(n>180){toast('OAuth timed out
|
|
844
|
+
if(n>180){toast('OAuth timed out - no account detected. Check terminal logs.',true);return;}
|
|
807
845
|
setTimeout(async()=>{
|
|
808
846
|
await fetchStatus();
|
|
809
847
|
renderSb();
|
|
@@ -813,7 +851,7 @@ function pollNewAccount(n,prevCount){
|
|
|
813
851
|
}
|
|
814
852
|
if(ST.accounts.length>prevCount){
|
|
815
853
|
const newAcc=ST.accounts.slice(-1)[0];
|
|
816
|
-
toast(
|
|
854
|
+
toast((newAcc?.label||'Account')+' connected!');
|
|
817
855
|
sbOAuthDevice=null;
|
|
818
856
|
if(sbScreen==='oauth-device')sbScreen='providers';
|
|
819
857
|
renderSb();
|
|
@@ -823,7 +861,7 @@ function pollNewAccount(n,prevCount){
|
|
|
823
861
|
},4000);
|
|
824
862
|
}
|
|
825
863
|
|
|
826
|
-
//
|
|
864
|
+
// Account / slot management
|
|
827
865
|
async function deleteAccount(id){
|
|
828
866
|
// Remove all canvas nodes belonging to this account
|
|
829
867
|
for(const [slotId,info] of Object.entries(nodeSlots)){
|
|
@@ -836,13 +874,14 @@ async function deleteAccount(id){
|
|
|
836
874
|
await fetchStatus();
|
|
837
875
|
}
|
|
838
876
|
|
|
839
|
-
function addToCanvas(accountId){
|
|
877
|
+
function addToCanvas(accountId){
|
|
840
878
|
const acc=ST.accounts.find(a=>a.id===accountId);
|
|
841
879
|
if(!acc)return;
|
|
842
880
|
const models=acc.models||[];
|
|
843
881
|
const model=sidebarModelSel[accountId]||models[0]||'';
|
|
844
|
-
const slotId='slot_'+Date.now()+'_'+Math.random().toString(36).slice(2,6);
|
|
845
|
-
|
|
882
|
+
const slotId='slot_'+Date.now()+'_'+Math.random().toString(36).slice(2,6);
|
|
883
|
+
hiddenSlots.delete(slotId);
|
|
884
|
+
nodeSlots[slotId]={accountId,model};
|
|
846
885
|
onCanvas.add(slotId);
|
|
847
886
|
if(!NP[slotId]){
|
|
848
887
|
const ws=document.getElementById('ws');
|
|
@@ -856,23 +895,27 @@ function addToCanvas(accountId){
|
|
|
856
895
|
renderSb();
|
|
857
896
|
}
|
|
858
897
|
|
|
859
|
-
async function removeFromCanvas(slotId){
|
|
860
|
-
const info=nodeSlots[slotId];
|
|
861
|
-
onCanvas.delete(slotId);
|
|
862
|
-
|
|
898
|
+
async function removeFromCanvas(slotId){
|
|
899
|
+
const info=nodeSlots[slotId];
|
|
900
|
+
onCanvas.delete(slotId);
|
|
901
|
+
hiddenSlots.add(slotId);
|
|
902
|
+
delete NP[slotId];
|
|
903
|
+
delete nodeSlots[slotId];
|
|
904
|
+
saveLS();
|
|
863
905
|
render();
|
|
864
906
|
if(sbOpen)renderSb();
|
|
865
907
|
if(info){
|
|
866
908
|
const acc=ST.accounts.find(a=>a.id===info.accountId);
|
|
867
909
|
const slot=(acc?.activeModels||[]).find(s=>s.slotId===slotId);
|
|
868
|
-
if(slot){
|
|
869
|
-
await api('DELETE',\`/api/accounts/\${info.accountId}/slots/\${slotId}\`);
|
|
870
|
-
await
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
910
|
+
if(slot){
|
|
911
|
+
const r=await api('DELETE',\`/api/accounts/\${info.accountId}/slots/\${slotId}\`);
|
|
912
|
+
if(!r.ok)toast('Remove failed: '+await r.text(),true);
|
|
913
|
+
await fetchStatus();
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
874
917
|
|
|
875
|
-
//
|
|
918
|
+
// Monitor node
|
|
876
919
|
function toggleMonitor(tab){
|
|
877
920
|
if(monitorOpen && monitorTab===tab){ monitorOpen=false; }
|
|
878
921
|
else{ monitorOpen=true; monitorTab=tab; if(!NP.monitor) NP.monitor={x:80,y:60}; }
|
|
@@ -938,7 +981,7 @@ function buildMonitorNode(){
|
|
|
938
981
|
usage:'<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="8" width="2.5" height="4.5" rx="1" stroke="currentColor" stroke-width="1.3"/><rect x="5.75" y="5" width="2.5" height="7.5" rx="1" stroke="currentColor" stroke-width="1.3"/><rect x="10" y="2" width="2.5" height="10.5" rx="1" stroke="currentColor" stroke-width="1.3"/></svg>',
|
|
939
982
|
};
|
|
940
983
|
const clearBtn=(monitorTab==='logs'||monitorTab==='requests')
|
|
941
|
-
?\`<button onclick="clearMonitor()" style="margin:4px 8px;padding:2px 9px;border-radius:5px;border:1px solid var(--b2);background:rgba(239,68,68,.08);color:#f87171;font-size:9px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s" onmouseover="this.style.background='rgba(239,68,68,.18)'" onmouseout="this.style.background='rgba(239,68,68,.08)'"
|
|
984
|
+
?\`<button onclick="clearMonitor()" style="margin:4px 8px;padding:2px 9px;border-radius:5px;border:1px solid var(--b2);background:rgba(239,68,68,.08);color:#f87171;font-size:9px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s" onmouseover="this.style.background='rgba(239,68,68,.18)'" onmouseout="this.style.background='rgba(239,68,68,.08)'">Clear</button>\`
|
|
942
985
|
:'';
|
|
943
986
|
const tabHtml=tabs.map(t=>\`<div class="mn-t \${monitorTab===t?'on':''}" onclick="switchMonitorTab('\${t}')">\${icons[t]} \${labels[t]}</div>\`).join('')+
|
|
944
987
|
\`<div style="flex:1"></div>\${clearBtn}\`;
|
|
@@ -951,22 +994,22 @@ function buildMonitorNode(){
|
|
|
951
994
|
const short=cwd.replace(new RegExp('^/Users/[^/]+'),'~');
|
|
952
995
|
inputRow=\`<div class="mn-inp-row">
|
|
953
996
|
<span class="mn-cwd-lbl" title="\${cwd}">\${short}$</span>
|
|
954
|
-
<input class="mn-inp" id="mn-inp" placeholder="Enter command
|
|
955
|
-
<button class="mn-run" onclick="sendTerm()"
|
|
997
|
+
<input class="mn-inp" id="mn-inp" placeholder="Enter command" autocomplete="off" spellcheck="false"/>
|
|
998
|
+
<button class="mn-run" onclick="sendTerm()">Run</button>
|
|
956
999
|
</div>\`;
|
|
957
1000
|
} else if(monitorTab==='status'){
|
|
958
|
-
content=\`<div id="mn-status-body"><div class="mn-empty">Loading
|
|
1001
|
+
content=\`<div id="mn-status-body"><div class="mn-empty">Loading</div></div>\`;
|
|
959
1002
|
} else if(monitorTab==='logs'){
|
|
960
|
-
content=\`<div class="log-wrap" id="mn-logs-body"><div class="mn-empty">Loading
|
|
1003
|
+
content=\`<div class="log-wrap" id="mn-logs-body"><div class="mn-empty">Loading</div></div>\`;
|
|
961
1004
|
} else if(monitorTab==='requests'){
|
|
962
|
-
content=\`<div class="req-wrap" id="mn-reqs-body"><div class="mn-empty">Loading
|
|
1005
|
+
content=\`<div class="req-wrap" id="mn-reqs-body"><div class="mn-empty">Loading</div></div>\`;
|
|
963
1006
|
} else if(monitorTab==='usage'){
|
|
964
|
-
content=\`<div id="mn-usage-body">\${usageHtmlCache||'<div class="mn-empty">Loading
|
|
1007
|
+
content=\`<div id="mn-usage-body">\${usageHtmlCache||'<div class="mn-empty">Loading</div>'}</div>\`;
|
|
965
1008
|
}
|
|
966
1009
|
|
|
967
1010
|
return \`<div class="nd mn" id="nd-monitor" style="left:\${pos.x}px;top:\${pos.y}px;width:\${sz.w}px">
|
|
968
1011
|
<div class="nh" style="border-radius:13px 13px 0 0;cursor:grab">
|
|
969
|
-
<div class="nic" style="background:rgba(99,102,241,.15);font-size:11px"
|
|
1012
|
+
<div class="nic" style="background:rgba(99,102,241,.15);font-size:11px">\${SVG_ICONS.terminal}</div>
|
|
970
1013
|
<span class="nn">Monitor</span>
|
|
971
1014
|
<button class="nd-rm" onclick="toggleMonitor(monitorTab)" title="Close">×</button>
|
|
972
1015
|
</div>
|
|
@@ -981,7 +1024,7 @@ function buildMonitorNode(){
|
|
|
981
1024
|
|
|
982
1025
|
function escHtml(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
983
1026
|
|
|
984
|
-
//
|
|
1027
|
+
// Terminal
|
|
985
1028
|
function addTermLine(t,v){
|
|
986
1029
|
termLines.push({t,v});
|
|
987
1030
|
if(termLines.length>500)termLines.splice(0,termLines.length-500);
|
|
@@ -1031,7 +1074,7 @@ function setupTermKeys(){
|
|
|
1031
1074
|
inp.focus();
|
|
1032
1075
|
}
|
|
1033
1076
|
|
|
1034
|
-
//
|
|
1077
|
+
// Status / Logs / Requests refresh
|
|
1035
1078
|
async function refreshStatus(){
|
|
1036
1079
|
const body=document.getElementById('mn-status-body');
|
|
1037
1080
|
if(!body)return;
|
|
@@ -1074,7 +1117,7 @@ async function refreshLogs(forceBottom=false){
|
|
|
1074
1117
|
const d=await(await fetch('/api/logs?n=120')).json();
|
|
1075
1118
|
if(!d.lines?.length){body.innerHTML='<div class="mn-empty">No log entries yet</div>';return;}
|
|
1076
1119
|
body.innerHTML=d.lines.map(l=>{
|
|
1077
|
-
const
|
|
1120
|
+
const lower=String(l).toLowerCase(); const cls=lower.includes('error')?'err':lower.includes('warn')?'warn':lower.includes('[ok]')?'ok':'';
|
|
1078
1121
|
return \`<div class="log-l \${cls}">\${escHtml(l)}</div>\`;
|
|
1079
1122
|
}).join('');
|
|
1080
1123
|
if(scroller&&atBottom) scroller.scrollTop=scroller.scrollHeight;
|
|
@@ -1102,37 +1145,37 @@ async function refreshRequests(){
|
|
|
1102
1145
|
} else if(r.failedModels?.length&&!r.usedModel){
|
|
1103
1146
|
modelLabel=r.failedModels.map(m=>\`<div style="opacity:.5;text-decoration:line-through;line-height:1.4">\${m}</div>\`).join('');
|
|
1104
1147
|
} else {
|
|
1105
|
-
modelLabel=r.usedModel||'
|
|
1148
|
+
modelLabel=r.usedModel||'-';
|
|
1106
1149
|
}
|
|
1107
1150
|
const tokensTxt=r.inputTokens!=null||r.outputTokens!=null
|
|
1108
|
-
?\`<span style="color:#6ee7b7">\${r.inputTokens??0}</span>/<span style="color:#f9a8d4">\${r.outputTokens??0}</span>\`:'
|
|
1151
|
+
?\`<span style="color:#6ee7b7">\${r.inputTokens??0}</span>/<span style="color:#f9a8d4">\${r.outputTokens??0}</span>\`:'-';
|
|
1109
1152
|
const hasDetail=!!(r.inputPreview||r.outputPreview||r.toolCalls?.length||r.toolCallDetails?.length||r.webFetches?.length||r.error);
|
|
1110
1153
|
const detailId=\`req-detail-\${r.ts}\`;
|
|
1111
|
-
const detailBtn=hasDetail?\`<button class="req-expand-btn" onclick="toggleReqDetail('\${detailId}')" title="Show detail"
|
|
1154
|
+
const detailBtn=hasDetail?\`<button class="req-expand-btn" onclick="toggleReqDetail('\${detailId}')" title="Show detail">+</button>\`:'';
|
|
1112
1155
|
|
|
1113
1156
|
// Build detail panel
|
|
1114
1157
|
let detailHtml='';
|
|
1115
1158
|
if(hasDetail){
|
|
1116
1159
|
const parts=[];
|
|
1117
|
-
if(r.inputPreview) parts.push(\`<div class="req-detail-section"><div class="req-detail-label"
|
|
1160
|
+
if(r.inputPreview) parts.push(\`<div class="req-detail-section"><div class="req-detail-label">User</div><div class="req-detail-val">\${escHtml(r.inputPreview)}</div></div>\`);
|
|
1118
1161
|
if(r.toolCallDetails?.length){
|
|
1119
1162
|
const detailLines=r.toolCallDetails.map(tc=>{
|
|
1120
1163
|
let argStr='';
|
|
1121
|
-
try{const p=JSON.parse(tc.args);const cmd=p.cmd??p.command??p.script??p.url??p.path;argStr=cmd?\` <span style="color:var(--mu)"
|
|
1164
|
+
try{const p=JSON.parse(tc.args);const cmd=p.cmd??p.command??p.script??p.url??p.path;argStr=cmd?\` <span style="color:var(--mu)">\${escHtml(String(cmd))}</span>\`:\`<span style="color:var(--mu);font-size:9px"> \${escHtml(tc.args.slice(0,80))}</span>\`;}catch{argStr=tc.args?\`<span style="color:var(--mu);font-size:9px"> \${escHtml(tc.args.slice(0,80))}</span>\`:'';};
|
|
1122
1165
|
return\`<code>\${escHtml(tc.name)}</code>\${argStr}\`;
|
|
1123
1166
|
});
|
|
1124
|
-
parts.push(\`<div class="req-detail-section"><div class="req-detail-label"
|
|
1125
|
-
} else if(r.toolCalls?.length) parts.push(\`<div class="req-detail-section"><div class="req-detail-label"
|
|
1126
|
-
if(r.webFetches?.length) parts.push(\`<div class="req-detail-section"><div class="req-detail-label"
|
|
1127
|
-
if(r.outputPreview) parts.push(\`<div class="req-detail-section"><div class="req-detail-label"
|
|
1128
|
-
if(r.error) parts.push(\`<div class="req-detail-section"><div class="req-detail-label" style="color:#f87171"
|
|
1167
|
+
parts.push(\`<div class="req-detail-section"><div class="req-detail-label">Tools called</div><div class="req-detail-val">\${detailLines.join('<br>')}</div></div>\`);
|
|
1168
|
+
} else if(r.toolCalls?.length) parts.push(\`<div class="req-detail-section"><div class="req-detail-label">Tools called</div><div class="req-detail-val">\${r.toolCalls.map(n=>\`<code>\${escHtml(n)}</code>\`).join(' ')}</div></div>\`);
|
|
1169
|
+
if(r.webFetches?.length) parts.push(\`<div class="req-detail-section"><div class="req-detail-label">Web fetched</div><div class="req-detail-val">\${r.webFetches.map(u=>\`<code>\${escHtml(u)}</code>\`).join('<br>')}</div></div>\`);
|
|
1170
|
+
if(r.outputPreview) parts.push(\`<div class="req-detail-section"><div class="req-detail-label">Model</div><div class="req-detail-val">\${escHtml(r.outputPreview)}</div></div>\`);
|
|
1171
|
+
if(r.error) parts.push(\`<div class="req-detail-section"><div class="req-detail-label" style="color:#f87171">Error</div><div class="req-detail-val" style="color:#f87171">\${escHtml(r.error)}</div></div>\`);
|
|
1129
1172
|
detailHtml=\`<div id="\${detailId}" class="req-detail-panel" style="display:none">\${parts.join('')}</div>\`;
|
|
1130
1173
|
}
|
|
1131
1174
|
|
|
1132
1175
|
return \`<div style="flex-direction:column;display:flex;border-bottom:1px solid rgba(255,255,255,.035)">
|
|
1133
1176
|
<div class="req-row \${stCls}-row" style="border-bottom:none">
|
|
1134
1177
|
<span class="req-ts">\${ts}</span>
|
|
1135
|
-
<span class="req-prov" style="color:\${col}">\${r.provider||'
|
|
1178
|
+
<span class="req-prov" style="color:\${col}">\${r.provider||'-'}</span>
|
|
1136
1179
|
<span class="req-model">\${modelLabel}</span>
|
|
1137
1180
|
<span class="req-ms">\${r.ms}</span>
|
|
1138
1181
|
<span>\${tokensTxt}</span>
|
|
@@ -1146,19 +1189,19 @@ async function refreshRequests(){
|
|
|
1146
1189
|
body.innerHTML=hdr+rows;
|
|
1147
1190
|
if(scroller&&atBottom) scroller.scrollTop=scroller.scrollHeight;
|
|
1148
1191
|
// Restore open state after rebuild
|
|
1149
|
-
openIds.forEach(id=>{const el=document.getElementById(id);if(el){el.style.display='block';const btn=el.previousElementSibling?.querySelector('.req-expand-btn');if(btn)btn.textContent='
|
|
1192
|
+
openIds.forEach(id=>{const el=document.getElementById(id);if(el){el.style.display='block';const btn=el.previousElementSibling?.querySelector('.req-expand-btn');if(btn)btn.textContent='-';}});
|
|
1150
1193
|
}catch{body.innerHTML='<div class="mn-empty">Failed to load</div>';}
|
|
1151
1194
|
}
|
|
1152
1195
|
function toggleReqDetail(id){
|
|
1153
1196
|
const el=document.getElementById(id);
|
|
1154
1197
|
if(!el)return;
|
|
1155
1198
|
const btn=el.previousElementSibling?.querySelector('.req-expand-btn');
|
|
1156
|
-
if(el.style.display==='none'){el.style.display='block';if(btn)btn.textContent='
|
|
1157
|
-
else{el.style.display='none';if(btn)btn.textContent='
|
|
1199
|
+
if(el.style.display==='none'){el.style.display='block';if(btn)btn.textContent='-';}
|
|
1200
|
+
else{el.style.display='none';if(btn)btn.textContent='-';}
|
|
1158
1201
|
}
|
|
1159
1202
|
function escHtml(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
|
1160
1203
|
|
|
1161
|
-
//
|
|
1204
|
+
// Usage
|
|
1162
1205
|
const PRICING={
|
|
1163
1206
|
'claude-opus-4-7':[15,75],'claude-opus-4-7-20250514':[15,75],'claude-opus-4-5':[15,75],
|
|
1164
1207
|
'claude-sonnet-4-6':[3,15],'claude-sonnet-4-5':[3,15],
|
|
@@ -1176,17 +1219,27 @@ function getPrice(model){
|
|
|
1176
1219
|
return key?PRICING[key]:null;
|
|
1177
1220
|
}
|
|
1178
1221
|
function fmtCost(usd){
|
|
1179
|
-
if(usd==null)return'
|
|
1222
|
+
if(usd==null)return'-';
|
|
1180
1223
|
if(usd<0.0001)return'<$0.0001';
|
|
1181
1224
|
if(usd<0.01)return'$'+usd.toFixed(4);
|
|
1182
1225
|
return'$'+usd.toFixed(3);
|
|
1183
1226
|
}
|
|
1184
|
-
function fmtTok(n){if(!n)return'
|
|
1185
|
-
|
|
1186
|
-
function fmtReset(isoStr){
|
|
1187
|
-
if(
|
|
1188
|
-
|
|
1189
|
-
if(
|
|
1227
|
+
function fmtTok(n){if(!n)return'-';if(n>=1e6)return(n/1e6).toFixed(2)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'K';return String(n);}
|
|
1228
|
+
|
|
1229
|
+
function fmtReset(isoStr){
|
|
1230
|
+
if(isoStr==null||isoStr==='')return'';
|
|
1231
|
+
let t;
|
|
1232
|
+
if(typeof isoStr==='number'){
|
|
1233
|
+
t=isoStr<1e12?isoStr*1000:isoStr;
|
|
1234
|
+
}else if(/^\d+$/.test(String(isoStr))){
|
|
1235
|
+
const n=Number(isoStr);
|
|
1236
|
+
t=n<1e12?n*1000:n;
|
|
1237
|
+
}else{
|
|
1238
|
+
t=Date.parse(String(isoStr));
|
|
1239
|
+
}
|
|
1240
|
+
if(!Number.isFinite(t))return'';
|
|
1241
|
+
const diff=t-Date.now();
|
|
1242
|
+
if(diff<=0)return'<1m';
|
|
1190
1243
|
const h=Math.floor(diff/3600000),m=Math.floor((diff%3600000)/60000);
|
|
1191
1244
|
if(diff<3600000)return m+'m';
|
|
1192
1245
|
if(diff<86400000)return h+'h '+m+'m';
|
|
@@ -1207,7 +1260,7 @@ function quotaRow(label,used,resetsAt){
|
|
|
1207
1260
|
<span style="font-size:9px;color:var(--mu);width:26px;flex-shrink:0">\${label}</span>
|
|
1208
1261
|
\${quotaBar(used,col)}
|
|
1209
1262
|
<span style="font-size:9px;width:28px;text-align:right;flex-shrink:0;\${used>80?'color:#ef4444':used>50?'color:#f59e0b':''}">\${remaining}%</span>
|
|
1210
|
-
\${reset?\`<span style="font-size:9px;color:var(--mu);flex-shrink:0">reset: \${reset}</span>\`:''}
|
|
1263
|
+
\${reset?\`<span style="font-size:9px;color:var(--mu);flex-shrink:0">reset: \${reset}</span>\`:''}
|
|
1211
1264
|
</div>\`;
|
|
1212
1265
|
}
|
|
1213
1266
|
|
|
@@ -1224,9 +1277,9 @@ function renderUsage(){
|
|
|
1224
1277
|
const icon=providerImg(a.provider,14);
|
|
1225
1278
|
let rows='';
|
|
1226
1279
|
if(qs.loading){
|
|
1227
|
-
rows=\`<div style="font-size:9px;color:var(--mu)">Fetching
|
|
1280
|
+
rows=\`<div style="font-size:9px;color:var(--mu)">Fetching</div>\`;
|
|
1228
1281
|
}else if(!qs.data){
|
|
1229
|
-
rows=\`<div style="font-size:9px;color:var(--mu);opacity:.5">Click
|
|
1282
|
+
rows=\`<div style="font-size:9px;color:var(--mu);opacity:.5">Click refresh to load</div>\`;
|
|
1230
1283
|
}else if(qs.data.error){
|
|
1231
1284
|
rows=\`<div style="font-size:9px;color:var(--rd)">Error: \${qs.data.error}</div>\`;
|
|
1232
1285
|
}else if(a.provider==='anthropic'){
|
|
@@ -1237,12 +1290,12 @@ function renderUsage(){
|
|
|
1237
1290
|
if(qs.data.secondary!=null)rows+=quotaRow('7d',qs.data.secondary.used,qs.data.secondary.resets_at);
|
|
1238
1291
|
}
|
|
1239
1292
|
if(!rows&&qs.data&&!qs.data.error)rows=\`<div style="font-size:9px;color:var(--mu)">No quota data</div>\`;
|
|
1240
|
-
const btnStyle='background:none;border:1px solid var(--b2);border-radius:5px;color:var(--di);cursor:pointer;font-size:12px;
|
|
1293
|
+
const btnStyle='background:none;border:1px solid var(--b2);border-radius:5px;color:var(--di);cursor:pointer;font-size:12px;width:28px;height:24px;display:inline-flex;align-items:center;justify-content:center;transition:all .12s;flex-shrink:0';
|
|
1241
1294
|
return\`<div style="background:var(--s2);border-radius:8px;padding:8px 10px">
|
|
1242
1295
|
<div style="display:flex;align-items:center;gap:6px;margin-bottom:\${rows?'6':'0'}px">
|
|
1243
1296
|
<div style="width:20px;height:20px;border-radius:5px;background:\${IBGS[a.provider]||'rgba(96,96,128,.15)'};display:flex;align-items:center;justify-content:center;flex-shrink:0">\${icon}</div>
|
|
1244
1297
|
<span style="font-size:11px;font-weight:600;flex:1">\${escHtml(a.label||a.provider)}</span>
|
|
1245
|
-
<button onclick="refreshQuota('\${a.id}')" \${qs.loading?'disabled':''} style="\${btnStyle}" title="Refresh quota">\${qs.loading?'
|
|
1298
|
+
<button onclick="refreshQuota('\${a.id}')" \${qs.loading?'disabled':''} style="\${btnStyle}" title="Refresh quota">\${qs.loading?'...':SVG_ICONS.refresh}</button>
|
|
1246
1299
|
</div>
|
|
1247
1300
|
\${rows}
|
|
1248
1301
|
</div>\`;
|
|
@@ -1273,7 +1326,7 @@ function renderUsage(){
|
|
|
1273
1326
|
const cards=\`<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px">
|
|
1274
1327
|
<div class="usg-card"><div class="usg-val">\${fmtTok(totIn)}</div><div class="usg-lbl">Input tokens</div></div>
|
|
1275
1328
|
<div class="usg-card"><div class="usg-val">\${fmtTok(totOut)}</div><div class="usg-lbl">Output tokens</div></div>
|
|
1276
|
-
<div class="usg-card"><div class="usg-val" style="color:var(--gr)">\${hasCost?fmtCost(totCost):'
|
|
1329
|
+
<div class="usg-card"><div class="usg-val" style="color:var(--gr)">\${hasCost?fmtCost(totCost):'-'}</div><div class="usg-lbl">Est. cost</div></div>
|
|
1277
1330
|
</div>\`;
|
|
1278
1331
|
const hdr=\`<div class="usg-row usg-hdr"><span>Model</span><span>Reqs</span><span>In</span><span>Out</span><span>Cost</span></div>\`;
|
|
1279
1332
|
const rows=Object.entries(byModel).sort((a,b)=>b[1].inT+b[1].outT-(a[1].inT+a[1].outT)).map(([model,m])=>{
|
|
@@ -1283,8 +1336,8 @@ function renderUsage(){
|
|
|
1283
1336
|
<span style="width:6px;height:6px;border-radius:50%;background:\${col};flex-shrink:0"></span>
|
|
1284
1337
|
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">\${model}</span>
|
|
1285
1338
|
</span>
|
|
1286
|
-
<span>\${m.reqs}</span><span>\${fmtTok(m.inT)||'
|
|
1287
|
-
<span>\${m.hasCost?fmtCost(m.cost):'
|
|
1339
|
+
<span>\${m.reqs}</span><span>\${fmtTok(m.inT)||'-'}</span><span>\${fmtTok(m.outT)||'-'}</span>
|
|
1340
|
+
<span>\${m.hasCost?fmtCost(m.cost):'-'}</span>
|
|
1288
1341
|
</div>\`;
|
|
1289
1342
|
}).join('');
|
|
1290
1343
|
tokenHtml=\`<div style="padding:10px 10px 0">
|
|
@@ -1320,7 +1373,7 @@ async function refreshQuota(accountId){
|
|
|
1320
1373
|
renderUsage();
|
|
1321
1374
|
}
|
|
1322
1375
|
|
|
1323
|
-
//
|
|
1376
|
+
// Canvas viewport
|
|
1324
1377
|
function applyVp(){
|
|
1325
1378
|
// Use CSS zoom for scaling so the browser re-renders content at the exact zoom level
|
|
1326
1379
|
// (crisp text at any zoom), and translate for panning (in parent pixel space)
|
|
@@ -1362,11 +1415,11 @@ function fitAll(){
|
|
|
1362
1415
|
applyVp();
|
|
1363
1416
|
}
|
|
1364
1417
|
|
|
1365
|
-
//
|
|
1418
|
+
// Canvas render
|
|
1366
1419
|
function render(){
|
|
1367
1420
|
const world=document.getElementById('world');
|
|
1368
1421
|
const savedScroll=document.getElementById('mn-body')?.scrollTop??0;
|
|
1369
|
-
// Snapshot current monitor content so rebuild doesn't flash "Loading
|
|
1422
|
+
// Snapshot current monitor content so rebuild doesn't flash "Loading
|
|
1370
1423
|
const savedReqs=document.getElementById('mn-reqs-body')?.innerHTML;
|
|
1371
1424
|
const savedLogs=document.getElementById('mn-logs-body')?.innerHTML;
|
|
1372
1425
|
const savedStat=document.getElementById('mn-status-body')?.innerHTML;
|
|
@@ -1410,19 +1463,19 @@ function accountSubtext(acc){
|
|
|
1410
1463
|
const pad=parts[1].replace(/-/g,'+').replace(/_/g,'/');
|
|
1411
1464
|
const payload=JSON.parse(atob(pad+'=='.slice(0,(4-pad.length%4)%4)));
|
|
1412
1465
|
const email=payload.email||payload.sub||'';
|
|
1413
|
-
if(email)return '
|
|
1466
|
+
if(email)return 'User '+email;
|
|
1414
1467
|
}
|
|
1415
1468
|
// No email in JWT ??show provider-specific label
|
|
1416
|
-
if(acc.provider==='anthropic')return '
|
|
1469
|
+
if(acc.provider==='anthropic')return 'Claude Code OAuth';
|
|
1417
1470
|
}
|
|
1418
1471
|
}catch{}
|
|
1419
|
-
if(acc.method==='oauth-official')return acc.provider==='anthropic'?'
|
|
1472
|
+
if(acc.method==='oauth-official')return acc.provider==='anthropic'?'Claude Code OAuth':'OAuth';
|
|
1420
1473
|
if(acc.method==='apikey'&&acc.apiKey){
|
|
1421
1474
|
const k=acc.apiKey;
|
|
1422
|
-
return '
|
|
1475
|
+
return 'Key '+k.slice(0,10)+'...'+k.slice(-4);
|
|
1423
1476
|
}
|
|
1424
|
-
if(acc.method==='oauth-unofficial')return '
|
|
1425
|
-
if(acc.method==='local')return '
|
|
1477
|
+
if(acc.method==='oauth-unofficial')return 'Session Token';
|
|
1478
|
+
if(acc.method==='local')return 'Local '+(ST.ollamaBaseUrl||'localhost:11434');
|
|
1426
1479
|
return '';
|
|
1427
1480
|
}
|
|
1428
1481
|
|
|
@@ -1432,20 +1485,24 @@ function buildAccNode(slotId){
|
|
|
1432
1485
|
const acc=ST.accounts.find(a=>a.id===info.accountId);
|
|
1433
1486
|
if(!acc)return '';
|
|
1434
1487
|
const model=info.model||'(auto)';
|
|
1435
|
-
const slot=(acc.activeModels||[]).find(s=>s.slotId===slotId);
|
|
1436
|
-
const isOut=!!slot;
|
|
1437
|
-
const sub=accountSubtext(acc);
|
|
1438
|
-
const
|
|
1488
|
+
const slot=(acc.activeModels||[]).find(s=>s.slotId===slotId);
|
|
1489
|
+
const isOut=!!slot;
|
|
1490
|
+
const sub=accountSubtext(acc);
|
|
1491
|
+
const no=accountNo(acc);
|
|
1492
|
+
const noBadge=no?\`<span class="acct-badge">#\${no}</span>\`:'';
|
|
1493
|
+
const subEl=sub?\`<div class="nd-acct" title="\${sub}">\${sub}</div>\`:'';
|
|
1439
1494
|
const pos=NP[slotId]||{x:80,y:80};
|
|
1440
1495
|
return \`<div class="nd \${isOut?'live':''}" id="nd-\${slotId}" style="left:\${pos.x}px;top:\${pos.y}px">
|
|
1441
1496
|
<div class="nh">
|
|
1442
|
-
<div class="nic" style="background:\${IBGS[acc.provider]}">\${providerImg(acc.provider,14)}</div>
|
|
1443
|
-
<div style="flex:1;min-width:0">
|
|
1444
|
-
<div
|
|
1445
|
-
|
|
1446
|
-
|
|
1497
|
+
<div class="nic" style="background:\${IBGS[acc.provider]}">\${providerImg(acc.provider,14)}</div>
|
|
1498
|
+
<div style="flex:1;min-width:0">
|
|
1499
|
+
<div style="display:flex;align-items:center;gap:6px;min-width:0" title="\${accountName(acc)}">
|
|
1500
|
+
<div class="nn">\${accountName(acc)}</div>\${noBadge}
|
|
1501
|
+
</div>
|
|
1502
|
+
<div style="font-size:9px;color:var(--mu);white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="\${model}">\${model}</div>
|
|
1503
|
+
</div>
|
|
1447
1504
|
<span class="bk \${isOut?'bk-on':'bk-off'}">\${isOut?'Active':'Idle'}</span>
|
|
1448
|
-
<button class="nd-rm" onclick="removeFromCanvas('\${slotId}')" title="Remove from canvas"
|
|
1505
|
+
<button class="nd-rm" onclick="removeFromCanvas('\${slotId}')" title="Remove from canvas">\${SVG_ICONS.close}</button>
|
|
1449
1506
|
</div>
|
|
1450
1507
|
\${sub?\`<div class="nb" style="padding:4px 12px 6px">\${subEl}</div>\`:''}
|
|
1451
1508
|
<div class="po \${isOut?'live':''}" id="po-\${slotId}" title="Drag to connect to OUT"></div>
|
|
@@ -1462,35 +1519,35 @@ function buildOutNode(){
|
|
|
1462
1519
|
allSlots.sort((a,b)=>a.slot.order-b.slot.order);
|
|
1463
1520
|
const piCls='pi'+(allSlots.length?' live':'');
|
|
1464
1521
|
const multi=allSlots.length>1;
|
|
1465
|
-
const subtitle=multi?'Priority order
|
|
1522
|
+
const subtitle=multi?'Priority order / fallback chain':'Codex uses these models';
|
|
1466
1523
|
const body=allSlots.length
|
|
1467
1524
|
?allSlots.map(({acc,slot},i)=>{
|
|
1468
1525
|
const m=slot.model||'(auto)';
|
|
1469
1526
|
const upDis=i===0?'disabled':'';
|
|
1470
1527
|
const dnDis=i===allSlots.length-1?'disabled':'';
|
|
1471
1528
|
const orderBtns=multi?\`<div class="oi-ord">
|
|
1472
|
-
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',-1)" \${upDis}
|
|
1473
|
-
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',1)" \${dnDis}
|
|
1529
|
+
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',-1)" \${upDis}>↑</button>
|
|
1530
|
+
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',1)" \${dnDis}>↓</button>
|
|
1474
1531
|
</div>\`:'';
|
|
1475
1532
|
return \`<div class="oi">
|
|
1476
1533
|
\${multi?\`<span class="oi-num">\${i+1}</span>\`:''}
|
|
1477
1534
|
<div class="oi-dot" style="background:\${COL[acc.provider]}"></div>
|
|
1478
1535
|
<div class="oi-inf">
|
|
1479
|
-
<div class="oi-pr">\${acc
|
|
1536
|
+
<div class="oi-pr">\${accountName(acc)}</div>
|
|
1480
1537
|
<div class="oi-mo" title="\${m}">\${m}</div>
|
|
1481
1538
|
</div>
|
|
1482
1539
|
\${orderBtns}
|
|
1483
|
-
<button class="oi-x" onclick="removeOut('\${acc.id}','\${slot.slotId}')"
|
|
1540
|
+
<button class="oi-x" onclick="removeOut('\${acc.id}','\${slot.slotId}')">\${SVG_ICONS.close}</button>
|
|
1484
1541
|
</div>\`;
|
|
1485
1542
|
}).join('')
|
|
1486
|
-
:'<div class="out-empty">No providers connected<br><span style="font-size:9px"
|
|
1543
|
+
:'<div class="out-empty">No providers connected<br><span style="font-size:9px">Drag from a node here</span></div>';
|
|
1487
1544
|
const pos=NP.out||{x:520,y:280};
|
|
1488
1545
|
const bypassed=codexMode==='openai';
|
|
1489
|
-
const bypassBanner=bypassed?'<div class="out-bypass"
|
|
1546
|
+
const bypassBanner=bypassed?'<div class="out-bypass">Bypassed - Codex using OpenAI directly</div>':'';
|
|
1490
1547
|
return \`<div class="nd out-node\${bypassed?' bypassed':''}" id="nd-out" style="left:\${pos.x}px;top:\${pos.y}px">
|
|
1491
1548
|
<div class="\${piCls}" id="pi"></div>
|
|
1492
1549
|
<div class="nh">
|
|
1493
|
-
<div class="out-ic"
|
|
1550
|
+
<div class="out-ic">O</div>
|
|
1494
1551
|
<div style="flex:1;min-width:0">
|
|
1495
1552
|
<div class="nn">Active Output</div>
|
|
1496
1553
|
<div style="font-size:9px;color:var(--mu)">\${subtitle}</div>
|
|
@@ -1501,7 +1558,7 @@ function buildOutNode(){
|
|
|
1501
1558
|
</div>\`;
|
|
1502
1559
|
}
|
|
1503
1560
|
|
|
1504
|
-
//
|
|
1561
|
+
// SVG lines
|
|
1505
1562
|
function portPos(el){
|
|
1506
1563
|
const ws=document.getElementById('ws').getBoundingClientRect();
|
|
1507
1564
|
const r=el.getBoundingClientRect();
|
|
@@ -1535,7 +1592,7 @@ function drawLines(){
|
|
|
1535
1592
|
});
|
|
1536
1593
|
}
|
|
1537
1594
|
|
|
1538
|
-
//
|
|
1595
|
+
// Node drag
|
|
1539
1596
|
function startNodeDrag(e,id){
|
|
1540
1597
|
if(e.target.tagName==='BUTTON'||e.target.tagName==='SELECT'||e.target.tagName==='INPUT')return;
|
|
1541
1598
|
e.preventDefault();e.stopPropagation();
|
|
@@ -1636,13 +1693,14 @@ WS.addEventListener('wheel',e=>{
|
|
|
1636
1693
|
zoomAt(e.deltaY<0?1.1:1/1.1,e.clientX-r.left,e.clientY-r.top);
|
|
1637
1694
|
},{passive:false});
|
|
1638
1695
|
|
|
1639
|
-
//
|
|
1640
|
-
async function connectOut(slotId){
|
|
1641
|
-
const info=nodeSlots[slotId];
|
|
1642
|
-
if(!info)return;
|
|
1643
|
-
const r=await api('POST',\`/api/accounts/\${info.accountId}/slots\`,{slotId,model:info.model});
|
|
1644
|
-
if(!r.ok){toast('Connection failed: '+await r.text(),true);return;}
|
|
1645
|
-
|
|
1696
|
+
// State changes
|
|
1697
|
+
async function connectOut(slotId){
|
|
1698
|
+
const info=nodeSlots[slotId];
|
|
1699
|
+
if(!info)return;
|
|
1700
|
+
const r=await api('POST',\`/api/accounts/\${info.accountId}/slots\`,{slotId,model:info.model});
|
|
1701
|
+
if(!r.ok){toast('Connection failed: '+await r.text(),true);return;}
|
|
1702
|
+
hiddenSlots.delete(slotId);
|
|
1703
|
+
toast('Connected to output');
|
|
1646
1704
|
await fetchStatus();
|
|
1647
1705
|
}
|
|
1648
1706
|
async function removeOut(accountId,slotId){
|
|
@@ -1669,7 +1727,7 @@ async function moveOut(accountId,slotId,dir){
|
|
|
1669
1727
|
await fetchStatus();
|
|
1670
1728
|
}
|
|
1671
1729
|
|
|
1672
|
-
//
|
|
1730
|
+
// Fetch
|
|
1673
1731
|
async function fetchStatus(){
|
|
1674
1732
|
try{
|
|
1675
1733
|
const d=await(await fetch('/api/accounts')).json();
|
|
@@ -1678,9 +1736,10 @@ async function fetchStatus(){
|
|
|
1678
1736
|
// Sync server activeModels ??canvas (add missing slots)
|
|
1679
1737
|
let idx=[...onCanvas].length;
|
|
1680
1738
|
for(const acc of ST.accounts){
|
|
1681
|
-
for(const slot of (acc.activeModels||[])){
|
|
1682
|
-
if(
|
|
1683
|
-
|
|
1739
|
+
for(const slot of (acc.activeModels||[])){
|
|
1740
|
+
if(hiddenSlots.has(slot.slotId))continue;
|
|
1741
|
+
if(!nodeSlots[slot.slotId]){
|
|
1742
|
+
nodeSlots[slot.slotId]={accountId:acc.id,model:slot.model};
|
|
1684
1743
|
}
|
|
1685
1744
|
if(!onCanvas.has(slot.slotId)){
|
|
1686
1745
|
onCanvas.add(slot.slotId);
|
|
@@ -1716,12 +1775,12 @@ function updateModeUI(){
|
|
|
1716
1775
|
}
|
|
1717
1776
|
async function switchProvider(mode){
|
|
1718
1777
|
try{
|
|
1719
|
-
toast(mode==='rcodex'?'Switching to rcodex Gateway
|
|
1778
|
+
toast(mode==='rcodex'?'Switching to rcodex Gateway':'Switching to OpenAI direct');
|
|
1720
1779
|
const r=await api('POST','/api/codex-provider',{mode});
|
|
1721
1780
|
if(!r.ok){toast('Switch failed',true);return;}
|
|
1722
1781
|
codexMode=mode;
|
|
1723
1782
|
updateModeUI();
|
|
1724
|
-
toast(mode==='rcodex'?'
|
|
1783
|
+
toast(mode==='rcodex'?'rcodex Gateway - Codex restarted':'OpenAI direct - Codex restarted');
|
|
1725
1784
|
}catch{toast('Switch failed',true);}
|
|
1726
1785
|
}
|
|
1727
1786
|
function toast(msg,err){
|
|
@@ -1732,7 +1791,7 @@ function toast(msg,err){
|
|
|
1732
1791
|
|
|
1733
1792
|
window.addEventListener('resize',drawLines);
|
|
1734
1793
|
|
|
1735
|
-
//
|
|
1794
|
+
// Init
|
|
1736
1795
|
async function init(){
|
|
1737
1796
|
await fetchStatus();
|
|
1738
1797
|
try{const d=await(await fetch('/api/status')).json();if(d.home)termCwd=d.home;}catch{}
|