openclacky 1.5.7 → 1.5.8

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +2 -2
  4. data/README_CN.md +1 -1
  5. data/README_JA.md +1 -1
  6. data/lib/clacky/agent.rb +2 -3
  7. data/lib/clacky/agent_config.rb +40 -18
  8. data/lib/clacky/cli.rb +0 -1
  9. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +23 -10
  10. data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +3 -2
  11. data/lib/clacky/default_extensions/general/ext.yml +1 -1
  12. data/lib/clacky/extension/api_loader.rb +4 -11
  13. data/lib/clacky/extension/packager.rb +1 -1
  14. data/lib/clacky/extension/verifier.rb +1 -1
  15. data/lib/clacky/media/generator.rb +3 -3
  16. data/lib/clacky/message_history.rb +1 -1
  17. data/lib/clacky/providers.rb +2 -1
  18. data/lib/clacky/server/http_server.rb +60 -76
  19. data/lib/clacky/session_manager.rb +7 -0
  20. data/lib/clacky/ui2/components/inline_input.rb +1 -0
  21. data/lib/clacky/ui2/components/input_area.rb +1 -0
  22. data/lib/clacky/ui2/components/modal_component.rb +0 -1
  23. data/lib/clacky/ui2/components/todo_area.rb +1 -0
  24. data/lib/clacky/ui2/line_editor.rb +1 -0
  25. data/lib/clacky/ui2/markdown_renderer.rb +5 -1
  26. data/lib/clacky/ui2/ui_controller.rb +1 -0
  27. data/lib/clacky/version.rb +1 -1
  28. data/lib/clacky/web/app.css +134 -210
  29. data/lib/clacky/web/components/chat-navigator.js +1 -1
  30. data/lib/clacky/web/features/billing/store.js +3 -3
  31. data/lib/clacky/web/features/billing/view.js +5 -61
  32. data/lib/clacky/web/features/extensions/store.js +1 -1
  33. data/lib/clacky/web/features/extensions/view.js +28 -5
  34. data/lib/clacky/web/features/new-session/view.js +24 -0
  35. data/lib/clacky/web/i18n.js +20 -18
  36. data/lib/clacky/web/index.html +12 -13
  37. data/lib/clacky/web/projects.js +73 -282
  38. data/lib/clacky/web/settings.js +6 -1
  39. metadata +1 -1
@@ -1,8 +1,8 @@
1
1
  // ── Billing · view — dashboard rendering, charts, tooltips, DOM wiring ─────
2
2
  //
3
3
  // Owns every render function (stats, heatmap, trend, breakdowns, sessions),
4
- // tooltip binding, and the clear-data popup. Reads through BillingStore.state
5
- // and currency helpers; period / model / clear / share go through store actions.
4
+ // tooltip binding. Reads through BillingStore.state
5
+ // and currency helpers; period / model / share go through store actions.
6
6
  //
7
7
  // Augments the `Billing` facade with onPanelShow.
8
8
  //
@@ -10,7 +10,6 @@
10
10
  // ───────────────────────────────────────────────────────────────────────────
11
11
 
