cicy-desktop 2.1.69 → 2.1.71

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.
Files changed (61) hide show
  1. package/package.json +7 -7
  2. package/src/backends/homepage-react/assets/index-BpljolQs.js +365 -0
  3. package/src/backends/homepage-react/assets/{index-BniEbx_j.css → index-C9AZlTew.css} +1 -1
  4. package/src/backends/homepage-react/index.html +2 -2
  5. package/src/backends/local-teams.js +42 -4
  6. package/src/backends/sidecar-ipc.js +23 -1
  7. package/src/i18n/locales/en.json +9 -7
  8. package/src/i18n/locales/zh-CN.json +9 -7
  9. package/src/sidecar/cicy-code.js +54 -111
  10. package/src/sidecar/localbin.js +133 -0
  11. package/src/sidecar/native.js +4 -2
  12. package/src/sidecar/runtime.js +7 -3
  13. package/workers/render/src/App.css +156 -10
  14. package/workers/render/src/App.jsx +254 -39
  15. package/.env.dev +0 -7
  16. package/src/backends/homepage-react/assets/index-B8gGhz8B.js +0 -365
  17. package/workers/render.bak.20260528-2338/DESIGN_v2.md +0 -254
  18. package/workers/render.bak.20260528-2338/index.html +0 -12
  19. package/workers/render.bak.20260528-2338/package-lock.json +0 -827
  20. package/workers/render.bak.20260528-2338/package.json +0 -19
  21. package/workers/render.bak.20260528-2338/public/_headers +0 -5
  22. package/workers/render.bak.20260528-2338/public/manifest.json +0 -6
  23. package/workers/render.bak.20260528-2338/src/App.css +0 -224
  24. package/workers/render.bak.20260528-2338/src/App.jsx +0 -1028
  25. package/workers/render.bak.20260528-2338/src/api.js +0 -285
  26. package/workers/render.bak.20260528-2338/src/cicycode-ops.js +0 -222
  27. package/workers/render.bak.20260528-2338/src/components/BackendCard.css +0 -299
  28. package/workers/render.bak.20260528-2338/src/components/BackendCard.jsx +0 -133
  29. package/workers/render.bak.20260528-2338/src/components/BackendModal.css +0 -161
  30. package/workers/render.bak.20260528-2338/src/components/BackendModal.jsx +0 -199
  31. package/workers/render.bak.20260528-2338/src/components/Button.css +0 -72
  32. package/workers/render.bak.20260528-2338/src/components/Button.jsx +0 -37
  33. package/workers/render.bak.20260528-2338/src/components/Card.css +0 -42
  34. package/workers/render.bak.20260528-2338/src/components/Card.jsx +0 -21
  35. package/workers/render.bak.20260528-2338/src/components/Icon.jsx +0 -30
  36. package/workers/render.bak.20260528-2338/src/components/Menu.css +0 -55
  37. package/workers/render.bak.20260528-2338/src/components/Menu.jsx +0 -91
  38. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.css +0 -79
  39. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.jsx +0 -84
  40. package/workers/render.bak.20260528-2338/src/components/StatusChip.css +0 -19
  41. package/workers/render.bak.20260528-2338/src/components/StatusChip.jsx +0 -31
  42. package/workers/render.bak.20260528-2338/src/components/Toast.css +0 -31
  43. package/workers/render.bak.20260528-2338/src/components/Toast.jsx +0 -23
  44. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.css +0 -464
  45. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.jsx +0 -716
  46. package/workers/render.bak.20260528-2338/src/dockerInstaller.js +0 -0
  47. package/workers/render.bak.20260528-2338/src/i18n/en.json +0 -116
  48. package/workers/render.bak.20260528-2338/src/i18n/fr.json +0 -116
  49. package/workers/render.bak.20260528-2338/src/i18n/index.js +0 -69
  50. package/workers/render.bak.20260528-2338/src/i18n/ja.json +0 -116
  51. package/workers/render.bak.20260528-2338/src/i18n/zh-CN.json +0 -121
  52. package/workers/render.bak.20260528-2338/src/main.js +0 -475
  53. package/workers/render.bak.20260528-2338/src/main.jsx +0 -18
  54. package/workers/render.bak.20260528-2338/src/style.css +0 -275
  55. package/workers/render.bak.20260528-2338/src/styles/base.css +0 -98
  56. package/workers/render.bak.20260528-2338/src/styles/tokens.css +0 -90
  57. package/workers/render.bak.20260528-2338/src/tos.js +0 -72
  58. package/workers/render.bak.20260528-2338/src/worker.js +0 -40
  59. package/workers/render.bak.20260528-2338/src/wslInstaller.js +0 -1563
  60. package/workers/render.bak.20260528-2338/vite.config.js +0 -36
  61. package/workers/render.bak.20260528-2338/wrangler.toml +0 -17
