cicy-desktop 2.1.252 → 2.1.254

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.252",
3
+ "version": "2.1.254",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -139,10 +139,10 @@
139
139
  "//optionalDependencies": "(2026-06 回调): mac/linux 改回 native cicy-code(:8008,colima 在 16G mac 上压垮内存)→ 重新内置 cicy-code-<plat> / cicy-mihomo-<plat>,localbin.fromBundle 零网络 seed(npm 仅作更新通道,带 npmmirror→npmjs 回退)。这些由 scripts/sync-runtime-deps.cjs 在 tag-push 时同步到最新版。npm 的 os/cpu 字段保证每个平台只装自己那份。Windows 仍走 docker(WSL :8009),它那份 cicy-code-windows 用不到但 bundle 着无害。",
140
140
  "optionalDependencies": {
141
141
  "electron": "41.0.3",
142
- "cicy-code-darwin-x64": "2.3.266",
143
- "cicy-code-darwin-arm64": "2.3.266",
144
- "cicy-code-linux-x64": "2.3.266",
145
- "cicy-code-linux-arm64": "2.3.266",
142
+ "cicy-code-darwin-x64": "2.3.311",
143
+ "cicy-code-darwin-arm64": "2.3.311",
144
+ "cicy-code-linux-x64": "2.3.311",
145
+ "cicy-code-linux-arm64": "2.3.311",
146
146
  "cicy-code-windows-x64": "2.3.193",
147
147
  "cicy-mihomo-darwin-x64": "1.10.4",
148
148
  "cicy-mihomo-darwin-arm64": "1.10.4",
@@ -98,7 +98,7 @@ class PanelCells {
98
98
  },
99
99
  });
100
100
  const wc = view.webContents;
101
- try { view.setBackgroundColor("#0d0d0f"); } catch (e) {}
101
+ try { view.setBackgroundColor("#ffffff"); } catch (e) {}
102
102
  try { wc.cicyAccountIdx = profileIdx; } catch (e) {}
103
103
  scrubUA(wc);
104
104
  try { require("../utils/context-menu-options").attachContextMenu(wc); } catch (e) {}
@@ -63,8 +63,8 @@
63
63
  .cbtn{flex:0 0 24px;width:24px;height:24px;border-radius:6px;display:flex;align-items:center;justify-content:center;color:var(--muted);cursor:default}