12
12
  const BillingView = (() => {
13
- let _clearPopupVisible = false;
14
13
 
15
14
  // ── Loading / error ───────────────────────────────────────────────────────
16
15
 
@@ -118,7 +117,7 @@ const BillingView = (() => {
118
117
 
119
118
  const periods = ["day", "week", "month", "year", "all"];
120
119
  const periodBtns = periods.map(p =>
121
- `<button class="billing-period-btn ${p === BillingStore.state.currentPeriod ? 'active' : ''}" data-period="${p}">${_periodLabel(p)}</button>`
120
+ `<button class="segmented-control__btn ${p === BillingStore.state.currentPeriod ? 'active' : ''}" data-period="${p}">${_periodLabel(p)}</button>`
122
121
  ).join("");
123
122
 
124
123
  const allModels = BillingStore.state.allModels;
@@ -135,21 +134,12 @@ const BillingView = (() => {
135
134
  <span class="billing-subtitle">${_getSummaryHint()}</span>
136
135
  </div>
137
136
  <div class="billing-controls">
138
- <div class="billing-period-group">${periodBtns}</div>
137
+ <div class="segmented-control">${periodBtns}</div>
139
138
  <select id="billing-model-filter" class="billing-model-filter">${modelOptions}</select>
140
139
  <button id="billing-share-btn" class="billing-share-btn" title="${I18n.t('billing.share.tooltip') || 'Share scorecard'}">
141
140
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><polyline points="16 6 12 2 8 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="2" x2="12" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
142
141
  ${I18n.t('billing.share.btn') || 'Share scorecard'}
143
142
  </button>
144
- <div class="billing-clear-container">
145
- <button id="billing-clear-btn" class="billing-clear-btn" title="${I18n.t('billing.clearData') || 'Clear Data'}">
146
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><polyline points="3 6 5 6 21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 11v6M14 11v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
147
- </button>
148
- <div id="billing-clear-popup" class="billing-clear-popup" style="display: none;">
149
- <button id="billing-clear-today" class="billing-clear-option">${I18n.t('billing.clearToday') || 'Clear Today'}</button>
150
- <button id="billing-clear-all" class="billing-clear-option billing-clear-danger">${I18n.t('billing.clearAll') || 'Clear All'}</button>
151
- </div>
152
- </div>
153
143
  </div>
154
144
  </div>
155
145
 
@@ -217,7 +207,7 @@ const BillingView = (() => {
217
207
  </div>
218
208
  `;
219
209
 
220
- document.querySelectorAll(".billing-period-btn").forEach(btn => {
210
+ document.querySelectorAll(".segmented-control__btn").forEach(btn => {
221
211
  btn.addEventListener("click", (e) => Billing.setPeriod(e.target.dataset.period));
222
212
  });
223
213
 
@@ -225,7 +215,6 @@ const BillingView = (() => {
225
215
  Billing.setModel(e.target.value);
226
216
  });
227
217
 
228
- _bindClearHandlers();
229
218
  document.getElementById("billing-share-btn")?.addEventListener("click", _openScorecardShare);
230
219
 
231
220
  _bindChartTooltip();
@@ -416,50 +405,6 @@ const BillingView = (() => {
416
405
  });
417
406
  }
418
407
 
419
- // ── Clear popup ────────────────────────────────────────────────────────────
420
-
421
- function _bindClearHandlers() {
422
- const clearBtn = document.getElementById("billing-clear-btn");
423
- const clearPopup = document.getElementById("billing-clear-popup");
424
- const clearToday = document.getElementById("billing-clear-today");
425
- const clearAll = document.getElementById("billing-clear-all");
426
-
427
- if (!clearBtn || !clearPopup) return;
428
-
429
- clearBtn.addEventListener("click", (e) => {
430
- e.stopPropagation();
431
- _clearPopupVisible = !_clearPopupVisible;
432
- clearPopup.style.display = _clearPopupVisible ? "flex" : "none";
433
- });
434
-
435
- clearToday?.addEventListener("click", (e) => {
436
- e.stopPropagation();
437
- _hideClearPopup();
438
- Billing.clearData("today");
439
- });
440
-
441
- clearAll?.addEventListener("click", (e) => {
442
- e.stopPropagation();
443
- _hideClearPopup();
444
- Billing.clearData("all");
445
- });
446
-
447
- document.addEventListener("click", _closeClearPopup);
448
- }
449
-
450
- function _hideClearPopup() {
451
- const clearPopup = document.getElementById("billing-clear-popup");
452
- if (clearPopup) clearPopup.style.display = "none";
453
- _clearPopupVisible = false;
454
- }
455
-
456
- function _closeClearPopup(e) {
457
- const clearPopup = document.getElementById("billing-clear-popup");
458
- const clearBtn = document.getElementById("billing-clear-btn");
459
- if (!clearPopup || !clearBtn) return;
460
- if (!clearPopup.contains(e.target) && !clearBtn.contains(e.target)) _hideClearPopup();
461
- }
462
-
463
408
  // ── Section renderers ────────────────────────────────────────────────────────
464
409
 
465
410
  function _getSummaryHint() {
@@ -678,7 +623,6 @@ const BillingView = (() => {
678
623
  <div class="billing-chart-card billing-trend-card">
679
624
  <div class="billing-chart-header">
680
625
  <h4>${I18n.t("billing.costTrend") || "Cost Trend"}</h4>
681
- <span class="billing-trend-total">${currencySymbol}${_formatCost(costs.reduce((a, b) => a + b, 0))}</span>
682
626
  </div>
683
627
  <div class="billing-trend-chart">
684
628
  <svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="xMidYMid meet" class="billing-trend-svg">
@@ -362,7 +362,7 @@ const ExtensionsStore = (() => {
362
362
  });
363
363
  const data = await res.json();
364
364
  if (!res.ok || !data.ok) throw new Error(data.error || "toggle failed");
365
- const activeTab = _filterSystem ? "system" : _filterBrand ? "brand" : "market";
365
+ const activeTab = _filterSystem ? "system" : _filterBrand ? "brand" : _filterInstalled ? "installed" : "market";
366
366
  try { sessionStorage.setItem(EXT_TAB_KEY, activeTab); } catch (_) {}
367
367
  location.reload();
368
368
  } catch (e) {
@@ -108,6 +108,8 @@ const ExtensionsView = (() => {
108
108
  ? `<span class="extension-updatable">${escapeHtml(I18n.t("extensions.updatable"))}</span>` : "";
109
109
  const unlistedHtml = (ext.unlisted && ext.installed)
110
110
  ? `<span class="extension-unlisted">${escapeHtml(I18n.t("extensions.unlisted"))}</span>` : "";
111
+ const verifiedHtml = (ext.verified && !ext.installed)
112
+ ? `<span class="extension-verified">${escapeHtml(I18n.t("extensions.verified"))}</span>` : "";
111
113
  const unitsText = _formatUnits(ext.units);
112
114
  const unitsHtml = unitsText
113
115
  ? `<span class="extension-units">${escapeHtml(unitsText)}</span>` : "";
@@ -137,6 +139,7 @@ const ExtensionsView = (() => {
137
139
  ${installedHtml}
138
140
  ${updatableHtml}
139
141
  ${unlistedHtml}
142
+ ${verifiedHtml}
140
143
  ${unitsHtml}
141
144
  ${authorHtml}
142
145
  ${installsHtml}
@@ -160,6 +163,7 @@ const ExtensionsView = (() => {
160
163
  } else {
161
164
  card.addEventListener("click", (e) => {
162
165
  if (e.target.closest("a")) return;
166
+ _saveCurrentTab();
163
167
  const router = window.Clacky && window.Clacky.Router;
164
168
  const isBrand = card.dataset.extOrigin === "self" || Extensions.state.filterBrand;
165
169
  const source = isBrand ? "brand" : null;
@@ -181,6 +185,17 @@ const ExtensionsView = (() => {
181
185
  </button>`;
182
186
  }
183
187
 
188
+ function _saveCurrentTab() {
189
+ try {
190
+ var s = ExtensionsStore.state;
191
+ var tab = "market";
192
+ if (s.filterSystem) tab = "system";
193
+ else if (s.filterBrand) tab = "brand";
194
+ else if (s.filterInstalled) tab = "installed";
195
+ sessionStorage.setItem(EXT_TAB_KEY, tab);
196
+ } catch (_) {}
197
+ }
198
+
184
199
  // Returns a human-readable button label for a given install job state.
185
200
  function _installJobLabel(job) {
186
201
  if (!job) return I18n.t("extensions.action.install");
@@ -300,13 +315,14 @@ const ExtensionsView = (() => {
300
315
 
301
316
  const installBtn = document.querySelector("[data-ext-install]");
302
317
  if (installBtn) {
303
- installBtn.addEventListener("click", () => {
318
+ installBtn.addEventListener("click", async () => {
304
319
  const id = installBtn.getAttribute("data-ext-install");
320
+ const ext = ExtensionsStore.state.detail;
321
+ if (ext && !ext.verified && !(typeof Brand !== "undefined" && Brand.branded)) {
322
+ const confirmed = await Modal.confirm(I18n.t("extensions.unverifiedInstallWarning"));
323
+ if (!confirmed) return;
324
+ }
305
325
  installBtn.disabled = true;
306
- // Show a "downloading" placeholder rather than a generic "installing" —
307
- // the next real state is always the download starting, so this avoids
308
- // a jarring "installing → downloading → installing" flash before any
309
- // real progress data has arrived.
310
326
  installBtn.textContent = _installJobLabel({ stage: "downloading", progress: null });
311
327
  Extensions.install(id);
312
328
  });
@@ -350,6 +366,10 @@ const ExtensionsView = (() => {
350
366
  ? `<span class="extension-installed">${installedLabel}</span>` : "";
351
367
  const unlistedHtml = ext.unlisted
352
368
  ? `<span class="extension-unlisted">${escapeHtml(I18n.t("extensions.unlisted"))}</span>` : "";
369
+ const verifiedHtml = ext.verified
370
+ ? `<span class="extension-verified">${escapeHtml(I18n.t("extensions.verified"))}</span>` : "";
371
+ const unverifiedBannerHtml = (!ext.verified && !ext.installed && !(typeof Brand !== "undefined" && Brand.branded))
372
+ ? `<div class="extension-unverified-banner"><svg class="extension-unverified-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="7" cy="7" r="6.25" stroke="currentColor" stroke-width="1.2"/><path d="M7 4V7.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="7" cy="10" r="0.7" fill="currentColor"/></svg>${escapeHtml(I18n.t("extensions.unverifiedBanner"))}</div>` : "";
353
373
  const unitsText = _formatUnits(ext.units);
354
374
  const unitsHtml = unitsText
355
375
  ? `<span class="extension-units">${escapeHtml(unitsText)}</span>` : "";
@@ -370,12 +390,14 @@ const ExtensionsView = (() => {
370
390
  ${versionHtml}
371
391
  ${installedHtml}
372
392
  ${unlistedHtml}
393
+ ${verifiedHtml}
373
394
  ${unitsHtml}
374
395
  ${authorHtml}
375
396
  ${installsHtml}
376
397
  </div>
377
398
  ${description ? `<div class="extension-desc extension-desc-detail">${escapeHtml(description)}</div>` : ""}
378
399
  ${homepageHtml ? `<div class="extension-meta">${homepageHtml}</div>` : ""}
400
+ ${unverifiedBannerHtml}
379
401
  ${_renderActions(ext)}
380
402
  </div>
381
403
  </div>
@@ -584,6 +606,7 @@ const ExtensionsView = (() => {
584
606
  if (e.target.closest("a")) return;
585
607
  const card = e.target.closest(".extension-card-clickable");
586
608
  if (card && card.dataset.extId) {
609
+ _saveCurrentTab();
587
610
  const router = window.Clacky && window.Clacky.Router;
588
611
  // Use origin stored on the card; origin="self" means brand-private extension.
589
612
  const isBrand = card.dataset.extOrigin === "self" || Extensions.state.filterBrand;
@@ -620,6 +620,27 @@ const NewSessionView = (() => {
620
620
  _renderContextBar();
621
621
  });
622
622
  pop.appendChild(clearItem);
623
+
624
+ // "Create new project" action
625
+ const actionSep = document.createElement("div");
626
+ actionSep.style.cssText = "height:1px;background:var(--color-border-primary);margin:4px 8px";
627
+ pop.appendChild(actionSep);
628
+ const createItem = document.createElement("button");
629
+ createItem.type = "button";
630
+ createItem.className = "ns-ctx-popover-item";
631
+ createItem.style.cssText = "display:flex;align-items:center;gap:6px";
632
+ const plusIcon = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0"><path d="M12 5v14M5 12h14"/></svg>';
633
+ createItem.innerHTML = plusIcon + `<span>${zh ? "新建项目" : "New project"}</span>`;
634
+ createItem.addEventListener("click", async () => {
635
+ _dismissCtxPopover();
636
+ const project = await Projects.promptCreate();
637
+ if (project) {
638
+ const dir = project.working_dir || NewSessionStore.state.defaultDir || "";
639
+ NewSessionStore.updateAdvanced({ projectId: project.id, workingDir: dir });
640
+ _renderContextBar();
641
+ }
642
+ });
643
+ pop.appendChild(createItem);
623
644
  });
624
645
  }
625
646
 
@@ -786,6 +807,9 @@ const NewSessionView = (() => {
786
807
  const display = hasFiles ? _buildBubbleHtml(finalContent) : null;
787
808
 
788
809
  Sessions.add(session);
810
+ if (session.project_id && typeof Projects !== "undefined" && Projects.expand) {
811
+ Projects.expand(session.project_id);
812
+ }
789
813
  Sessions.setPendingMessage(session.id, finalContent, display, files);
790
814
  input.value = "";
791
815
  _pendingImages.length = 0;
@@ -40,26 +40,26 @@ const I18n = (() => {
40
40
  "projects.create.dirPlaceholder": "Working directory (optional)",
41
41
  "projects.create.sourceFolders": "Source folders",
42
42
  "projects.create.folderHint": "Add a working folder",
43
+ "projects.create.folderDefaultHint": "If not set, the default working directory ~/clacky_workspace will be used",
43
44
  "projects.create.submit": "Create Project",
44
45
  "projects.create.colorLabel": "Color",
45
46
  "projects.create.iconLabel": "Icon",
46
47
  "projects.picker.done": "Done",
47
48
  "projects.emptyHint": "No projects yet",
48
49
  "projects.noSessions": "No sessions",
49
- "projects.tooltip.sessions": "{n} sessions",
50
50
  "projects.organize.grouped": "By Project",
51
51
  "projects.organize.flat": "In a flat list",
52
52
  "projects.organize.sortLabel": "Sort by",
53
53
  "projects.organize.sortPriority": "Priority",
54
54
  "projects.organize.sortRecent": "Last updated",
55
55
  "projects.menu.rename": "Rename",
56
- "projects.menu.edit": "Edit",
57
- "projects.menu.delete": "Delete",
58
- "projects.menu.deleteSessions": "Delete Sessions",
56
+ "projects.menu.edit": "Edit Project",
57
+ "projects.menu.delete": "Delete Project",
58
+ "projects.menu.deleteSessions": "Clear Sessions",
59
59
  "projects.edit.title": "Edit Project",
60
60
  "projects.edit.submit": "Save",
61
61
  "projects.delete.confirm": "Delete project \"{{name}}\"? All sessions in this project will also be deleted.",
62
- "projects.deleteSessions.confirm": "Delete all sessions in project \"{{name}}\"? The project itself will be kept.",
62
+ "projects.deleteSessions.confirm": "Clear all sessions in project \"{{name}}\"? The project itself will be kept.",
63
63
  "projects.modal.name": "Project Name",
64
64
 
65
65
  // ── Welcome screen ──
@@ -575,6 +575,10 @@ const I18n = (() => {
575
575
  "extensions.homepage": "Homepage",
576
576
  "extensions.by": "by ",
577
577
  "extensions.installs": "installs",
578
+ "extensions.verified": "Verified",
579
+ "extensions.unverified": "Unverified",
580
+ "extensions.unverifiedBanner": "This extension has not been verified by the platform. It may contain un-reviewed code or behaviors. Install at your own risk.",
581
+ "extensions.unverifiedInstallWarning": "This extension has not been verified by the platform. It may contain un-reviewed code or behaviors. Install anyway?",
578
582
  "extensions.installed": "Installed",
579
583
  "extensions.updatable": "Update available",
580
584
  "extensions.unlisted": "Unlisted",
@@ -1064,9 +1068,6 @@ const I18n = (() => {
1064
1068
  "billing.period.month": "This Month",
1065
1069
  "billing.period.year": "This Year",
1066
1070
  "billing.period.all": "All Time",
1067
- "billing.clearData": "Clear Data",
1068
- "billing.clearToday": "Clear Today",
1069
- "billing.clearAll": "Clear All",
1070
1071
  "billing.allModels": "All Models",
1071
1072
  "billing.cacheHit": "Cache Hit",
1072
1073
  "billing.inputCacheHit": "Input (Cache Hit)",
@@ -1090,7 +1091,7 @@ const I18n = (() => {
1090
1091
  "billing.headerHit": "Hit",
1091
1092
  "billing.headerMiss": "Miss",
1092
1093
  "billing.headerOutput": "Output",
1093
- "billing.disclaimer": "Costs are estimated based on token usage and may differ from your provider's actual bill.",
1094
+ "billing.disclaimer": "Costs are estimated based on token usage and may differ from your provider's actual bill. If you notice discrepancies, please check your provider's billing dashboard.",
1094
1095
  },
1095
1096
  zh: {
1096
1097
  // ── Phases ──
@@ -1119,26 +1120,26 @@ const I18n = (() => {
1119
1120
  "projects.create.placeholder": "项目名称",
1120
1121
  "projects.create.sourceFolders": "工作目录",
1121
1122
  "projects.create.folderHint": "添加工作文件夹",
1123
+ "projects.create.folderDefaultHint": "不设置时将使用默认工作目录 ~/clacky_workspace",
1122
1124
  "projects.create.submit": "创建项目",
1123
1125
  "projects.create.colorLabel": "颜色",
1124
1126
  "projects.create.iconLabel": "图标",
1125
1127
  "projects.picker.done": "完成",
1126
1128
  "projects.emptyHint": "暂无项目",
1127
1129
  "projects.noSessions": "暂无会话",
1128
- "projects.tooltip.sessions": "{n} 个会话",
1129
1130
  "projects.organize.grouped": "按项目分组",
1130
1131
  "projects.organize.flat": "在一个列表中",
1131
1132
  "projects.organize.sortLabel": "排序方式",
1132
1133
  "projects.organize.sortPriority": "优先级",
1133
1134
  "projects.organize.sortRecent": "最近更新",
1134
1135
  "projects.menu.rename": "重命名",
1135
- "projects.menu.edit": "编辑",
1136
- "projects.menu.delete": "删除",
1137
- "projects.menu.deleteSessions": "删除会话",
1136
+ "projects.menu.edit": "编辑项目",
1137
+ "projects.menu.delete": "删除项目",
1138
+ "projects.menu.deleteSessions": "清空会话",
1138
1139
  "projects.edit.title": "编辑项目",
1139
1140
  "projects.edit.submit": "保存",
1140
1141
  "projects.delete.confirm": "删除项目「{{name}}」?该项目下的所有会话也将被删除。",
1141
- "projects.deleteSessions.confirm": "删除项目「{{name}}」下的所有会话?项目本身会被保留。",
1142
+ "projects.deleteSessions.confirm": "清空项目「{{name}}」下的所有会话?项目本身会被保留。",
1142
1143
  "projects.modal.name": "项目名称",
1143
1144
 
1144
1145
  // ── Welcome screen ──
@@ -1651,6 +1652,10 @@ const I18n = (() => {
1651
1652
  "extensions.homepage": "主页",
1652
1653
  "extensions.by": "作者:",
1653
1654
  "extensions.installs": "次安装",
1655
+ "extensions.verified": "已验证",
1656
+ "extensions.unverified": "未验证",
1657
+ "extensions.unverifiedBanner": "此扩展尚未通过平台审核验证,可能包含未经审查的代码或行为,建议谨慎安装。",
1658
+ "extensions.unverifiedInstallWarning": "此扩展尚未通过平台审核验证,可能包含未经审查的代码或行为。确定安装?",
1654
1659
  "extensions.installed": "已安装",
1655
1660
  "extensions.updatable": "有更新",
1656
1661
  "extensions.unlisted": "已下架",
@@ -2147,9 +2152,6 @@ const I18n = (() => {
2147
2152
  "billing.period.month": "本月",
2148
2153
  "billing.period.year": "今年",
2149
2154
  "billing.period.all": "全部",
2150
- "billing.clearData": "清除数据",
2151
- "billing.clearToday": "清除今日",
2152
- "billing.clearAll": "全部清除",
2153
2155
  "billing.allModels": "所有模型",
2154
2156
  "billing.cacheHit": "缓存命中",
2155
2157
  "billing.inputCacheHit": "输入(命中缓存)",
@@ -2173,7 +2175,7 @@ const I18n = (() => {
2173
2175
  "billing.headerHit": "命中",
2174
2176
  "billing.headerMiss": "未命中",
2175
2177
  "billing.headerOutput": "输出",
2176
- "billing.disclaimer": "费用为估算值,基于 Token 用量和模型定价计算,可能与 API 提供商实际账单有所差异。",
2178
+ "billing.disclaimer": "费用为估算值,基于 Token 用量和模型定价计算,可能与 API 提供商实际账单有所差异。如有异常,请前往 API 提供商查看实际账单。",
2177
2179
  }
2178
2180
  };
2179
2181
  // ── State ──────────────────────────────────────────────────────────────────
@@ -126,7 +126,7 @@
126
126
  <span class="chat-section-title">
127
127
  <span data-i18n="sidebar.chat">Sessions</span>
128
128
  <!-- Chevron: hidden by default, shown on hover or when collapsed -->
129
- <svg class="chat-section-chevron" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
129
+ <svg class="chat-section-chevron" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
130
130
  <path d="M6 9l6 6 6-6"/>
131
131
  </svg>
132
132
  </span>
@@ -177,7 +177,7 @@
177
177
  <div class="sidebar-divider" id="projects-section-header">
178
178
  <span class="projects-section-title">
179
179
  <span data-i18n="sidebar.projects">Projects</span>
180
- <svg class="projects-section-chevron" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
180
+ <svg class="projects-section-chevron" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
181
181
  <path d="M6 9l6 6 6-6"/>
182
182
  </svg>
183
183
  </span>
@@ -219,7 +219,7 @@
219
219
  <div id="skills-sidebar-item" class="task-item task-item-summary">
220
220
  <div class="task-row">
221
221
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
222
- <path d="M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.077.877.528 1.073 1.01a2.5 2.5 0 1 0 3.259-3.259c-.482-.196-.933-.558-1.01-1.073-.05-.336.062-.676.303-.917l1.525-1.525A2.402 2.402 0 0 1 12 1.998c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.877.29.493-.074.84-.504 1.02-.968a2.5 2.5 0 1 1 3.237 3.237c-.464.18-.894.527-.967 1.02Z"/>
222
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"/>
223
223
  </svg>
224
224
  <div class="task-info">
225
225
  <span class="task-name" id="skills-sidebar-label" data-i18n="sidebar.skills">Skill Management</span>
@@ -229,8 +229,7 @@
229
229
  <div id="extensions-sidebar-item" class="task-item task-item-summary">
230
230
  <div class="task-row">
231
231
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
232
- <path d="M20 7h-9"/><path d="M14 17H5"/>
233
- <circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/>
232
+ <path d="M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.077.877.528 1.073 1.01a2.5 2.5 0 1 0 3.259-3.259c-.482-.196-.933-.558-1.01-1.073-.05-.336.062-.676.303-.917l1.525-1.525A2.402 2.402 0 0 1 12 1.998c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.877.29.493-.074.84-.504 1.02-.968a2.5 2.5 0 1 1 3.237 3.237c-.464.18-.894.527-.967 1.02Z"/>
234
233
  </svg>
235
234
  <div class="task-info">
236
235
  <span class="task-name" id="extensions-sidebar-label" data-i18n="sidebar.extensions">Extension Management</span>
@@ -240,7 +239,7 @@
240
239
  <div id="channels-sidebar-item" class="task-item task-item-summary">
241
240
  <div class="task-row">
242
241
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
243
- <rect x="5" y="2" width="14" height="20" rx="2"/>
242
+ <rect x="4" y="2" width="16" height="20" rx="2"/>
244
243
  <path d="M12 18h.01"/>
245
244
  </svg>
246
245
  <div class="task-info">
@@ -270,8 +269,9 @@
270
269
  <div id="profile-sidebar-item" class="task-item task-item-summary">
271
270
  <div class="task-row">
272
271
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
273
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
274
- <circle cx="12" cy="7" r="4"/>
272
+ <path d="M2 21a8 8 0 0 1 10.821-7.487"/>
273
+ <path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>
274
+ <circle cx="10" cy="8" r="5"/>
275
275
  </svg>
276
276
  <div class="task-info">
277
277
  <span class="task-name" id="profile-sidebar-label" data-i18n="sidebar.profile">Profile &amp; Soul</span>
@@ -286,10 +286,9 @@
286
286
  <div id="trash-sidebar-item" class="task-item task-item-summary">
287
287
  <div class="task-row">
288
288
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
289
- <polyline points="3 6 5 6 21 6"/>
290
- <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
291
- <path d="M10 11v6"/><path d="M14 11v6"/>
292
- <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
289
+ <rect x="2" y="3" width="20" height="5" rx="1"/>
290
+ <path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/>
291
+ <path d="M10 12h4"/>
293
292
  </svg>
294
293
  <div class="task-info">
295
294
  <span class="task-name" id="trash-sidebar-label" data-i18n="sidebar.trash">File Recall</span>
@@ -299,7 +298,7 @@
299
298
  <div id="billing-sidebar-item" class="task-item task-item-summary">
300
299
  <div class="task-row">
301
300
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
302
- <line x1="12" y1="1" x2="12" y2="23"/>
301
+ <line x1="12" x2="12" y1="2" y2="22"/>
303
302
  <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
304
303
  </svg>
305
304
  <div class="task-info">