@@ -1,475 +0,0 @@
1
- import "./style.css";
2
- import { api, apiMode, devLocalBase } from "./api.js";
3
- import { TOS_TEXT, TOS_VERSION } from "./tos.js";
4
-
5
- const $local = document.getElementById("local-cards");
6
- const $cloud = document.getElementById("cloud-cards");
7
- const $recent = document.getElementById("recent-list");
8
- const $addForm = document.getElementById("add-form");
9
- const $fStatus = document.getElementById("f-status");
10
- const $toast = document.getElementById("toast");
11
- const $envPill = document.getElementById("env-pill");
12
- const $topVer = document.getElementById("topbar-version");
13
-
14
- $envPill.textContent = apiMode;
15
- if (apiMode === "browser") $envPill.title = `Browser dev mode\nLocal base: ${devLocalBase}`;
16
-
17
- const esc = s => String(s == null ? "" : s).replace(/[&<>"']/g, ch => ({
18
- "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"
19
- }[ch]));
20
- const bytes = n => { if (!n) return "—"; const u=["B","KB","MB","GB"]; let i=0; while(n>1024&&i<u.length-1){n/=1024;i++;} return n.toFixed(i?1:0)+" "+u[i]; };
21
- const dur = sec => { if (!sec || sec<0) return "—"; const d=Math.floor(sec/86400), h=Math.floor(sec%86400/3600), m=Math.floor(sec%3600/60); if(d) return `${d}d ${h}h`; if(h) return `${h}h ${m}m`; if(m) return `${m}m`; return `${sec}s`; };
22
- const ago = ms => { const s=Math.floor((Date.now()-ms)/1000); if(s<60) return s+"s ago"; if(s<3600) return Math.floor(s/60)+"m ago"; if(s<86400) return Math.floor(s/3600)+"h ago"; return Math.floor(s/86400)+"d ago"; };
23
-
24
- function dotClass(health) {
25
- if (!health) return "s-unknown";
26
- if (health.ok && health.status === "ok") return "s-up";
27
- if (health.ok) return "s-warn";
28
- return "s-down";
29
- }
30
-
31
- let healthByBackend = {};
32
- let updateInfo = null;
33
- let lastBackends = [];
34
- let prereq = null; // { platform, kind, ok, required, version, installUrl }
35
- let installState = null; // { installed, version }
36
-
37
- function showToast(msg, isErr = false) {
38
- $toast.textContent = msg;
39
- $toast.className = "toast show" + (isErr ? " err" : "");
40
- setTimeout(() => { $toast.className = "toast"; }, 2200);
41
- }
42
-
43
- function localState() {
44
- if (!prereq || !prereq.ok) return "prereq-missing";
45
- if (!installState || !installState.installed) return "not-installed";
46
- const h = healthByBackend["local"];
47
- return (h && h.ok) ? "running" : "stopped";
48
- }
49
-
50
- // Local card has four states. Each shares the header + footer skeleton but
51
- // swaps the metric strip and action set. Window-vs-non-Windows shows up as a
52
- // difference in prereq.kind ("docker" vs "node") — the action labels follow.
53
-
54
- function renderLocal(backend) {
55
- const state = localState();
56
- const isWin = prereq && prereq.platform === "windows";
57
- const kindLabel = isWin ? "Docker" : "Node.js";
58
- return ({
59
- "prereq-missing": renderLocalPrereqMissing(backend, isWin),
60
- "not-installed": renderLocalNotInstalled(backend, isWin, kindLabel),
61
- "stopped": renderLocalStopped(backend, isWin),
62
- "running": renderLocalRunning(backend, isWin),
63
- })[state];
64
- }
65
-
66
- function localHeader(backend, dotClsName, title, sub, right = "") {
67
- return `
68
- <div class="card-row">
69
- <div class="status-dot ${dotClsName}"></div>
70
- <div class="head-text">
71
- <div class="name">${esc(backend.name)} <span class="badge">LOCAL</span></div>
72
- <div class="meta">${title} · ${sub}</div>
73
- </div>
74
- <div class="head-right">${right}</div>
75
- </div>
76
- `;
77
- }
78
-
79
- function renderLocalPrereqMissing(backend, isWin) {
80
- const req = prereq && prereq.required || (isWin ? "Docker Desktop" : "Node.js 22+");
81
- const link = prereq && prereq.installUrl || "#";
82
- const cur = prereq && prereq.version ? `installed: ${esc(prereq.version)}` : "not installed";
83
- return `
84
- <div class="card" data-backend="${esc(backend.id)}">
85
- ${localHeader(backend, "s-down", `Local engine — ${esc(req)} required`, cur, "")}
86
- <div class="metric-row">
87
- <div class="m"><span class="k">Reason</span><span class="v">${isWin ? "cicy-code runs in a Docker container on Windows" : "cicy-code's npm package needs Node 22+"}</span></div>
88
- </div>
89
- <div class="action-row">
90
- <button class="primary" data-act="install-prereq" data-url="${esc(link)}">⤓ Install ${esc(req)}</button>
91
- <button data-act="recheck">↻ I installed it — re-check</button>
92
- <span class="grow"></span>
93
- <span class="upd-tip" style="color:var(--fg-mute)">Cloud engines still work without local.</span>
94
- </div>
95
- </div>
96
- `;
97
- }
98
-
99
- function renderLocalNotInstalled(backend, isWin, kindLabel) {
100
- const installCmd = isWin
101
- ? "docker pull ghcr.io/cicy-ai/cicy-code:latest"
102
- : "npm i -g cicy-code@latest";
103
- return `
104
- <div class="card" data-backend="${esc(backend.id)}">
105
- ${localHeader(backend, "s-warn", `Local engine — not installed`, `prereq ${kindLabel} ready`, "")}
106
- <div class="metric-row">
107
- <div class="m"><span class="k">Install via</span><span class="v" style="font-family:ui-monospace,monospace;font-size:11px">${esc(installCmd)}</span></div>
108
- </div>
109
- <div class="action-row">
110
- <button class="primary" data-act="install-cicy">⤓ Install cicy-code</button>
111
- <button data-act="copy-install-cmd" data-cmd="${esc(installCmd)}">⎘ Copy command</button>
112
- <span class="grow"></span>
113
- </div>
114
- </div>
115
- `;
116
- }
117
-
118
- function renderLocalStopped(backend, isWin) {
119
- const installed = installState && installState.version || "?";
120
- return `
121
- <div class="card" data-backend="${esc(backend.id)}">
122
- ${localHeader(backend, "s-warn", `Local engine — stopped`, `installed v${esc(installed)}`, `v${esc(installed)}`)}
123
- <div class="metric-row">
124
- <div class="m"><span class="k">Status</span><span class="v">Stopped</span></div>
125
- <div class="m"><span class="k">Mode</span><span class="v">${isWin ? "docker" : "npx"}</span></div>
126
- </div>
127
- <div class="action-row">
128
- <button class="primary" data-act="start">▷ Start</button>
129
- <button data-act="check-update">⇧ Check for updates</button>
130
- <button data-act="uninstall" class="danger">✕ Uninstall</button>
131
- <span class="grow"></span>
132
- </div>
133
- </div>
134
- `;
135
- }
136
-
137
- function renderLocalRunning(backend, isWin) {
138
- const h = healthByBackend[backend.id] || {};
139
- const memTxt = h.mem_bytes ? bytes(h.mem_bytes) : "—";
140
- const upTxt = h.uptime_sec != null ? dur(h.uptime_sec) : "—";
141
- const agentsTxt = h.agents_count != null && h.agents_count >= 0 ? h.agents_count : "—";
142
- const verTxt = h.version ? `v${esc(h.version)}` : "—";
143
- const pidTxt = h.pid ? `pid ${h.pid}` : "—";
144
- const updTip = updateInfo && updateInfo.hasUpdate
145
- ? `<span class="upd-tip">Update available: v${esc(updateInfo.latestVersion)}</span>`
146
- : "";
147
- if (h.version) $topVer.textContent = `v${esc(h.version)}`;
148
- return `
149
- <div class="card" data-backend="${esc(backend.id)}">
150
- ${localHeader(backend, "s-up", `${isWin ? "docker container" : "npx cicy-code"} on :8008`, esc(pidTxt), esc(verTxt))}
151
- <div class="metric-row">
152
- <div class="m"><span class="k">Status</span><span class="v">Running</span></div>
153
- <div class="m"><span class="k">Uptime</span><span class="v">${esc(upTxt)}</span></div>
154
- <div class="m"><span class="k">Mem</span><span class="v">${esc(memTxt)}</span></div>
155
- <div class="m"><span class="k">Agents</span><span class="v">${esc(String(agentsTxt))}</span></div>
156
- </div>
157
- <div class="action-row">
158
- <button class="primary" data-act="open">▷ Open</button>
159
- <button data-act="restart">↻ Restart</button>
160
- <button data-act="stop">◼ Stop</button>
161
- <button data-act="check-update">⇧ Check for updates</button>
162
- <button data-act="copy">⎘ Copy URL</button>
163
- <span class="grow"></span>
164
- ${updTip}
165
- </div>
166
- </div>
167
- `;
168
- }
169
-
170
- function renderCloud(backend) {
171
- const h = healthByBackend[backend.id] || null;
172
- const dot = dotClass(h);
173
- const latencyTxt = h && h.latencyMs != null ? `${h.latencyMs} ms` : "—";
174
- const verTxt = h && h.version ? `v${esc(h.version)}` : (h && h.ok ? "ok" : "—");
175
- const agentsTxt = h && h.agents_count != null && h.agents_count >= 0 ? h.agents_count : "—";
176
- return `
177
- <div class="card" data-backend="${esc(backend.id)}">
178
- <div class="card-row">
179
- <div class="status-dot ${dot}"></div>
180
- <div class="head-text">
181
- <div class="name">${esc(backend.name)}</div>
182
- <div class="meta">${esc(backend.url || "")}</div>
183
- </div>
184
- <div class="head-right">${esc(verTxt)} · ${esc(latencyTxt)}</div>
185
- </div>
186
- <div class="metric-row">
187
- <div class="m"><span class="k">Status</span><span class="v">${h && h.ok ? "Healthy" : h ? "Unreachable" : "Unknown"}</span></div>
188
- <div class="m"><span class="k">Agents</span><span class="v">${esc(String(agentsTxt))}</span></div>
189
- ${h && h.uptime_sec != null ? `<div class="m"><span class="k">Uptime</span><span class="v">${esc(dur(h.uptime_sec))}</span></div>` : ""}
190
- </div>
191
- <div class="action-row">
192
- <button class="primary" data-act="open">▷ Open</button>
193
- <button data-act="copy">⎘ Copy URL</button>
194
- <span class="grow"></span>
195
- <button class="danger" data-act="remove">✕ Remove</button>
196
- </div>
197
- </div>
198
- `;
199
- }
200
-
201
- function wireCards(list) {
202
- document.querySelectorAll(".card").forEach(card => {
203
- const id = card.getAttribute("data-backend");
204
- const backend = list.find(b => b.id === id);
205
- if (!backend) return;
206
- card.querySelectorAll("[data-act]").forEach(btn => {
207
- const act = btn.getAttribute("data-act");
208
- btn.onclick = (event) => handleAction(act, backend, event);
209
- });
210
- });
211
- }
212
-
213
- async function refreshBackends() {
214
- lastBackends = await api.backends.list();
215
- // Pull prereq + install state in parallel so the Local card draws the
216
- // correct state machine immediately.
217
- if (api.system) {
218
- const [p, i] = await Promise.all([
219
- api.system.checkPrereq().catch(() => null),
220
- api.system.checkCicyCodeInstalled().catch(() => null),
221
- ]);
222
- prereq = p;
223
- installState = i;
224
- }
225
- rerender();
226
- await refreshHealth();
227
- }
228
- async function refreshHealth() {
229
- try {
230
- const results = await api.backends.healthAll();
231
- results.forEach(r => { healthByBackend[r.id] = r.health; });
232
- } catch {}
233
- rerender();
234
- }
235
- function rerender() {
236
- const local = lastBackends.filter(b => b.kind === "local");
237
- const cloud = lastBackends.filter(b => b.kind !== "local");
238
- $local.innerHTML = local.map(renderLocal).join("") || `<div class="empty">No local engine</div>`;
239
- $cloud.innerHTML = cloud.length ? cloud.map(renderCloud).join("") : `<div class="empty">No cloud backends yet — add one above.</div>`;
240
- wireCards(lastBackends);
241
- }
242
-
243
- async function handleAction(act, backend, event) {
244
- switch (act) {
245
- case "open": {
246
- const r = await api.backends.open(backend.id);
247
- if (r && r.ok) { showToast(`Opened ${backend.name}`); setTimeout(refreshWindows, 600); }
248
- else showToast(`Open failed: ${r && r.error || "unknown"}`, true);
249
- break;
250
- }
251
- case "copy": {
252
- const url = backend.resolvedUrl || backend.url || "";
253
- await api.clipboard.write(url);
254
- showToast("URL copied");
255
- break;
256
- }
257
- case "remove": {
258
- if (!confirm(`Remove backend "${backend.name}"?`)) return;
259
- await api.backends.remove(backend.id);
260
- await refreshBackends();
261
- showToast(`Removed ${backend.name}`);
262
- break;
263
- }
264
- case "restart": {
265
- showToast("Restarting cicy-code sidecar…");
266
- const r = await api.backends.restartSidecar();
267
- if (r && r.ok) { showToast(`Sidecar restarted (pid ${r.pid})`); setTimeout(refreshHealth, 1500); }
268
- else showToast(`Restart failed: ${r && r.error || "unknown"}`, true);
269
- break;
270
- }
271
- case "check-update": {
272
- showToast("Checking GitHub for updates…");
273
- try {
274
- updateInfo = await api.updates.check();
275
- if (updateInfo.hasUpdate) {
276
- const accept = confirm(`Update available: v${updateInfo.latestVersion} (you have v${updateInfo.currentVersion || "?"})\n\nOpen the release page in your browser?`);
277
- if (accept) await api.updates.openReleasePage(updateInfo.releaseUrl);
278
- } else {
279
- showToast(`You're on the latest version (v${updateInfo.currentVersion || "?"})`);
280
- }
281
- rerender();
282
- } catch (e) {
283
- showToast(`Update check failed: ${e.message}`, true);
284
- }
285
- break;
286
- }
287
- // --- new state-machine actions for the Local card. Real implementations
288
- // land once cicy-desktop exposes the corresponding IPC; for browser-dev
289
- // they just toast / open URLs / copy strings.
290
- case "install-prereq": {
291
- const url = (event?.target || document.activeElement)?.dataset?.url
292
- || (prereq && prereq.installUrl);
293
- if (url) {
294
- await api.updates.openReleasePage(url);
295
- showToast("Opened install page in browser");
296
- }
297
- break;
298
- }
299
- case "recheck": {
300
- showToast("Re-checking prerequisites…");
301
- await refreshBackends();
302
- showToast(prereq && prereq.ok ? "Prereq OK" : "Still missing", !(prereq && prereq.ok));
303
- break;
304
- }
305
- case "install-cicy":
306
- case "start":
307
- case "stop":
308
- case "uninstall": {
309
- const verb = { "install-cicy": "install", "start": "start", "stop": "stop", "uninstall": "uninstall" }[act];
310
- if (verb === "uninstall" && !confirm("Uninstall cicy-code?")) return;
311
- showToast(`${verb}ing cicy-code…`);
312
- const fn = api.cicycode[verb === "install-cicy" ? "install" : verb] || api.cicycode[verb];
313
- const r = await fn();
314
- if (r && r.ok) {
315
- showToast(`cicy-code ${verb} ok`);
316
- // Re-probe so the card flips state immediately.
317
- setTimeout(refreshBackends, 800);
318
- } else {
319
- showToast(`${verb} failed: ${r && r.error || "unknown"}`, true);
320
- }
321
- break;
322
- }
323
- case "copy-install-cmd": {
324
- const cmd = (event?.target || document.activeElement)?.dataset?.cmd || "";
325
- if (cmd) { await api.clipboard.write(cmd); showToast("Install command copied"); }
326
- break;
327
- }
328
- }
329
- }
330
-
331
- async function refreshWindows() {
332
- const wins = await api.windows.list();
333
- if (!wins.length) {
334
- $recent.innerHTML = `<div class="empty">No backend windows open yet.</div>`;
335
- return;
336
- }
337
- const backends = await api.backends.list();
338
- const byId = Object.fromEntries(backends.map(b => [b.id, b]));
339
- $recent.innerHTML = wins.map(w => {
340
- const backend = byId[w.backendId] || { name: "?" };
341
- return `
342
- <div class="recent-row" data-win="${w.windowId}">
343
- <div class="label">
344
- <div class="name">${esc(backend.name)}${w.title ? " — " + esc(w.title) : ""}</div>
345
- <div class="sub">window ${w.windowId} · ${esc(ago(w.openedAt))}</div>
346
- </div>
347
- <span class="ago"></span>
348
- <button data-act="focus">Focus</button>
349
- </div>
350
- `;
351
- }).join("");
352
- $recent.querySelectorAll(".recent-row").forEach(row => {
353
- const id = Number(row.getAttribute("data-win"));
354
- row.querySelector("[data-act=focus]").onclick = async () => { await api.windows.focus(id); };
355
- });
356
- }
357
-
358
- document.getElementById("btn-toggle-add").onclick = () => {
359
- $addForm.classList.toggle("open");
360
- if ($addForm.classList.contains("open")) document.getElementById("f-name").focus();
361
- };
362
- document.getElementById("f-cancel").onclick = () => {
363
- $addForm.classList.remove("open");
364
- $fStatus.className = "status"; $fStatus.textContent = "";
365
- };
366
- document.getElementById("f-submit").onclick = async () => {
367
- const name = document.getElementById("f-name").value.trim();
368
- const url = document.getElementById("f-url").value.trim();
369
- const token = document.getElementById("f-token").value.trim() || undefined;
370
- if (!url) { $fStatus.className = "status err"; $fStatus.textContent = "URL required"; return; }
371
- $fStatus.className = "status"; $fStatus.textContent = "Probing…";
372
- try {
373
- const probe = await api.backends.probe({ url, token });
374
- if (probe && probe.ok) { $fStatus.className = "status ok"; $fStatus.textContent = `Probe OK (${probe.statusCode})`; }
375
- else { $fStatus.className = "status err"; $fStatus.textContent = `Probe ${probe && probe.error || probe && probe.statusCode || "failed"} (saving anyway)`; }
376
- await api.backends.add({ name, url, token });
377
- document.getElementById("f-name").value = "";
378
- document.getElementById("f-url").value = "";
379
- document.getElementById("f-token").value = "";
380
- $addForm.classList.remove("open");
381
- await refreshBackends();
382
- showToast(`Added ${name || url}`);
383
- } catch (e) { $fStatus.className = "status err"; $fStatus.textContent = `Error: ${e.message}`; }
384
- };
385
-
386
- document.getElementById("btn-refresh").onclick = () => {
387
- refreshBackends();
388
- refreshWindows();
389
- };
390
-
391
- // --- ToS gate ---
392
- // Block all backend rendering until the user has accepted the current
393
- // version. Acceptance is stored in localStorage (browser-dev) or via the
394
- // future tos:accept IPC (Electron). Versioned key so a future legal text
395
- // bump re-prompts every user.
396
- const TOS_KEY = "cicy-tos-accepted";
397
- function tosAccepted() {
398
- try {
399
- const v = localStorage.getItem(TOS_KEY);
400
- return v === TOS_VERSION;
401
- } catch { return false; }
402
- }
403
- function markTosAccepted() {
404
- try { localStorage.setItem(TOS_KEY, TOS_VERSION); } catch {}
405
- }
406
- function renderTos() {
407
- const overlay = document.createElement("div");
408
- overlay.className = "tos-overlay";
409
- overlay.innerHTML = `
410
- <div class="tos-modal">
411
- <div class="tos-header">
412
- <span class="icon">📜</span>
413
- <h2>使用条款 / Terms of Service</h2>
414
- <span class="ver">v${TOS_VERSION}</span>
415
- </div>
416
- <div class="tos-body" id="tos-body"></div>
417
- <div class="tos-footer">
418
- <label>
419
- <input type="checkbox" id="tos-read" />
420
- 我已阅读并理解以上条款 / I have read and understand the terms
421
- </label>
422
- <button class="decline" id="tos-decline" type="button">Decline</button>
423
- <button class="accept" id="tos-accept" type="button" disabled>Accept &amp; Continue</button>
424
- </div>
425
- </div>
426
- `;
427
- document.body.appendChild(overlay);
428
- // Render TOS text. Keep it minimal — no full markdown engine, just newlines.
429
- document.getElementById("tos-body").textContent = TOS_TEXT;
430
- const $read = document.getElementById("tos-read");
431
- const $accept = document.getElementById("tos-accept");
432
- $read.addEventListener("change", () => { $accept.disabled = !$read.checked; });
433
- $accept.addEventListener("click", () => {
434
- markTosAccepted();
435
- overlay.remove();
436
- bootHomepage();
437
- });
438
- document.getElementById("tos-decline").addEventListener("click", () => {
439
- if (apiMode === "electron" && window.close) window.close();
440
- else window.location.href = "about:blank";
441
- });
442
- }
443
-
444
- function bootHomepage() {
445
- refreshBackends();
446
- refreshWindows();
447
- refreshLocalIPs();
448
- setInterval(refreshHealth, 30000);
449
- setInterval(refreshWindows, 5000);
450
- setInterval(refreshLocalIPs, 60000);
451
- }
452
-
453
- // Stash this machine's non-loopback IPv4 addresses in localStorage so any
454
- // part of the UI (frp-server install drawer, multi-backend selector, etc.)
455
- // can read them without re-probing the host shell. Stored as JSON so we
456
- // don't need to re-parse comma vs newline. Schema is intentionally simple:
457
- // { ips: ["192.168.1.5", "10.0.0.7"], primary: "192.168.1.5", at: 17... }
458
- async function refreshLocalIPs() {
459
- try {
460
- const ips = await api.system.localIPs();
461
- const payload = { ips, primary: ips[0] || null, at: Date.now() };
462
- localStorage.setItem("cicy.localIPs", JSON.stringify(payload));
463
- if (window.dispatchEvent && window.CustomEvent) {
464
- window.dispatchEvent(new CustomEvent("cicy:local-ips", { detail: payload }));
465
- }
466
- } catch (e) {
467
- console.warn("[localIPs] probe failed:", e?.message || e);
468
- }
469
- }
470
-
471
- if (tosAccepted()) {
472
- bootHomepage();
473
- } else {
474
- renderTos();
475
- }
@@ -1,18 +0,0 @@
1
- import React from "react";
2
- import { createRoot } from "react-dom/client";
3
- import App from "./App.jsx";
4
- import "./styles/tokens.css";
5
- import "./styles/base.css";
6
-
7
- // Tag the document with the host platform so platform-specific CSS rules
8
- // can fire (e.g. only macOS gets the traffic-light spacer in the topbar).
9
- const platform = (window.cicy && window.cicy.platform)
10
- || (() => {
11
- const ua = navigator.userAgent || "";
12
- if (/Windows/i.test(ua)) return "win32";
13
- if (/Mac/i.test(ua)) return "darwin";
14
- return "linux";
15
- })();
16
- document.documentElement.dataset.platform = platform;
17
-
18
- createRoot(document.getElementById("root")).render(<App />);