@kevin0181/rcodex 0.0.1
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/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +114 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gateway-daemon.d.ts +2 -0
- package/dist/commands/gateway-daemon.d.ts.map +1 -0
- package/dist/commands/gateway-daemon.js +22 -0
- package/dist/commands/gateway-daemon.js.map +1 -0
- package/dist/commands/launch.d.ts +2 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +129 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/migrate.d.ts +4 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +137 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +78 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +20 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/switch.d.ts +4 -0
- package/dist/commands/switch.d.ts.map +1 -0
- package/dist/commands/switch.js +78 -0
- package/dist/commands/switch.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +107 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/core/codex.d.ts +6 -0
- package/dist/core/codex.d.ts.map +1 -0
- package/dist/core/codex.js +123 -0
- package/dist/core/codex.js.map +1 -0
- package/dist/core/config.d.ts +6 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +68 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +7 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/ollama.d.ts +3 -0
- package/dist/core/ollama.d.ts.map +1 -0
- package/dist/core/ollama.js +20 -0
- package/dist/core/ollama.js.map +1 -0
- package/dist/gateway/auth.d.ts +58 -0
- package/dist/gateway/auth.d.ts.map +1 -0
- package/dist/gateway/auth.js +248 -0
- package/dist/gateway/auth.js.map +1 -0
- package/dist/gateway/providers/anthropic.d.ts +15 -0
- package/dist/gateway/providers/anthropic.d.ts.map +1 -0
- package/dist/gateway/providers/anthropic.js +122 -0
- package/dist/gateway/providers/anthropic.js.map +1 -0
- package/dist/gateway/providers/antigravity-oauth-flow.d.ts +21 -0
- package/dist/gateway/providers/antigravity-oauth-flow.d.ts.map +1 -0
- package/dist/gateway/providers/antigravity-oauth-flow.js +231 -0
- package/dist/gateway/providers/antigravity-oauth-flow.js.map +1 -0
- package/dist/gateway/providers/antigravity.d.ts +5 -0
- package/dist/gateway/providers/antigravity.d.ts.map +1 -0
- package/dist/gateway/providers/antigravity.js +111 -0
- package/dist/gateway/providers/antigravity.js.map +1 -0
- package/dist/gateway/providers/claude-oauth-flow.d.ts +16 -0
- package/dist/gateway/providers/claude-oauth-flow.d.ts.map +1 -0
- package/dist/gateway/providers/claude-oauth-flow.js +178 -0
- package/dist/gateway/providers/claude-oauth-flow.js.map +1 -0
- package/dist/gateway/providers/copilot.d.ts +19 -0
- package/dist/gateway/providers/copilot.d.ts.map +1 -0
- package/dist/gateway/providers/copilot.js +141 -0
- package/dist/gateway/providers/copilot.js.map +1 -0
- package/dist/gateway/providers/google.d.ts +12 -0
- package/dist/gateway/providers/google.d.ts.map +1 -0
- package/dist/gateway/providers/google.js +58 -0
- package/dist/gateway/providers/google.js.map +1 -0
- package/dist/gateway/providers/ollama.d.ts +6 -0
- package/dist/gateway/providers/ollama.d.ts.map +1 -0
- package/dist/gateway/providers/ollama.js +54 -0
- package/dist/gateway/providers/ollama.js.map +1 -0
- package/dist/gateway/providers/openai-oauth-flow.d.ts +15 -0
- package/dist/gateway/providers/openai-oauth-flow.d.ts.map +1 -0
- package/dist/gateway/providers/openai-oauth-flow.js +149 -0
- package/dist/gateway/providers/openai-oauth-flow.js.map +1 -0
- package/dist/gateway/providers/openai.d.ts +8 -0
- package/dist/gateway/providers/openai.d.ts.map +1 -0
- package/dist/gateway/providers/openai.js +193 -0
- package/dist/gateway/providers/openai.js.map +1 -0
- package/dist/gateway/proxy.d.ts +119 -0
- package/dist/gateway/proxy.d.ts.map +1 -0
- package/dist/gateway/proxy.js +1949 -0
- package/dist/gateway/proxy.js.map +1 -0
- package/dist/gateway/server.d.ts +6 -0
- package/dist/gateway/server.d.ts.map +1 -0
- package/dist/gateway/server.js +890 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/gateway/ui.d.ts +2 -0
- package/dist/gateway/ui.d.ts.map +1 -0
- package/dist/gateway/ui.js +1748 -0
- package/dist/gateway/ui.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +25 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +22 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/shell.d.ts +8 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +27 -0
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/updates.d.ts +2 -0
- package/dist/utils/updates.d.ts.map +1 -0
- package/dist/utils/updates.js +83 -0
- package/dist/utils/updates.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1748 @@
|
|
|
1
|
+
export function getHTML(port) {
|
|
2
|
+
return `<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>rcodex Gateway</title>
|
|
8
|
+
<style>
|
|
9
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
:root{
|
|
11
|
+
--bg:#0d0d12;--s1:#15151f;--s2:#1c1c28;--s3:#21212e;
|
|
12
|
+
--b1:#252535;--b2:#383850;--b3:#5a5a80;
|
|
13
|
+
--tx:#e0e0f0;--mu:#606080;--di:#9090b0;
|
|
14
|
+
--gr:#22c55e;--bl:#6366f1;--bl2:#818cf8;
|
|
15
|
+
--rd:#ef4444;--yw:#f59e0b;
|
|
16
|
+
}
|
|
17
|
+
html,body{height:100%;overflow:hidden;background:var(--bg);color:var(--tx);
|
|
18
|
+
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
19
|
+
text-rendering:geometricPrecision;-webkit-text-size-adjust:100%}
|
|
20
|
+
|
|
21
|
+
/* ?�?� Header ?�?� */
|
|
22
|
+
.hdr{height:46px;display:flex;align-items:center;justify-content:space-between;
|
|
23
|
+
padding:0 14px 0 10px;background:rgba(13,13,18,.98);border-bottom:1px solid var(--b1);
|
|
24
|
+
position:relative;z-index:100;flex-shrink:0;gap:8px}
|
|
25
|
+
.hdr-l{display:flex;align-items:center;gap:8px}
|
|
26
|
+
.icon-btn{width:32px;height:32px;border-radius:8px;border:1px solid var(--b1);
|
|
27
|
+
background:transparent;color:var(--di);cursor:pointer;display:flex;
|
|
28
|
+
align-items:center;justify-content:center;transition:all .15s;flex-shrink:0}
|
|
29
|
+
.icon-btn:hover{background:var(--s2);color:var(--tx);border-color:var(--b2)}
|
|
30
|
+
.icon-btn.on{background:var(--s2);color:var(--bl2);border-color:var(--b2)}
|
|
31
|
+
.logo{display:flex;align-items:center;gap:7px}
|
|
32
|
+
.logo-ic{width:24px;height:24px;border-radius:6px;
|
|
33
|
+
background:linear-gradient(135deg,#6366f1,#8b5cf6);
|
|
34
|
+
display:flex;align-items:center;justify-content:center;font-size:11px}
|
|
35
|
+
.logo-txt{font-size:13px;font-weight:600}
|
|
36
|
+
.logo-sep{color:var(--b2)}
|
|
37
|
+
.logo-port{font-size:11px;color:var(--mu)}
|
|
38
|
+
.pill{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--gr)}
|
|
39
|
+
.dot{width:5px;height:5px;border-radius:50%;background:var(--gr);
|
|
40
|
+
box-shadow:0 0 5px var(--gr);animation:blink 2s infinite}
|
|
41
|
+
@keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
|
|
42
|
+
|
|
43
|
+
/* ?�?� Layout ?�?� */
|
|
44
|
+
.layout{display:flex;flex:1;overflow:hidden;position:relative}
|
|
45
|
+
|
|
46
|
+
/* ?�?� Sidebar shell ?�?� */
|
|
47
|
+
.sb{position:absolute;left:0;top:0;bottom:0;width:280px;
|
|
48
|
+
background:var(--s1);border-right:1px solid var(--b1);
|
|
49
|
+
display:flex;flex-direction:column;z-index:60;
|
|
50
|
+
transform:translateX(-100%);transition:transform .22s cubic-bezier(.4,0,.2,1);
|
|
51
|
+
box-shadow:4px 0 32px rgba(0,0,0,.5)}
|
|
52
|
+
.sb.open{transform:translateX(0)}
|
|
53
|
+
.sb-hdr{display:flex;align-items:center;padding:12px 14px 10px;border-bottom:1px solid var(--b1);gap:8px;flex-shrink:0}
|
|
54
|
+
.sb-back{width:26px;height:26px;border-radius:6px;border:none;background:transparent;
|
|
55
|
+
color:var(--mu);cursor:pointer;font-size:16px;display:flex;align-items:center;
|
|
56
|
+
justify-content:center;transition:all .15s}
|
|
57
|
+
.sb-back:hover{background:var(--s2);color:var(--tx)}
|
|
58
|
+
.sb-title{font-size:13px;font-weight:600;flex:1}
|
|
59
|
+
.sb-x{width:26px;height:26px;border-radius:6px;border:none;background:transparent;
|
|
60
|
+
color:var(--mu);cursor:pointer;font-size:18px;display:flex;align-items:center;
|
|
61
|
+
justify-content:center;transition:all .15s;line-height:1}
|
|
62
|
+
.sb-x:hover{background:var(--s2);color:var(--tx)}
|
|
63
|
+
.sb-body{flex:1;overflow-y:auto}
|
|
64
|
+
|
|
65
|
+
/* ?�?� Scrollbars (visible, styled) ?�?� */
|
|
66
|
+
.sb-body::-webkit-scrollbar,.mn-body::-webkit-scrollbar{width:5px}
|
|
67
|
+
.sb-body::-webkit-scrollbar-track,.mn-body::-webkit-scrollbar-track{background:transparent}
|
|
68
|
+
.sb-body::-webkit-scrollbar-thumb,.mn-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:3px}
|
|
69
|
+
.sb-body::-webkit-scrollbar-thumb:hover,.mn-body::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.28)}
|
|
70
|
+
|
|
71
|
+
/* ?�?� Home nav items ?�?� */
|
|
72
|
+
.nav-item{display:flex;align-items:center;gap:11px;padding:11px 16px;
|
|
73
|
+
cursor:pointer;transition:background .12s;user-select:none}
|
|
74
|
+
.nav-item:hover{background:var(--s2)}
|
|
75
|
+
.nav-ic{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;
|
|
76
|
+
justify-content:center;font-size:15px;flex-shrink:0}
|
|
77
|
+
.nav-info{flex:1}
|
|
78
|
+
.nav-name{font-size:13px;font-weight:500}
|
|
79
|
+
.nav-sub{font-size:10px;color:var(--mu);margin-top:1px}
|
|
80
|
+
.nav-arr{color:var(--mu);font-size:14px}
|
|
81
|
+
.nav-badge{font-size:9px;padding:2px 7px;border-radius:10px;
|
|
82
|
+
background:rgba(99,102,241,.15);color:var(--bl2);border:1px solid rgba(99,102,241,.2)}
|
|
83
|
+
.sb-sep{height:1px;background:var(--b1);margin:4px 0}
|
|
84
|
+
|
|
85
|
+
/* ?�?� Provider type list ?�?� */
|
|
86
|
+
.ptype{display:flex;align-items:center;gap:10px;padding:10px 14px;
|
|
87
|
+
cursor:pointer;transition:background .12s;border-radius:0}
|
|
88
|
+
.ptype:hover{background:var(--s2)}
|
|
89
|
+
.ptype-ic{width:34px;height:34px;border-radius:9px;display:flex;align-items:center;
|
|
90
|
+
justify-content:center;font-size:16px;flex-shrink:0}
|
|
91
|
+
.ptype-info{flex:1;min-width:0}
|
|
92
|
+
.ptype-name{font-size:12px;font-weight:600}
|
|
93
|
+
.ptype-sub{font-size:10px;color:var(--mu);margin-top:1px}
|
|
94
|
+
.add-btn{padding:5px 12px;border-radius:7px;border:1px solid rgba(99,102,241,.3);
|
|
95
|
+
background:rgba(99,102,241,.1);color:var(--bl2);font-size:10px;font-weight:600;
|
|
96
|
+
cursor:pointer;transition:all .15s;white-space:nowrap;flex-shrink:0}
|
|
97
|
+
.add-btn:hover{background:rgba(99,102,241,.22);border-color:var(--bl)}
|
|
98
|
+
|
|
99
|
+
/* ?�?� Connected accounts ?�?� */
|
|
100
|
+
.sb-section{font-size:9px;font-weight:700;text-transform:uppercase;
|
|
101
|
+
letter-spacing:.1em;color:var(--mu);padding:12px 16px 6px}
|
|
102
|
+
.acc-item{display:flex;align-items:center;gap:10px;padding:9px 14px;
|
|
103
|
+
transition:background .12s}
|
|
104
|
+
.acc-item:hover{background:var(--s2)}
|
|
105
|
+
.acc-ic{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;
|
|
106
|
+
justify-content:center;font-size:14px;flex-shrink:0}
|
|
107
|
+
.acc-info{flex:1;min-width:0}
|
|
108
|
+
.acc-name{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
109
|
+
.acc-sub{font-size:10px;color:var(--mu);margin-top:1px}
|
|
110
|
+
.acc-actions{display:flex;gap:4px;flex-shrink:0}
|
|
111
|
+
.canvas-btn{padding:4px 9px;border-radius:6px;border:1px solid rgba(34,197,94,.25);
|
|
112
|
+
background:rgba(34,197,94,.07);color:var(--gr);font-size:9px;font-weight:600;
|
|
113
|
+
cursor:pointer;transition:all .15s;white-space:nowrap}
|
|
114
|
+
.canvas-btn:hover{background:rgba(34,197,94,.15)}
|
|
115
|
+
.canvas-btn.on-canvas{border-color:rgba(99,102,241,.3);background:rgba(99,102,241,.1);color:var(--bl2)}
|
|
116
|
+
.canvas-btn.on-canvas:hover{border-color:rgba(239,68,68,.3);background:rgba(239,68,68,.08);color:var(--rd)}
|
|
117
|
+
.del-btn{width:22px;height:22px;border-radius:5px;border:none;background:transparent;
|
|
118
|
+
color:var(--mu);cursor:pointer;font-size:13px;display:flex;align-items:center;
|
|
119
|
+
justify-content:center;transition:all .12s}
|
|
120
|
+
.del-btn:hover{background:rgba(239,68,68,.1);color:var(--rd)}
|
|
121
|
+
|
|
122
|
+
/* ?�?� Auth method cards ?�?� */
|
|
123
|
+
.auth-cards{display:flex;flex-direction:column;gap:8px;padding:12px 14px}
|
|
124
|
+
.auth-card{border:1px solid var(--b1);border-radius:11px;padding:12px 14px;
|
|
125
|
+
cursor:pointer;transition:all .15s;background:var(--s2)}
|
|
126
|
+
.auth-card:hover{border-color:var(--bl);background:rgba(99,102,241,.06)}
|
|
127
|
+
.auth-card-hdr{display:flex;align-items:center;gap:9px;margin-bottom:4px}
|
|
128
|
+
.auth-card-ic{font-size:16px}
|
|
129
|
+
.auth-card-name{font-size:12px;font-weight:600}
|
|
130
|
+
.auth-card-sub{font-size:10px;color:var(--mu);line-height:1.5}
|
|
131
|
+
.auth-warn{background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.22);
|
|
132
|
+
border-radius:8px;padding:8px 11px;font-size:10px;color:#fcd34d;
|
|
133
|
+
margin:0 14px 10px;line-height:1.5}
|
|
134
|
+
|
|
135
|
+
/* ?�?� Auth form ?�?� */
|
|
136
|
+
.auth-form{padding:12px 14px;display:flex;flex-direction:column;gap:8px}
|
|
137
|
+
.form-label{font-size:10px;color:var(--mu)}
|
|
138
|
+
.form-input{width:100%;padding:8px 11px;border-radius:8px;background:var(--bg);
|
|
139
|
+
border:1px solid var(--b1);color:var(--tx);font-size:12px;outline:none;
|
|
140
|
+
font-family:'SF Mono',monospace;transition:border-color .15s}
|
|
141
|
+
.form-input:focus{border-color:var(--b2)}
|
|
142
|
+
.form-input::placeholder{color:var(--mu)}
|
|
143
|
+
.form-actions{display:flex;gap:7px;margin-top:2px}
|
|
144
|
+
.form-cancel{flex:1;padding:8px;border-radius:8px;border:1px solid var(--b1);
|
|
145
|
+
background:transparent;color:var(--mu);font-size:11px;cursor:pointer}
|
|
146
|
+
.form-submit{flex:2;padding:8px;border-radius:8px;border:none;
|
|
147
|
+
background:linear-gradient(135deg,#6366f1,#8b5cf6);
|
|
148
|
+
color:#fff;font-size:11px;font-weight:600;cursor:pointer}
|
|
149
|
+
.form-submit:hover{opacity:.9}
|
|
150
|
+
|
|
151
|
+
/* ?�?� Canvas ?�?� */
|
|
152
|
+
.ws{position:relative;flex:1;overflow:hidden;cursor:default;user-select:none;
|
|
153
|
+
background-color:var(--bg);
|
|
154
|
+
background-image:radial-gradient(circle,var(--b1) 1px,transparent 1px);
|
|
155
|
+
background-size:28px 28px;
|
|
156
|
+
image-rendering:crisp-edges;
|
|
157
|
+
image-rendering:pixelated}
|
|
158
|
+
#world{position:absolute;top:0;left:0}
|
|
159
|
+
#svgl{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
|
|
160
|
+
.cp{fill:none;stroke-width:2.5;opacity:.75}
|
|
161
|
+
.ct{fill:none;stroke:var(--bl2);stroke-width:2;stroke-dasharray:7 4;opacity:.9;
|
|
162
|
+
animation:sdash .5s linear infinite}
|
|
163
|
+
@keyframes sdash{to{stroke-dashoffset:-22}}
|
|
164
|
+
|
|
165
|
+
/* ?�?� Canvas nodes ?�?� */
|
|
166
|
+
.nd{position:absolute;width:215px;background:var(--s1);border:1px solid var(--b1);
|
|
167
|
+
border-radius:13px;box-shadow:0 6px 24px rgba(0,0,0,.5);
|
|
168
|
+
transition:border-color .2s,box-shadow .2s}
|
|
169
|
+
.nd:hover{border-color:var(--b2)}
|
|
170
|
+
.nd.live{border-color:rgba(34,197,94,.4);box-shadow:0 0 0 1px rgba(34,197,94,.08),0 6px 24px rgba(0,0,0,.5)}
|
|
171
|
+
.nd.sel{border-color:var(--bl);box-shadow:0 0 0 2px rgba(99,102,241,.18),0 6px 24px rgba(0,0,0,.5)}
|
|
172
|
+
.nh{display:flex;align-items:center;gap:8px;padding:9px 8px 9px 12px;
|
|
173
|
+
border-bottom:1px solid var(--b1);border-radius:13px 13px 0 0;cursor:grab;min-height:42px}
|
|
174
|
+
.nh:active{cursor:grabbing}
|
|
175
|
+
.nic{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;
|
|
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}
|
|
178
|
+
.bk{font-size:9px;padding:2px 6px;border-radius:5px;font-weight:600;white-space:nowrap;flex-shrink:0}
|
|
179
|
+
.bk-on{background:rgba(34,197,94,.12);color:var(--gr);border:1px solid rgba(34,197,94,.2)}
|
|
180
|
+
.bk-off{background:rgba(96,96,128,.1);color:var(--mu);border:1px solid var(--b1)}
|
|
181
|
+
.nd-rm{width:20px;height:20px;border-radius:5px;border:none;background:transparent;
|
|
182
|
+
color:var(--mu);cursor:pointer;font-size:14px;display:flex;align-items:center;
|
|
183
|
+
justify-content:center;transition:all .12s;flex-shrink:0}
|
|
184
|
+
.nd-rm:hover{background:rgba(239,68,68,.1);color:var(--rd)}
|
|
185
|
+
.nb{padding:9px 12px;display:flex;flex-direction:column;gap:5px}
|
|
186
|
+
.msel{width:100%;padding:5px 8px;background:var(--bg);border:1px solid var(--b1);
|
|
187
|
+
border-radius:7px;color:var(--tx);font-size:11px;cursor:pointer;outline:none}
|
|
188
|
+
.msel:focus{border-color:var(--b2)}
|
|
189
|
+
.msel option{background:var(--s1)}
|
|
190
|
+
.nd-hint{font-size:10px;color:var(--mu);text-align:center;padding:3px 0}
|
|
191
|
+
.nd-acct{font-size:9px;color:var(--mu);padding:4px 10px 6px;text-align:center;
|
|
192
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:.65;
|
|
193
|
+
letter-spacing:.01em}
|
|
194
|
+
|
|
195
|
+
/* ?�?� Ports ?�?� */
|
|
196
|
+
.po{position:absolute;right:-11px;top:50%;transform:translateY(-50%);
|
|
197
|
+
width:22px;height:22px;border-radius:50%;background:var(--s1);
|
|
198
|
+
border:2px solid var(--b2);cursor:crosshair;z-index:15;
|
|
199
|
+
display:flex;align-items:center;justify-content:center;transition:all .15s;pointer-events:all}
|
|
200
|
+
.po::after{content:'';width:8px;height:8px;border-radius:50%;background:var(--b2);transition:background .15s}
|
|
201
|
+
.po:hover{border-color:var(--bl);transform:translateY(-50%) scale(1.2)}
|
|
202
|
+
.po:hover::after{background:var(--bl)}
|
|
203
|
+
.po.live{border-color:var(--gr)}.po.live::after{background:var(--gr)}
|
|
204
|
+
.po.dragging{border-color:var(--bl);background:rgba(99,102,241,.15)}.po.dragging::after{background:var(--bl)}
|
|
205
|
+
.pi{position:absolute;left:-11px;top:50%;transform:translateY(-50%);
|
|
206
|
+
width:22px;height:22px;border-radius:50%;background:var(--s1);
|
|
207
|
+
border:2px solid var(--b2);z-index:15;
|
|
208
|
+
display:flex;align-items:center;justify-content:center;transition:all .15s;pointer-events:all}
|
|
209
|
+
.pi::after{content:'';width:8px;height:8px;border-radius:50%;background:var(--b2);transition:background .15s}
|
|
210
|
+
.pi.live{border-color:var(--gr)}.pi.live::after{background:var(--gr)}
|
|
211
|
+
.pi.acc{border-color:var(--bl);box-shadow:0 0 0 4px rgba(99,102,241,.2)}.pi.acc::after{background:var(--bl)}
|
|
212
|
+
|
|
213
|
+
/* ?�?� OUT node ?�?� */
|
|
214
|
+
.out-node{width:240px}
|
|
215
|
+
.out-ic{width:26px;height:26px;border-radius:7px;
|
|
216
|
+
background:linear-gradient(135deg,#6366f1,#8b5cf6);
|
|
217
|
+
display:flex;align-items:center;justify-content:center;font-size:11px}
|
|
218
|
+
.oi{display:flex;align-items:center;gap:6px;padding:5px 0;border-bottom:1px solid var(--b1)}
|
|
219
|
+
.oi:last-child{border-bottom:none}
|
|
220
|
+
.oi-num{font-size:9px;font-weight:700;color:var(--mu);min-width:12px;text-align:center;flex-shrink:0}
|
|
221
|
+
.oi-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
222
|
+
.oi-inf{flex:1;min-width:0}
|
|
223
|
+
.oi-pr{font-size:9px;color:var(--mu)}
|
|
224
|
+
.oi-mo{font-size:10px;color:var(--di);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
225
|
+
.oi-ord{display:flex;flex-direction:column;gap:1px;flex-shrink:0}
|
|
226
|
+
.oi-arr{width:16px;height:14px;border-radius:3px;border:none;background:transparent;
|
|
227
|
+
color:var(--mu);cursor:pointer;font-size:10px;line-height:1;padding:0;
|
|
228
|
+
display:flex;align-items:center;justify-content:center;transition:all .1s}
|
|
229
|
+
.oi-arr:hover{background:var(--s3);color:var(--tx)}
|
|
230
|
+
.oi-arr:disabled{opacity:.2;cursor:default}
|
|
231
|
+
.oi-x{background:none;border:none;color:var(--mu);cursor:pointer;font-size:15px;
|
|
232
|
+
line-height:1;padding:0;flex-shrink:0}
|
|
233
|
+
.oi-x:hover{color:var(--rd)}
|
|
234
|
+
.out-empty{font-size:10px;color:var(--mu);text-align:center;padding:12px 0;line-height:1.8}
|
|
235
|
+
|
|
236
|
+
/* ?�?� Zoom ?�?� */
|
|
237
|
+
.zbar{position:absolute;bottom:18px;right:18px;display:flex;align-items:center;gap:3px;
|
|
238
|
+
background:var(--s1);border:1px solid var(--b1);border-radius:8px;padding:3px 5px;
|
|
239
|
+
z-index:50;box-shadow:0 4px 16px rgba(0,0,0,.35)}
|
|
240
|
+
.zbtn{width:28px;height:28px;border-radius:6px;border:none;background:transparent;
|
|
241
|
+
color:var(--di);cursor:pointer;font-size:16px;display:flex;align-items:center;
|
|
242
|
+
justify-content:center;transition:all .1s}
|
|
243
|
+
.zbtn:hover{background:var(--s2);color:var(--tx)}
|
|
244
|
+
.zpct{font-size:11px;color:var(--di);min-width:38px;text-align:center}
|
|
245
|
+
.hint{position:absolute;bottom:18px;left:18px;font-size:10px;color:var(--mu);
|
|
246
|
+
pointer-events:none;z-index:50;line-height:1.8}
|
|
247
|
+
|
|
248
|
+
/* ?�?� Monitor node ?�?� */
|
|
249
|
+
.mn{position:absolute;width:620px;background:var(--s1);border:1px solid var(--b1);
|
|
250
|
+
border-radius:13px;box-shadow:0 8px 40px rgba(0,0,0,.65);z-index:20;min-width:320px;min-height:160px}
|
|
251
|
+
.mn-rs-e{position:absolute;right:-4px;top:12px;bottom:12px;width:8px;cursor:ew-resize;z-index:21}
|
|
252
|
+
.mn-rs-s{position:absolute;bottom:-4px;left:12px;right:12px;height:8px;cursor:ns-resize;z-index:21}
|
|
253
|
+
.mn-rs-se{position:absolute;bottom:-4px;right:-4px;width:14px;height:14px;cursor:se-resize;z-index:22}
|
|
254
|
+
.mn-tabbar{display:flex;padding:0 10px;border-bottom:1px solid var(--b1);background:rgba(0,0,0,.12)}
|
|
255
|
+
.mn-t{padding:8px 13px;font-size:10px;font-weight:600;color:var(--mu);cursor:pointer;
|
|
256
|
+
border-bottom:2px solid transparent;transition:all .12s;white-space:nowrap;user-select:none}
|
|
257
|
+
.mn-t.on{color:var(--tx);border-bottom-color:var(--bl2)}
|
|
258
|
+
.mn-t:hover:not(.on){color:var(--di)}
|
|
259
|
+
.mn-body{height:320px;overflow-y:auto;overflow-x:hidden;user-select:text;cursor:auto;
|
|
260
|
+
font-family:'SF Mono',Menlo,'Cascadia Code',monospace;font-size:11px;
|
|
261
|
+
background:rgba(0,0,0,.28);border-radius:0 0 0 0}
|
|
262
|
+
.mn-body.last{border-radius:0 0 12px 12px}
|
|
263
|
+
/* terminal */
|
|
264
|
+
.term-wrap{height:100%;padding:8px 10px;display:flex;flex-direction:column;gap:0}
|
|
265
|
+
.term-line{white-space:pre-wrap;word-break:break-all;line-height:1.55;padding:1px 0}
|
|
266
|
+
.t-cmd{color:#e0e0f0}.t-out{color:#8888aa}.t-err{color:#f87171}.t-info{color:#818cf8}
|
|
267
|
+
.mn-inp-row{display:flex;align-items:center;gap:6px;padding:5px 10px;
|
|
268
|
+
border-top:1px solid var(--b1);background:rgba(0,0,0,.18);
|
|
269
|
+
border-radius:0 0 12px 12px;flex-shrink:0}
|
|
270
|
+
.mn-cwd-lbl{font-size:9px;color:var(--bl2);font-family:'SF Mono',monospace;
|
|
271
|
+
max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}
|
|
272
|
+
.mn-inp{flex:1;background:transparent;border:none;color:var(--tx);
|
|
273
|
+
font-size:11px;font-family:'SF Mono',monospace;outline:none;min-width:0}
|
|
274
|
+
.mn-inp::placeholder{color:var(--mu)}
|
|
275
|
+
.mn-run{padding:3px 9px;border-radius:5px;border:1px solid var(--b2);
|
|
276
|
+
background:rgba(99,102,241,.12);color:var(--bl2);font-size:10px;
|
|
277
|
+
cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s}
|
|
278
|
+
.mn-run:hover{background:rgba(99,102,241,.22)}
|
|
279
|
+
/* status */
|
|
280
|
+
.st-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;padding:12px}
|
|
281
|
+
.st-card{background:rgba(0,0,0,.25);border-radius:9px;padding:10px 12px}
|
|
282
|
+
.st-val{font-size:20px;font-weight:700;color:var(--tx);font-family:'SF Mono',monospace}
|
|
283
|
+
.st-key{font-size:9px;color:var(--mu);text-transform:uppercase;letter-spacing:.08em;margin-top:2px}
|
|
284
|
+
.st-prov{padding:0 12px 10px}
|
|
285
|
+
.st-pr{display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.04);font-size:10px}
|
|
286
|
+
.st-pr:last-child{border-bottom:none}
|
|
287
|
+
/* logs */
|
|
288
|
+
.log-wrap{padding:6px 10px}
|
|
289
|
+
.log-l{font-size:10px;color:var(--di);line-height:1.6;padding:1px 0;
|
|
290
|
+
white-space:pre-wrap;word-break:break-all;border-bottom:1px solid rgba(255,255,255,.025)}
|
|
291
|
+
.log-l:last-child{border-bottom:none}
|
|
292
|
+
.log-l.err{color:#f87171}.log-l.warn{color:#fbbf24}.log-l.ok{color:#4ade80}
|
|
293
|
+
/* requests */
|
|
294
|
+
.req-wrap{padding:4px 0}
|
|
295
|
+
.req-row{display:grid;grid-template-columns:52px 80px 1fr 40px 72px 20px;
|
|
296
|
+
gap:4px;align-items:start;padding:5px 8px;font-size:10px;
|
|
297
|
+
border-bottom:1px solid rgba(255,255,255,.035)}
|
|
298
|
+
.req-row:last-child{border-bottom:none}
|
|
299
|
+
.req-ts{color:var(--mu);font-size:9px;font-family:'SF Mono',monospace;padding-top:2px}
|
|
300
|
+
.req-prov{font-weight:600;white-space:nowrap;padding-top:2px}
|
|
301
|
+
.req-model{color:var(--di);overflow:hidden;text-overflow:ellipsis}
|
|
302
|
+
.req-ms{color:var(--mu);font-size:9px;text-align:right;font-family:'SF Mono',monospace}
|
|
303
|
+
.req-ok{color:var(--gr);font-size:9px;font-weight:600;text-align:right}
|
|
304
|
+
.req-fb{color:var(--gr);font-size:9px;font-weight:600;text-align:right;opacity:.7}
|
|
305
|
+
.req-err{color:var(--rd);font-size:9px;font-weight:600;text-align:right}
|
|
306
|
+
.req-expand-btn{background:none;border:none;color:var(--mu);cursor:pointer;font-size:9px;padding:0;line-height:1}
|
|
307
|
+
.req-expand-btn:hover{color:var(--fg)}
|
|
308
|
+
.req-detail-panel{padding:6px 8px 8px;background:rgba(0,0,0,.2);border-bottom:1px solid var(--b1)}
|
|
309
|
+
.req-detail-section{margin-bottom:5px}
|
|
310
|
+
.req-detail-label{font-size:9px;color:var(--mu);font-weight:600;margin-bottom:2px;text-transform:uppercase;letter-spacing:.04em}
|
|
311
|
+
.req-detail-val{font-size:10px;color:var(--fg);line-height:1.5;white-space:pre-wrap;word-break:break-all}
|
|
312
|
+
.req-detail-val code{background:rgba(255,255,255,.07);padding:1px 4px;border-radius:3px;font-size:9px}
|
|
313
|
+
.usg-card{background:var(--s2);border-radius:8px;padding:8px 10px;text-align:center}
|
|
314
|
+
.usg-val{font-size:15px;font-weight:700;letter-spacing:-.3px}
|
|
315
|
+
.usg-lbl{font-size:9px;color:var(--mu);margin-top:2px;text-transform:uppercase;letter-spacing:.06em}
|
|
316
|
+
.usg-row{display:grid;grid-template-columns:1fr 36px 56px 56px 60px;gap:4px;padding:5px 8px;font-size:10px;align-items:center}
|
|
317
|
+
.usg-hdr{font-size:9px;font-weight:700;color:var(--mu);text-transform:uppercase;letter-spacing:.06em;padding:6px 8px 4px;border-bottom:1px solid var(--b1)}
|
|
318
|
+
.usg-row:not(.usg-hdr):hover{background:rgba(255,255,255,.03)}
|
|
319
|
+
.req-hdr{color:var(--mu);font-size:9px;font-weight:700;text-transform:uppercase;
|
|
320
|
+
letter-spacing:.06em;border-bottom:1px solid var(--b2) !important;padding:4px 10px}
|
|
321
|
+
.mn-empty{display:flex;align-items:center;justify-content:center;height:100%;
|
|
322
|
+
font-size:11px;color:var(--mu)}
|
|
323
|
+
|
|
324
|
+
/* ?�?� Mode switcher ?�?� */
|
|
325
|
+
.mode-sw{display:flex;align-items:center;background:var(--s2);border:1px solid var(--b1);border-radius:7px;padding:2px;gap:1px}
|
|
326
|
+
.ms-btn{padding:3px 10px;border-radius:5px;border:none;font-size:10px;font-weight:600;cursor:pointer;
|
|
327
|
+
transition:all .15s;color:var(--mu);background:transparent;white-space:nowrap}
|
|
328
|
+
.ms-btn.active{background:var(--bl);color:#fff}
|
|
329
|
+
.ms-btn:not(.active):hover{color:var(--tx);background:rgba(255,255,255,.06)}
|
|
330
|
+
|
|
331
|
+
/* ?�?� OUT node bypass warning ?�?� */
|
|
332
|
+
.out-bypass{font-size:9px;color:#fcd34d;background:rgba(245,158,11,.1);
|
|
333
|
+
border-top:1px solid rgba(245,158,11,.25);padding:5px 10px;text-align:center;
|
|
334
|
+
border-radius:0 0 12px 12px;letter-spacing:.01em}
|
|
335
|
+
.nd.bypassed{border-color:rgba(245,158,11,.5)!important;
|
|
336
|
+
box-shadow:0 0 0 1px rgba(245,158,11,.1),0 6px 24px rgba(0,0,0,.5)!important}
|
|
337
|
+
|
|
338
|
+
/* ?�?� Toast ?�?� */
|
|
339
|
+
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);
|
|
340
|
+
background:var(--s1);border:1px solid var(--b1);border-radius:8px;padding:8px 16px;
|
|
341
|
+
font-size:11px;box-shadow:0 8px 32px rgba(0,0,0,.45);z-index:300;
|
|
342
|
+
animation:tIn .2s ease;white-space:nowrap;pointer-events:none}
|
|
343
|
+
@keyframes tIn{from{opacity:0;transform:translateX(-50%) translateY(6px)}}
|
|
344
|
+
</style>
|
|
345
|
+
</head>
|
|
346
|
+
<body style="display:flex;flex-direction:column;height:100vh">
|
|
347
|
+
|
|
348
|
+
<div class="hdr">
|
|
349
|
+
<div class="hdr-l">
|
|
350
|
+
<button class="icon-btn" id="sb-btn" onclick="toggleSb()" title="Menu">
|
|
351
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
|
|
352
|
+
<rect x="1" y="2.5" width="13" height="1.5" rx=".75" fill="currentColor"/>
|
|
353
|
+
<rect x="1" y="6.75" width="13" height="1.5" rx=".75" fill="currentColor"/>
|
|
354
|
+
<rect x="1" y="11" width="9" height="1.5" rx=".75" fill="currentColor"/>
|
|
355
|
+
</svg>
|
|
356
|
+
</button>
|
|
357
|
+
<div class="logo">
|
|
358
|
+
<div class="logo-ic">??/div>
|
|
359
|
+
<span class="logo-txt">rcodex Gateway</span>
|
|
360
|
+
<span class="logo-sep"> · </span>
|
|
361
|
+
<span class="logo-port">:${port}</span>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
<div style="display:flex;align-items:center;gap:5px">
|
|
365
|
+
<button class="icon-btn" id="hb-term" onclick="toggleMonitor('terminal')" title="Terminal">
|
|
366
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M1.5 3.5L5 7l-3.5 3.5M6 10.5h6.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
367
|
+
</button>
|
|
368
|
+
<button class="icon-btn" id="hb-stat" onclick="toggleMonitor('status')" title="Gateway Status">
|
|
369
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.4"/><path d="M7 4v3.5l2 1.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
|
370
|
+
</button>
|
|
371
|
+
<button class="icon-btn" id="hb-logs" onclick="toggleMonitor('logs')" title="Gateway Logs">
|
|
372
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 3.5h10M2 7h7M2 10.5h5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
|
373
|
+
</button>
|
|
374
|
+
<button class="icon-btn" id="hb-reqs" onclick="toggleMonitor('requests')" title="Request History">
|
|
375
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2.5 4.5h9M2.5 7h6M2.5 9.5h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><circle cx="11" cy="9.5" r="2" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
376
|
+
</button>
|
|
377
|
+
<button class="icon-btn" id="hb-usage" onclick="toggleMonitor('usage')" title="Token Usage">
|
|
378
|
+
<svg width="14" height="14" 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>
|
|
379
|
+
</button>
|
|
380
|
+
<div style="width:1px;height:18px;background:var(--b1);margin:0 2px"></div>
|
|
381
|
+
<div class="mode-sw">
|
|
382
|
+
<button class="ms-btn" id="ms-rcodex" onclick="switchProvider('rcodex')" title="Route Codex through rcodex Gateway">rcodex</button>
|
|
383
|
+
<button class="ms-btn" id="ms-oai" onclick="switchProvider('openai')" title="Use OpenAI directly (bypass gateway)">OpenAI</button>
|
|
384
|
+
</div>
|
|
385
|
+
<div style="width:1px;height:18px;background:var(--b1);margin:0 2px"></div>
|
|
386
|
+
<div class="pill"><div class="dot"></div>Running</div>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div class="layout">
|
|
391
|
+
|
|
392
|
+
<!-- ?�?� Sidebar ?�?� -->
|
|
393
|
+
<div class="sb" id="sb">
|
|
394
|
+
<div class="sb-hdr">
|
|
395
|
+
<button class="sb-back" id="sb-back" onclick="sbGoBack()" style="display:none">??/button>
|
|
396
|
+
<span class="sb-title" id="sb-title">Menu</span>
|
|
397
|
+
<button class="sb-x" onclick="toggleSb()">×</button>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="sb-body" id="sb-body"></div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
<!-- ?�?� Canvas ?�?� -->
|
|
403
|
+
<div class="ws" id="ws">
|
|
404
|
+
<svg id="svgl"></svg>
|
|
405
|
+
<div id="world"></div>
|
|
406
|
+
<div class="zbar">
|
|
407
|
+
<button class="zbtn" onclick="zoomStep(-1)">??/button>
|
|
408
|
+
<div class="zpct" id="zpct">100%</div>
|
|
409
|
+
<button class="zbtn" onclick="zoomStep(1)">+</button>
|
|
410
|
+
<button class="zbtn" onclick="fitAll()" style="font-size:12px">??/button>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="hint">Scroll to zoom · Drag to pan · Drag ??to connect</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<script>
|
|
418
|
+
// ?�?�?� Provider definitions ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
419
|
+
const PDEFS = [
|
|
420
|
+
{id:'anthropic',name:'Claude',sub:'Anthropic',icon:'?��',ibg:'rgba(249,115,22,.15)',color:'#f97316',
|
|
421
|
+
methods:[
|
|
422
|
+
{id:'oauth',icon:'?��',name:'Login with Claude Code',desc:'OAuth login ??uses your Claude Pro/Max subscription',warn:null},
|
|
423
|
+
{id:'apikey',icon:'?��',name:'API Key',desc:'Use Anthropic API key from console.anthropic.com',warn:null},
|
|
424
|
+
{id:'session',icon:'??,name:'Session Token',desc:'Use claude.ai browser cookie (unofficial)',warn:'Unofficial ??may break. Against ToS.'},
|
|
425
|
+
]},
|
|
426
|
+
{id:'openai',name:'ChatGPT / Codex',sub:'OpenAI',icon:'??,ibg:'rgba(16,163,127,.15)',color:'#10a37f',
|
|
427
|
+
methods:[
|
|
428
|
+
{id:'oauth',icon:'?��',name:'Login with ChatGPT',desc:'OAuth login ??uses your ChatGPT subscription',warn:null},
|
|
429
|
+
{id:'apikey',icon:'?��',name:'API Key',desc:'Use OpenAI API key from platform.openai.com',warn:null},
|
|
430
|
+
{id:'session',icon:'??,name:'Session Token',desc:'Use chatgpt.com browser cookie (unofficial)',warn:'Unofficial ??may break. Against ToS.'},
|
|
431
|
+
]},
|
|
432
|
+
{id:'google',name:'Gemini',sub:'Google',icon:'??,ibg:'rgba(66,133,244,.15)',color:'#4285f4',
|
|
433
|
+
methods:[
|
|
434
|
+
{id:'apikey',icon:'?��',name:'API Key',desc:'Use Google AI Studio key from aistudio.google.com',warn:null},
|
|
435
|
+
]},
|
|
436
|
+
{id:'ollama',name:'Ollama',sub:'Local models',icon:'?��',ibg:'rgba(168,85,247,.15)',color:'#a855f7',
|
|
437
|
+
methods:[
|
|
438
|
+
{id:'local',icon:'?��',name:'Connect Local',desc:'Use locally running Ollama (localhost:11434)',warn:null},
|
|
439
|
+
]},
|
|
440
|
+
{id:'antigravity',name:'Antigravity',sub:'Google (Daily)',icon:'??,ibg:'rgba(52,211,153,.15)',color:'#34d399',
|
|
441
|
+
methods:[
|
|
442
|
+
{id:'oauth',icon:'?��',name:'Login with Google',desc:'OAuth login ??uses your Google Cloud / Gemini Code Assist account',warn:null},
|
|
443
|
+
]},
|
|
444
|
+
{id:'copilot',name:'Copilot',sub:'GitHub',icon:'?��',ibg:'rgba(31,111,235,.15)',color:'#2f81f7',
|
|
445
|
+
methods:[
|
|
446
|
+
{id:'oauth',icon:'?��',name:'Login with GitHub',desc:'OAuth device login ??uses your GitHub Copilot subscription',warn:null},
|
|
447
|
+
]},
|
|
448
|
+
];
|
|
449
|
+
const COL={anthropic:'#f97316',openai:'#10a37f',google:'#4285f4',ollama:'#a855f7',antigravity:'#34d399',copilot:'#2f81f7'};
|
|
450
|
+
const ICONS={anthropic:'?��',openai:'??,google:'??,ollama:'?��',antigravity:'??,copilot:'?��'};
|
|
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={
|
|
453
|
+
anthropic:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKQAAACUCAMAAAAqEXLeAAAAYFBMVEX////Zd1fZdVTYc1HYcU/XbkrWaUP9+fjXbEf89fPx0cn+/Pz46OTXcEzz2NHVZj7bfmH57er03dfcg2fkoo/ei3LmqpnswLTquKrgk3zvysDosqPhmYTVYznae1zfj3cv0j9UAAANcUlEQVR4nMVc6aKrqg5eAioOWOe59f3f8mpbNUBwqmff79/eS20ImUn4+zuC8GEpIPHeOxVfHubdoV/5EZFGpEXbnXdisj78SP8BkTonLda7m6+k8BVnb0V3wH1qRFp2tPnKQMGz/4STAdy7mch8643wBZ99bnP9JpRCZ+XmL7fSqvi/oPGv5TqRYktnKxs8SYt/QmT60vebZJ75hRoSKcp/QqTbUI1Iy67ML0jywTcevBMl04mkhZGVbg6fd7YNwW1IbJ3IjR/3esh59i8s0ITK0YkkmenppKDwsdD83bB4I6vvINIdkA13TCxKoGGljVnD/BcjlFJCHxsLOY4I2W9qMughNAYsD0wf7fj8IC1uMfidLpbkZWBlCO2q0aC6OXjMviVUChzENxpEKYUSTE0WKId2ioh7NhxxO3aCPupDIonBCOSyLhJyB5Gy8fuA46yU3SjKoqBWxeeeDU8R3/hClaLm8BH0YxUi4rfY01LfcIYKHPSKdMCeaJEQlfl3EOkxjZcUjdB7IBjoLvpILEAyXMDPAgnZ0Ag9A4vBnGdq65JjtrongURDFPONFGZhur8JMRotcTgTSrqqjVIj3yNd3B8Ip0AWRixNHpIM2eyR5RvxqQSPCSZs23ZKg5fqNSpJpis40ArWq3/2MiQMOJLKz+8vSZ5d4PYgobruaNvkASI1p+Tq68QfNCBo1jUyCzcItaY7dFD3CebpXF0CEgO88TzmFoNGIsApMSHxCo2VD5WOCDg8NQap9ELDG+IgI1tljbzHFMjXC0Oq1wWWihTyN1IkAJjAmoOhmmZhWYwZwULTTTUlq1eHQ2W9STLM+IyPMWPMqSDUPkCelb7AUPM7qq/I12VQKZ11G6TKMH3AOuy2XeR1O9epLDXRFzIrG2L4i/7m91dOJL1ICcBiVNO6RCsNkRfUsWBY/25DFlUGgeRnqgcNZmWJHlnrqaMDn0mAAeBA1iILF0iBxkkmdOhKiVYmCbRfk+LKcNUOqPjBC/WG47unEgfEnbxhZ4pc6y6cgYAsWut+Yo1sAtxjKxJxAC3qVUcSXrL7cXv1B2EFAJiytQwUYOXDCc7pugASe39oYLVkyCJtNSDbAU5hrfGqnmLhozkrN1NJDZviNJIt7DW+rJEWUGIyv5Qi6cIEWlwJx/2XYVvkuCjUWL4ehKwhxBJ7mDzN0bBCRdAL/IOEd2Bn9GBmVlKQ+ory84arM/678svnEn5skvF+XbfOm7mcAnKM2ZN0SE1uAivPC+SMcDB8VLzWiENL7on1ES9vdUjfeNgU+tDspypVZdAfAoJMzaLbH6PvrdY2fotxiJywvD9Gj2Y1BviZwWTwYd5yX2P3JzFMlv//1v4Noc/+0d8uvJzj62fx17C7mqv/sDJZrA1ppn/jznZh/G+oXrj/IY/vlkfaKt65wnqs+Hb6vikWPxVWGBEWBv2Zt1wrFbxLKmuGMyVhelz3XSq5pR45bmhn0B/6sW+Jmu6Q2IMZzmipg8IQDhyvV+wiHQyb9dFyzdVP9fk16X3IFWeIWw/qve5hiFSnsEczLlOGv/oiUqJh9AiK1zUvI8wMydMUhlWaOEilcGaIVma7fx/c2sZ/a0xNQvX/WKKrkw72H7QShC/csotGK2ew6s8U8KzYPtC/iqC28EqYXr1qsMRYWYjxuO9HRIVBTVW8+t1HTGcmvyOoDJKpYvcpdswdBu6VECltDjJzB2RXs70waus8K9oLNsCtnseYuYnHdnaYtl0/ZITbjFC7qC6Y06RBS/JnYDqOdb0kqnri2IzRRR+JMBy5bSIwRcPHiUQKyMm0uwV/cP2c6JrZT03R8EEaG+V7YZsXmSVsnb4vrpmCbl+BzXBWxiRh1BXP58i+LRG6Gr6npjhzH3M0nkR1PljOvlUj8dVcze3ERWaOka6btHn8IsIUfMi4ojgrM6/ZTFoM9InohxHIod9xJCXS7XCEynNbYPc/0DgxMzYEtHeBCvqK5/pTW7V+FCaJe9K6e+XPlt0IIhw6lNVMYtLbXBArzrJiaPq8q1s/DY8JQhA5hrT/J/oI5U8r9wERvsO+f5pAqbC583g8n0+HZM1IcuVHaRqGI6M9hNNu/pNl10AFp/GQtwqTeqNgjSQzm3PHeTijbLzZXI58jtIEWK6gukswCRvdYlFWfqgbRkPqrhE8dZQxIUb7+BolY8i7NhqFONBT7wvkjV8eg4qmThMPVwvTwe42xYTZzigTfGT2jxRSzuMmr/3NMEKvOp2k95eX7YdT1P4BNTUVD/9bjBtsxUOdBgfNXnTNdVzHaANFkVfRKYdXP56OoJR88Z/SR5jzpHkbJufTgqTt8r4ZprbVLI6nohnntm0LwUa1o3cRTjgdzYL/YzNnMOYZYZhGfttWVVV3ZZlPtGexNSrzaOLfcByHT0sYrdE54knRbuvwRaKDMfd1Pc9L3hj9TppG7yXU4wryJnvRE0ENiSePe2Gff0d3wntPHnf0t69hcretDxBF6dvz/kcrMJ29brF0DBDe/haAkzibPW81RjjJjXMnAdYzfQ1fzzsqKv1YmuGmmnra/4c2ljlIJ81puKWhbHkXxPCrNfCq54l8yppiMXHuhdFe79SN9tCeTRdJ1lZ5/OAnE7Ef6tZhc74kxMK/wAurYsxWDP0o2NLsqyltmF8qAfJvWpXWfUG44eRfxcXTZrfT9YUfCUrJWntKorbMHs4RGWXdeQvvVi/t04z6+nkORiUHXRzBu9wyxm07e6+3kO4hiArtgIzYeQL7/LYg1OJk0pZ95mxp0+kOiLDX9cXmfmDs5lNBiF5CdcOoGh4P28DRk4eQYamvmNLcM7fAIlQ+DaMDYT0GVUwr1jHSnPGOXkV0y8iz6TdhB+uuKpiLol5UlYOARRHq5KfGIdpMtxnfFoIEBL8i2+Wl2Gro9EZtoo9P8Zc4WXpGHqMMG2v6tFx5oGw1ErBfBHb2VMHzyyYTtDnVDRiWQreD9Fm/bYMHDmVpE6QHxFPsn38Goe+f4WJQEp1EMrdUBqBkRRwPHRXVYO9MsJ/FqC+IeWHWHJ5AxZ6s4P458pvKn4MwCL/ACqXOMItLChR7OllYhs8JealvwQ+JrZHmc/ByzBcwa9HPELTAvs/jlnSC/nXKxr+k4REa3zRgG2VYzPjIV/PaAIHk03/PPpzEf57cEC46ueJKDQMgJ4E2pTIODk5rYG8+TdzzQMF0OtdKij6N7XTwfyjiIs8ijRGFISIHstQC5/A5aQ9m5Z6sTCBXFaeO9xae+BHn1+H0jiBsFDH0aUm8PvJt2V/mut8TQEovm4imewHgZ883RkOE2Lk25bJ5g4rwHaJe2lM/rdvSsOynx9iTWobtCxHtF27lIGzkhSRDAQzP5omfcKbgMwfoyRLDJ08jq49z1awn2BAXUeecfMBHOk/8LE7xO1Ney578PZjndrCbm+eXDGaIsJEIdbAAjh4Se2bH0vD3Pdb2pNtPPk2B000eQJYuOZ8QCQkpVWXHg4MMa1KwiAD7/rKv6M4nAEph+6h9oR1en6azHN07wGIaaHsdFls+b6LSuPYdapXsGztPpXawI166oYADOQx0VczvrldkeMqk+Xc+R+qrpGeH5NVhD+IgJTjYN05s8PdZJcBtQkrb8TLbI1FpurvAAGUcnjKk/iY168NqzaLcYg2+E2U4j84Vghqq3rmeFReeiBKGTS5KI3PS1QzL9Duc6VamYugiri2IDYxZJA5/LVAIu8UcAmzkpVKzyTJKJF0ZozT+rlNrLXS951prUjGdx06k9qidbYEqEAE57S4tnxKRXixv+GqxQugjz92Fkw7TaXxrGPJOoR2V217XY0r5/5XJUrqOYkrO7VGfceTexlGFNI7G5LQPWG5ZD5STf2BY3foJEuK7Lj8aYIFBGRtfx65U66/UeZ5gDbAp5nK4IQMO4RAqk7KaWO3yHeVCBelmqaRfhfyWXDeC0qUOwHqLRSTqnUyBHFnKhsst13kV9vudXCEstmh92GAOR2vkVMc0uGQWo3Xe6/nzqQi82IhaqoFabwQkapPkqFTySCCxpL+Gy4iFNv1/FtCtqwL5B+WVIh1myniTMjEU1N+iHfuxT16aRUYKeGuAizVpJ8pctto22360XOtUPQfpoiLR6KZ0VSr0tohKZqV29pG8j69+k0mp6wWbVAlXInDBUpp79AGIkti/XQIZQJlCrwgAmRl2E4o+mM21p9Kq/clOSgH7A4urunW7DfePKYPJ12Z9NyCFPvg0DRj5fhrSVCXq/62AoSGEqolftQAvGngaPuMqhbZbx3Kk0pOhXxne/uiYPiQP1dLhzsYQqeZsiKfADKXhaq0JNdxwwy1d1wDrBcYrK0C+jl+t9YYrlTT4jUSC5RunuwIQ2G65Ng9O6JoU7ArWBiVz27cL6jubt7jByus9VyW+sZRvLWE+e05AlLOdUoHQErW31+DNui02IlIYezubv+2uWZK478wpqLj40LjhIWDhY+eC4/R7rk+ftxpzr2eMia1bQ6WTML5T2akYoeP3ilOnhwcQ5vlmfJKA8y+CX5MI4UV1/R/0Uv5trxo21t11Z9/tgJ7kfP/Jv0EA9YbiNaT/O5JBKr7crBD/A7lysQvKJgpRAAAAAElFTkSuQmCC',
|
|
454
|
+
openai:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKIAAACUCAMAAAAnDwKZAAAAbFBMVEX///8AAAD09PS/v7/6+vrx8fHp6emamprs7OyPj4/m5ubb29uVlZXi4uK8vLz39/fNzc0qKiozMzPT09M/Pz8aGhokJCR5eXm1tbUUFBRlZWWlpaVVVVXGxsYKCgpFRUVxcXGDg4NMTExdXV2UsopLAAALQUlEQVR4nO1c24KqOgwdkTuKchMUUZH//8cj0xTSS6A4zHE/TJ721g4u2lxW0rRfX3/yJ3/yJ1jcY+Xteqmb6PBpMIrYTvzIk2IDcj6lXe3bn0aFJIwvG410lftpZCBWm+sA9nIP/oWZ3LcUPia182mEVTKNcLO5Rh8FaN1FOKckfUlyOuMPi/qDCKsCIUlLL46+V/XgNrWgn631KYQeXs7tXrSMsOnGb8vPuEm7HCew1dqEXw4Lfv/IPI4IW9Igmqcyj04Yhu7/Y+Ut//H8ODHKDviwwLai+nHP0+QlaX4v6+iXZ3bQw3JmRiq+2GoAurXV/vcQNrdhcuaGHhVoSPJyag1+IiH/icpg8HYK42bzbH7D3A8PeLxnMLi53CYhbjaPbH2IFTx7Nz90PwvwJedgbYQWBJXHPMCdACXpysCr4qreBeVVgL52IAdrLmbZYJViGOU2Gq3f9qMKhZ9Nsl0TYQg/PPNQu8FhutMZluU9xxEmem0qNSzztLuJWryOlOU71fge6ymkc2VPnNQea4dI0LmaeBtriD/rzWPDnneZGHKoTwjgbibQZcNqr0UsgT5M+LIjjnWXeafn8vHnlRwk/DI5NXsM8GkW34bF9tdAGE2viRWgNT5VprGNM7sZG5wV22mCK/M4J/2S2MhAXzHDPI22uf2bBH1Sshq52rv2bY/IzW0uiwLGgT/8fba7faAF1Nuzc0GJX3JcuGR7+MM3vaMYKPSq6Abo6/wN9wGcLXkrWoflRhY5+AlKeFughOgRwPAM6JMiuoKD9KoZVsKOYvv29NrDUueLHY+FF3CTPjVK7eNZ7ih2EQZJQeeKvcBjmoUIB4LdS9nsgc0KjvuKlZRy6UwTEm9iJjP2iKnQqkM4qtiz7p++VSFGw5BTS7hqOxqU5RaT7pxPxyKEzuAKT1CE00Ac0icyBxHt7UISTaB5SyL1WBO5ch3WQATqk26JNTzUAvl+TWRJWHzGfO8SUjZkHqObIyEWBGGwGwkgQ6E12wOrBT7METb8gWhlaIh6QwwfKr5entpgzPTqYhyYXKhvnvH8aCAeaYg+9ljXY4h5mq4W1IpaNSstPCvGH2ogZiTEGIWcU/2yZHuLy7ptKP8Byy1z00qPCw8S4zoJ8azYaYST0BLgODVKahQvybxuahqmQYk68SmkX5Qh7vEaX9AU+y1K8tNYeHy8CGLGnK1sqKYQa8xtJfIdkbRy2SzCJLTSxxqILPzfsMoeEcAiUMPJFoN8jCrJdPFuposOqIxMgk0gCrtFD62/dDyskkNgZ39oaNFMK1QOrIEYihBtD/PzkvJxPnaYKbwH+EWzpIyxl7OiFRqIrgAxlgh6R3KrDIPsepAQXUojhAc2Ew/lfTQQfQRxrxJ0jfvjP1LhokXrLIvRgEQdrIHoDBAdgf6OEpDKtcOMvgJOYMZ02OBCtf4piNjRFDWGm9aUSu41b2WEEDh6on5BQjwHWLV6RyJk1U9aJfGwXsxYt3+lBpMQsXSwVPFT86Eqlbgfa5a77NmSafJFE4jV8K1fo4+LklJJFw+7m3nFjNUVYvWbWYgncbPIbrGXJBNsZ9zRzM3CH+RimqXRQLQwQrWcGAk+mizYjjuaKeWkdBA1Y6chJrr8xcYRm97VHBP2qwnGRRAP4xpTz6twqn2hQG65k+wMMoO3IE5V65waVc1ulCvPOLcwKI8t0kWAOBO2RBft6cuIDrctjaFKErF31mRpGog2+2imXidFkbt+eAQZ7bznAb+omZi3IYq7gZs+IdQN47vXs0sNhZLrEogzCw1RX7AbnfJyLz5r1Uti9JcRRMb5TwfcOVNoGnhsyLVnSeMSprME4rkPJCjeFLWidJE+r1OR3Aj9AoiC41oA8db/MxOKEso0BGbaCNav7tQARKG2tQAi44KHBjHZpxy3wYkVc9sbkLsonnEoJiJGsBjiS+qRgCllDDD+uV0iKBgrdGxsFxk92zsQ0SaIAhF4lsafCMLzaDm/cpHXSKB95S2Ir0eVBETY3jjNrTSltBGmLYx63d6DyB2qWldmvvE21y5xZNOobib5uLZ16vuxzm9CtCiIUaHXMllgGTQlU6vFrZXewSj7XQLxixn8rPfmTVe6n46wZ7ueTd4ZIOIXpiEyhe9md1W5yenDPQ62JhDrJRBNq7U+GAZR7qulVt+ZaLAIInN5akWJGEhav+MtgQiDsROjIdKUWha+1AUx1g9QTbjQ76UwGZTXDGJoDNHmBXWyo0vod3mSrHbs6Vgb4lc4GEFA7ZBKGaguSd0i0qDJbn8GcWhhm6gb2UJ/3UNRcYF6GUI018UvHO/OZGOv0BZxasWCSSC0fQi1cxoiK2MbNj85uLhVUyUZv8TxZmTSVrWRxAwio2OGu1hiP/Gd5HDCcj4hDc66jSxmENnDOrMamdyNTTeBNU9pmLCJ+gjOCyAaxmgmyjRsArKBRNgabzEhyuuv+GYO0ZTpfAuYlod1KiH/1KfK8S9gSyBCKmbWXgvZb/blBJglkh11YasCLL+nHSBig6MgQhvHyewMAiON301sQiChu8UbyURysDCAiIsMFMTILHeB9wHTYv87Cj6aMjcb7w6dhh3nBRBBXearY73AzgHfTj3UKJQV+gLcEdsx6m4CiNjWCIhQO5/No5nsGSccibfgSJ6qPh+wygo1Y4CI4yMBsWUfG3YJQrkPT7mQEzzFiHyoEDmTGgS3Z0OIPK8zbCgCiMJD7Bip5K1FKikYlOw+TSFyAvgwQ8ghSmHP9rBK8uOSbosAduouMYOI50YLkbtW0xZIqNYqVFVwf/d+J0PoNs81xtgURhB5jJAbHkhxn9T4DCdXlzDDCaF2jwogYgvSQGxgY+Fu3EdqC35RlEreAQV56EvAABETYxVizB+yoM2S+Rh9i7Tvqfg2aUNs6xwNIA67lUv6aYcYrRW3lE4tJTXVY7lN1emRIPqDspA9HzoBRkumdluBT5YUTxs7NbDiCRCt0d4uy9rPQb/o5tJ42DlLIqrHEm34En7RrkfNzhceuoNZmnD14CVzKmew8H64wF4AYrxvWjRk8UkIaLOc5OhRWyQkF98K/XcCPQK+8BTaRL3FJ1VdSFKnkzGyqXyPAT6ltYg2iuTvHGTbvTn9vbi45pMrG/rqSdD2rVsceEf2G6+Hzx3oQk4tAby+e66Jx/WF54TtI+IaZ60fERxW+oMzvryBpFvkC/YC99X+uo3C/KP60YnPgXyYq6MboD4DVQmZQBNbeqmzn5435wdRTKsDYkfdKaBiBYT4VQ7M+oPKGw0XmsAupApboIprIBw25XpiOD9WOKc4YQLZsqWZk8E9yN5XFstDSniieM+3wKssPYBDykgNyYryV1+wRQCL6bOoEFqu610hMRaU8op6qnAme/oM02CDa17Bgij2UxvqBYA5Rb65QPBLV723CNf9N10VoaubnGgrtPmms8c2XSharHm0/CWRsO13u5bBrq7iygvKTjyGt5uPlbDMxep3hATCVT6E0J4QPQjG/uiQrF4y4gwQAmjCiLhe0/nGD+TQENkzFxOEQ1XaqN3zDclK8s6uXmYp1XB/zW01r63KvmrVWz+G8/Yz+uUMtr+yNctiRXU5Xt/zqCNrvNxnkrSNu5rGhaUfieOGYciTusMwPXQnPLrvYi36sEjGS8fO+iZzpx3T0UU1kfXkgEJM14jmau+32PP/sh7SYrUIRd7Wjfvt+Jwo9kqczxe/4LKNBW/89dcw9hfMJUkhOoDPXDo2SKT078iSfHIKv8WRE3hJ2l+8t8tY7OBO4cs/d0GfJG6lbue/5BL/CzPIxfbrLh2vYSyS/BE7/8Klm6Icoqb+vgPWq47/yr2lf/Inf/KvyH89U5Cyc1o70AAAAABJRU5ErkJggg==',
|
|
455
|
+
google:'data:image/jpeg;base64,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',
|
|
456
|
+
ollama:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMwAAADACAMAAAB/Pny7AAAAbFBMVEX///8AAAD6+vrz8/Pc3Nz29vbu7u7f39/Q0NBOTk6xsbGlpaXo6Og2Njbr6+uoqKjCwsJJSUl+fn5CQkJlZWXIyMhfX1+3t7eMjIzW1tZZWVkwMDCenp4VFRV2dnZtbW0fHx8nJyeVlZUMDAwxiauXAAAN/ElEQVR4nO1da7tDuhJeRdHSUqVoKdr//x9Pb+QdxC3BPvvZ77fV1SYZydxn4u/vP/ywVVVVWXsRUqBqbuRf74lpSxlO10zXDWJ7jYejmFG6+eIUipOju/f8M9jOiRcnR0s2gMwUHC7w2GA3y5CyxMEwLxuCgysymrIvyGgX0WczCsF5U8NNgBrFymujiT2bcYgbtLzmn/4092ljtCKQuNxO2NcmLa+zMVUKBPV9+ez0QnyzfbTRstkk06SQ0fpoNldd8rLbYbbTstnEU0ZTEs5oiex1t0FtYZjfQVMnDBfzRtvI0cXdCHHC5xP/miCDlDuXmLv8tTdwgPkKL7rBn+fxo5GNOWcF/jk/12gwW/7ieQfnHy+efaQl2BLtGc2wfIoMzpin1M7JbuxoNm7z/vWBBTonnWH5BArMfju+PzHwoI1l2gh+m71Fu4J20tx2gAtzPZoLGnsyQH7cvpIdj/FF7tobwDP+U5JbWNFh3GioskrhdYLRtlLX3gDIMr/8DMz3fJziBAZMyxMKez+zhRYXbKpKdOmTzxmMdqo+ZJ8955VnaOCyT2G7RskzNDEZs8N2zcs0cKLYo/zbs0/PY+QZig72KZgYu1mZBgTng30K56wIR4wG9jLs6BEezSTbdSC2OzYRMKfCztnzwf91HSoIrj1MwqTjbcyjGQsDLOYjfA7npWkeqrYWv2Ac60a1eWgdTWHiP53TDwhgejzOIE59+FzRwiTy7v5198Ilu0ePvQkUOYz/c3DsFHD+vBmJCQt2nNGvNODwl/6u6kb+7kZchBdLnS/3sKQH9jPD0UACzOkGQBjFx+mPTGKfv7wUZCe02QhBp+zL1yCDyXECuyCbMSAIzzLCaXQmlz486xyaARdEfnqpFQWIIaoezLNsivM6EKBmyLNUmR+QWkrSTckXZ1MHOU9iMeAXXGf0nYGYPX6OPHtGV7QLO/giWTMSo81HDDhiRAMo1kACOEhRzv/ZjDNPMwZqgRhq0IYtKxyBM3H3bbZlCxFDZ3FbVjgCNOJ3ZJbBnMSA/KGzBGLE0IjbkRlN5/k8GmUaMentvDsdeGqnSQwI+hmJ4R8zLjGXh2vGmmYYmhYH1p0jtPnEzBjT4BLTKgDyKLB1NOEU1Q68Z8s3T4RnVt6ZNtF8c7dttoiit0TKCyKal+EZJIbsv9LIcjy7VuHVv02V5pE5GnNKM1gFUZqqT9eWW93jGHUrgVoAoGdmdDWBGAc/p3mJtD+JtvVouoxsAFgAu2WIqQxNZavbO1xY8Rhg6m6TAn8T2bpasZjNZMScthm4AG9fX9cCN7QeUYbrOuwH+SBbi1CTedEj2YdBfFzMagbj3jP3iXdtasJi3z/Ml5pHi9LJd/ckBDnvz5ijgWTMYddcyns11mDfUM9aR9jkEDWZ09OEGAAH9xGz2/2ez5wxgKBv+nTUsQjbrIHFiGkrzSDo0S91XHqGmzXUpLczCsPI8bY9w42Jj44Gyca2YPTcTcOG4j5b5Nzm5+y/yEfLHrWPa/yZamjM9iIXhikmbtjls71xncXUdLnMn95uh/Pp4k2yo9z7Oc+LF3jBttMMXkDYKpXPWZRYe9d1A3OqFaUG4Qd7J4myU8sc8t0At+U4ZJZrSi12VezYtfwmNZKtTa1ByzmRS0gJpcWzPkmVaUp9+w/hjPXHalyX2FITtXWTMJm54EipV7RKzKFbVNLsFqifrDniN2kep0Ye03NOewkQEc65yhqWKP50pC05HSQk1RciGQqipJ+L0fL3R+JXVylnWyWiZZGa1hLyZ3aLeaTKAKjo71wkbI2KBS7+sf8HMmHiCR8aKekaD6J1+WIV+j8oDhBzFw47keEei7fp2KhuhA1ObGI4L9rW8gVUf01tNmDA+s85HXIeDJABouFNPGWHpTnmA8z8CNo0KpiY8wUXumCCwe6InTMdDKQ1ThmtaPfFKmmAZdZg/zcS9jyfYp4HBOQvq5yy1zkDm13MCOisV1wGaNOIGQEsHDtruWQngGnETENmy4yq8ZUKSEoJuWjQYHKYMbvYDTDbbyLjADHnqZJEc51HFEWWa07UEjGEHyeu4QMo/j61L0V5gfdrxd77tVBIovG/zRsJahw2IloT8r6tHQuGc8mLq9O2aaqRtAZ080hr+7q+vxaFv2+zvjBoJ0IM6MwWYpQyOpAH9UmOJif7+sY1qPt4SvDbwdYObSBGRGti8XXzv8wGfFJRpwcdpLyRBdQwcZiWb1ElkK4T0ZrQftHkGawxIT3njehqE88If0Ci8s31SiIGco6nhpFH4s9eRau678vifnDeV+bRlgTmsvo8yDMiHg2K5vo42JwFRS5qVAyh5aWFo/LxmDQsX1+EKolnUGnWjWZayVje1LDNaD7i5IWx9oEZenTRz7KhY08rnOoumCzRDN1kDXOGVjKmP8Yl6z07hgq29lY3HHIEf51etTLCukOLSlOIGCaVGp15tJT5RyvqyPbCNw3TvN/O7tq1E/UjADUuuQgtGO6tM6ZCVvDlGXiG/IgBPunPoCYtlWisgdE6uneaAB5/4x4IKs3enzCpVHQa6xFjkvfpVIgo9+vfluYCgNZsKOcYWP1TAhBWj7Av3c3S8MVbc6CeSRtSU5pzpsNxbgQ0WC3fp2LOrraqv0ZTq07Wp6LMSclIBC6cSjG3GfVZ1oia739Lun64v0p1+QNUW1xS85XpIRmJANh2XAFYEyCAW3zN2PIuvud8mD8uN2ZYWqh64N9yu9i5Xy6/kQgwpCkauUMDzGsJW+mG8XtcZdauxluhF70/sBMvIj+v6rrLiptjNRIB2KAbUc/9CPZvZ0bDLr9IOus+G3BL/uK3l0blVDVyZ6QE1dJT2HNHRd/kGlh18f1OLa/+WUt6/UovumdlQv7ewQlbTHV1fXEYUMVtOkpjywh7LbFOlBGVh2W2pKuqHLNDEq7UoNcPcccrs4V1exSzkmntfz9dmXJZYUu0qYykJq2Y5Q14/Mnwegc/VHWmdf1d9jZzVSF18iTkNGvtJLwwnOHz/u//xNHBqf+nVPyNf/xAb7iSU+BIfScO15TqoIXYr9RKm6Kw3HMeMaRQOJcUUXXQeeJIlFIyt9i1XybeNZVU384gLc9IUhICkwq8nTn+zvepSazFI6a8mInHDOiz7mS1a0C6B68BISg5q+WiQy4xPwHIlWbZDMQcB+XiOXrmj09M2fXJrSAG/0NaEhLjDT735JYMsGvYHF/zrnnmy2o/vphC9S+nqknBIfnKmtlm9f98VF/L7YvluPxnTqJZUpJdMXjoWYd1VKmjpnAKPKfJMeFvw/MOZYhBEyl6Bgs+uqyjynnkmyeISnt1uaU6sqsERYN2a3elabU1adgf3qoaC7q7SnBrIvH7NDQ4Zd1VTVAz9OjxPFRWjH/tLGGzQTwfxCtoIQr77LHBXQhwtaaNftjChcZ9ZxIPubClqcBovR2gGGfNLE4OU9tjaL1PfaCnKeyc6WCGR72sQHyfk5e4Bv2J7SbeFYOxWa/FBTZ7LirP8KopnkHIUL9I9rDzMy9JLMdxrIeXZdcDzREM6CnFAKFojRhEZ4Y0Likttxykaf5GS7OPN6B+FUWAaFkl8P+Q2N6A7kcGa5BVD6ph0k29DHhuBvYAkNrhDgw1tvAGV7FKZAwpDA7Ba35RX3kd+W6wnNUk5Wdfj5mNNLid/IX4vmu76/uH9OSPME3gIjrBYJPO1jSu3vRo3S/nlibM5+FyT8Y9YIjdi1nOUDzTdFR6YLsvcezvzof8uXnmh8PpknkPyx3tMkIjjVjrjuilCaoRB0EY7sPw3fw4rU8N4iliLUFQPOMLCvnJgHqzCVd1A/B6PklrG42A2QBCxXOoM1cjBq7bLIQGcv8jRi7+VccM7OaRF3XX8E8QAFBuwounDsM/gRgoNxGreocIqaD9PR1g64rFaMGckRMfnQDwzsRCGnir9UotJ0cIaYgtQWdZszEugExgN71YXmOScyYXEJ65icWaiNu8iqWJxWieYCoQtOZ1zqvgucD0sGjoHHyArtzDfJDY2kg6aPsjmvJxBKdZOK+J7wGb8x5IHlwIjAi3A5NabPHRxoIE4cQ7eDVgwNPiRoBZsNmHRVQ7QUpnlt4acuWFjNs78NqJfOEuOiyqlGJO6eR1WYtujYKl9nIMEAeTEQvdoPMF5kckvWJTJT1XS/ae4ryyupFJiZZQq+Q4oOiRd7MCaaNaTjxj3F1em7iGZZrL+WgwqbxbtGhB73L36EBq9iDvPODOiAUVRwEMZolvPMOrxvrz59KAFVpXWbEhHXXXkpdPgDEj7U3OeMHZooEArGuUNTFae4teo7UdWLc3AtiOw6/RnAVgz0h6dwPeBb7wDUd4zuRcSAJWRbHEu5QBaLFLiUBgJY6sGvbBwFv1ZehNLJ5f8E33v8lBOMs44lAKKKFKciQMkGcSnA8SoF3mzfAAnfcqkmk4yh1uLMA+k1wKvMINRyABhtQN9gAiV2ukm6C8WELqDpPnC2uZN0CWSnghLZQ1LK5l/khd8034YOB1jWvccGbzXro5BSiZ1yAGZbMwMZjSWEEyyyUGcwprEKPORMzz/5+Yf9Mx24IAWPB68wpH7stVJwBfYbiGNDPgXeHiBggYR2soTehFlGABgDkj43aB0dOzMNfoKvEm4I7zNQxNOBgSTEODGUdr1AHgKRf3Z0DRrGE1Q1GDDPlTDbfkmycqMEOzkBHQqLbmvvAbDr6oQpByZOnR+kiUaIVD9sL218EfSXqUiuEmgbZW8axiWJfrw5Q4/VrtJt/JVXXV+dfB/wDLTbH3ozcAUwAAAABJRU5ErkJggg==',
|
|
457
|
+
};
|
|
458
|
+
function providerImg(provider,size){
|
|
459
|
+
size=size||18;
|
|
460
|
+
const src=IMG_ICONS[provider];
|
|
461
|
+
return src?\`<img src="\${src}" style="width:\${size}px;height:\${size}px;object-fit:contain;border-radius:2px">\`:(ICONS[provider]||'?');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ?�?�?� App state ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
465
|
+
let ST = { accounts:[], ollamaRunning:false, ollamaModels:[], ollamaBaseUrl:'http://localhost:11434' };
|
|
466
|
+
let sbOpen = false;
|
|
467
|
+
let sbScreen = 'home'; // 'home' | 'providers' | 'add-type' | 'add-method' | 'oauth-device'
|
|
468
|
+
let sbAddingDef = null;
|
|
469
|
+
let sbAddingMethod = null;
|
|
470
|
+
let sbOAuthDevice = null;
|
|
471
|
+
let sidebarModelSel = {}; // accountId ??currently selected model in sidebar
|
|
472
|
+
|
|
473
|
+
// Monitor state
|
|
474
|
+
let monitorOpen = false;
|
|
475
|
+
let resizeD = null;
|
|
476
|
+
let monitorTab = 'terminal';
|
|
477
|
+
let termLines = [];
|
|
478
|
+
let termCwd = '';
|
|
479
|
+
let termHistory = [];
|
|
480
|
+
let termHistIdx = -1;
|
|
481
|
+
let termBusy = false;
|
|
482
|
+
let monitorRefreshTimer = null;
|
|
483
|
+
let usageHtmlCache = '';
|
|
484
|
+
let quotaState = {};
|
|
485
|
+
let lastReqData = null;
|
|
486
|
+
let codexMode = 'rcodex';
|
|
487
|
+
|
|
488
|
+
// 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
|
+
function loadPos(){ try{return JSON.parse(localStorage.getItem(LS_POS)||'{}')}catch{return {}} }
|
|
493
|
+
function loadCanvas(){ try{return new Set(JSON.parse(localStorage.getItem(LS_CANVAS)||'[]'))}catch{return new Set()} }
|
|
494
|
+
function loadSlots(){ try{return JSON.parse(localStorage.getItem(LS_SLOTS)||'{}')}catch{return {}} }
|
|
495
|
+
let NP = loadPos();
|
|
496
|
+
let onCanvas = loadCanvas(); // Set of slotIds (+ 'out', 'monitor')
|
|
497
|
+
let nodeSlots = loadSlots(); // {[slotId]: {accountId, model}}
|
|
498
|
+
|
|
499
|
+
function saveLS(){
|
|
500
|
+
localStorage.setItem(LS_POS, JSON.stringify(NP));
|
|
501
|
+
localStorage.setItem(LS_CANVAS, JSON.stringify([...onCanvas]));
|
|
502
|
+
localStorage.setItem(LS_SLOTS, JSON.stringify(nodeSlots));
|
|
503
|
+
}
|
|
504
|
+
if(!NP.out) NP.out={x:520,y:280};
|
|
505
|
+
|
|
506
|
+
// Viewport
|
|
507
|
+
let vp={x:60,y:40,s:1};
|
|
508
|
+
let panD=null,nodeD=null,connD=null;
|
|
509
|
+
|
|
510
|
+
// ?�?�?� Sidebar navigation ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
511
|
+
function toggleSb(){
|
|
512
|
+
sbOpen=!sbOpen;
|
|
513
|
+
document.getElementById('sb').classList.toggle('open',sbOpen);
|
|
514
|
+
document.getElementById('sb-btn').classList.toggle('on',sbOpen);
|
|
515
|
+
if(sbOpen){ sbScreen='home'; renderSb(); }
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function sbGoBack(){
|
|
519
|
+
if(sbScreen==='oauth-device'){ sbScreen='providers'; renderSb(); }
|
|
520
|
+
else
|
|
521
|
+
if(sbScreen==='add-method'){ sbScreen='add-type'; renderSb(); }
|
|
522
|
+
else if(sbScreen==='add-type'){ sbScreen='providers'; renderSb(); }
|
|
523
|
+
else if(sbScreen==='providers'){ sbScreen='home'; renderSb(); }
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function renderSb(){
|
|
527
|
+
const back=document.getElementById('sb-back');
|
|
528
|
+
const title=document.getElementById('sb-title');
|
|
529
|
+
const body=document.getElementById('sb-body');
|
|
530
|
+
const showBack=sbScreen!=='home';
|
|
531
|
+
back.style.display=showBack?'flex':'none';
|
|
532
|
+
|
|
533
|
+
if(sbScreen==='home'){
|
|
534
|
+
title.textContent='Menu';
|
|
535
|
+
body.innerHTML=\`
|
|
536
|
+
<div style="padding:8px 0">
|
|
537
|
+
<div class="nav-item" onclick="sbGoTo('providers')">
|
|
538
|
+
<div class="nav-ic" style="background:rgba(99,102,241,.15)">?��</div>
|
|
539
|
+
<div class="nav-info">
|
|
540
|
+
<div class="nav-name">Providers</div>
|
|
541
|
+
<div class="nav-sub">Manage AI provider accounts</div>
|
|
542
|
+
</div>
|
|
543
|
+
<span class="nav-arr">??/span>
|
|
544
|
+
</div>
|
|
545
|
+
<div class="nav-item" style="opacity:.4;pointer-events:none">
|
|
546
|
+
<div class="nav-ic" style="background:rgba(96,96,128,.1)">??/div>
|
|
547
|
+
<div class="nav-info">
|
|
548
|
+
<div class="nav-name">Settings</div>
|
|
549
|
+
<div class="nav-sub">Gateway configuration</div>
|
|
550
|
+
</div>
|
|
551
|
+
<span class="nav-badge">Soon</span>
|
|
552
|
+
</div>
|
|
553
|
+
<div class="nav-item" style="opacity:.4;pointer-events:none">
|
|
554
|
+
<div class="nav-ic" style="background:rgba(96,96,128,.1)">?��</div>
|
|
555
|
+
<div class="nav-info">
|
|
556
|
+
<div class="nav-name">Monitor</div>
|
|
557
|
+
<div class="nav-sub">Request logs & metrics</div>
|
|
558
|
+
</div>
|
|
559
|
+
<span class="nav-badge">Soon</span>
|
|
560
|
+
</div>
|
|
561
|
+
</div>\`;
|
|
562
|
+
}
|
|
563
|
+
else if(sbScreen==='providers'){
|
|
564
|
+
title.textContent='Providers';
|
|
565
|
+
const connectedHtml=renderConnectedAccounts();
|
|
566
|
+
body.innerHTML=\`
|
|
567
|
+
<div class="sb-section">Add Provider</div>
|
|
568
|
+
\${PDEFS.map(p=>\`
|
|
569
|
+
<div class="ptype" onclick="sbGoToAdd('\${p.id}')">
|
|
570
|
+
<div class="ptype-ic" style="background:\${p.ibg}">\${providerImg(p.id,20)}</div>
|
|
571
|
+
<div class="ptype-info">
|
|
572
|
+
<div class="ptype-name">\${p.name}</div>
|
|
573
|
+
<div class="ptype-sub">\${p.sub}</div>
|
|
574
|
+
</div>
|
|
575
|
+
<button class="add-btn" onclick="event.stopPropagation();sbGoToAdd('\${p.id}')">+ Add</button>
|
|
576
|
+
</div>\`).join('')}
|
|
577
|
+
<div class="sb-sep"></div>
|
|
578
|
+
<div class="sb-section">Connected Providers</div>
|
|
579
|
+
\${connectedHtml}\`;
|
|
580
|
+
}
|
|
581
|
+
else if(sbScreen==='add-type'){
|
|
582
|
+
title.textContent='Add '+sbAddingDef.name;
|
|
583
|
+
const methods=sbAddingDef.methods;
|
|
584
|
+
body.innerHTML=\`
|
|
585
|
+
<div style="padding:10px 14px 6px;font-size:11px;color:var(--mu)">Choose how to connect:</div>
|
|
586
|
+
<div class="auth-cards">
|
|
587
|
+
\${methods.map(m=>\`
|
|
588
|
+
<div class="auth-card" onclick="sbGoToMethod('\${m.id}')">
|
|
589
|
+
<div class="auth-card-hdr">
|
|
590
|
+
<span class="auth-card-ic">\${m.icon}</span>
|
|
591
|
+
<span class="auth-card-name">\${m.name}</span>
|
|
592
|
+
</div>
|
|
593
|
+
<div class="auth-card-sub">\${m.desc}</div>
|
|
594
|
+
</div>\`).join('')}
|
|
595
|
+
</div>\`;
|
|
596
|
+
}
|
|
597
|
+
else if(sbScreen==='add-method'){
|
|
598
|
+
const m=sbAddingMethod;
|
|
599
|
+
title.textContent=m.name;
|
|
600
|
+
let warn=m.warn?\`<div class="auth-warn">??\${m.warn}</div>\`:'';
|
|
601
|
+
|
|
602
|
+
if(m.id==='oauth'){
|
|
603
|
+
body.innerHTML=\`\${warn}
|
|
604
|
+
<div class="auth-form">
|
|
605
|
+
<div class="form-label">Account label (optional)</div>
|
|
606
|
+
<input class="form-input" id="f-label" placeholder="\${sbAddingDef.name} Account" value="\${sbAddingDef.name}"/>
|
|
607
|
+
<div class="form-actions">
|
|
608
|
+
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
609
|
+
<button class="form-submit" onclick="doOAuth()">?�� Open Login</button>
|
|
610
|
+
</div>
|
|
611
|
+
</div>\`;
|
|
612
|
+
}
|
|
613
|
+
else if(m.id==='apikey'){
|
|
614
|
+
const ph=sbAddingDef.id==='anthropic'?'sk-ant-...'
|
|
615
|
+
:sbAddingDef.id==='openai'?'sk-...'
|
|
616
|
+
:sbAddingDef.id==='google'?'AIza...':'';
|
|
617
|
+
body.innerHTML=\`\${warn}
|
|
618
|
+
<div class="auth-form">
|
|
619
|
+
<div class="form-label">Account label</div>
|
|
620
|
+
<input class="form-input" id="f-label" placeholder="\${sbAddingDef.name} Account" value="\${sbAddingDef.name}"/>
|
|
621
|
+
<div class="form-label">API Key</div>
|
|
622
|
+
<input class="form-input" id="f-key" type="password" placeholder="\${ph}"/>
|
|
623
|
+
<div class="form-actions">
|
|
624
|
+
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
625
|
+
<button class="form-submit" onclick="doApiKey()">Connect</button>
|
|
626
|
+
</div>
|
|
627
|
+
</div>\`;
|
|
628
|
+
setTimeout(()=>document.getElementById('f-key')?.focus(),50);
|
|
629
|
+
}
|
|
630
|
+
else if(m.id==='session'){
|
|
631
|
+
const site=sbAddingDef.id==='anthropic'?'claude.ai':'chatgpt.com';
|
|
632
|
+
body.innerHTML=\`\${warn}
|
|
633
|
+
<div style="padding:0 14px 10px;font-size:10px;color:var(--mu);line-height:1.6">
|
|
634
|
+
Open DevTools ??Application ??Cookies ??\${site} ??copy session token.
|
|
635
|
+
</div>
|
|
636
|
+
<div class="auth-form">
|
|
637
|
+
<div class="form-label">Account label</div>
|
|
638
|
+
<input class="form-input" id="f-label" placeholder="\${sbAddingDef.name} Account" value="\${sbAddingDef.name}"/>
|
|
639
|
+
<div class="form-label">Session Token</div>
|
|
640
|
+
<input class="form-input" id="f-ses" type="password" placeholder="Paste token??/>
|
|
641
|
+
<div class="form-actions">
|
|
642
|
+
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
643
|
+
<button class="form-submit" onclick="doSession()">Connect</button>
|
|
644
|
+
</div>
|
|
645
|
+
</div>\`;
|
|
646
|
+
setTimeout(()=>document.getElementById('f-ses')?.focus(),50);
|
|
647
|
+
}
|
|
648
|
+
else if(m.id==='local'){
|
|
649
|
+
body.innerHTML=\`
|
|
650
|
+
<div class="auth-form">
|
|
651
|
+
<div class="form-label">Account label</div>
|
|
652
|
+
<input class="form-input" id="f-label" placeholder="Ollama Local" value="Ollama Local"/>
|
|
653
|
+
<div class="form-label">Ollama base URL</div>
|
|
654
|
+
<input class="form-input" id="f-url" placeholder="http://localhost:11434" value="\${ST.ollamaBaseUrl||'http://localhost:11434'}"/>
|
|
655
|
+
<div class="form-actions">
|
|
656
|
+
<button class="form-cancel" onclick="sbGoBack()">Cancel</button>
|
|
657
|
+
<button class="form-submit" onclick="doLocal()">Add Ollama</button>
|
|
658
|
+
</div>
|
|
659
|
+
</div>\`;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else if(sbScreen==='oauth-device'){
|
|
663
|
+
const d=sbOAuthDevice||{};
|
|
664
|
+
title.textContent='Enter GitHub Code';
|
|
665
|
+
const mins=d.expiresIn?Math.max(1,Math.ceil((d.expiresIn*1000-(Date.now()-(d.startedAt||Date.now())))/60000)):10;
|
|
666
|
+
body.innerHTML=\`
|
|
667
|
+
<div class="auth-form">
|
|
668
|
+
<div class="form-label">GitHub verification code</div>
|
|
669
|
+
<input class="form-input" style="font-size:22px;letter-spacing:2px;text-align:center;font-weight:800;color:var(--fg);font-family:ui-monospace,monospace" value="\${d.userCode||''}" readonly onclick="this.select()"/>
|
|
670
|
+
<div style="font-size:10px;color:var(--mu);line-height:1.5;margin-top:4px">
|
|
671
|
+
Enter this code on the GitHub device page. rcodex will connect automatically after approval.
|
|
672
|
+
</div>
|
|
673
|
+
<div class="form-actions" style="flex-wrap:wrap">
|
|
674
|
+
<button class="form-cancel" onclick="copyOAuthCode()">Copy Code</button>
|
|
675
|
+
<button class="form-submit" onclick="openOAuthDevicePage()">Open GitHub</button>
|
|
676
|
+
</div>
|
|
677
|
+
<div style="font-size:10px;color:var(--mu);padding-top:8px">Waiting for approval · expires in ~\${mins}m</div>
|
|
678
|
+
</div>\`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function renderConnectedAccounts(){
|
|
683
|
+
const all=[...ST.accounts];
|
|
684
|
+
if(!all.length&&!ST.ollamaRunning){
|
|
685
|
+
return \`<div style="padding:12px 16px;font-size:10px;color:var(--mu)">No accounts connected yet.</div>\`;
|
|
686
|
+
}
|
|
687
|
+
const methodLabel={apikey:'API Key','oauth-official':'OAuth','oauth-unofficial':'Session',local:'Local'};
|
|
688
|
+
let html=all.map(a=>{
|
|
689
|
+
const models=a.models||[];
|
|
690
|
+
const activeSlots=(a.activeModels||[]).length;
|
|
691
|
+
const canvasCount=Object.values(nodeSlots).filter(s=>s.accountId===a.id).length;
|
|
692
|
+
const sub=accountSubtext(a);
|
|
693
|
+
if(!sidebarModelSel[a.id]&&models.length) sidebarModelSel[a.id]=models[0];
|
|
694
|
+
const curSel=sidebarModelSel[a.id]||'';
|
|
695
|
+
const modelPicker=models.length
|
|
696
|
+
?\`<select class="msel" style="flex:1;font-size:10px;padding:4px 6px"
|
|
697
|
+
onchange="sidebarModelSel['\${a.id}']=this.value">
|
|
698
|
+
\${models.map(m=>\`<option value="\${m}"\${m===curSel?' selected':''}>\${m}</option>\`).join('')}
|
|
699
|
+
</select>\`
|
|
700
|
+
:\`<div style="flex:1;font-size:10px;color:var(--mu);padding:4px 0">No models loaded</div>\`;
|
|
701
|
+
return \`<div class="acc-item" style="flex-direction:column;align-items:stretch;gap:5px">
|
|
702
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
703
|
+
<div class="acc-ic" style="background:\${IBGS[a.provider]||'rgba(96,96,128,.1)'}">\${providerImg(a.provider,16)}</div>
|
|
704
|
+
<div class="acc-info">
|
|
705
|
+
<div class="acc-name">\${a.label}</div>
|
|
706
|
+
<div class="acc-sub">\${methodLabel[a.method]||a.method}\${activeSlots?' · <span style="color:var(--gr)">??/span> '+activeSlots+' active':canvasCount?' · '+canvasCount+' on canvas':''}</div>
|
|
707
|
+
\${sub?\`<div class="acc-sub" style="opacity:.6;margin-top:1px;font-size:9px">\${sub}</div>\`:''}
|
|
708
|
+
</div>
|
|
709
|
+
<button class="del-btn" onclick="deleteAccount('\${a.id}')" title="Delete">×</button>
|
|
710
|
+
</div>
|
|
711
|
+
<div style="display:flex;gap:6px;padding:0 0 2px 40px">
|
|
712
|
+
\${modelPicker}
|
|
713
|
+
<button class="add-btn" onclick="addToCanvas('\${a.id}')">+ Canvas</button>
|
|
714
|
+
</div>
|
|
715
|
+
</div>\`;
|
|
716
|
+
}).join('');
|
|
717
|
+
if(ST.ollamaRunning&&!ST.accounts.find(a=>a.provider==='ollama')){
|
|
718
|
+
html+=\`<div style="padding:8px 16px;font-size:10px;color:var(--mu)">
|
|
719
|
+
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
|
+
</div>\`;
|
|
721
|
+
}
|
|
722
|
+
return html;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function sbGoTo(screen){ sbScreen=screen; renderSb(); }
|
|
726
|
+
function sbGoToAdd(providerId){
|
|
727
|
+
sbAddingDef=PDEFS.find(p=>p.id===providerId);
|
|
728
|
+
sbScreen='add-type';
|
|
729
|
+
renderSb();
|
|
730
|
+
}
|
|
731
|
+
function sbGoToMethod(methodId){
|
|
732
|
+
sbAddingMethod=sbAddingDef.methods.find(m=>m.id===methodId);
|
|
733
|
+
sbScreen='add-method';
|
|
734
|
+
renderSb();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// ?�?�?� Auth actions ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
738
|
+
async function doApiKey(){
|
|
739
|
+
const label=document.getElementById('f-label')?.value.trim()||sbAddingDef.name;
|
|
740
|
+
const key=document.getElementById('f-key')?.value.trim();
|
|
741
|
+
if(!key){toast('API key is required',true);return}
|
|
742
|
+
const r=await api('POST','/api/accounts',{provider:sbAddingDef.id,label,method:'apikey',apiKey:key});
|
|
743
|
+
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
744
|
+
toast('??'+label+' connected');
|
|
745
|
+
sbScreen='providers';
|
|
746
|
+
await fetchStatus();
|
|
747
|
+
renderSb();
|
|
748
|
+
}
|
|
749
|
+
async function doSession(){
|
|
750
|
+
const label=document.getElementById('f-label')?.value.trim()||sbAddingDef.name;
|
|
751
|
+
const ses=document.getElementById('f-ses')?.value.trim();
|
|
752
|
+
if(!ses){toast('Session token is required',true);return}
|
|
753
|
+
const method=sbAddingDef.id==='anthropic'?'oauth-unofficial':'oauth-unofficial';
|
|
754
|
+
const r=await api('POST','/api/accounts',{provider:sbAddingDef.id,label,method,sessionToken:ses});
|
|
755
|
+
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
756
|
+
toast('??'+label+' connected');
|
|
757
|
+
sbScreen='providers';
|
|
758
|
+
await fetchStatus();
|
|
759
|
+
renderSb();
|
|
760
|
+
}
|
|
761
|
+
async function doOAuth(){
|
|
762
|
+
const label=document.getElementById('f-label')?.value.trim()||sbAddingDef.name;
|
|
763
|
+
const endpoint=sbAddingDef.id==='anthropic'?'/api/oauth/anthropic/start'
|
|
764
|
+
:sbAddingDef.id==='antigravity'?'/api/oauth/antigravity/start'
|
|
765
|
+
:sbAddingDef.id==='copilot'?'/api/oauth/copilot/start'
|
|
766
|
+
:'/api/oauth/openai/start';
|
|
767
|
+
const r=await api('POST',endpoint,{label});
|
|
768
|
+
if(!r.ok){toast('Error starting OAuth',true);return}
|
|
769
|
+
const d=await r.json();
|
|
770
|
+
window.open(d.authUrl,'_blank','width=600,height=700');
|
|
771
|
+
if(d.userCode){
|
|
772
|
+
sbOAuthDevice={provider:sbAddingDef.id,label,authUrl:d.authUrl,userCode:d.userCode,expiresIn:d.expiresIn,startedAt:Date.now()};
|
|
773
|
+
try{await navigator.clipboard?.writeText(d.userCode);}catch{}
|
|
774
|
+
toast('GitHub code: '+d.userCode+' (copied if allowed)');
|
|
775
|
+
sbScreen='oauth-device';
|
|
776
|
+
renderSb();
|
|
777
|
+
pollNewAccount(0,ST.accounts.length);
|
|
778
|
+
return;
|
|
779
|
+
}else{
|
|
780
|
+
toast('Complete login in the opened window??);
|
|
781
|
+
}
|
|
782
|
+
sbScreen='providers';
|
|
783
|
+
renderSb();
|
|
784
|
+
pollNewAccount(0,ST.accounts.length);
|
|
785
|
+
}
|
|
786
|
+
async function copyOAuthCode(){
|
|
787
|
+
if(!sbOAuthDevice?.userCode)return;
|
|
788
|
+
try{await navigator.clipboard?.writeText(sbOAuthDevice.userCode);toast('Code copied');}
|
|
789
|
+
catch{toast('Copy failed ??select the code manually',true);}
|
|
790
|
+
}
|
|
791
|
+
function openOAuthDevicePage(){
|
|
792
|
+
if(sbOAuthDevice?.authUrl)window.open(sbOAuthDevice.authUrl,'_blank','width=600,height=700');
|
|
793
|
+
}
|
|
794
|
+
async function doLocal(){
|
|
795
|
+
const label=document.getElementById('f-label')?.value.trim()||'Ollama Local';
|
|
796
|
+
const url=document.getElementById('f-url')?.value.trim()||'http://localhost:11434';
|
|
797
|
+
await api('POST','/api/ollama/config',{baseUrl:url});
|
|
798
|
+
const r=await api('POST','/api/accounts',{provider:'ollama',label,method:'local'});
|
|
799
|
+
if(!r.ok){toast('Error: '+await r.text(),true);return}
|
|
800
|
+
toast('??'+label+' added');
|
|
801
|
+
sbScreen='providers';
|
|
802
|
+
await fetchStatus();
|
|
803
|
+
renderSb();
|
|
804
|
+
}
|
|
805
|
+
function pollNewAccount(n,prevCount){
|
|
806
|
+
if(n>180){toast('OAuth timed out ??no account detected. Check terminal logs.',true);return;}
|
|
807
|
+
setTimeout(async()=>{
|
|
808
|
+
await fetchStatus();
|
|
809
|
+
renderSb();
|
|
810
|
+
if(ST.oauthError){
|
|
811
|
+
toast('Login failed: '+ST.oauthError,true);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if(ST.accounts.length>prevCount){
|
|
815
|
+
const newAcc=ST.accounts.slice(-1)[0];
|
|
816
|
+
toast('??'+(newAcc?.label||'Account')+' connected!');
|
|
817
|
+
sbOAuthDevice=null;
|
|
818
|
+
if(sbScreen==='oauth-device')sbScreen='providers';
|
|
819
|
+
renderSb();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
pollNewAccount(n+1,prevCount);
|
|
823
|
+
},4000);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ?�?�?� Account / slot management ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
827
|
+
async function deleteAccount(id){
|
|
828
|
+
// Remove all canvas nodes belonging to this account
|
|
829
|
+
for(const [slotId,info] of Object.entries(nodeSlots)){
|
|
830
|
+
if(info.accountId===id){ onCanvas.delete(slotId); delete nodeSlots[slotId]; delete NP[slotId]; }
|
|
831
|
+
}
|
|
832
|
+
saveLS();
|
|
833
|
+
const r=await api('DELETE',\`/api/accounts/\${id}\`);
|
|
834
|
+
if(!r.ok){toast('Delete failed: '+await r.text(),true);await fetchStatus();return;}
|
|
835
|
+
toast('Account removed');
|
|
836
|
+
await fetchStatus();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function addToCanvas(accountId){
|
|
840
|
+
const acc=ST.accounts.find(a=>a.id===accountId);
|
|
841
|
+
if(!acc)return;
|
|
842
|
+
const models=acc.models||[];
|
|
843
|
+
const model=sidebarModelSel[accountId]||models[0]||'';
|
|
844
|
+
const slotId='slot_'+Date.now()+'_'+Math.random().toString(36).slice(2,6);
|
|
845
|
+
nodeSlots[slotId]={accountId,model};
|
|
846
|
+
onCanvas.add(slotId);
|
|
847
|
+
if(!NP[slotId]){
|
|
848
|
+
const ws=document.getElementById('ws');
|
|
849
|
+
const wr=ws.getBoundingClientRect();
|
|
850
|
+
const cx=(wr.width/2-vp.x)/vp.s,cy=(wr.height/2-vp.y)/vp.s;
|
|
851
|
+
const off=([...onCanvas].length-1)*28;
|
|
852
|
+
NP[slotId]={x:Math.max(20,cx-107+off),y:Math.max(20,cy-90+off)};
|
|
853
|
+
}
|
|
854
|
+
saveLS();
|
|
855
|
+
render();
|
|
856
|
+
renderSb();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function removeFromCanvas(slotId){
|
|
860
|
+
const info=nodeSlots[slotId];
|
|
861
|
+
onCanvas.delete(slotId);
|
|
862
|
+
saveLS();
|
|
863
|
+
render();
|
|
864
|
+
if(sbOpen)renderSb();
|
|
865
|
+
if(info){
|
|
866
|
+
const acc=ST.accounts.find(a=>a.id===info.accountId);
|
|
867
|
+
const slot=(acc?.activeModels||[]).find(s=>s.slotId===slotId);
|
|
868
|
+
if(slot){
|
|
869
|
+
await api('DELETE',\`/api/accounts/\${info.accountId}/slots/\${slotId}\`);
|
|
870
|
+
await fetchStatus();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// ?�?�?� Monitor node ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
876
|
+
function toggleMonitor(tab){
|
|
877
|
+
if(monitorOpen && monitorTab===tab){ monitorOpen=false; }
|
|
878
|
+
else{ monitorOpen=true; monitorTab=tab; if(!NP.monitor) NP.monitor={x:80,y:60}; }
|
|
879
|
+
['terminal','status','logs','requests','usage'].forEach(t=>{
|
|
880
|
+
document.getElementById('hb-'+{terminal:'term',status:'stat',logs:'logs',requests:'reqs',usage:'usage'}[t])
|
|
881
|
+
?.classList.toggle('on', monitorOpen&&monitorTab===t);
|
|
882
|
+
});
|
|
883
|
+
render();
|
|
884
|
+
if(monitorOpen) startMonitorRefresh();
|
|
885
|
+
else stopMonitorRefresh();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function startMonitorRefresh(){
|
|
889
|
+
stopMonitorRefresh();
|
|
890
|
+
if(monitorTab==='status') refreshStatus();
|
|
891
|
+
else if(monitorTab==='logs') refreshLogs(true);
|
|
892
|
+
else if(monitorTab==='requests') refreshRequests();
|
|
893
|
+
else if(monitorTab==='usage') refreshUsage();
|
|
894
|
+
monitorRefreshTimer=setInterval(()=>{
|
|
895
|
+
if(!monitorOpen){stopMonitorRefresh();return;}
|
|
896
|
+
if(monitorTab==='status') refreshStatus();
|
|
897
|
+
else if(monitorTab==='logs') refreshLogs();
|
|
898
|
+
else if(monitorTab==='requests') refreshRequests();
|
|
899
|
+
else if(monitorTab==='usage') refreshUsage();
|
|
900
|
+
},3000);
|
|
901
|
+
}
|
|
902
|
+
function stopMonitorRefresh(){if(monitorRefreshTimer){clearInterval(monitorRefreshTimer);monitorRefreshTimer=null;}}
|
|
903
|
+
|
|
904
|
+
async function clearMonitor(){
|
|
905
|
+
if(monitorTab==='logs'){
|
|
906
|
+
await api('DELETE','/api/logs');
|
|
907
|
+
const body=document.getElementById('mn-logs-body');
|
|
908
|
+
if(body)body.innerHTML='<div class="mn-empty">Cleared</div>';
|
|
909
|
+
}else if(monitorTab==='requests'){
|
|
910
|
+
await api('DELETE','/api/requests');
|
|
911
|
+
lastReqData=null;
|
|
912
|
+
const body=document.getElementById('mn-reqs-body');
|
|
913
|
+
if(body)body.innerHTML='<div class="mn-empty">Cleared</div>';
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function switchMonitorTab(tab){
|
|
918
|
+
monitorTab=tab;
|
|
919
|
+
render();
|
|
920
|
+
startMonitorRefresh();
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function startMonitorResize(e,dir){
|
|
924
|
+
e.preventDefault();e.stopPropagation();
|
|
925
|
+
const sz=NP.monitorSize||{w:620,h:320};
|
|
926
|
+
resizeD={dir,sx:e.clientX,sy:e.clientY,w0:sz.w,h0:sz.h};
|
|
927
|
+
}
|
|
928
|
+
function buildMonitorNode(){
|
|
929
|
+
const pos=NP.monitor||{x:80,y:60};
|
|
930
|
+
const sz=NP.monitorSize||{w:620,h:320};
|
|
931
|
+
const tabs=['terminal','status','logs','requests','usage'];
|
|
932
|
+
const labels={terminal:'Terminal',status:'Status',logs:'Logs',requests:'Requests',usage:'Usage'};
|
|
933
|
+
const icons={
|
|
934
|
+
terminal:'<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M1.5 3.5L5 7l-3.5 3.5M6 10.5h6.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
935
|
+
status:'<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.4"/><path d="M7 4v3.5l2 1.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>',
|
|
936
|
+
logs:'<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M2 3.5h10M2 7h7M2 10.5h5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
937
|
+
requests:'<svg width="11" height="11" viewBox="0 0 14 14" fill="none"><path d="M2.5 4.5h9M2.5 7h6M2.5 9.5h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><circle cx="11" cy="9.5" r="2" stroke="currentColor" stroke-width="1.2"/></svg>',
|
|
938
|
+
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
|
+
};
|
|
940
|
+
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)'">??Clear</button>\`
|
|
942
|
+
:'';
|
|
943
|
+
const tabHtml=tabs.map(t=>\`<div class="mn-t \${monitorTab===t?'on':''}" onclick="switchMonitorTab('\${t}')">\${icons[t]} \${labels[t]}</div>\`).join('')+
|
|
944
|
+
\`<div style="flex:1"></div>\${clearBtn}\`;
|
|
945
|
+
|
|
946
|
+
let content='',inputRow='';
|
|
947
|
+
if(monitorTab==='terminal'){
|
|
948
|
+
const linesHtml=termLines.map(l=>\`<div class="term-line t-\${l.t}">\${escHtml(l.v)}</div>\`).join('');
|
|
949
|
+
content=\`<div class="term-wrap" id="term-out">\${linesHtml||'<div class="t-info">Ready. Type a command below.</div>'}</div>\`;
|
|
950
|
+
const cwd=termCwd||'~';
|
|
951
|
+
const short=cwd.replace(new RegExp('^/Users/[^/]+'),'~');
|
|
952
|
+
inputRow=\`<div class="mn-inp-row">
|
|
953
|
+
<span class="mn-cwd-lbl" title="\${cwd}">\${short}$</span>
|
|
954
|
+
<input class="mn-inp" id="mn-inp" placeholder="Enter command?? autocomplete="off" spellcheck="false"/>
|
|
955
|
+
<button class="mn-run" onclick="sendTerm()">??/button>
|
|
956
|
+
</div>\`;
|
|
957
|
+
} else if(monitorTab==='status'){
|
|
958
|
+
content=\`<div id="mn-status-body"><div class="mn-empty">Loading??/div></div>\`;
|
|
959
|
+
} else if(monitorTab==='logs'){
|
|
960
|
+
content=\`<div class="log-wrap" id="mn-logs-body"><div class="mn-empty">Loading??/div></div>\`;
|
|
961
|
+
} else if(monitorTab==='requests'){
|
|
962
|
+
content=\`<div class="req-wrap" id="mn-reqs-body"><div class="mn-empty">Loading??/div></div>\`;
|
|
963
|
+
} else if(monitorTab==='usage'){
|
|
964
|
+
content=\`<div id="mn-usage-body">\${usageHtmlCache||'<div class="mn-empty">Loading??/div>'}</div>\`;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return \`<div class="nd mn" id="nd-monitor" style="left:\${pos.x}px;top:\${pos.y}px;width:\${sz.w}px">
|
|
968
|
+
<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">??/div>
|
|
970
|
+
<span class="nn">Monitor</span>
|
|
971
|
+
<button class="nd-rm" onclick="toggleMonitor(monitorTab)" title="Close">×</button>
|
|
972
|
+
</div>
|
|
973
|
+
<div class="mn-tabbar">\${tabHtml}</div>
|
|
974
|
+
<div class="mn-body \${!inputRow?'last':''}" id="mn-body" style="height:\${sz.h}px">\${content}</div>
|
|
975
|
+
\${inputRow}
|
|
976
|
+
<div class="mn-rs-e" onpointerdown="startMonitorResize(event,'e')"></div>
|
|
977
|
+
<div class="mn-rs-s" onpointerdown="startMonitorResize(event,'s')"></div>
|
|
978
|
+
<div class="mn-rs-se" onpointerdown="startMonitorResize(event,'se')"></div>
|
|
979
|
+
</div>\`;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function escHtml(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
983
|
+
|
|
984
|
+
// ?�?�?� Terminal ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
985
|
+
function addTermLine(t,v){
|
|
986
|
+
termLines.push({t,v});
|
|
987
|
+
if(termLines.length>500)termLines.splice(0,termLines.length-500);
|
|
988
|
+
const out=document.getElementById('term-out');
|
|
989
|
+
if(out){
|
|
990
|
+
const el=document.createElement('div');
|
|
991
|
+
el.className=\`term-line t-\${t}\`;
|
|
992
|
+
el.textContent=v;
|
|
993
|
+
out.appendChild(el);
|
|
994
|
+
out.scrollTop=out.scrollHeight;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
async function sendTerm(){
|
|
999
|
+
const inp=document.getElementById('mn-inp');
|
|
1000
|
+
if(!inp||termBusy)return;
|
|
1001
|
+
const cmd=inp.value.trim();
|
|
1002
|
+
if(!cmd)return;
|
|
1003
|
+
inp.value='';
|
|
1004
|
+
termHistory.unshift(cmd);
|
|
1005
|
+
termHistIdx=-1;
|
|
1006
|
+
termBusy=true;
|
|
1007
|
+
addTermLine('cmd',\`\${(termCwd||'~').replace(new RegExp('^/Users/[^/]+'),'~')}$ \${cmd}\`);
|
|
1008
|
+
try{
|
|
1009
|
+
const r=await api('POST','/api/terminal/exec',{cmd,cwd:termCwd||undefined});
|
|
1010
|
+
const d=await r.json();
|
|
1011
|
+
if(d.cwd)termCwd=d.cwd;
|
|
1012
|
+
if(d.stdout?.trim())d.stdout.trimEnd().split('\\n').forEach(l=>addTermLine('out',l));
|
|
1013
|
+
if(d.stderr?.trim())d.stderr.trimEnd().split('\\n').forEach(l=>addTermLine('err',l));
|
|
1014
|
+
// Re-render only header of monitor (for cwd update in prompt)
|
|
1015
|
+
const lbl=document.querySelector('.mn-cwd-lbl');
|
|
1016
|
+
if(lbl){const short=(d.cwd||termCwd||'~').replace(new RegExp('^/Users/[^/]+'),'~');lbl.textContent=short+'$';lbl.title=d.cwd||'';}
|
|
1017
|
+
}catch(e){addTermLine('err','Error: '+e.message);}
|
|
1018
|
+
termBusy=false;
|
|
1019
|
+
document.getElementById('mn-inp')?.focus();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function setupTermKeys(){
|
|
1023
|
+
const inp=document.getElementById('mn-inp');
|
|
1024
|
+
if(!inp)return;
|
|
1025
|
+
inp.onkeydown=e=>{
|
|
1026
|
+
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendTerm();}
|
|
1027
|
+
else if(e.key==='ArrowUp'){e.preventDefault();if(termHistIdx<termHistory.length-1){termHistIdx++;inp.value=termHistory[termHistIdx];}}
|
|
1028
|
+
else if(e.key==='ArrowDown'){e.preventDefault();if(termHistIdx>0){termHistIdx--;inp.value=termHistory[termHistIdx];}else{termHistIdx=-1;inp.value='';}}
|
|
1029
|
+
else if(e.key==='l'&&e.ctrlKey){e.preventDefault();termLines=[];document.getElementById('term-out').innerHTML='';}
|
|
1030
|
+
};
|
|
1031
|
+
inp.focus();
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// ?�?�?� Status / Logs / Requests refresh ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1035
|
+
async function refreshStatus(){
|
|
1036
|
+
const body=document.getElementById('mn-status-body');
|
|
1037
|
+
if(!body)return;
|
|
1038
|
+
try{
|
|
1039
|
+
const d=await(await fetch('/api/status')).json();
|
|
1040
|
+
const upSec=Math.floor(d.uptimeMs/1000);
|
|
1041
|
+
const upStr=upSec<60?\`\${upSec}s\`:upSec<3600?\`\${Math.floor(upSec/60)}m\`:\`\${Math.floor(upSec/3600)}h \${Math.floor((upSec%3600)/60)}m\`;
|
|
1042
|
+
const provHtml=(d.connectedProviders||[]).map(p=>\`
|
|
1043
|
+
<div class="st-pr">
|
|
1044
|
+
<span style="width:8px;height:8px;border-radius:50%;background:\${COL[p.provider]||'#888'};flex-shrink:0;display:inline-block"></span>
|
|
1045
|
+
<span style="font-weight:600">\${p.label}</span>
|
|
1046
|
+
<span style="color:var(--mu)">\${p.model}</span>
|
|
1047
|
+
</div>\`).join('');
|
|
1048
|
+
body.innerHTML=\`
|
|
1049
|
+
<div class="st-grid">
|
|
1050
|
+
<div class="st-card"><div class="st-val">:\${d.port}</div><div class="st-key">Port</div></div>
|
|
1051
|
+
<div class="st-card"><div class="st-val">\${d.connectedCount}</div><div class="st-key">Connected</div></div>
|
|
1052
|
+
<div class="st-card"><div class="st-val">\${upStr}</div><div class="st-key">Uptime</div></div>
|
|
1053
|
+
</div>
|
|
1054
|
+
\${provHtml?\`<div class="st-prov">\${provHtml}</div>\`:''}
|
|
1055
|
+
\`;
|
|
1056
|
+
}catch{body.innerHTML='<div class="mn-empty">Failed to load</div>';}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function preserveScrollTop(_body,render){
|
|
1060
|
+
// Requests: newest at top ??restore mn-body scroll position after innerHTML reset
|
|
1061
|
+
const scroller=document.getElementById('mn-body');
|
|
1062
|
+
const saved=scroller?scroller.scrollTop:0;
|
|
1063
|
+
render();
|
|
1064
|
+
if(scroller) requestAnimationFrame(()=>{ scroller.scrollTop=saved; });
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
async function refreshLogs(forceBottom=false){
|
|
1068
|
+
const body=document.getElementById('mn-logs-body');
|
|
1069
|
+
const scroller=document.getElementById('mn-body');
|
|
1070
|
+
if(!body)return;
|
|
1071
|
+
// Snapshot scroll state before async fetch (so user scroll during fetch doesn't matter)
|
|
1072
|
+
const atBottom=forceBottom||!scroller||scroller.scrollHeight-scroller.scrollTop-scroller.clientHeight<40;
|
|
1073
|
+
try{
|
|
1074
|
+
const d=await(await fetch('/api/logs?n=120')).json();
|
|
1075
|
+
if(!d.lines?.length){body.innerHTML='<div class="mn-empty">No log entries yet</div>';return;}
|
|
1076
|
+
body.innerHTML=d.lines.map(l=>{
|
|
1077
|
+
const cls=l.includes('??)||l.includes('error')||l.includes('Error')?'err':l.includes('??)||l.includes('warn')?'warn':l.includes('??)?'ok':'';
|
|
1078
|
+
return \`<div class="log-l \${cls}">\${escHtml(l)}</div>\`;
|
|
1079
|
+
}).join('');
|
|
1080
|
+
if(scroller&&atBottom) scroller.scrollTop=scroller.scrollHeight;
|
|
1081
|
+
}catch{body.innerHTML='<div class="mn-empty">Failed to load</div>';}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async function refreshRequests(){
|
|
1085
|
+
const body=document.getElementById('mn-reqs-body');
|
|
1086
|
+
const scroller=document.getElementById('mn-body');
|
|
1087
|
+
if(!body)return;
|
|
1088
|
+
const atBottom=!scroller||scroller.scrollHeight-scroller.scrollTop-scroller.clientHeight<60;
|
|
1089
|
+
try{
|
|
1090
|
+
const d=await(await fetch('/api/requests')).json();
|
|
1091
|
+
if(!d.requests?.length){body.innerHTML='<div class="mn-empty">No requests yet</div>';return;}
|
|
1092
|
+
const hdr='<div class="req-row req-hdr"><span>Time</span><span>Provider</span><span>Model</span><span>ms</span><span>Tokens</span><span></span></div>';
|
|
1093
|
+
const rows=d.requests.map((r,i)=>{
|
|
1094
|
+
const t=new Date(r.ts);
|
|
1095
|
+
const ts=t.toLocaleTimeString('en',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
|
1096
|
+
const stCls=r.status==='error'?'req-err':r.fallback?'req-fb':'req-ok';
|
|
1097
|
+
const col=COL[r.provider]||'#888';
|
|
1098
|
+
let modelLabel;
|
|
1099
|
+
if(r.failedModels?.length&&r.usedModel){
|
|
1100
|
+
const failedLines=r.failedModels.map(m=>\`<div style="opacity:.38;text-decoration:line-through;line-height:1.4">\${m}</div>\`).join('');
|
|
1101
|
+
modelLabel=failedLines+\`<div style="line-height:1.4">\${r.usedModel}</div>\`;
|
|
1102
|
+
} else if(r.failedModels?.length&&!r.usedModel){
|
|
1103
|
+
modelLabel=r.failedModels.map(m=>\`<div style="opacity:.5;text-decoration:line-through;line-height:1.4">\${m}</div>\`).join('');
|
|
1104
|
+
} else {
|
|
1105
|
+
modelLabel=r.usedModel||'??;
|
|
1106
|
+
}
|
|
1107
|
+
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>\`:'??;
|
|
1109
|
+
const hasDetail=!!(r.inputPreview||r.outputPreview||r.toolCalls?.length||r.toolCallDetails?.length||r.webFetches?.length||r.error);
|
|
1110
|
+
const detailId=\`req-detail-\${r.ts}\`;
|
|
1111
|
+
const detailBtn=hasDetail?\`<button class="req-expand-btn" onclick="toggleReqDetail('\${detailId}')" title="Show detail">??/button>\`:'';
|
|
1112
|
+
|
|
1113
|
+
// Build detail panel
|
|
1114
|
+
let detailHtml='';
|
|
1115
|
+
if(hasDetail){
|
|
1116
|
+
const parts=[];
|
|
1117
|
+
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
|
+
if(r.toolCallDetails?.length){
|
|
1119
|
+
const detailLines=r.toolCallDetails.map(tc=>{
|
|
1120
|
+
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)">??\${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
|
+
return\`<code>\${escHtml(tc.name)}</code>\${argStr}\`;
|
|
1123
|
+
});
|
|
1124
|
+
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>\`);
|
|
1125
|
+
} 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>\`);
|
|
1126
|
+
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>\`);
|
|
1127
|
+
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>\`);
|
|
1128
|
+
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
|
+
detailHtml=\`<div id="\${detailId}" class="req-detail-panel" style="display:none">\${parts.join('')}</div>\`;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return \`<div style="flex-direction:column;display:flex;border-bottom:1px solid rgba(255,255,255,.035)">
|
|
1133
|
+
<div class="req-row \${stCls}-row" style="border-bottom:none">
|
|
1134
|
+
<span class="req-ts">\${ts}</span>
|
|
1135
|
+
<span class="req-prov" style="color:\${col}">\${r.provider||'??}</span>
|
|
1136
|
+
<span class="req-model">\${modelLabel}</span>
|
|
1137
|
+
<span class="req-ms">\${r.ms}</span>
|
|
1138
|
+
<span>\${tokensTxt}</span>
|
|
1139
|
+
<span>\${detailBtn}</span>
|
|
1140
|
+
</div>
|
|
1141
|
+
\${detailHtml}
|
|
1142
|
+
</div>\`;
|
|
1143
|
+
}).join('');
|
|
1144
|
+
// Save which detail panels are open (by ts-based ID) before rebuilding
|
|
1145
|
+
const openIds=new Set([...body.querySelectorAll('.req-detail-panel')].filter(el=>el.style.display!=='none').map(el=>el.id));
|
|
1146
|
+
body.innerHTML=hdr+rows;
|
|
1147
|
+
if(scroller&&atBottom) scroller.scrollTop=scroller.scrollHeight;
|
|
1148
|
+
// 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='??;}});
|
|
1150
|
+
}catch{body.innerHTML='<div class="mn-empty">Failed to load</div>';}
|
|
1151
|
+
}
|
|
1152
|
+
function toggleReqDetail(id){
|
|
1153
|
+
const el=document.getElementById(id);
|
|
1154
|
+
if(!el)return;
|
|
1155
|
+
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='??;}
|
|
1158
|
+
}
|
|
1159
|
+
function escHtml(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
|
1160
|
+
|
|
1161
|
+
// ?�?�?� Usage ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1162
|
+
const PRICING={
|
|
1163
|
+
'claude-opus-4-7':[15,75],'claude-opus-4-7-20250514':[15,75],'claude-opus-4-5':[15,75],
|
|
1164
|
+
'claude-sonnet-4-6':[3,15],'claude-sonnet-4-5':[3,15],
|
|
1165
|
+
'claude-haiku-4-5':[0.8,4],'claude-haiku-4-5-20251001':[0.8,4],
|
|
1166
|
+
'gpt-4o':[2.5,10],'gpt-4o-mini':[0.15,0.6],
|
|
1167
|
+
'gpt-4.1':[2,8],'gpt-4.1-mini':[0.4,1.6],'gpt-4.5':[75,150],
|
|
1168
|
+
'gpt-5':[15,60],'gpt-5.1':[15,60],'gpt-5.2':[15,60],'gpt-5.3':[15,60],'gpt-5.4':[15,60],'gpt-5.5':[15,60],
|
|
1169
|
+
'o3':[10,40],'o4-mini':[1.1,4.4],
|
|
1170
|
+
'gemini-2.5-pro':[1.25,10],'gemini-2.5-flash':[0.15,0.6],'gemini-2.0-flash':[0.1,0.4],
|
|
1171
|
+
};
|
|
1172
|
+
function getPrice(model){
|
|
1173
|
+
if(!model)return null;
|
|
1174
|
+
if(PRICING[model])return PRICING[model];
|
|
1175
|
+
const key=Object.keys(PRICING).find(k=>model.startsWith(k));
|
|
1176
|
+
return key?PRICING[key]:null;
|
|
1177
|
+
}
|
|
1178
|
+
function fmtCost(usd){
|
|
1179
|
+
if(usd==null)return'??;
|
|
1180
|
+
if(usd<0.0001)return'<$0.0001';
|
|
1181
|
+
if(usd<0.01)return'$'+usd.toFixed(4);
|
|
1182
|
+
return'$'+usd.toFixed(3);
|
|
1183
|
+
}
|
|
1184
|
+
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);}
|
|
1185
|
+
|
|
1186
|
+
function fmtReset(isoStr){
|
|
1187
|
+
if(!isoStr)return'';
|
|
1188
|
+
const diff=new Date(isoStr)-Date.now();
|
|
1189
|
+
if(diff<=0)return'now';
|
|
1190
|
+
const h=Math.floor(diff/3600000),m=Math.floor((diff%3600000)/60000);
|
|
1191
|
+
if(diff<3600000)return m+'m';
|
|
1192
|
+
if(diff<86400000)return h+'h '+m+'m';
|
|
1193
|
+
const d=Math.floor(diff/86400000);return d+'d '+(h%24)+'h';
|
|
1194
|
+
}
|
|
1195
|
+
function quotaBar(pct,col){
|
|
1196
|
+
const used=Math.min(100,Math.max(0,pct));
|
|
1197
|
+
const barCol=used>80?'#ef4444':used>50?'#f59e0b':col||'#6366f1';
|
|
1198
|
+
return\`<div style="flex:1;height:5px;border-radius:3px;background:rgba(255,255,255,.08);overflow:hidden">
|
|
1199
|
+
<div style="height:100%;width:\${used}%;background:\${barCol};border-radius:3px;transition:width .4s"></div>
|
|
1200
|
+
</div>\`;
|
|
1201
|
+
}
|
|
1202
|
+
function quotaRow(label,used,resetsAt){
|
|
1203
|
+
const remaining=Math.max(0,100-used);
|
|
1204
|
+
const col=used>80?'#ef4444':used>50?'#f59e0b':'#6366f1';
|
|
1205
|
+
const reset=fmtReset(resetsAt);
|
|
1206
|
+
return\`<div style="display:flex;align-items:center;gap:8px;padding:3px 0">
|
|
1207
|
+
<span style="font-size:9px;color:var(--mu);width:26px;flex-shrink:0">\${label}</span>
|
|
1208
|
+
\${quotaBar(used,col)}
|
|
1209
|
+
<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>\`:''}
|
|
1211
|
+
</div>\`;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function renderUsage(){
|
|
1215
|
+
const body=document.getElementById('mn-usage-body');
|
|
1216
|
+
if(!body)return;
|
|
1217
|
+
|
|
1218
|
+
// ?�?� Quota section ??built from ST.accounts + quotaState (no auto-fetch) ?�?�
|
|
1219
|
+
const oauthAccts=(ST.accounts||[]).filter(a=>a.method==='oauth-official'&&(a.provider==='anthropic'||a.provider==='openai'));
|
|
1220
|
+
let quotaHtml='';
|
|
1221
|
+
if(oauthAccts.length){
|
|
1222
|
+
const cards=oauthAccts.map(a=>{
|
|
1223
|
+
const qs=quotaState[a.id]||{data:null,loading:false};
|
|
1224
|
+
const icon=providerImg(a.provider,14);
|
|
1225
|
+
let rows='';
|
|
1226
|
+
if(qs.loading){
|
|
1227
|
+
rows=\`<div style="font-size:9px;color:var(--mu)">Fetching??/div>\`;
|
|
1228
|
+
}else if(!qs.data){
|
|
1229
|
+
rows=\`<div style="font-size:9px;color:var(--mu);opacity:.5">Click ??to load</div>\`;
|
|
1230
|
+
}else if(qs.data.error){
|
|
1231
|
+
rows=\`<div style="font-size:9px;color:var(--rd)">Error: \${qs.data.error}</div>\`;
|
|
1232
|
+
}else if(a.provider==='anthropic'){
|
|
1233
|
+
if(qs.data.five_hour!=null)rows+=quotaRow('5h',qs.data.five_hour.utilization,qs.data.five_hour.resets_at);
|
|
1234
|
+
if(qs.data.seven_day!=null)rows+=quotaRow('7d',qs.data.seven_day.utilization,qs.data.seven_day.resets_at);
|
|
1235
|
+
}else if(a.provider==='openai'){
|
|
1236
|
+
if(qs.data.primary!=null)rows+=quotaRow('5h',qs.data.primary.used,qs.data.primary.resets_at);
|
|
1237
|
+
if(qs.data.secondary!=null)rows+=quotaRow('7d',qs.data.secondary.used,qs.data.secondary.resets_at);
|
|
1238
|
+
}
|
|
1239
|
+
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;padding:0 7px;line-height:1.8;transition:all .12s;flex-shrink:0';
|
|
1241
|
+
return\`<div style="background:var(--s2);border-radius:8px;padding:8px 10px">
|
|
1242
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:\${rows?'6':'0'}px">
|
|
1243
|
+
<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
|
+
<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?'??:'??}</button>
|
|
1246
|
+
</div>
|
|
1247
|
+
\${rows}
|
|
1248
|
+
</div>\`;
|
|
1249
|
+
}).join('');
|
|
1250
|
+
quotaHtml=\`<div style="padding:10px 10px 0">
|
|
1251
|
+
<div style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--mu);margin-bottom:6px">Subscription Quota</div>
|
|
1252
|
+
<div style="display:flex;flex-direction:column;gap:6px">\${cards}</div>
|
|
1253
|
+
</div>\`;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ?�?� Token usage section ??built from lastReqData ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1257
|
+
const reqs=(lastReqData?.requests||[]);
|
|
1258
|
+
const ok=reqs.filter(r=>r.status==='ok'&&r.usedModel);
|
|
1259
|
+
let tokenHtml='';
|
|
1260
|
+
if(ok.length){
|
|
1261
|
+
let totIn=0,totOut=0,totCost=0,hasCost=false;
|
|
1262
|
+
const byModel={};
|
|
1263
|
+
for(const r of ok){
|
|
1264
|
+
const inT=r.inputTokens||0,outT=r.outputTokens||0;
|
|
1265
|
+
totIn+=inT;totOut+=outT;
|
|
1266
|
+
const p=getPrice(r.usedModel);
|
|
1267
|
+
const cost=p&&(inT||outT)?(inT*p[0]+outT*p[1])/1e6:null;
|
|
1268
|
+
if(cost!=null){totCost+=cost;hasCost=true;}
|
|
1269
|
+
if(!byModel[r.usedModel])byModel[r.usedModel]={provider:r.provider,reqs:0,inT:0,outT:0,cost:0,hasCost:false};
|
|
1270
|
+
byModel[r.usedModel].reqs++;byModel[r.usedModel].inT+=inT;byModel[r.usedModel].outT+=outT;
|
|
1271
|
+
if(cost!=null){byModel[r.usedModel].cost+=cost;byModel[r.usedModel].hasCost=true;}
|
|
1272
|
+
}
|
|
1273
|
+
const cards=\`<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px">
|
|
1274
|
+
<div class="usg-card"><div class="usg-val">\${fmtTok(totIn)}</div><div class="usg-lbl">Input tokens</div></div>
|
|
1275
|
+
<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):'??}</div><div class="usg-lbl">Est. cost</div></div>
|
|
1277
|
+
</div>\`;
|
|
1278
|
+
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
|
+
const rows=Object.entries(byModel).sort((a,b)=>b[1].inT+b[1].outT-(a[1].inT+a[1].outT)).map(([model,m])=>{
|
|
1280
|
+
const col=COL[m.provider]||'#888';
|
|
1281
|
+
return\`<div class="usg-row">
|
|
1282
|
+
<span style="display:flex;align-items:center;gap:5px;min-width:0">
|
|
1283
|
+
<span style="width:6px;height:6px;border-radius:50%;background:\${col};flex-shrink:0"></span>
|
|
1284
|
+
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">\${model}</span>
|
|
1285
|
+
</span>
|
|
1286
|
+
<span>\${m.reqs}</span><span>\${fmtTok(m.inT)||'??}</span><span>\${fmtTok(m.outT)||'??}</span>
|
|
1287
|
+
<span>\${m.hasCost?fmtCost(m.cost):'??}</span>
|
|
1288
|
+
</div>\`;
|
|
1289
|
+
}).join('');
|
|
1290
|
+
tokenHtml=\`<div style="padding:10px 10px 0">
|
|
1291
|
+
<div style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--mu);margin-bottom:6px">Token Usage <span style="font-weight:400;opacity:.5">(24h)</span></div>
|
|
1292
|
+
\${cards}
|
|
1293
|
+
<div style="margin-top:6px;background:var(--s2);border-radius:8px;overflow:hidden">\${hdr}\${rows}</div>
|
|
1294
|
+
</div>\`;
|
|
1295
|
+
}else if(!quotaHtml){
|
|
1296
|
+
body.innerHTML='<div class="mn-empty">No usage data yet.</div>';return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const note=\`<div style="padding:6px 10px;font-size:9px;color:var(--mu);border-top:1px solid var(--b1);margin-top:8px">Tokens: resets at midnight · Quota: manual refresh</div>\`;
|
|
1300
|
+
usageHtmlCache=quotaHtml+tokenHtml+note;
|
|
1301
|
+
body.innerHTML=usageHtmlCache;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
async function refreshUsage(){
|
|
1305
|
+
try{lastReqData=await fetch('/api/requests').then(r=>r.json());}catch{/* keep last */}
|
|
1306
|
+
renderUsage();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
async function refreshQuota(accountId){
|
|
1310
|
+
if(!quotaState[accountId])quotaState[accountId]={data:null,loading:false};
|
|
1311
|
+
quotaState[accountId]={...quotaState[accountId],loading:true};
|
|
1312
|
+
renderUsage();
|
|
1313
|
+
try{
|
|
1314
|
+
const d=await fetch('/api/quota?bust='+encodeURIComponent(accountId)).then(r=>r.json());
|
|
1315
|
+
const entry=(d.quota||[]).find(q=>q.id===accountId)||null;
|
|
1316
|
+
quotaState[accountId]={data:entry,loading:false};
|
|
1317
|
+
}catch{
|
|
1318
|
+
quotaState[accountId]={data:null,loading:false};
|
|
1319
|
+
}
|
|
1320
|
+
renderUsage();
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// ?�?�?� Canvas viewport ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1324
|
+
function applyVp(){
|
|
1325
|
+
// Use CSS zoom for scaling so the browser re-renders content at the exact zoom level
|
|
1326
|
+
// (crisp text at any zoom), and translate for panning (in parent pixel space)
|
|
1327
|
+
const rx=Math.round(vp.x),ry=Math.round(vp.y);
|
|
1328
|
+
const world=document.getElementById('world');
|
|
1329
|
+
world.style.transform=\`translate(\${rx}px,\${ry}px)\`;
|
|
1330
|
+
world.style.zoom=String(vp.s);
|
|
1331
|
+
const gs=28*vp.s,ws=document.getElementById('ws');
|
|
1332
|
+
ws.style.backgroundSize=\`\${gs}px \${gs}px\`;
|
|
1333
|
+
ws.style.backgroundPosition=\`\${rx}px \${ry}px\`;
|
|
1334
|
+
document.getElementById('zpct').textContent=Math.round(vp.s*100)+'%';
|
|
1335
|
+
drawLines();
|
|
1336
|
+
}
|
|
1337
|
+
function zoomAt(f,cx,cy){
|
|
1338
|
+
const r=document.getElementById('ws').getBoundingClientRect();
|
|
1339
|
+
const px=cx??r.width/2,py=cy??r.height/2;
|
|
1340
|
+
const wx=(px-vp.x)/vp.s,wy=(py-vp.y)/vp.s;
|
|
1341
|
+
vp.s=Math.max(.08,Math.min(4,vp.s*f));
|
|
1342
|
+
vp.x=px-wx*vp.s;vp.y=py-wy*vp.s;
|
|
1343
|
+
applyVp();
|
|
1344
|
+
}
|
|
1345
|
+
function zoomStep(d){zoomAt(d>0?1.25:1/1.25)}
|
|
1346
|
+
function fitAll(){
|
|
1347
|
+
const ws=document.getElementById('ws'),wr=ws.getBoundingClientRect();
|
|
1348
|
+
const ids=['out',...onCanvas];
|
|
1349
|
+
let x0=Infinity,y0=Infinity,x1=-Infinity,y1=-Infinity;
|
|
1350
|
+
ids.forEach(id=>{
|
|
1351
|
+
const el=document.getElementById('nd-'+id);
|
|
1352
|
+
if(!el||!NP[id])return;
|
|
1353
|
+
x0=Math.min(x0,NP[id].x);y0=Math.min(y0,NP[id].y);
|
|
1354
|
+
x1=Math.max(x1,NP[id].x+(el.offsetWidth||215));
|
|
1355
|
+
y1=Math.max(y1,NP[id].y+(el.offsetHeight||180));
|
|
1356
|
+
});
|
|
1357
|
+
if(!isFinite(x0))return;
|
|
1358
|
+
const pad=70,cw=x1-x0+pad*2,ch=y1-y0+pad*2;
|
|
1359
|
+
vp.s=Math.min(wr.width/cw,wr.height/ch,1.4);
|
|
1360
|
+
vp.x=Math.round((wr.width-cw*vp.s)/2-(x0-pad)*vp.s);
|
|
1361
|
+
vp.y=Math.round((wr.height-ch*vp.s)/2-(y0-pad)*vp.s);
|
|
1362
|
+
applyVp();
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// ?�?�?� Canvas render ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1366
|
+
function render(){
|
|
1367
|
+
const world=document.getElementById('world');
|
|
1368
|
+
const savedScroll=document.getElementById('mn-body')?.scrollTop??0;
|
|
1369
|
+
// Snapshot current monitor content so rebuild doesn't flash "Loading??
|
|
1370
|
+
const savedReqs=document.getElementById('mn-reqs-body')?.innerHTML;
|
|
1371
|
+
const savedLogs=document.getElementById('mn-logs-body')?.innerHTML;
|
|
1372
|
+
const savedStat=document.getElementById('mn-status-body')?.innerHTML;
|
|
1373
|
+
const slotNodes=[...onCanvas]
|
|
1374
|
+
.filter(id=>id!=='out'&&id!=='monitor')
|
|
1375
|
+
.map(slotId=>buildAccNode(slotId))
|
|
1376
|
+
.join('');
|
|
1377
|
+
world.innerHTML=slotNodes+buildOutNode()+(monitorOpen?buildMonitorNode():'');
|
|
1378
|
+
// Restore content immediately before async refresh fires
|
|
1379
|
+
if(savedReqs){const el=document.getElementById('mn-reqs-body');if(el)el.innerHTML=savedReqs;}
|
|
1380
|
+
if(savedLogs){const el=document.getElementById('mn-logs-body');if(el)el.innerHTML=savedLogs;}
|
|
1381
|
+
if(savedStat){const el=document.getElementById('mn-status-body');if(el)el.innerHTML=savedStat;}
|
|
1382
|
+
if(savedScroll>0){const mb=document.getElementById('mn-body');if(mb)mb.scrollTop=savedScroll;}
|
|
1383
|
+
|
|
1384
|
+
[...onCanvas].filter(id=>id!=='out'&&id!=='monitor').forEach(slotId=>{
|
|
1385
|
+
const port=document.getElementById('po-'+slotId);
|
|
1386
|
+
if(port)port.addEventListener('pointerdown',e=>{e.stopPropagation();startConn(e,slotId)});
|
|
1387
|
+
const hdr=document.querySelector('#nd-'+slotId+' .nh');
|
|
1388
|
+
if(hdr)hdr.addEventListener('pointerdown',e=>startNodeDrag(e,slotId));
|
|
1389
|
+
});
|
|
1390
|
+
const outHdr=document.querySelector('#nd-out .nh');
|
|
1391
|
+
if(outHdr)outHdr.addEventListener('pointerdown',e=>startNodeDrag(e,'out'));
|
|
1392
|
+
|
|
1393
|
+
if(monitorOpen){
|
|
1394
|
+
const monHdr=document.querySelector('#nd-monitor .nh');
|
|
1395
|
+
if(monHdr)monHdr.addEventListener('pointerdown',e=>startNodeDrag(e,'monitor'));
|
|
1396
|
+
if(monitorTab==='terminal') setupTermKeys();
|
|
1397
|
+
else if(monitorTab==='status') refreshStatus();
|
|
1398
|
+
else if(monitorTab==='logs') refreshLogs();
|
|
1399
|
+
else if(monitorTab==='requests') refreshRequests();
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
drawLines();
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function accountSubtext(acc){
|
|
1406
|
+
try{
|
|
1407
|
+
if(acc.method==='oauth-official'&&acc.oauthToken){
|
|
1408
|
+
const parts=acc.oauthToken.split('.');
|
|
1409
|
+
if(parts.length>=2){
|
|
1410
|
+
const pad=parts[1].replace(/-/g,'+').replace(/_/g,'/');
|
|
1411
|
+
const payload=JSON.parse(atob(pad+'=='.slice(0,(4-pad.length%4)%4)));
|
|
1412
|
+
const email=payload.email||payload.sub||'';
|
|
1413
|
+
if(email)return '?�� '+email;
|
|
1414
|
+
}
|
|
1415
|
+
// No email in JWT ??show provider-specific label
|
|
1416
|
+
if(acc.provider==='anthropic')return '?�� Claude Code OAuth';
|
|
1417
|
+
}
|
|
1418
|
+
}catch{}
|
|
1419
|
+
if(acc.method==='oauth-official')return acc.provider==='anthropic'?'?�� Claude Code OAuth':'?�� OAuth';
|
|
1420
|
+
if(acc.method==='apikey'&&acc.apiKey){
|
|
1421
|
+
const k=acc.apiKey;
|
|
1422
|
+
return '?�� '+k.slice(0,10)+'···'+k.slice(-4);
|
|
1423
|
+
}
|
|
1424
|
+
if(acc.method==='oauth-unofficial')return '?�� Session Token';
|
|
1425
|
+
if(acc.method==='local')return '?�� '+(ST.ollamaBaseUrl||'localhost:11434');
|
|
1426
|
+
return '';
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function buildAccNode(slotId){
|
|
1430
|
+
const info=nodeSlots[slotId];
|
|
1431
|
+
if(!info)return '';
|
|
1432
|
+
const acc=ST.accounts.find(a=>a.id===info.accountId);
|
|
1433
|
+
if(!acc)return '';
|
|
1434
|
+
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 subEl=sub?\`<div class="nd-acct" title="\${sub}">\${sub}</div>\`:'';
|
|
1439
|
+
const pos=NP[slotId]||{x:80,y:80};
|
|
1440
|
+
return \`<div class="nd \${isOut?'live':''}" id="nd-\${slotId}" style="left:\${pos.x}px;top:\${pos.y}px">
|
|
1441
|
+
<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 class="nn">\${acc.label}</div>
|
|
1445
|
+
<div style="font-size:9px;color:var(--mu);white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="\${model}">\${model}</div>
|
|
1446
|
+
</div>
|
|
1447
|
+
<span class="bk \${isOut?'bk-on':'bk-off'}">\${isOut?'Active':'Idle'}</span>
|
|
1448
|
+
<button class="nd-rm" onclick="removeFromCanvas('\${slotId}')" title="Remove from canvas">×</button>
|
|
1449
|
+
</div>
|
|
1450
|
+
\${sub?\`<div class="nb" style="padding:4px 12px 6px">\${subEl}</div>\`:''}
|
|
1451
|
+
<div class="po \${isOut?'live':''}" id="po-\${slotId}" title="Drag to connect to OUT"></div>
|
|
1452
|
+
</div>\`;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function buildOutNode(){
|
|
1456
|
+
const allSlots=[];
|
|
1457
|
+
for(const acc of ST.accounts){
|
|
1458
|
+
for(const slot of (acc.activeModels||[])){
|
|
1459
|
+
allSlots.push({acc,slot});
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
allSlots.sort((a,b)=>a.slot.order-b.slot.order);
|
|
1463
|
+
const piCls='pi'+(allSlots.length?' live':'');
|
|
1464
|
+
const multi=allSlots.length>1;
|
|
1465
|
+
const subtitle=multi?'Priority order · fallback chain':'Codex uses these models';
|
|
1466
|
+
const body=allSlots.length
|
|
1467
|
+
?allSlots.map(({acc,slot},i)=>{
|
|
1468
|
+
const m=slot.model||'(auto)';
|
|
1469
|
+
const upDis=i===0?'disabled':'';
|
|
1470
|
+
const dnDis=i===allSlots.length-1?'disabled':'';
|
|
1471
|
+
const orderBtns=multi?\`<div class="oi-ord">
|
|
1472
|
+
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',-1)" \${upDis}>??/button>
|
|
1473
|
+
<button class="oi-arr" onclick="moveOut('\${acc.id}','\${slot.slotId}',1)" \${dnDis}>??/button>
|
|
1474
|
+
</div>\`:'';
|
|
1475
|
+
return \`<div class="oi">
|
|
1476
|
+
\${multi?\`<span class="oi-num">\${i+1}</span>\`:''}
|
|
1477
|
+
<div class="oi-dot" style="background:\${COL[acc.provider]}"></div>
|
|
1478
|
+
<div class="oi-inf">
|
|
1479
|
+
<div class="oi-pr">\${acc.label}</div>
|
|
1480
|
+
<div class="oi-mo" title="\${m}">\${m}</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
\${orderBtns}
|
|
1483
|
+
<button class="oi-x" onclick="removeOut('\${acc.id}','\${slot.slotId}')">×</button>
|
|
1484
|
+
</div>\`;
|
|
1485
|
+
}).join('')
|
|
1486
|
+
:'<div class="out-empty">No providers connected<br><span style="font-size:9px">??Drag ??from a node here</span></div>';
|
|
1487
|
+
const pos=NP.out||{x:520,y:280};
|
|
1488
|
+
const bypassed=codexMode==='openai';
|
|
1489
|
+
const bypassBanner=bypassed?'<div class="out-bypass">??Bypassed ??Codex using OpenAI directly</div>':'';
|
|
1490
|
+
return \`<div class="nd out-node\${bypassed?' bypassed':''}" id="nd-out" style="left:\${pos.x}px;top:\${pos.y}px">
|
|
1491
|
+
<div class="\${piCls}" id="pi"></div>
|
|
1492
|
+
<div class="nh">
|
|
1493
|
+
<div class="out-ic">??/div>
|
|
1494
|
+
<div style="flex:1;min-width:0">
|
|
1495
|
+
<div class="nn">Active Output</div>
|
|
1496
|
+
<div style="font-size:9px;color:var(--mu)">\${subtitle}</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
</div>
|
|
1499
|
+
<div class="nb" id="out-body">\${body}</div>
|
|
1500
|
+
\${bypassBanner}
|
|
1501
|
+
</div>\`;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// ?�?�?� SVG lines ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1505
|
+
function portPos(el){
|
|
1506
|
+
const ws=document.getElementById('ws').getBoundingClientRect();
|
|
1507
|
+
const r=el.getBoundingClientRect();
|
|
1508
|
+
return{x:r.left+r.width/2-ws.left,y:r.top+r.height/2-ws.top};
|
|
1509
|
+
}
|
|
1510
|
+
function bezier(x1,y1,x2,y2){
|
|
1511
|
+
const dx=Math.abs(x2-x1)*.55;
|
|
1512
|
+
return \`M\${x1},\${y1} C\${x1+dx},\${y1} \${x2-dx},\${y2} \${x2},\${y2}\`;
|
|
1513
|
+
}
|
|
1514
|
+
function drawLines(){
|
|
1515
|
+
const svg=document.getElementById('svgl');
|
|
1516
|
+
svg.querySelectorAll('.cp').forEach(e=>e.remove());
|
|
1517
|
+
const pi=document.getElementById('pi');
|
|
1518
|
+
if(!pi)return;
|
|
1519
|
+
const tp=portPos(pi);
|
|
1520
|
+
[...onCanvas].filter(id=>id!=='out'&&id!=='monitor').forEach(slotId=>{
|
|
1521
|
+
const info=nodeSlots[slotId];
|
|
1522
|
+
if(!info)return;
|
|
1523
|
+
const acc=ST.accounts.find(a=>a.id===info.accountId);
|
|
1524
|
+
if(!acc)return;
|
|
1525
|
+
const slot=(acc.activeModels||[]).find(s=>s.slotId===slotId);
|
|
1526
|
+
if(!slot)return;
|
|
1527
|
+
const pe=document.getElementById('po-'+slotId);
|
|
1528
|
+
if(!pe)return;
|
|
1529
|
+
const sp=portPos(pe);
|
|
1530
|
+
const path=document.createElementNS('http://www.w3.org/2000/svg','path');
|
|
1531
|
+
path.classList.add('cp');
|
|
1532
|
+
path.setAttribute('stroke',COL[acc.provider]||'#818cf8');
|
|
1533
|
+
path.setAttribute('d',bezier(sp.x,sp.y,tp.x,tp.y));
|
|
1534
|
+
svg.appendChild(path);
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// ?�?�?� Node drag ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1539
|
+
function startNodeDrag(e,id){
|
|
1540
|
+
if(e.target.tagName==='BUTTON'||e.target.tagName==='SELECT'||e.target.tagName==='INPUT')return;
|
|
1541
|
+
e.preventDefault();e.stopPropagation();
|
|
1542
|
+
const pos=NP[id]||{x:0,y:0};
|
|
1543
|
+
nodeD={id,sx:e.clientX,sy:e.clientY,nx:pos.x,ny:pos.y};
|
|
1544
|
+
document.getElementById('nd-'+id)?.classList.add('sel');
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const WS=document.getElementById('ws');
|
|
1548
|
+
WS.addEventListener('pointerdown',e=>{
|
|
1549
|
+
if(e.target.closest('.nd')||e.target.closest('.zbar'))return;
|
|
1550
|
+
panD={sx:e.clientX,sy:e.clientY,vx:vp.x,vy:vp.y};
|
|
1551
|
+
WS.style.cursor='grabbing';
|
|
1552
|
+
e.preventDefault();
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
function startConn(e,fromId){
|
|
1556
|
+
e.preventDefault();
|
|
1557
|
+
const pe=document.getElementById('po-'+fromId);
|
|
1558
|
+
if(!pe)return;
|
|
1559
|
+
pe.classList.add('dragging');
|
|
1560
|
+
connD={fromId};
|
|
1561
|
+
const sp=portPos(pe);
|
|
1562
|
+
const svg=document.getElementById('svgl');
|
|
1563
|
+
const tmp=document.createElementNS('http://www.w3.org/2000/svg','path');
|
|
1564
|
+
tmp.classList.add('ct');tmp.id='ctmp';
|
|
1565
|
+
tmp.setAttribute('d',bezier(sp.x,sp.y,sp.x,sp.y));
|
|
1566
|
+
svg.appendChild(tmp);
|
|
1567
|
+
document.getElementById('pi')?.classList.add('acc');
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
window.addEventListener('pointermove',e=>{
|
|
1571
|
+
if(resizeD){
|
|
1572
|
+
const dx=e.clientX-resizeD.sx,dy=e.clientY-resizeD.sy;
|
|
1573
|
+
const sz=NP.monitorSize||{w:620,h:320};
|
|
1574
|
+
if(resizeD.dir==='e'||resizeD.dir==='se') sz.w=Math.max(320,resizeD.w0+dx);
|
|
1575
|
+
if(resizeD.dir==='s'||resizeD.dir==='se') sz.h=Math.max(120,resizeD.h0+dy);
|
|
1576
|
+
NP.monitorSize=sz;
|
|
1577
|
+
const mn=document.getElementById('nd-monitor');
|
|
1578
|
+
if(mn) mn.style.width=sz.w+'px';
|
|
1579
|
+
const mb=document.getElementById('mn-body');
|
|
1580
|
+
if(mb) mb.style.height=sz.h+'px';
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if(nodeD){
|
|
1584
|
+
const dx=(e.clientX-nodeD.sx)/vp.s,dy=(e.clientY-nodeD.sy)/vp.s;
|
|
1585
|
+
NP[nodeD.id]={x:Math.round(nodeD.nx+dx),y:Math.round(nodeD.ny+dy)};
|
|
1586
|
+
const el=document.getElementById('nd-'+nodeD.id);
|
|
1587
|
+
if(el){el.style.left=NP[nodeD.id].x+'px';el.style.top=NP[nodeD.id].y+'px'}
|
|
1588
|
+
drawLines();
|
|
1589
|
+
}
|
|
1590
|
+
if(panD){vp.x=Math.round(panD.vx+(e.clientX-panD.sx));vp.y=Math.round(panD.vy+(e.clientY-panD.sy));applyVp()}
|
|
1591
|
+
if(connD){
|
|
1592
|
+
const pe=document.getElementById('po-'+connD.fromId);
|
|
1593
|
+
if(!pe)return;
|
|
1594
|
+
const sp=portPos(pe);
|
|
1595
|
+
const wr=document.getElementById('ws').getBoundingClientRect();
|
|
1596
|
+
const mx=e.clientX-wr.left,my=e.clientY-wr.top;
|
|
1597
|
+
const tmp=document.getElementById('ctmp');
|
|
1598
|
+
if(tmp)tmp.setAttribute('d',bezier(sp.x,sp.y,mx,my));
|
|
1599
|
+
const pi=document.getElementById('pi');
|
|
1600
|
+
if(pi){
|
|
1601
|
+
const pr=pi.getBoundingClientRect();
|
|
1602
|
+
const near=Math.hypot(e.clientX-(pr.left+pr.width/2),e.clientY-(pr.top+pr.height/2))<34;
|
|
1603
|
+
const hasConn=ST.accounts.some(a=>(a.activeModels||[]).length>0);
|
|
1604
|
+
pi.className='pi '+(near?'acc':hasConn?'live':'');
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
window.addEventListener('pointerup',async e=>{
|
|
1610
|
+
if(resizeD){saveLS();resizeD=null;}
|
|
1611
|
+
if(nodeD){
|
|
1612
|
+
document.getElementById('nd-'+nodeD.id)?.classList.remove('sel');
|
|
1613
|
+
saveLS();
|
|
1614
|
+
nodeD=null;
|
|
1615
|
+
}
|
|
1616
|
+
if(panD){panD=null;WS.style.cursor='default'}
|
|
1617
|
+
if(connD){
|
|
1618
|
+
document.getElementById('po-'+connD.fromId)?.classList.remove('dragging');
|
|
1619
|
+
document.getElementById('ctmp')?.remove();
|
|
1620
|
+
document.getElementById('pi')?.classList.remove('acc');
|
|
1621
|
+
const pi=document.getElementById('pi');
|
|
1622
|
+
if(pi){
|
|
1623
|
+
const pr=pi.getBoundingClientRect();
|
|
1624
|
+
const near=Math.hypot(e.clientX-(pr.left+pr.width/2),e.clientY-(pr.top+pr.height/2))<36;
|
|
1625
|
+
if(near)await connectOut(connD.fromId);
|
|
1626
|
+
}
|
|
1627
|
+
connD=null;
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
WS.addEventListener('wheel',e=>{
|
|
1632
|
+
// Don't intercept scroll events from inside panels (monitor, sidebar)
|
|
1633
|
+
if(e.target instanceof Element&&e.target.closest('.mn-body,.sb-body,.mn-inp-row'))return;
|
|
1634
|
+
e.preventDefault();
|
|
1635
|
+
const r=WS.getBoundingClientRect();
|
|
1636
|
+
zoomAt(e.deltaY<0?1.1:1/1.1,e.clientX-r.left,e.clientY-r.top);
|
|
1637
|
+
},{passive:false});
|
|
1638
|
+
|
|
1639
|
+
// ?�?�?� State changes ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
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
|
+
toast('??Connected to output');
|
|
1646
|
+
await fetchStatus();
|
|
1647
|
+
}
|
|
1648
|
+
async function removeOut(accountId,slotId){
|
|
1649
|
+
await api('DELETE',\`/api/accounts/\${accountId}/slots/\${slotId}\`);
|
|
1650
|
+
// Also remove from canvas
|
|
1651
|
+
onCanvas.delete(slotId);
|
|
1652
|
+
saveLS();
|
|
1653
|
+
await fetchStatus();
|
|
1654
|
+
}
|
|
1655
|
+
async function moveOut(accountId,slotId,dir){
|
|
1656
|
+
const allSlots=[];
|
|
1657
|
+
for(const acc of ST.accounts){
|
|
1658
|
+
for(const slot of (acc.activeModels||[])){
|
|
1659
|
+
allSlots.push({accountId:acc.id,slotId:slot.slotId,order:slot.order});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
allSlots.sort((a,b)=>a.order-b.order);
|
|
1663
|
+
const idx=allSlots.findIndex(s=>s.slotId===slotId);
|
|
1664
|
+
if(idx<0)return;
|
|
1665
|
+
const newIdx=idx+dir;
|
|
1666
|
+
if(newIdx<0||newIdx>=allSlots.length)return;
|
|
1667
|
+
[allSlots[idx],allSlots[newIdx]]=[allSlots[newIdx],allSlots[idx]];
|
|
1668
|
+
await api('POST','/api/slots/reorder',{items:allSlots.map(s=>({accountId:s.accountId,slotId:s.slotId}))});
|
|
1669
|
+
await fetchStatus();
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// ?�?�?� Fetch ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1673
|
+
async function fetchStatus(){
|
|
1674
|
+
try{
|
|
1675
|
+
const d=await(await fetch('/api/accounts')).json();
|
|
1676
|
+
ST=d;
|
|
1677
|
+
|
|
1678
|
+
// Sync server activeModels ??canvas (add missing slots)
|
|
1679
|
+
let idx=[...onCanvas].length;
|
|
1680
|
+
for(const acc of ST.accounts){
|
|
1681
|
+
for(const slot of (acc.activeModels||[])){
|
|
1682
|
+
if(!nodeSlots[slot.slotId]){
|
|
1683
|
+
nodeSlots[slot.slotId]={accountId:acc.id,model:slot.model};
|
|
1684
|
+
}
|
|
1685
|
+
if(!onCanvas.has(slot.slotId)){
|
|
1686
|
+
onCanvas.add(slot.slotId);
|
|
1687
|
+
if(!NP[slot.slotId])NP[slot.slotId]={x:80+(idx%4)*240,y:80+Math.floor(idx/4)*180};
|
|
1688
|
+
idx++;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// Remove stale canvas entries (account deleted from server)
|
|
1694
|
+
const serverAccountIds=new Set(ST.accounts.map(a=>a.id));
|
|
1695
|
+
[...onCanvas].forEach(id=>{
|
|
1696
|
+
if(id==='out'||id==='monitor')return;
|
|
1697
|
+
const info=nodeSlots[id];
|
|
1698
|
+
if(!info||!serverAccountIds.has(info.accountId)){
|
|
1699
|
+
onCanvas.delete(id); delete nodeSlots[id]; delete NP[id];
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
saveLS();
|
|
1704
|
+
render();
|
|
1705
|
+
if(sbOpen)renderSb();
|
|
1706
|
+
}catch(e){console.error(e)}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function api(method,url,body){
|
|
1710
|
+
return fetch(url,{method,headers:body?{'content-type':'application/json'}:{},body:body?JSON.stringify(body):undefined});
|
|
1711
|
+
}
|
|
1712
|
+
function updateModeUI(){
|
|
1713
|
+
document.getElementById('ms-rcodex')?.classList.toggle('active',codexMode==='rcodex');
|
|
1714
|
+
document.getElementById('ms-oai')?.classList.toggle('active',codexMode==='openai');
|
|
1715
|
+
render();
|
|
1716
|
+
}
|
|
1717
|
+
async function switchProvider(mode){
|
|
1718
|
+
try{
|
|
1719
|
+
toast(mode==='rcodex'?'Switching to rcodex Gateway??:'Switching to OpenAI direct??);
|
|
1720
|
+
const r=await api('POST','/api/codex-provider',{mode});
|
|
1721
|
+
if(!r.ok){toast('Switch failed',true);return;}
|
|
1722
|
+
codexMode=mode;
|
|
1723
|
+
updateModeUI();
|
|
1724
|
+
toast(mode==='rcodex'?'??rcodex Gateway ??Codex restarted':'??OpenAI direct ??Codex restarted');
|
|
1725
|
+
}catch{toast('Switch failed',true);}
|
|
1726
|
+
}
|
|
1727
|
+
function toast(msg,err){
|
|
1728
|
+
const t=document.createElement('div');t.className='toast';
|
|
1729
|
+
t.style.borderColor=err?'rgba(239,68,68,.3)':'rgba(34,197,94,.2)';
|
|
1730
|
+
t.textContent=msg;document.body.appendChild(t);setTimeout(()=>t.remove(),3000);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
window.addEventListener('resize',drawLines);
|
|
1734
|
+
|
|
1735
|
+
// ?�?�?� Init ?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�
|
|
1736
|
+
async function init(){
|
|
1737
|
+
await fetchStatus();
|
|
1738
|
+
try{const d=await(await fetch('/api/status')).json();if(d.home)termCwd=d.home;}catch{}
|
|
1739
|
+
try{const d=await fetch('/api/codex-provider').then(r=>r.json());codexMode=d.mode||'rcodex';updateModeUI();}catch{}
|
|
1740
|
+
setTimeout(()=>{applyVp();if([...onCanvas].length>0||NP.out)fitAll();},120);
|
|
1741
|
+
setInterval(fetchStatus,10000);
|
|
1742
|
+
}
|
|
1743
|
+
init();
|
|
1744
|
+
</script>
|
|
1745
|
+
</body>
|
|
1746
|
+
</html>`;
|
|
1747
|
+
}
|
|
1748
|
+
//# sourceMappingURL=ui.js.map
|