openclacky 1.4.1 → 1.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/lib/clacky/agent/llm_caller.rb +3 -3
- data/lib/clacky/agent/session_serializer.rb +39 -5
- data/lib/clacky/brand_config.rb +49 -7
- data/lib/clacky/client.rb +22 -16
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +135 -84
- data/lib/clacky/default_extensions/ext-studio/ext.yml +2 -4
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +522 -0
- data/lib/clacky/extension/api_extension.rb +5 -6
- data/lib/clacky/extension/loader.rb +16 -2
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/mcp/http_transport.rb +1 -1
- data/lib/clacky/providers.rb +109 -1
- data/lib/clacky/server/http_server.rb +115 -19
- data/lib/clacky/server/session_registry.rb +9 -0
- data/lib/clacky/utils/model_pricing.rb +36 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +321 -32
- data/lib/clacky/web/app.js +29 -6
- data/lib/clacky/web/components/onboard.js +7 -3
- data/lib/clacky/web/features/extensions/store.js +54 -2
- data/lib/clacky/web/features/extensions/view.js +40 -5
- data/lib/clacky/web/features/new-session/store.js +8 -4
- data/lib/clacky/web/features/version/view.js +5 -1
- data/lib/clacky/web/i18n.js +52 -18
- data/lib/clacky/web/index.html +51 -5
- data/lib/clacky/web/sessions.js +40 -11
- data/lib/clacky/web/settings.js +22 -3
- data/lib/clacky/web/theme.js +27 -58
- data/scripts/build/src/uninstall.sh.cc +1 -1
- metadata +2 -5
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +0 -71
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +0 -73
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +0 -65
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +0 -106
|
@@ -209,8 +209,11 @@ const ExtensionsView = (() => {
|
|
|
209
209
|
if (remove) {
|
|
210
210
|
remove.addEventListener("click", async () => {
|
|
211
211
|
const id = remove.getAttribute("data-ext-remove");
|
|
212
|
-
const ok = await Modal.
|
|
213
|
-
|
|
212
|
+
const { ok, checked } = await Modal.confirmWithCheckbox(
|
|
213
|
+
I18n.t("extensions.action.removeConfirm"),
|
|
214
|
+
I18n.t("extensions.action.removePurgeData")
|
|
215
|
+
);
|
|
216
|
+
if (ok) Extensions.uninstall(id, checked);
|
|
214
217
|
});
|
|
215
218
|
}
|
|
216
219
|
|
|
@@ -313,7 +316,9 @@ const ExtensionsView = (() => {
|
|
|
313
316
|
function _renderActions(ext) {
|
|
314
317
|
const id = ext.id != null ? String(ext.id) : (ext.name || ext.slug || "");
|
|
315
318
|
if (!ext.installed) {
|
|
316
|
-
|
|
319
|
+
// Show install button for both marketplace and brand-private (origin=self) extensions,
|
|
320
|
+
// as long as a download_url is available.
|
|
321
|
+
if (ext.download_url) {
|
|
317
322
|
return `
|
|
318
323
|
<div class="extension-detail-actions">
|
|
319
324
|
<button type="button" class="extension-action extension-action-install" data-ext-install="${escapeHtml(id)}">${escapeHtml(I18n.t("extensions.action.install"))}</button>
|
|
@@ -407,6 +412,17 @@ const ExtensionsView = (() => {
|
|
|
407
412
|
</div>`;
|
|
408
413
|
}
|
|
409
414
|
|
|
415
|
+
// Show/hide the brand filter tab based on brand status from the store.
|
|
416
|
+
async function _applyBrandTab() {
|
|
417
|
+
try {
|
|
418
|
+
const data = await Extensions.fetchBrandStatus();
|
|
419
|
+
const brandTab = $("tab-extensions-brand");
|
|
420
|
+
if (brandTab) brandTab.style.display = data.branded ? "" : "none";
|
|
421
|
+
} catch (_e) {
|
|
422
|
+
// On error, keep tab hidden.
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
410
426
|
function _wireDom() {
|
|
411
427
|
if (_domWired) return;
|
|
412
428
|
|
|
@@ -432,10 +448,25 @@ const ExtensionsView = (() => {
|
|
|
432
448
|
btn.addEventListener("click", () => {
|
|
433
449
|
document.querySelectorAll(".extensions-filter-tab").forEach(b => b.classList.remove("extensions-filter-tab-active"));
|
|
434
450
|
btn.classList.add("extensions-filter-tab-active");
|
|
435
|
-
|
|
451
|
+
const filter = btn.dataset.filter;
|
|
452
|
+
if (filter === "installed") {
|
|
453
|
+
Extensions.setFilterInstalled(true);
|
|
454
|
+
} else if (filter === "brand") {
|
|
455
|
+
Extensions.setFilterBrand(true);
|
|
456
|
+
} else {
|
|
457
|
+
Extensions.setFilterInstalled(false);
|
|
458
|
+
}
|
|
436
459
|
});
|
|
437
460
|
});
|
|
438
461
|
|
|
462
|
+
// Subscribe to brand status changes to show/hide the brand tab.
|
|
463
|
+
if (window.Skills && Skills.on) {
|
|
464
|
+
Skills.on("brandStatus:changed", (p) => {
|
|
465
|
+
const brandTab = $("tab-extensions-brand");
|
|
466
|
+
if (brandTab) brandTab.style.display = p.branded ? "" : "none";
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
439
470
|
const list = $("extensions-list");
|
|
440
471
|
if (list) {
|
|
441
472
|
list.addEventListener("click", (e) => {
|
|
@@ -466,8 +497,12 @@ const ExtensionsView = (() => {
|
|
|
466
497
|
}
|
|
467
498
|
|
|
468
499
|
const viewApi = {
|
|
469
|
-
onPanelShow(opts) {
|
|
500
|
+
async onPanelShow(opts) {
|
|
470
501
|
_wireDom();
|
|
502
|
+
// Apply brand tab visibility based on current status (fast path),
|
|
503
|
+
// then also refresh in background so it stays up-to-date.
|
|
504
|
+
_applyBrandTab();
|
|
505
|
+
if (window.Skills && Skills.refreshBrandStatus) Skills.refreshBrandStatus();
|
|
471
506
|
const detailId = opts && opts.detailId;
|
|
472
507
|
if (detailId) {
|
|
473
508
|
Extensions.load();
|
|
@@ -122,19 +122,23 @@ const NewSessionStore = (() => {
|
|
|
122
122
|
return "Session " + (maxN + 1);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
async function createSession({ existingSessions } = {}) {
|
|
125
|
+
async function createSession({ existingSessions, useDefaults = false } = {}) {
|
|
126
126
|
if (_state.creating) return null;
|
|
127
127
|
_state.creating = true;
|
|
128
128
|
_emit("newSession:creating", { creating: true });
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
|
-
|
|
131
|
+
// useDefaults=true: quick-create from sidebar — use the default agent and
|
|
132
|
+
// skip advanced options (model / dir) so the session always starts clean.
|
|
133
|
+
const agentId = useDefaults ? "general" : (_state.selectedAgentId || "general");
|
|
132
134
|
const adv = _state.advanced;
|
|
133
135
|
const name = adv.name.trim() || _autoName(existingSessions);
|
|
134
136
|
|
|
135
137
|
const payload = { name, agent_profile: agentId, source: "manual" };
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
+
if (!useDefaults) {
|
|
139
|
+
if (adv.workingDir.trim()) payload.working_dir = adv.workingDir.trim();
|
|
140
|
+
if (adv.modelId) payload.model_id = adv.modelId;
|
|
141
|
+
}
|
|
138
142
|
|
|
139
143
|
const res = await fetch("/api/sessions", {
|
|
140
144
|
method: "POST",
|
|
@@ -237,7 +237,11 @@ const VersionView = (() => {
|
|
|
237
237
|
_closePopover();
|
|
238
238
|
if (!_autoReloaded) {
|
|
239
239
|
_autoReloaded = true;
|
|
240
|
-
|
|
240
|
+
// Delay reload to ensure the server is fully ready after restart.
|
|
241
|
+
// /api/version responding OK does not guarantee the full server stack
|
|
242
|
+
// (static assets, routes) is ready; a short extra wait avoids a blank
|
|
243
|
+
// page caused by the browser hitting a not-yet-ready server.
|
|
244
|
+
setTimeout(() => window.location.reload(), 3000);
|
|
241
245
|
}
|
|
242
246
|
return;
|
|
243
247
|
}
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -75,12 +75,11 @@ const I18n = (() => {
|
|
|
75
75
|
"chat.send": "Send",
|
|
76
76
|
"chat.edit.warn": "Editing this message will discard the conversation history, file snapshots, and token costs after it. Continue?",
|
|
77
77
|
"chat.edit.warnSkip": "Don't show this again",
|
|
78
|
-
"chat.empty.title": "
|
|
79
|
-
"chat.empty.subtitle": "Ask
|
|
80
|
-
"chat.empty.tip1": "
|
|
81
|
-
"chat.empty.tip2": "
|
|
82
|
-
"chat.empty.tip3": "
|
|
83
|
-
"chat.empty.tip4": "Each session is isolated, but long-term memories are shared across sessions",
|
|
78
|
+
"chat.empty.title": "How can I help you today?",
|
|
79
|
+
"chat.empty.subtitle": "Ask a question, or start with a skill.",
|
|
80
|
+
"chat.empty.tip1": "Research, write, and translate",
|
|
81
|
+
"chat.empty.tip2": "Summarize and organize information",
|
|
82
|
+
"chat.empty.tip3": "Upload an image or file for me to analyze",
|
|
84
83
|
|
|
85
84
|
// ── Session list ──
|
|
86
85
|
"sessions.empty": "No sessions yet",
|
|
@@ -162,6 +161,11 @@ const I18n = (() => {
|
|
|
162
161
|
"sib.variant.default": "default",
|
|
163
162
|
"sib.signal.tooltip": "Recent LLM latency",
|
|
164
163
|
"sib.reasoning.tooltip": "Click to change reasoning effort",
|
|
164
|
+
"sib.id.label": "Session file",
|
|
165
|
+
"sib.status.tooltip": "Current session status",
|
|
166
|
+
"sib.mode.tooltip": "Permission mode — controls whether the agent asks before making changes",
|
|
167
|
+
"sib.tasks.tooltip": "Total number of tasks completed in this session",
|
|
168
|
+
"sib.cost.tooltip": "Estimated token cost for this session",
|
|
165
169
|
"sib.reasoning.label": "Reasoning",
|
|
166
170
|
"sib.reasoning.heading": "Reasoning effort",
|
|
167
171
|
"sib.reasoning.hint": "Higher effort = deeper thinking, slower response.",
|
|
@@ -520,6 +524,7 @@ const I18n = (() => {
|
|
|
520
524
|
"extensions.sort.downloads": "Most installed",
|
|
521
525
|
"extensions.filter.all": "All",
|
|
522
526
|
"extensions.filter.installed": "Installed",
|
|
527
|
+
"extensions.filter.brand": "Brand",
|
|
523
528
|
"extensions.empty": "No extensions available yet.",
|
|
524
529
|
"extensions.noResults": "No extensions match your search.",
|
|
525
530
|
"extensions.loadFailed": "Could not reach the extension store.",
|
|
@@ -554,6 +559,7 @@ const I18n = (() => {
|
|
|
554
559
|
"extensions.action.enable": "Enable",
|
|
555
560
|
"extensions.action.remove": "Remove",
|
|
556
561
|
"extensions.action.removeConfirm": "Remove this extension? Its files will be deleted. You can reinstall it later.",
|
|
562
|
+
"extensions.action.removePurgeData": "Also delete this extension's data (kept by default; reinstalling restores it)",
|
|
557
563
|
"mcp.loading": "Loading…",
|
|
558
564
|
"mcp.empty.title": "No MCP servers configured",
|
|
559
565
|
"mcp.empty.body": "Create ~/.clacky/mcp.json to plug in MCP servers. The format matches Claude Desktop and Cursor, so existing configs work as-is.",
|
|
@@ -572,10 +578,10 @@ const I18n = (() => {
|
|
|
572
578
|
"mcp.btn.hide": "Hide tools",
|
|
573
579
|
"mcp.btn.refresh": "Refresh",
|
|
574
580
|
"mcp.btn.remove": "Remove",
|
|
575
|
-
"mcp.btn.askClacky": "Let
|
|
581
|
+
"mcp.btn.askClacky": "Let AI help me set this up",
|
|
576
582
|
"mcp.cta.title": "Want to add a new MCP server?",
|
|
577
|
-
"mcp.cta.body": "
|
|
578
|
-
"mcp.cta.button": "Ask
|
|
583
|
+
"mcp.cta.body": "Describe what you need in plain words — the AI will pick the right package, ask for the parameters that matter, and verify it works.",
|
|
584
|
+
"mcp.cta.button": "Ask AI to add one",
|
|
579
585
|
"mcp.remove.confirm": "Remove MCP server '{{name}}'? Its tools will no longer be available.",
|
|
580
586
|
"mcp.remove.error": "Failed to remove: {{msg}}",
|
|
581
587
|
"mcp.loadError": "Failed to load MCP servers: {{msg}}",
|
|
@@ -657,6 +663,9 @@ const I18n = (() => {
|
|
|
657
663
|
"settings.models.baseurl.variant.mainland_cn_coding": "Mainland · Coding Plan",
|
|
658
664
|
"settings.models.baseurl.variant.international_payg": "International · Pay-as-you-go",
|
|
659
665
|
"settings.models.baseurl.variant.international_coding": "International · Coding Plan",
|
|
666
|
+
"settings.models.baseurl.variant.ark_payg": "Pay-as-you-go",
|
|
667
|
+
"settings.models.baseurl.variant.ark_coding": "Coding Plan",
|
|
668
|
+
"settings.models.baseurl.variant.ark_agent": "Agent Plan",
|
|
660
669
|
"settings.models.btn.save": "Save",
|
|
661
670
|
"settings.models.btn.saving": "Saving…",
|
|
662
671
|
"settings.models.btn.saved": "Saved ✓",
|
|
@@ -778,6 +787,13 @@ const I18n = (() => {
|
|
|
778
787
|
|
|
779
788
|
"settings.accentColor.title": "Accent Color",
|
|
780
789
|
|
|
790
|
+
"settings.bgTheme.title": "Background",
|
|
791
|
+
"settings.bgTheme.light": "Light",
|
|
792
|
+
"settings.bgTheme.dark": "Dark",
|
|
793
|
+
"settings.bgTheme.dim": "Dim",
|
|
794
|
+
"settings.bgTheme.warm": "Warm",
|
|
795
|
+
"settings.bgTheme.black": "Blue Dark",
|
|
796
|
+
|
|
781
797
|
// ── Onboard ──
|
|
782
798
|
"onboard.title": "Welcome to {{brand}}",
|
|
783
799
|
"onboard.subtitle": "Let's get you set up in a minute.",
|
|
@@ -812,6 +828,7 @@ const I18n = (() => {
|
|
|
812
828
|
"onboard.manual.toggle": "Configure manually with your own API key",
|
|
813
829
|
"onboard.provider.custom": "Custom",
|
|
814
830
|
"provider.recommended": "Recommended",
|
|
831
|
+
"provider.name.volcengine_ark": "Volcengine Ark (Doubao)",
|
|
815
832
|
"provider.promo.openclacky.title": "OpenClacky AI Keys",
|
|
816
833
|
"provider.promo.openclacky.1": "All frontier models, one key — Claude, Gemini, DeepSeek & more.",
|
|
817
834
|
"provider.promo.openclacky.2": "Top reasoning, best value, best vision — unified in one API.",
|
|
@@ -1049,12 +1066,11 @@ const I18n = (() => {
|
|
|
1049
1066
|
"chat.send": "发送",
|
|
1050
1067
|
"chat.edit.warn": "编辑此消息将丢弃后续对话历史、文件快照及已产生的 token 费用,是否继续?",
|
|
1051
1068
|
"chat.edit.warnSkip": "不再提示",
|
|
1052
|
-
"chat.empty.title": "
|
|
1053
|
-
"chat.empty.subtitle": "
|
|
1054
|
-
"chat.empty.tip1": "
|
|
1055
|
-
"chat.empty.tip2": "
|
|
1056
|
-
"chat.empty.tip3": "
|
|
1057
|
-
"chat.empty.tip4": "每个会话上下文独立,但长期记忆全局共享",
|
|
1069
|
+
"chat.empty.title": "有什么可以帮你?",
|
|
1070
|
+
"chat.empty.subtitle": "直接提问,或用一个技能开始。",
|
|
1071
|
+
"chat.empty.tip1": "查资料、写文案、做翻译",
|
|
1072
|
+
"chat.empty.tip2": "归纳总结、整理信息",
|
|
1073
|
+
"chat.empty.tip3": "上传图片或文件,让我帮你分析",
|
|
1058
1074
|
|
|
1059
1075
|
// ── Session list ──
|
|
1060
1076
|
"sessions.empty": "暂无会话",
|
|
@@ -1136,6 +1152,11 @@ const I18n = (() => {
|
|
|
1136
1152
|
"sib.variant.default": "默认",
|
|
1137
1153
|
"sib.signal.tooltip": "最近一次 LLM 响应延迟",
|
|
1138
1154
|
"sib.reasoning.tooltip": "点击调整思考等级",
|
|
1155
|
+
"sib.id.label": "会话文件",
|
|
1156
|
+
"sib.status.tooltip": "当前会话状态",
|
|
1157
|
+
"sib.mode.tooltip": "权限模式 — 控制 Agent 执行操作前是否需要确认",
|
|
1158
|
+
"sib.tasks.tooltip": "本次会话累计完成的任务数",
|
|
1159
|
+
"sib.cost.tooltip": "本次会话的预估 Token 费用",
|
|
1139
1160
|
"sib.reasoning.label": "思考",
|
|
1140
1161
|
"sib.reasoning.heading": "思考等级",
|
|
1141
1162
|
"sib.reasoning.hint": "等级越高,思考越深,响应越慢。",
|
|
@@ -1493,6 +1514,7 @@ const I18n = (() => {
|
|
|
1493
1514
|
"extensions.sort.downloads": "安装最多",
|
|
1494
1515
|
"extensions.filter.all": "全部",
|
|
1495
1516
|
"extensions.filter.installed": "已安装",
|
|
1517
|
+
"extensions.filter.brand": "品牌扩展",
|
|
1496
1518
|
"extensions.empty": "暂无可用扩展。",
|
|
1497
1519
|
"extensions.noResults": "没有匹配的扩展。",
|
|
1498
1520
|
"extensions.loadFailed": "无法连接扩展市场。",
|
|
@@ -1527,6 +1549,7 @@ const I18n = (() => {
|
|
|
1527
1549
|
"extensions.action.enable": "启用",
|
|
1528
1550
|
"extensions.action.remove": "移除",
|
|
1529
1551
|
"extensions.action.removeConfirm": "确定移除此扩展?相关文件会被删除,之后可重新安装。",
|
|
1552
|
+
"extensions.action.removePurgeData": "同时删除该扩展的数据(默认保留,重新安装会自动恢复)",
|
|
1530
1553
|
"mcp.loading": "加载中…",
|
|
1531
1554
|
"mcp.empty.title": "尚未配置 MCP 服务",
|
|
1532
1555
|
"mcp.empty.body": "新建 ~/.clacky/mcp.json 即可接入 MCP 服务。格式与 Claude Desktop、Cursor 一致,现有配置可直接复用。",
|
|
@@ -1545,10 +1568,10 @@ const I18n = (() => {
|
|
|
1545
1568
|
"mcp.btn.hide": "收起",
|
|
1546
1569
|
"mcp.btn.refresh": "刷新",
|
|
1547
1570
|
"mcp.btn.remove": "移除",
|
|
1548
|
-
"mcp.btn.askClacky": "让
|
|
1571
|
+
"mcp.btn.askClacky": "让 AI 帮我配置",
|
|
1549
1572
|
"mcp.cta.title": "想接入新的 MCP 服务?",
|
|
1550
|
-
"mcp.cta.body": "
|
|
1551
|
-
"mcp.cta.button": "让
|
|
1573
|
+
"mcp.cta.body": "用一句大白话描述你想做什么——AI 会挑合适的包、问关键参数、跑通后回复你。",
|
|
1574
|
+
"mcp.cta.button": "让 AI 帮我添加",
|
|
1552
1575
|
"mcp.remove.confirm": "移除 MCP 服务 '{{name}}'?相关工具将不再可用。",
|
|
1553
1576
|
"mcp.remove.error": "移除失败:{{msg}}",
|
|
1554
1577
|
"mcp.loadError": "加载 MCP 服务失败:{{msg}}",
|
|
@@ -1637,6 +1660,9 @@ const I18n = (() => {
|
|
|
1637
1660
|
"settings.models.baseurl.variant.mainland_cn_coding": "大陆 · Coding Plan",
|
|
1638
1661
|
"settings.models.baseurl.variant.international_payg": "海外 · 按量付费",
|
|
1639
1662
|
"settings.models.baseurl.variant.international_coding": "海外 · Coding Plan",
|
|
1663
|
+
"settings.models.baseurl.variant.ark_payg": "按量付费",
|
|
1664
|
+
"settings.models.baseurl.variant.ark_coding": "Coding Plan",
|
|
1665
|
+
"settings.models.baseurl.variant.ark_agent": "Agent Plan",
|
|
1640
1666
|
"settings.models.btn.save": "保存",
|
|
1641
1667
|
"settings.models.btn.saving": "保存中…",
|
|
1642
1668
|
"settings.models.btn.saved": "已保存 ✓",
|
|
@@ -1758,6 +1784,13 @@ const I18n = (() => {
|
|
|
1758
1784
|
|
|
1759
1785
|
"settings.accentColor.title": "主色",
|
|
1760
1786
|
|
|
1787
|
+
"settings.bgTheme.title": "背景",
|
|
1788
|
+
"settings.bgTheme.light": "浅色",
|
|
1789
|
+
"settings.bgTheme.dark": "深色",
|
|
1790
|
+
"settings.bgTheme.dim": "暗灰",
|
|
1791
|
+
"settings.bgTheme.warm": "暖色",
|
|
1792
|
+
"settings.bgTheme.black": "蓝黑",
|
|
1793
|
+
|
|
1761
1794
|
// ── Onboard ──
|
|
1762
1795
|
"onboard.title": "欢迎使用 {{brand}}",
|
|
1763
1796
|
"onboard.subtitle": "一分钟完成配置,马上开始。",
|
|
@@ -1792,6 +1825,7 @@ const I18n = (() => {
|
|
|
1792
1825
|
"onboard.manual.toggle": "使用自己的 API Key 手动配置",
|
|
1793
1826
|
"onboard.provider.custom": "自定义",
|
|
1794
1827
|
"provider.recommended": "推荐",
|
|
1828
|
+
"provider.name.volcengine_ark": "火山方舟(豆包)",
|
|
1795
1829
|
"provider.promo.openclacky.title": "推荐 OpenClacky AI Keys",
|
|
1796
1830
|
"provider.promo.openclacky.1": "一个 Key 直通全球顶级模型 — Claude、Gemini、DeepSeek 等。",
|
|
1797
1831
|
"provider.promo.openclacky.2": "最强推理 × 最优性价比 × 最佳视觉 — 一站式统一接入。",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -54,6 +54,14 @@
|
|
|
54
54
|
<div id="header-right">
|
|
55
55
|
<span id="ext-slot-header-right" data-slot="header.right"></span>
|
|
56
56
|
|
|
57
|
+
<button id="reload-header" class="theme-toggle-btn" title="Reload" onclick="location.reload()">
|
|
58
|
+
<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="icon-sm">
|
|
59
|
+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
|
60
|
+
<path d="M21 3v5h-5"/>
|
|
61
|
+
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
|
|
62
|
+
<path d="M8 16H3v5"/>
|
|
63
|
+
</svg>
|
|
64
|
+
</button>
|
|
57
65
|
<button id="share-toggle-header" class="theme-toggle-btn" data-i18n-title="share.tooltip" title="Share">
|
|
58
66
|
<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="icon-sm">
|
|
59
67
|
<circle cx="18" cy="5" r="3"/>
|
|
@@ -64,11 +72,7 @@
|
|
|
64
72
|
</svg>
|
|
65
73
|
</button>
|
|
66
74
|
<button id="notify-toggle-header" class="theme-toggle-btn" data-i18n-title="notify.tooltip.off" title="Sound on task complete"></button>
|
|
67
|
-
|
|
68
|
-
<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="icon-sm">
|
|
69
|
-
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
|
|
70
|
-
</svg>
|
|
71
|
-
</button>
|
|
75
|
+
|
|
72
76
|
</div>
|
|
73
77
|
</header>
|
|
74
78
|
|
|
@@ -680,6 +684,7 @@
|
|
|
680
684
|
<div class="extensions-toolbar">
|
|
681
685
|
<div class="extensions-filter-tabs">
|
|
682
686
|
<button type="button" class="extensions-filter-tab extensions-filter-tab-active" data-filter="all" data-i18n="extensions.filter.all">All</button>
|
|
687
|
+
<button type="button" class="extensions-filter-tab" data-filter="brand" id="tab-extensions-brand" data-i18n="extensions.filter.brand" style="display:none">Brand</button>
|
|
683
688
|
<button type="button" class="extensions-filter-tab" data-filter="installed" data-i18n="extensions.filter.installed">Installed</button>
|
|
684
689
|
</div>
|
|
685
690
|
<div class="extensions-toolbar-right">
|
|
@@ -949,6 +954,43 @@
|
|
|
949
954
|
</div>
|
|
950
955
|
</section>
|
|
951
956
|
|
|
957
|
+
<!-- Background Theme section -->
|
|
958
|
+
<section class="settings-section" id="bg-theme-section">
|
|
959
|
+
<div class="settings-section-title">
|
|
960
|
+
<span data-i18n="settings.bgTheme.title">Background</span>
|
|
961
|
+
</div>
|
|
962
|
+
<div class="settings-bg-theme-cards">
|
|
963
|
+
<button class="settings-bg-theme-card bg-theme-dark" data-bg-theme="dark">
|
|
964
|
+
<div class="bg-card-preview">
|
|
965
|
+
<div class="bg-card-sidebar"></div>
|
|
966
|
+
<div class="bg-card-main"></div>
|
|
967
|
+
</div>
|
|
968
|
+
<span class="bg-card-label" data-i18n="settings.bgTheme.dark">Dark</span>
|
|
969
|
+
</button>
|
|
970
|
+
<button class="settings-bg-theme-card bg-theme-dim" data-bg-theme="dim">
|
|
971
|
+
<div class="bg-card-preview">
|
|
972
|
+
<div class="bg-card-sidebar"></div>
|
|
973
|
+
<div class="bg-card-main"></div>
|
|
974
|
+
</div>
|
|
975
|
+
<span class="bg-card-label" data-i18n="settings.bgTheme.dim">Dim</span>
|
|
976
|
+
</button>
|
|
977
|
+
<button class="settings-bg-theme-card bg-theme-warm" data-bg-theme="warm">
|
|
978
|
+
<div class="bg-card-preview">
|
|
979
|
+
<div class="bg-card-sidebar"></div>
|
|
980
|
+
<div class="bg-card-main"></div>
|
|
981
|
+
</div>
|
|
982
|
+
<span class="bg-card-label" data-i18n="settings.bgTheme.warm">Warm</span>
|
|
983
|
+
</button>
|
|
984
|
+
<button class="settings-bg-theme-card bg-theme-light" data-bg-theme="light">
|
|
985
|
+
<div class="bg-card-preview">
|
|
986
|
+
<div class="bg-card-sidebar"></div>
|
|
987
|
+
<div class="bg-card-main"></div>
|
|
988
|
+
</div>
|
|
989
|
+
<span class="bg-card-label" data-i18n="settings.bgTheme.light">Light</span>
|
|
990
|
+
</button>
|
|
991
|
+
</div>
|
|
992
|
+
</section>
|
|
993
|
+
|
|
952
994
|
<!-- Accent Color section -->
|
|
953
995
|
<section class="settings-section" id="accent-color-section">
|
|
954
996
|
<div class="settings-section-title">
|
|
@@ -961,6 +1003,10 @@
|
|
|
961
1003
|
<button class="settings-accent-swatch swatch-sunrise-orange" data-color="#F59E0B" title="Sunrise Orange"></button>
|
|
962
1004
|
<button class="settings-accent-swatch swatch-rose-violet" data-color="#8B5CF6" title="Rose Violet"></button>
|
|
963
1005
|
<button class="settings-accent-swatch swatch-coral-red" data-color="#EF4444" title="Coral Red"></button>
|
|
1006
|
+
<button class="settings-accent-swatch swatch-sky-blue" data-color="#38BDF8" title="Sky Blue"></button>
|
|
1007
|
+
<button class="settings-accent-swatch swatch-hot-pink" data-color="#EC4899" title="Hot Pink"></button>
|
|
1008
|
+
<button class="settings-accent-swatch swatch-warm-brown" data-color="#92745A" title="Warm Brown"></button>
|
|
1009
|
+
<button class="settings-accent-swatch swatch-slate-gray" data-color="#6B7280" title="Slate Gray"></button>
|
|
964
1010
|
</div>
|
|
965
1011
|
</section>
|
|
966
1012
|
</div>
|
data/lib/clacky/web/sessions.js
CHANGED
|
@@ -390,11 +390,13 @@ const Sessions = (() => {
|
|
|
390
390
|
const tip1 = I18n.t("chat.empty.tip1");
|
|
391
391
|
const tip2 = I18n.t("chat.empty.tip2");
|
|
392
392
|
const tip3 = I18n.t("chat.empty.tip3");
|
|
393
|
-
const tip4 = I18n.t("chat.empty.tip4");
|
|
394
393
|
return `
|
|
395
394
|
<div class="chat-empty-icon" aria-hidden="true">
|
|
396
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
397
|
-
<path d="M21
|
|
395
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
396
|
+
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
|
|
397
|
+
<circle cx="8.5" cy="12" r="0.9" fill="currentColor" stroke="none"/>
|
|
398
|
+
<circle cx="12" cy="12" r="0.9" fill="currentColor" stroke="none"/>
|
|
399
|
+
<circle cx="15.5" cy="12" r="0.9" fill="currentColor" stroke="none"/>
|
|
398
400
|
</svg>
|
|
399
401
|
</div>
|
|
400
402
|
<div class="chat-empty-title">${escapeHtml(title)}</div>
|
|
@@ -403,7 +405,6 @@ const Sessions = (() => {
|
|
|
403
405
|
<li>${escapeHtml(tip1)}</li>
|
|
404
406
|
<li>${escapeHtml(tip2)}</li>
|
|
405
407
|
<li>${escapeHtml(tip3)}</li>
|
|
406
|
-
<li>${escapeHtml(tip4)}</li>
|
|
407
408
|
</ul>
|
|
408
409
|
`;
|
|
409
410
|
}
|
|
@@ -503,7 +504,7 @@ const Sessions = (() => {
|
|
|
503
504
|
if (_btnNewInline.disabled) return;
|
|
504
505
|
_btnNewInline.disabled = true;
|
|
505
506
|
try {
|
|
506
|
-
const session = await NewSessionStore.createSession({ existingSessions: _sessions });
|
|
507
|
+
const session = await NewSessionStore.createSession({ existingSessions: _sessions, useDefaults: true });
|
|
507
508
|
if (!session) return;
|
|
508
509
|
NewSessionStore.reset();
|
|
509
510
|
// Add to local list immediately so renderList shows it and findOrFetch
|
|
@@ -1271,11 +1272,16 @@ const Sessions = (() => {
|
|
|
1271
1272
|
|
|
1272
1273
|
// Mark the last tool-item in a group as done (update status indicator).
|
|
1273
1274
|
// collapsed: true → keep stdout hidden (history mode); false → show immediately (live mode).
|
|
1274
|
-
function _completeLastToolItem(group, result,
|
|
1275
|
+
function _completeLastToolItem(group, result, opts = {}) {
|
|
1275
1276
|
const body = group.querySelector(".tool-group-body");
|
|
1276
1277
|
const items = body.querySelectorAll(".tool-item");
|
|
1277
1278
|
if (!items.length) return;
|
|
1278
|
-
|
|
1279
|
+
_completeToolItem(items[items.length - 1], result, opts);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Mark a specific tool-item element as done (update status + render result).
|
|
1283
|
+
function _completeToolItem(last, result, { collapsed = false } = {}) {
|
|
1284
|
+
if (!last) return;
|
|
1279
1285
|
const status = last.querySelector(".tool-item-status");
|
|
1280
1286
|
if (status) {
|
|
1281
1287
|
status.className = "tool-item-status ok";
|
|
@@ -3248,13 +3254,19 @@ const Sessions = (() => {
|
|
|
3248
3254
|
if (sibStatus) {
|
|
3249
3255
|
sibStatus.innerHTML = `<span class="sib-dot"></span>${s.status || "idle"}`;
|
|
3250
3256
|
sibStatus.className = `sib-status-${s.status || "idle"}`;
|
|
3257
|
+
sibStatus.title = I18n.t("sib.status.tooltip");
|
|
3251
3258
|
}
|
|
3252
3259
|
|
|
3253
|
-
// Session ID
|
|
3254
|
-
// trigger for the session actions dropdown (download, etc.).
|
|
3260
|
+
// Session ID label — shows localised "Session file" label with short hash suffix.
|
|
3261
|
+
// The span itself is the click trigger for the session actions dropdown (download, etc.).
|
|
3255
3262
|
const sibId = $("sib-id");
|
|
3256
3263
|
if (sibId) {
|
|
3257
|
-
|
|
3264
|
+
const shortHash = s.id ? s.id.slice(0, 8) : "";
|
|
3265
|
+
const lp = I18n.lang() === "zh" ? "(" : " (";
|
|
3266
|
+
const rp = I18n.lang() === "zh" ? ")" : ")";
|
|
3267
|
+
sibId.textContent = shortHash
|
|
3268
|
+
? `${I18n.t("sib.id.label")}${lp}${shortHash}${rp}`
|
|
3269
|
+
: I18n.t("sib.id.label");
|
|
3258
3270
|
sibId.title = s.id || "";
|
|
3259
3271
|
if (s.id) {
|
|
3260
3272
|
sibId.dataset.sessionId = s.id;
|
|
@@ -3277,6 +3289,7 @@ const Sessions = (() => {
|
|
|
3277
3289
|
const sibSepAfterMode = document.querySelector(".sib-sep-after-mode");
|
|
3278
3290
|
if (sibMode) {
|
|
3279
3291
|
sibMode.textContent = s.permission_mode || "";
|
|
3292
|
+
sibMode.title = s.permission_mode ? I18n.t("sib.mode.tooltip") : "";
|
|
3280
3293
|
sibMode.style.display = s.permission_mode ? "" : "none";
|
|
3281
3294
|
}
|
|
3282
3295
|
if (sibSepAfterMode) {
|
|
@@ -3331,7 +3344,10 @@ const Sessions = (() => {
|
|
|
3331
3344
|
|
|
3332
3345
|
// Tasks
|
|
3333
3346
|
const sibTasks = $("sib-tasks");
|
|
3334
|
-
if (sibTasks)
|
|
3347
|
+
if (sibTasks) {
|
|
3348
|
+
sibTasks.textContent = I18n.t("sessions.metaTasks", { n: s.total_tasks || 0 });
|
|
3349
|
+
sibTasks.title = I18n.t("sib.tasks.tooltip");
|
|
3350
|
+
}
|
|
3335
3351
|
|
|
3336
3352
|
// Cost — show N/A when pricing is unknown (estimated)
|
|
3337
3353
|
const sibCost = $("sib-cost");
|
|
@@ -3343,6 +3359,7 @@ const Sessions = (() => {
|
|
|
3343
3359
|
} else {
|
|
3344
3360
|
sibCost.textContent = "N/A";
|
|
3345
3361
|
}
|
|
3362
|
+
sibCost.title = I18n.t("sib.cost.tooltip");
|
|
3346
3363
|
}
|
|
3347
3364
|
|
|
3348
3365
|
const bar = $("session-info-bar");
|
|
@@ -3484,9 +3501,21 @@ const Sessions = (() => {
|
|
|
3484
3501
|
},
|
|
3485
3502
|
|
|
3486
3503
|
// Update the last tool-item with a result status tick.
|
|
3504
|
+
// If _liveToolGroup was collapsed by an intervening info message (e.g.
|
|
3505
|
+
// "Subagent start/completed" during invoke_skill), fall back to the last
|
|
3506
|
+
// still-running .tool-item in the DOM.
|
|
3487
3507
|
appendToolResult(result) {
|
|
3488
3508
|
if (Sessions._liveToolGroup && Sessions._liveLastToolItem) {
|
|
3489
3509
|
_completeLastToolItem(Sessions._liveToolGroup, result);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const messages = RenderTarget.current();
|
|
3513
|
+
if (messages) {
|
|
3514
|
+
const running = messages.querySelectorAll(".tool-item-status.running");
|
|
3515
|
+
if (running.length > 0) {
|
|
3516
|
+
const item = running[running.length - 1].closest(".tool-item");
|
|
3517
|
+
if (item) { _completeToolItem(item, result); return; }
|
|
3518
|
+
}
|
|
3490
3519
|
}
|
|
3491
3520
|
},
|
|
3492
3521
|
|
data/lib/clacky/web/settings.js
CHANGED
|
@@ -59,9 +59,15 @@ const Settings = (() => {
|
|
|
59
59
|
_models.forEach((m, i) => _renderCard(container, m, i));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function _providerDisplayName(p) {
|
|
63
|
+
if (!p) return I18n.t("settings.models.provider.custom");
|
|
64
|
+
const translated = p.name_key ? I18n.t(p.name_key) : null;
|
|
65
|
+
return (translated && translated !== p.name_key) ? translated : p.name;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
function _getProviderName(model) {
|
|
63
69
|
const p = _findProviderByBaseUrl(model.base_url);
|
|
64
|
-
return p
|
|
70
|
+
return _providerDisplayName(p);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
function _findProviderByBaseUrl(baseUrl) {
|
|
@@ -82,7 +88,7 @@ const Settings = (() => {
|
|
|
82
88
|
const isDefault = model.type === "default";
|
|
83
89
|
const isLite = model.type === "lite";
|
|
84
90
|
const provider = _findProviderByBaseUrl(model.base_url);
|
|
85
|
-
const providerName =
|
|
91
|
+
const providerName = _providerDisplayName(provider);
|
|
86
92
|
const websiteUrl = provider && provider.website_url;
|
|
87
93
|
const displayName = model.model || I18n.t("settings.models.unnamed");
|
|
88
94
|
|
|
@@ -266,7 +272,7 @@ const Settings = (() => {
|
|
|
266
272
|
const dropdown = document.getElementById("model-modal-provider-dropdown");
|
|
267
273
|
dropdown.innerHTML = `
|
|
268
274
|
<div class="custom-select-option" data-value="">${I18n.t("settings.models.placeholder.provider")}</div>
|
|
269
|
-
${_providers.map(p => `<div class="custom-select-option" data-value="${p.id}" data-label="${_esc(
|
|
275
|
+
${_providers.map(p => { const dn = _providerDisplayName(p); return `<div class="custom-select-option" data-value="${p.id}" data-label="${_esc(dn)}">${_esc(dn)}${p.id === "openclacky" ? ` <span class="provider-badge-recommended">${I18n.t("provider.recommended")}</span>` : ""}</div>`; }).join("")}
|
|
270
276
|
<div class="custom-select-option" data-value="custom">${I18n.t("settings.models.custom")}</div>
|
|
271
277
|
`;
|
|
272
278
|
|
|
@@ -2207,6 +2213,7 @@ const Settings = (() => {
|
|
|
2207
2213
|
_initFontBtns();
|
|
2208
2214
|
_initCurrencyBtns();
|
|
2209
2215
|
_initAccentColorBtns();
|
|
2216
|
+
_initBgThemeCards();
|
|
2210
2217
|
|
|
2211
2218
|
// Re-render model cards when language changes (dynamic HTML, not data-i18n)
|
|
2212
2219
|
document.addEventListener("langchange", () => {
|
|
@@ -2396,6 +2403,18 @@ const Settings = (() => {
|
|
|
2396
2403
|
});
|
|
2397
2404
|
}
|
|
2398
2405
|
|
|
2406
|
+
// ── Background Theme Cards ────────────────────────────────────────────
|
|
2407
|
+
function _initBgThemeCards() {
|
|
2408
|
+
// Sync initial active state from Theme module.
|
|
2409
|
+
const current = Theme.current();
|
|
2410
|
+
document.querySelectorAll("#bg-theme-section .settings-bg-theme-card").forEach(btn => {
|
|
2411
|
+
btn.classList.toggle("active", btn.dataset.bgTheme === current);
|
|
2412
|
+
btn.addEventListener("click", () => {
|
|
2413
|
+
Theme.applyBg(btn.dataset.bgTheme);
|
|
2414
|
+
});
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2399
2418
|
// ── QR Code Lightbox ───────────────────────────────────────────────────
|
|
2400
2419
|
// Sets up click-to-enlarge behaviour for the support QR code.
|
|
2401
2420
|
// Safe to call multiple times — idempotent via a data attribute guard.
|