64
64
  .cbtn:hover{background:rgba(255,255,255,.10);color:#fff}
65
65
  .cbtn.close:hover{background:rgba(249,112,102,.18);color:#f97066}
66
- .cell__body{position:relative;flex:1;min-height:0;background:#0d0d0f}
67
- .cell__body webview{position:absolute;inset:0;width:100%;height:100%}
66
+ .cell__body{position:relative;flex:1;min-height:0;background:#fff}
67
+ .cell__body webview{position:absolute;inset:0;width:100%;height:100%;background:#fff}
68
68
  /* while dragging a divider, a transparent shield stops webviews from
69
69
  swallowing pointermove (webview = separate guest, events die inside it) */
70
70
  .cell__body .shield{position:absolute;inset:0;display:none;z-index:5}
@@ -132,12 +132,24 @@
132
132
  const emptyEl = document.getElementById('empty');
133
133
  const fabEl = document.getElementById('fab');
134
134
  let suspendVersion = 0;
135
+ const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve));
135
136
  async function suspendNativeViews() {
136
137
  if (!NATIVE) return;
137
138
  const version = ++suspendVersion;
138
139
  try {
139
140
  const shots = await window.panelAPI.snapshots();
140
141
  if (version !== suspendVersion) return;
142
+ // Decode every data URL before detaching the BrowserViews. Setting a
143
+ // background-image and detaching in the same frame exposes the black cell
144
+ // background while Chromium is still decoding the snapshot.
145
+ await Promise.all((shots || []).map((shot) => new Promise((resolve) => {
146
+ if (!shot.dataUrl) return resolve();
147
+ const image = new Image();
148
+ image.onload = image.onerror = resolve;
149
+ image.src = shot.dataUrl;
150
+ if (image.complete) resolve();
151
+ })));
152
+ if (version !== suspendVersion) return;
141
153
  for (const shot of shots || []) {
142
154
  const cell = cells.get(Number(shot.id)) || cells.get(shot.id);
143
155
  if (!cell || !shot.dataUrl) continue;
@@ -145,20 +157,32 @@
145
157
  cell.body.style.backgroundSize = 'cover';
146
158
  cell.body.style.backgroundPosition = 'center top';
147
159
  }
160
+ // Let the decoded backgrounds paint before removing the native views.
161
+ await nextFrame();
162
+ await nextFrame();
163
+ if (version !== suspendVersion) return;
148
164
  window.panelAPI.dragging(true);
149
165
  } catch (e) {
150
- try { window.panelAPI.dragging(true); } catch (_) {}
166
+ if (version === suspendVersion) {
167
+ try { window.panelAPI.dragging(true); } catch (_) {}
168
+ }
151
169
  }
152
170
  }
153
171
  function resumeNativeViews() {
154
172
  if (!NATIVE) return;
155
- suspendVersion++;
173
+ const version = ++suspendVersion;
156
174
  try { window.panelAPI.dragging(false); } catch (e) {}
157
- for (const cell of cells.values()) {
158
- cell.body.style.backgroundImage = '';
159
- cell.body.style.backgroundSize = '';
160
- cell.body.style.backgroundPosition = '';
161
- }
175
+ // Keep the snapshot behind the reattached BrowserView for a couple frames;
176
+ // clearing synchronously can reveal black before its compositor surface is
177
+ // visible again.
178
+ requestAnimationFrame(() => requestAnimationFrame(() => {
179
+ if (version !== suspendVersion) return;
180
+ for (const cell of cells.values()) {
181
+ cell.body.style.backgroundImage = '';
182
+ cell.body.style.backgroundSize = '';
183
+ cell.body.style.backgroundPosition = '';
184
+ }
185
+ }));
162
186
  }
163
187
 
164
188
  const newLeaf = (url, profile = 1) => ({ type: 'leaf', id: seq++, url: url || '', profile: Number(profile) > 0 ? Number(profile) : 1 });
@@ -338,6 +362,9 @@
338
362
  aistudio: '<img src="https://www.gstatic.com/images/branding/productlogos/ai_studio/v1/web-64dp/logo_ai_studio_color_1x_web_64dp.png" alt="">',
339
363
  grok: '<img src="https://upload.wikimedia.org/wikipedia/commons/f/f4/Grok_logo_without_text.svg" alt="">',
340
364
  chatgpt: '<img src="http://127.0.0.1:8008/openai.svg" alt="">',
365
+ myip: '<svg viewBox="0 0 24 24" fill="none" stroke="#60a5fa" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.4 2.5 3.6 5.5 3.6 9S14.4 18.5 12 21M12 3C9.6 5.5 8.4 8.5 8.4 12S9.6 18.5 12 21"/></svg>',
366
+ colab: '<img src="https://ssl.gstatic.com/colaboratory-static/common/52d6fb476890a764bd46064a9a454157/img/favicon.ico" alt="">',
367
+ drive: '<img src="https://ssl.gstatic.com/images/branding/product/2x/drive_2020q4_48dp.png" alt="">',
341
368
  agent: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="5" width="18" height="16" rx="5" fill="#2563eb"/><path d="M9 2h6M12 2v3M8 12h.01M16 12h.01M8 16c2.5 1.7 5.5 1.7 8 0" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg>',
342
369
  };
343
370
  const AGENT_LOGOS = {
@@ -357,7 +384,25 @@
357
384
  { id: 'aistudio', name: 'Google AI Studio', meta: 'Google AI', url: 'https://aistudio.google.com/' },
358
385
  { id: 'grok', name: 'Grok', meta: 'xAI', url: 'https://grok.com/' },
359
386
  { id: 'chatgpt', name: 'ChatGPT', meta: 'OpenAI', url: 'https://chatgpt.com/' },
387
+ { id: 'myip', name: 'My IP', meta: 'api.myip.com', url: 'https://api.myip.com/' },
388
+ { id: 'colab', name: 'Google Colab', meta: 'Google Research', url: 'https://colab.research.google.com/' },
389
+ { id: 'drive', name: 'Google Drive', meta: 'Google Workspace', url: 'https://drive.google.com/' },
360
390
  ];
391
+ const CUSTOM_SERVICES_KEY = 'cicy_panel_custom_services';
392
+ function readCustomServices() {
393
+ try {
394
+ const rows = JSON.parse(localStorage.getItem(CUSTOM_SERVICES_KEY) || '[]');
395
+ return Array.isArray(rows) ? rows.filter((x) => x && x.name && x.url).slice(0, 50) : [];
396
+ } catch (e) { return []; }
397
+ }
398
+ let customServices = readCustomServices();
399
+ function saveCustomService(url) {
400
+ let name = url;
401
+ try { name = new URL(url).hostname || url; } catch (e) {}
402
+ const old = customServices.find((x) => x.url === url);
403
+ if (!old) customServices.push({ name, url });
404
+ try { localStorage.setItem(CUSTOM_SERVICES_KEY, JSON.stringify(customServices)); } catch (e) {}
405
+ }
361
406
  let agentList = [];
362
407
  let agentsPromise = null;
363
408
  function loadAgents() {
@@ -405,7 +450,17 @@
405
450
  document.body.appendChild(picker);
406
451
  const search = picker.querySelector('.service-search');
407
452
  search.oninput = () => renderPicker(search.value);
408
- search.onkeydown = (e) => { if (e.key === 'Escape') closePicker(); };
453
+ search.onkeydown = (e) => {
454
+ if (e.key === 'Escape') closePicker();
455
+ if (e.key === 'Enter') {
456
+ const custom = customUrlFromQuery(search.value);
457
+ if (!custom || !pickerState) return;
458
+ e.preventDefault();
459
+ const id = pickerState.leafId;
460
+ closePicker();
461
+ setCellUrl(id, custom);
462
+ }
463
+ };
409
464
  document.addEventListener('pointerdown', (e) => {
410
465
  if (pickerState && !picker.contains(e.target) && !pickerState.trigger.contains(e.target)) closePicker();
411
466
  });
@@ -426,13 +481,19 @@
426
481
  list.innerHTML = '';
427
482
  const q = String(query || '').trim().toLowerCase();
428
483
  const current = selectedServiceValue(pickerState.url);
484
+ const custom = customUrlFromQuery(query);
429
485
  const services = SERVICES.filter((s) => !q || `${s.name} ${s.meta}`.toLowerCase().includes(q));
486
+ const customs = customServices.filter((s) => !q || `${s.name} ${s.url}`.toLowerCase().includes(q));
430
487
  const agents = agentList.filter((a) => !q || `${a.id} ${a.title} ${a.type}`.toLowerCase().includes(q));
431
488
  const addSection = (label) => { const d = document.createElement('div'); d.className = 'service-section'; d.textContent = label; list.appendChild(d); };
432
489
  if (services.length) {
433
490
  addSection('应用');
434
491
  for (const s of services) list.appendChild(pickerItem(ICONS[s.id], s.name, s.meta, s.url, current === s.url));
435
492
  }
493
+ if (customs.length) {
494
+ addSection('自定义');
495
+ for (const s of customs) list.appendChild(pickerItem(ICONS.apps, s.name, s.url, s.url, pickerState.url === s.url));
496
+ }
436
497
  if (agents.length) {
437
498
  addSection('CiCy Code Agents');
438
499
  for (const a of agents) {
@@ -440,14 +501,24 @@
440
501
  list.appendChild(pickerItem(agentIcon(a.type), a.title || a.id, `${a.id} · ${a.type || 'agent'}`, url, current === url));
441
502
  }
442
503
  }
443
- if (!services.length && !agents.length) list.innerHTML = '<div class="service-empty">没有匹配结果</div>';
504
+ if (custom) {
505
+ addSection('自定义 URL');
506
+ list.appendChild(pickerItem(ICONS.apps, query.trim(), custom, custom, pickerState.url === custom));
507
+ if (!customServices.some((x) => x.url === custom)) {
508
+ const add = pickerItem(ICONS.apps, '保存为自定义条目', custom, custom, false);
509
+ add.onclick = () => { saveCustomService(custom); renderPicker(query); };
510
+ list.appendChild(add);
511
+ }
512
+ }
513
+ if (!services.length && !customs.length && !agents.length && !custom) list.innerHTML = '<div class="service-empty">没有匹配结果</div>';
444
514
  }
445
- function openPicker(leafId, trigger, url) {
515
+ async function openPicker(leafId, trigger, url) {
446
516
  if (pickerState) closePicker();
447
517
  const pop = ensurePicker();
448
518
  pickerState = { leafId, trigger, url: url || '' };
519
+ await suspendNativeViews();
520
+ if (!pickerState || pickerState.leafId !== leafId) return;
449
521
  trigger.classList.add('open');
450
- suspendNativeViews();
451
522
  pop.classList.add('open');
452
523
  const r = trigger.getBoundingClientRect();
453
524
  const left = Math.max(8, Math.min(r.left, innerWidth - Math.min(340, innerWidth - 16) - 8));
@@ -491,6 +562,8 @@
491
562
  if (!hit || isLocalCicyCode(hit.node.url)) return;
492
563
  const profiles = await loadProfiles();
493
564
  if (!findParent(root, leafId)) return;
565
+ await suspendNativeViews();
566
+ if (!findParent(root, leafId)) { resumeNativeViews(); return; }
494
567
  const pop = document.createElement('div'); pop.className = 'profile-pop';
495
568
  const rows = profiles.length ? profiles : [{ accountIdx: 1, name: '' }];
496
569
  for (const p of rows) {
@@ -520,7 +593,6 @@
520
593
  pop.style.top = Math.min(r.bottom + 6, innerHeight - Math.min(300, pop.offsetHeight) - 8) + 'px';
521
594
  trigger.classList.add('open');
522
595
  profilePop = { el: pop, trigger };
523
- suspendNativeViews();
524
596
  }
525
597
  document.addEventListener('pointerdown', (e) => {
526
598
  if (profilePop && !profilePop.el.contains(e.target) && !profilePop.trigger.contains(e.target)) closeProfilePicker();
@@ -538,6 +610,15 @@
538
610
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(v) || /^about:/i.test(v)) return v;
539
611
  return (/\.\w/.test(v) && !/\s/.test(v)) ? 'https://' + v : 'https://www.google.com/search?q=' + encodeURIComponent(v);
540
612
  }
613
+ function customUrlFromQuery(v) {
614
+ const raw = String(v || '').trim();
615
+ if (!raw || /\s/.test(raw)) return '';
616
+ const looksLikeUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw)
617
+ || /^localhost(?::\d+)?(?:\/|$)/i.test(raw)
618
+ || /^(?:\d{1,3}\.){3}\d{1,3}(?::\d+)?(?:\/|$)/.test(raw)
619
+ || /^[\w-]+(?:\.[\w-]+)+(?:\/|$)/.test(raw);
620
+ return looksLikeUrl ? normalizeUrl(raw) : '';
621
+ }
541
622
  function mountCell(leaf) {
542
623
  const el = document.createElement('div');
543
624
  el.className = 'cell';