openclacky 1.3.4 → 1.3.6
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 +39 -0
- data/lib/clacky/agent/fake_tool_call_detector.rb +52 -0
- data/lib/clacky/agent/message_compressor.rb +32 -8
- data/lib/clacky/agent/message_compressor_helper.rb +113 -12
- data/lib/clacky/agent/session_serializer.rb +3 -2
- data/lib/clacky/agent/tool_executor.rb +0 -12
- data/lib/clacky/agent.rb +74 -12
- data/lib/clacky/api_extension.rb +81 -0
- data/lib/clacky/api_extension_loader.rb +13 -1
- data/lib/clacky/cli.rb +0 -1
- data/lib/clacky/client.rb +14 -17
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +22 -0
- data/lib/clacky/default_agents/base_prompt.md +1 -0
- data/lib/clacky/default_extensions/meeting/handler.rb +331 -0
- data/lib/clacky/default_extensions/meeting/meeting.js +790 -0
- data/lib/clacky/default_extensions/meeting/meta.yml +3 -0
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +44 -0
- data/lib/clacky/default_skills/media-gen/SKILL.md +63 -0
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +114 -0
- data/lib/clacky/json_ui_controller.rb +1 -1
- data/lib/clacky/media/base.rb +60 -0
- data/lib/clacky/media/dashscope.rb +385 -21
- data/lib/clacky/media/gemini.rb +9 -0
- data/lib/clacky/media/generator.rb +52 -0
- data/lib/clacky/media/openai_compat.rb +166 -0
- data/lib/clacky/null_ui_controller.rb +13 -0
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/providers.rb +50 -2
- data/lib/clacky/rich_ui/rich_ui_controller.rb +1 -1
- data/lib/clacky/server/channel/channel_ui_controller.rb +1 -1
- data/lib/clacky/server/http_server.rb +260 -122
- data/lib/clacky/server/session_registry.rb +54 -3
- data/lib/clacky/server/web_ui_controller.rb +3 -2
- data/lib/clacky/session_manager.rb +35 -4
- data/lib/clacky/skill_loader.rb +14 -2
- data/lib/clacky/tools/terminal/output_cleaner.rb +1 -3
- data/lib/clacky/tools/terminal.rb +0 -43
- data/lib/clacky/ui2/components/modal_component.rb +1 -1
- data/lib/clacky/ui2/layout_manager.rb +0 -5
- data/lib/clacky/ui2/progress_handle.rb +0 -3
- data/lib/clacky/ui2/ui_controller.rb +140 -40
- data/lib/clacky/ui_interface.rb +10 -1
- data/lib/clacky/utils/encoding.rb +25 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +199 -23
- data/lib/clacky/web/components/onboard.js +1 -14
- data/lib/clacky/web/components/sidebar.js +1 -3
- data/lib/clacky/web/features/backup/store.js +23 -0
- data/lib/clacky/web/features/backup/view.js +49 -22
- data/lib/clacky/web/features/brand/view.js +8 -5
- data/lib/clacky/web/features/channels/store.js +1 -20
- data/lib/clacky/web/features/mcp/store.js +1 -20
- data/lib/clacky/web/features/profile/store.js +1 -13
- data/lib/clacky/web/features/profile/view.js +16 -4
- data/lib/clacky/web/features/skills/store.js +6 -21
- data/lib/clacky/web/features/tasks/view.js +77 -28
- data/lib/clacky/web/features/version/store.js +2 -0
- data/lib/clacky/web/i18n.js +46 -3
- data/lib/clacky/web/index.html +67 -26
- data/lib/clacky/web/sessions.js +177 -87
- data/lib/clacky/web/settings.js +34 -2
- data/lib/clacky/web/ws-dispatcher.js +12 -4
- data/lib/clacky.rb +12 -5
- metadata +8 -1
|
@@ -49,7 +49,7 @@ const ProfileView = (() => {
|
|
|
49
49
|
|
|
50
50
|
function flushPara() {
|
|
51
51
|
if (paraBuf.length === 0) return;
|
|
52
|
-
out.push("<p>" + _renderInline(paraBuf.join("
|
|
52
|
+
out.push("<p>" + _renderInline(paraBuf.join("<br>")) + "</p>");
|
|
53
53
|
paraBuf = [];
|
|
54
54
|
}
|
|
55
55
|
function openList(type) {
|
|
@@ -152,11 +152,17 @@ const ProfileView = (() => {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
const openSet = new Set(
|
|
156
|
+
[...list.querySelectorAll(".memory-card")]
|
|
157
|
+
.filter(c => c.querySelector(".memory-card-body")?.style.display !== "none")
|
|
158
|
+
.map(c => c.dataset.filename)
|
|
159
|
+
);
|
|
160
|
+
|
|
155
161
|
list.innerHTML = "";
|
|
156
|
-
memories.forEach(m => list.appendChild(_buildMemoryCard(m)));
|
|
162
|
+
memories.forEach(m => list.appendChild(_buildMemoryCard(m, openSet.has(m.filename))));
|
|
157
163
|
}
|
|
158
164
|
|
|
159
|
-
function _buildMemoryCard(m) {
|
|
165
|
+
function _buildMemoryCard(m, expanded = false) {
|
|
160
166
|
const card = document.createElement("div");
|
|
161
167
|
card.className = "memory-card";
|
|
162
168
|
card.dataset.filename = m.filename;
|
|
@@ -254,6 +260,8 @@ const ProfileView = (() => {
|
|
|
254
260
|
expandBtn.addEventListener("click", (e) => { e.stopPropagation(); toggle(); });
|
|
255
261
|
head.querySelector(".memory-card-info").addEventListener("click", toggle);
|
|
256
262
|
|
|
263
|
+
if (expanded) toggle();
|
|
264
|
+
|
|
257
265
|
return card;
|
|
258
266
|
}
|
|
259
267
|
|
|
@@ -303,6 +311,7 @@ const ProfileView = (() => {
|
|
|
303
311
|
onSave: async (newContent) => {
|
|
304
312
|
const r = await Profile.updateMemory(m.filename, newContent);
|
|
305
313
|
if (!r.ok) throw new Error(r.error);
|
|
314
|
+
Modal.toast(_t("profile.savedOk"), "success");
|
|
306
315
|
}
|
|
307
316
|
});
|
|
308
317
|
}
|
|
@@ -333,7 +342,10 @@ const ProfileView = (() => {
|
|
|
333
342
|
content: file.content || "",
|
|
334
343
|
title,
|
|
335
344
|
language: "markdown",
|
|
336
|
-
onSave: (newContent) =>
|
|
345
|
+
onSave: async (newContent) => {
|
|
346
|
+
await Profile.updateProfile(kind, newContent);
|
|
347
|
+
Modal.toast(_t("profile.savedOk"), "success");
|
|
348
|
+
}
|
|
337
349
|
});
|
|
338
350
|
}
|
|
339
351
|
|
|
@@ -71,28 +71,13 @@ const SkillsStore = (() => {
|
|
|
71
71
|
// Resolve the next "Session N" name and create a session, then hand off to
|
|
72
72
|
// Sessions. Used by every "open a session and run a command" action.
|
|
73
73
|
async function _openSessionWith(message) {
|
|
74
|
-
const maxN = Sessions.all.reduce((max, s) => {
|
|
75
|
-
const m = s.name.match(/^Session (\d+)$/);
|
|
76
|
-
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
77
|
-
}, 0);
|
|
78
|
-
const res = await fetch("/api/sessions", {
|
|
79
|
-
method: "POST",
|
|
80
|
-
headers: { "Content-Type": "application/json" },
|
|
81
|
-
body: JSON.stringify({ name: "Session " + (maxN + 1), source: "manual" })
|
|
82
|
-
});
|
|
83
|
-
const data = await res.json();
|
|
84
|
-
if (!res.ok) { alert(I18n.t("tasks.sessionError") + (data.error || "unknown")); return null; }
|
|
85
|
-
|
|
86
|
-
const session = data.session;
|
|
87
|
-
if (!session) return null;
|
|
88
|
-
|
|
89
74
|
if (!WS.ready) { WS.connect(); Skills.load(); }
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
75
|
+
try {
|
|
76
|
+
return await Sessions.startWith(message, { source: "manual" });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
alert(I18n.t("tasks.sessionError") + (e.message || "unknown"));
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
96
81
|
}
|
|
97
82
|
|
|
98
83
|
/** Return a user-friendly message for install/update errors. */
|
|
@@ -16,58 +16,107 @@ const TasksView = (() => {
|
|
|
16
16
|
if (!cron) return cron;
|
|
17
17
|
const parts = cron.trim().split(/\s+/);
|
|
18
18
|
if (parts.length !== 5) return cron;
|
|
19
|
-
|
|
19
|
+
let [min, hour, dom, month, dow] = parts;
|
|
20
|
+
|
|
21
|
+
// normalize */1 → *
|
|
22
|
+
if (min === "*/1") min = "*";
|
|
23
|
+
if (hour === "*/1") hour = "*";
|
|
24
|
+
if (dom === "*/1") dom = "*";
|
|
25
|
+
if (month === "*/1") month = "*";
|
|
26
|
+
if (dow === "*/1") dow = "*";
|
|
20
27
|
|
|
21
28
|
const isAny = v => v === "*";
|
|
22
|
-
const
|
|
29
|
+
const isInt = v => /^\d+$/.test(v);
|
|
23
30
|
const pad = n => String(n).padStart(2, "0");
|
|
24
31
|
|
|
25
32
|
const lang = (typeof I18n !== "undefined" && I18n.lang()) || "zh";
|
|
26
33
|
const isZh = lang === "zh";
|
|
27
34
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
const DOW_ZH = ["周日","周一","周二","周三","周四","周五","周六"];
|
|
36
|
+
const DOW_EN = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
|
|
37
|
+
const MON_ZH = ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"];
|
|
38
|
+
const MON_EN = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
// dow → label, e.g. "1-5"→"工作日", "1,3,5"→"周一、周三、周五", "1"→"每周一"
|
|
41
|
+
function dowLabel(d) {
|
|
42
|
+
if (isAny(d)) return null;
|
|
43
|
+
if (d === "1-5") return isZh ? "工作日" : "Weekdays";
|
|
44
|
+
if (d === "0,6" || d === "6,0") return isZh ? "周末" : "Weekends";
|
|
45
|
+
if (isInt(d)) {
|
|
46
|
+
const name = (isZh ? DOW_ZH : DOW_EN)[parseInt(d, 10)] || d;
|
|
47
|
+
return isZh ? `每${name}` : name;
|
|
48
|
+
}
|
|
49
|
+
if (/^[\d,]+$/.test(d)) {
|
|
50
|
+
const names = d.split(",").map(n => (isZh ? DOW_ZH : DOW_EN)[parseInt(n, 10)] || n);
|
|
51
|
+
return isZh ? names.join("、") : names.join("/");
|
|
52
|
+
}
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
// build HH:MM string; supports single hour or comma-list like "10,14"
|
|
57
|
+
function timeStr() {
|
|
58
|
+
if (!isInt(min)) return null;
|
|
59
|
+
if (isInt(hour)) return `${pad(hour)}:${pad(min)}`;
|
|
60
|
+
if (/^[\d,]+$/.test(hour))
|
|
61
|
+
return hour.split(",").map(h => `${pad(h)}:${pad(min)}`).join(isZh ? "、" : "/");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
37
64
|
|
|
65
|
+
// ── every-N-minutes ───────────────────────────────────────────────────
|
|
38
66
|
if (min.startsWith("*/") && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
39
67
|
const n = min.slice(2);
|
|
40
68
|
return isZh ? `每 ${n} 分钟` : `Every ${n} min`;
|
|
41
69
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
// ── every-N-hours ─────────────────────────────────────────────────────
|
|
71
|
+
if (isAny(dom) && isAny(month) && isAny(dow)) {
|
|
72
|
+
if (isAny(min) && hour.startsWith("*/")) {
|
|
73
|
+
return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
74
|
+
}
|
|
75
|
+
if (isInt(min) && hour.startsWith("*/")) {
|
|
76
|
+
return isZh ? `每 ${hour.slice(2)} 小时` : `Every ${hour.slice(2)} hr`;
|
|
77
|
+
}
|
|
45
78
|
}
|
|
79
|
+
// ── every minute ──────────────────────────────────────────────────────
|
|
46
80
|
if (isAny(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
47
81
|
return isZh ? "每分钟" : "Every minute";
|
|
48
82
|
}
|
|
49
|
-
|
|
83
|
+
// ── hourly at :MM ─────────────────────────────────────────────────────
|
|
84
|
+
if (isInt(min) && isAny(hour) && isAny(dom) && isAny(month) && isAny(dow)) {
|
|
50
85
|
return isZh ? `每小时 :${pad(min)}` : `Hourly at :${pad(min)}`;
|
|
51
86
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
|
|
88
|
+
// ── every-N-min within hour range on certain days e.g. */1 9-14 * * 1-5
|
|
89
|
+
if (min.startsWith("*/") && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
|
|
90
|
+
const n = min.slice(2);
|
|
91
|
+
const dl = dowLabel(dow);
|
|
92
|
+
const hr = isZh ? `${hour}时` : `${hour}h`;
|
|
93
|
+
const ev = isZh ? `每 ${n} 分钟` : `Every ${n} min`;
|
|
94
|
+
return dl ? `${dl} ${hr} ${ev}` : `${hr} ${ev}`;
|
|
58
95
|
}
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
// ── every minute within hour range e.g. * 9-14 * * 1-5
|
|
97
|
+
if (isAny(min) && /^\d+-\d+$/.test(hour) && isAny(dom) && isAny(month)) {
|
|
98
|
+
const dl = dowLabel(dow);
|
|
99
|
+
const hr = isZh ? `${hour}时` : `${hour}h`;
|
|
100
|
+
const ev = isZh ? "每分钟" : "Every minute";
|
|
101
|
+
return dl ? `${dl} ${hr} ${ev}` : `${hr} ${ev}`;
|
|
61
102
|
}
|
|
62
|
-
|
|
63
|
-
|
|
103
|
+
|
|
104
|
+
const ts = timeStr();
|
|
105
|
+
const dl = dowLabel(dow);
|
|
106
|
+
|
|
107
|
+
// ── fixed time, variable days ─────────────────────────────────────────
|
|
108
|
+
if (ts && isAny(dom) && isAny(month)) {
|
|
109
|
+
if (dl) return `${dl} ${ts}`;
|
|
110
|
+
return isZh ? `每天 ${ts}` : `Daily ${ts}`;
|
|
64
111
|
}
|
|
65
|
-
|
|
66
|
-
|
|
112
|
+
// ── fixed time, fixed day-of-month ────────────────────────────────────
|
|
113
|
+
if (ts && isInt(dom) && isAny(month) && isAny(dow)) {
|
|
114
|
+
return isZh ? `每月 ${dom} 日 ${ts}` : `Monthly day ${dom} ${ts}`;
|
|
67
115
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
116
|
+
// ── fixed time, fixed date ────────────────────────────────────────────
|
|
117
|
+
if (ts && isInt(dom) && isInt(month) && isAny(dow)) {
|
|
118
|
+
const m = (isZh ? MON_ZH : MON_EN)[parseInt(month, 10) - 1] || month;
|
|
119
|
+
return isZh ? `${m}${dom}日 ${ts}` : `${m} ${dom} ${ts}`;
|
|
71
120
|
}
|
|
72
121
|
|
|
73
122
|
return cron;
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -68,6 +68,9 @@ const I18n = (() => {
|
|
|
68
68
|
"chat.resetSession": "Reset session",
|
|
69
69
|
"chat.resetSessionConfirm": "Reset will start a brand-new session. The current conversation history stays in your sidebar but will no longer be active. Continue?",
|
|
70
70
|
"chat.copy": "Copy",
|
|
71
|
+
"chat.todo.spawn": "Start",
|
|
72
|
+
"chat.todo.spawning": "Starting…",
|
|
73
|
+
"chat.todo.spawnFailed": "Failed to start session: {msg}",
|
|
71
74
|
"chat.copied": "Copied",
|
|
72
75
|
"chat.continue": "Continue",
|
|
73
76
|
"chat.edit": "Edit",
|
|
@@ -549,6 +552,7 @@ const I18n = (() => {
|
|
|
549
552
|
"settings.tabs.models": "Models",
|
|
550
553
|
"settings.tabs.ui": "UI",
|
|
551
554
|
"settings.tabs.general": "General",
|
|
555
|
+
"settings.tabs.data": "Data Management",
|
|
552
556
|
"settings.tabs.about": "About",
|
|
553
557
|
"settings.about.version": "Version",
|
|
554
558
|
"settings.about.appName": "Application",
|
|
@@ -570,6 +574,8 @@ const I18n = (() => {
|
|
|
570
574
|
"settings.media.kind.image": "Image",
|
|
571
575
|
"settings.media.kind.video": "Video",
|
|
572
576
|
"settings.media.kind.audio": "Audio",
|
|
577
|
+
"settings.media.kind.stt": "Speech-to-Text",
|
|
578
|
+
"settings.media.kind.video_understanding": "Video Understanding",
|
|
573
579
|
"settings.media.kind.ocr": "Vision",
|
|
574
580
|
"settings.media.source.off": "Off",
|
|
575
581
|
"settings.media.source.auto": "Auto",
|
|
@@ -669,13 +675,22 @@ const I18n = (() => {
|
|
|
669
675
|
"settings.network.cleared": "Cleared — direct connection",
|
|
670
676
|
"settings.network.invalidUrl": "Invalid URL — use http://host:port or http://user:pass@host:port",
|
|
671
677
|
"settings.backup.title": "Backup",
|
|
678
|
+
"settings.backup.autoTitle": "Auto Backup",
|
|
679
|
+
"settings.backup.manualTitle": "Manual Backup",
|
|
672
680
|
"settings.backup.desc": "Back up your ~/.clacky directory (config, skills, memories, tasks, sessions). Regenerable caches and logs are excluded.",
|
|
673
681
|
"settings.backup.includeSessions": "Include session history (larger archive)",
|
|
674
682
|
"settings.backup.autoLabel": "Automatic backup",
|
|
675
683
|
"settings.backup.autoHint": "Daily at 03:00, keeps the latest 7",
|
|
676
|
-
"settings.backup.
|
|
684
|
+
"settings.backup.destLabel": "Backup folder",
|
|
685
|
+
"settings.backup.openFolder": "Open",
|
|
686
|
+
"settings.backup.runNow": "Download backup",
|
|
687
|
+
"settings.backup.restoreBtn": "Restore backup",
|
|
677
688
|
"settings.backup.running": "Preparing backup…",
|
|
689
|
+
"settings.backup.downloading": "Downloading…",
|
|
678
690
|
"settings.backup.downloaded": "Backup downloaded.",
|
|
691
|
+
"settings.backup.restoring": "Restoring… server will restart automatically.",
|
|
692
|
+
"settings.backup.restoreOk": "Restored. Restarting…",
|
|
693
|
+
"settings.backup.restartOk": "Restarted successfully.",
|
|
679
694
|
"settings.backup.lastOk": "Last backup: {{time}}",
|
|
680
695
|
"settings.backup.lastError": "Backup failed: {{msg}}",
|
|
681
696
|
"settings.brand.title": "Brand & License",
|
|
@@ -727,6 +742,8 @@ const I18n = (() => {
|
|
|
727
742
|
"settings.currency.updated": "Updated from {{source}} on {{date}}",
|
|
728
743
|
"settings.currency.updateFailed": "Failed to fetch the latest rate. You can still enter it manually.",
|
|
729
744
|
|
|
745
|
+
"settings.accentColor.title": "Accent Color",
|
|
746
|
+
|
|
730
747
|
// ── Onboard ──
|
|
731
748
|
"onboard.title": "Welcome to {{brand}}",
|
|
732
749
|
"onboard.subtitle": "Let's get you set up in a minute.",
|
|
@@ -920,6 +937,7 @@ const I18n = (() => {
|
|
|
920
937
|
|
|
921
938
|
"error.insufficient_credit": "Insufficient LLM credit. Please top up your account to continue.",
|
|
922
939
|
"error.insufficient_credit.action": "Top up",
|
|
940
|
+
"error.show_detail": "Show details",
|
|
923
941
|
|
|
924
942
|
"billing.sessions": "Sessions",
|
|
925
943
|
"billing.sessionId": "Session",
|
|
@@ -986,6 +1004,9 @@ const I18n = (() => {
|
|
|
986
1004
|
"chat.newMessageHint": "有新消息 ↓",
|
|
987
1005
|
"chat.retry": "重试",
|
|
988
1006
|
"chat.copy": "复制",
|
|
1007
|
+
"chat.todo.spawn": "开干",
|
|
1008
|
+
"chat.todo.spawning": "创建中…",
|
|
1009
|
+
"chat.todo.spawnFailed": "创建会话失败:{msg}",
|
|
989
1010
|
"chat.copied": "已复制",
|
|
990
1011
|
"chat.continue": "继续",
|
|
991
1012
|
"chat.edit": "编辑",
|
|
@@ -1466,6 +1487,7 @@ const I18n = (() => {
|
|
|
1466
1487
|
"settings.tabs.models": "模型",
|
|
1467
1488
|
"settings.tabs.ui": "界面",
|
|
1468
1489
|
"settings.tabs.general": "通用",
|
|
1490
|
+
"settings.tabs.data": "数据管理",
|
|
1469
1491
|
"settings.tabs.about": "关于",
|
|
1470
1492
|
"settings.about.version": "版本",
|
|
1471
1493
|
"settings.about.appName": "应用程序",
|
|
@@ -1481,12 +1503,14 @@ const I18n = (() => {
|
|
|
1481
1503
|
"settings.models.badge.default": "默认",
|
|
1482
1504
|
"settings.models.badge.lite": "轻量",
|
|
1483
1505
|
"settings.media.title": "配置副模型",
|
|
1484
|
-
"settings.media.desc": "
|
|
1506
|
+
"settings.media.desc": "图片生成 / 视频生成 / 音频生成 / 语音转写 / 视频理解 / 视觉理解(可选)",
|
|
1485
1507
|
"settings.media.loading": "加载中…",
|
|
1486
1508
|
"settings.media.error": "加载失败:{{msg}}",
|
|
1487
1509
|
"settings.media.kind.image": "图片生成",
|
|
1488
1510
|
"settings.media.kind.video": "视频生成",
|
|
1489
1511
|
"settings.media.kind.audio": "音频生成",
|
|
1512
|
+
"settings.media.kind.stt": "语音转写",
|
|
1513
|
+
"settings.media.kind.video_understanding": "视频理解",
|
|
1490
1514
|
"settings.media.kind.ocr": "视觉理解",
|
|
1491
1515
|
"settings.media.source.off": "关闭",
|
|
1492
1516
|
"settings.media.source.auto": "自动",
|
|
@@ -1514,6 +1538,13 @@ const I18n = (() => {
|
|
|
1514
1538
|
"settings.media.apiKey.required": "请填写 API Key",
|
|
1515
1539
|
"settings.media.model.required": "请填写模型名称",
|
|
1516
1540
|
"settings.media.baseUrl.required": "请填写 Base URL",
|
|
1541
|
+
"settings.media.output_dir.desc": "生成的图片、视频、音频文件保存位置(可选)",
|
|
1542
|
+
"settings.media.output_dir.browse": "选择目录…",
|
|
1543
|
+
"settings.media.output_dir.picker": "选择媒体输出目录",
|
|
1544
|
+
"settings.media.output_dir.clear": "清除",
|
|
1545
|
+
"settings.media.output_dir.saved": "已保存",
|
|
1546
|
+
"settings.media.output_dir.cleared": "已清除",
|
|
1547
|
+
"settings.media.output_dir.invalid": "目录无效",
|
|
1517
1548
|
"settings.models.field.quicksetup": "快速配置",
|
|
1518
1549
|
"settings.models.field.model": "Model",
|
|
1519
1550
|
"settings.models.field.baseurl": "Base URL",
|
|
@@ -1586,13 +1617,22 @@ const I18n = (() => {
|
|
|
1586
1617
|
"settings.network.cleared": "已清除 — 直连",
|
|
1587
1618
|
"settings.network.invalidUrl": "地址格式不正确,请使用 http://host:port 或 http://user:pass@host:port",
|
|
1588
1619
|
"settings.backup.title": "备份",
|
|
1620
|
+
"settings.backup.autoTitle": "自动备份",
|
|
1621
|
+
"settings.backup.manualTitle": "手动备份",
|
|
1589
1622
|
"settings.backup.desc": "备份你的 ~/.clacky 目录(配置、技能、记忆、任务、会话)。可重新生成的缓存和日志会被排除。",
|
|
1590
1623
|
"settings.backup.includeSessions": "包含会话历史(归档更大)",
|
|
1591
1624
|
"settings.backup.autoLabel": "自动备份",
|
|
1592
1625
|
"settings.backup.autoHint": "每天 03:00 自动备份,保留最近 7 份",
|
|
1593
|
-
"settings.backup.
|
|
1626
|
+
"settings.backup.destLabel": "备份目录",
|
|
1627
|
+
"settings.backup.openFolder": "打开",
|
|
1628
|
+
"settings.backup.runNow": "下载备份",
|
|
1629
|
+
"settings.backup.restoreBtn": "恢复备份",
|
|
1594
1630
|
"settings.backup.running": "正在准备备份…",
|
|
1631
|
+
"settings.backup.downloading": "正在下载…",
|
|
1595
1632
|
"settings.backup.downloaded": "备份已下载。",
|
|
1633
|
+
"settings.backup.restoring": "正在恢复…服务将自动重启。",
|
|
1634
|
+
"settings.backup.restoreOk": "恢复成功,正在重启…",
|
|
1635
|
+
"settings.backup.restartOk": "重启成功。",
|
|
1596
1636
|
"settings.backup.lastOk": "上次备份:{{time}}",
|
|
1597
1637
|
"settings.backup.lastError": "备份失败:{{msg}}",
|
|
1598
1638
|
"settings.brand.title": "品牌 & 授权",
|
|
@@ -1644,6 +1684,8 @@ const I18n = (() => {
|
|
|
1644
1684
|
"settings.currency.updated": "已从 {{source}} 更新,日期 {{date}}",
|
|
1645
1685
|
"settings.currency.updateFailed": "获取最新汇率失败,仍可手动输入。",
|
|
1646
1686
|
|
|
1687
|
+
"settings.accentColor.title": "主色",
|
|
1688
|
+
|
|
1647
1689
|
// ── Onboard ──
|
|
1648
1690
|
"onboard.title": "欢迎使用 {{brand}}",
|
|
1649
1691
|
"onboard.subtitle": "一分钟完成配置,马上开始。",
|
|
@@ -1838,6 +1880,7 @@ const I18n = (() => {
|
|
|
1838
1880
|
|
|
1839
1881
|
"error.insufficient_credit": "LLM(大模型)余额不足,请充值后继续使用。",
|
|
1840
1882
|
"error.insufficient_credit.action": "去充值",
|
|
1883
|
+
"error.show_detail": "查看详情",
|
|
1841
1884
|
"billing.sessions": "会话消耗",
|
|
1842
1885
|
"billing.sessionId": "会话",
|
|
1843
1886
|
"billing.tokens": "Token",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -805,6 +805,7 @@
|
|
|
805
805
|
<button class="settings-tab active" data-tab="models" data-i18n="settings.tabs.models">Models</button>
|
|
806
806
|
<button class="settings-tab" data-tab="ui" data-i18n="settings.tabs.ui">UI</button>
|
|
807
807
|
<button class="settings-tab" data-tab="general" data-i18n="settings.tabs.general">General</button>
|
|
808
|
+
<button class="settings-tab" data-tab="data" data-i18n="settings.tabs.data">Data</button>
|
|
808
809
|
<button class="settings-tab" data-tab="about" data-i18n="settings.tabs.about">About</button>
|
|
809
810
|
<!-- Extension tab slot: extensions add custom settings tabs here (button needs data-tab="<id>"). -->
|
|
810
811
|
<span id="ext-slot-settings-tabs" data-slot="settings.tabs"></span>
|
|
@@ -890,6 +891,21 @@
|
|
|
890
891
|
</label>
|
|
891
892
|
</div>
|
|
892
893
|
</section>
|
|
894
|
+
|
|
895
|
+
<!-- Accent Color section -->
|
|
896
|
+
<section class="settings-section" id="accent-color-section">
|
|
897
|
+
<div class="settings-section-title">
|
|
898
|
+
<span data-i18n="settings.accentColor.title">Accent Color</span>
|
|
899
|
+
</div>
|
|
900
|
+
<div class="settings-accent-swatches">
|
|
901
|
+
<button class="settings-accent-swatch swatch-indigo" data-color="#4f46e5" title="Indigo"></button>
|
|
902
|
+
<button class="settings-accent-swatch swatch-aurora-blue" data-color="#3B82F6" title="Aurora Blue"></button>
|
|
903
|
+
<button class="settings-accent-swatch swatch-forest-green" data-color="#10B981" title="Forest Green"></button>
|
|
904
|
+
<button class="settings-accent-swatch swatch-sunrise-orange" data-color="#F59E0B" title="Sunrise Orange"></button>
|
|
905
|
+
<button class="settings-accent-swatch swatch-rose-violet" data-color="#8B5CF6" title="Rose Violet"></button>
|
|
906
|
+
<button class="settings-accent-swatch swatch-coral-red" data-color="#EF4444" title="Coral Red"></button>
|
|
907
|
+
</div>
|
|
908
|
+
</section>
|
|
893
909
|
</div>
|
|
894
910
|
|
|
895
911
|
<!-- ══ Tab: General ══ -->
|
|
@@ -942,32 +958,7 @@
|
|
|
942
958
|
</section>
|
|
943
959
|
|
|
944
960
|
<!-- Backup section -->
|
|
945
|
-
<section class="settings-section" id="backup-section">
|
|
946
|
-
<div class="settings-section-title">
|
|
947
|
-
<span data-i18n="settings.backup.title">Backup</span>
|
|
948
|
-
</div>
|
|
949
|
-
<p class="settings-section-desc" data-i18n="settings.backup.desc">Back up your ~/.clacky directory (config, skills, memories, tasks, sessions). Regenerable caches and logs are excluded.</p>
|
|
950
|
-
|
|
951
|
-
<div class="backup-auto-card">
|
|
952
|
-
<div class="backup-auto-row">
|
|
953
|
-
<label class="toggle-switch">
|
|
954
|
-
<input type="checkbox" id="backup-auto-toggle">
|
|
955
|
-
<span class="toggle-slider"></span>
|
|
956
|
-
</label>
|
|
957
|
-
<span class="backup-auto-label" data-i18n="settings.backup.autoLabel">Automatic backup</span>
|
|
958
|
-
<span class="backup-auto-hint" data-i18n="settings.backup.autoHint">Daily at 03:00, keeps the latest 7</span>
|
|
959
|
-
</div>
|
|
960
|
-
|
|
961
|
-
<label class="backup-option backup-option-nested">
|
|
962
|
-
<input type="checkbox" id="backup-include-sessions">
|
|
963
|
-
<span data-i18n="settings.backup.includeSessions">Include session history (larger archive)</span>
|
|
964
|
-
</label>
|
|
965
|
-
</div>
|
|
966
|
-
|
|
967
|
-
<div class="backup-actions">
|
|
968
|
-
<button id="btn-backup-now" class="btn-settings-action" data-i18n="settings.backup.runNow">Download backup (full snapshot)</button>
|
|
969
|
-
<span id="backup-status" class="model-test-result"></span>
|
|
970
|
-
</div>
|
|
961
|
+
<section class="settings-section" id="backup-section" style="display:none">
|
|
971
962
|
</section>
|
|
972
963
|
|
|
973
964
|
<!-- Brand & License section -->
|
|
@@ -1058,6 +1049,56 @@
|
|
|
1058
1049
|
</section>
|
|
1059
1050
|
</div><!-- end Tab: General -->
|
|
1060
1051
|
|
|
1052
|
+
<!-- ══ Tab: Data ══ -->
|
|
1053
|
+
<div class="settings-tab-content" data-tab-content="data" style="display:none">
|
|
1054
|
+
|
|
1055
|
+
<!-- 自动备份 -->
|
|
1056
|
+
<section class="settings-section">
|
|
1057
|
+
<div class="settings-section-title">
|
|
1058
|
+
<span data-i18n="settings.backup.autoTitle">Auto Backup</span>
|
|
1059
|
+
</div>
|
|
1060
|
+
<p class="settings-section-desc" data-i18n="settings.backup.desc">Back up your ~/.clacky directory (config, skills, memories, tasks, sessions). Regenerable caches and logs are excluded.</p>
|
|
1061
|
+
|
|
1062
|
+
<div class="backup-auto-card">
|
|
1063
|
+
<div class="backup-auto-row">
|
|
1064
|
+
<label class="toggle-switch">
|
|
1065
|
+
<input type="checkbox" id="backup-auto-toggle">
|
|
1066
|
+
<span class="toggle-slider"></span>
|
|
1067
|
+
</label>
|
|
1068
|
+
<span class="backup-auto-label" data-i18n="settings.backup.autoLabel">Automatic backup</span>
|
|
1069
|
+
<span class="backup-auto-hint" data-i18n="settings.backup.autoHint">Daily at 03:00, keeps the latest 7</span>
|
|
1070
|
+
</div>
|
|
1071
|
+
<label class="backup-option backup-option-nested">
|
|
1072
|
+
<input type="checkbox" id="backup-include-sessions">
|
|
1073
|
+
<span data-i18n="settings.backup.includeSessions">Include session history (larger archive)</span>
|
|
1074
|
+
</label>
|
|
1075
|
+
<div class="backup-dest-row">
|
|
1076
|
+
<span class="backup-dest-label" data-i18n="settings.backup.destLabel">Backup folder</span>
|
|
1077
|
+
<span class="backup-dest-path" id="backup-dest-path">—</span>
|
|
1078
|
+
<button id="btn-backup-open-folder" class="btn-settings-link" data-i18n="settings.backup.openFolder">Open</button>
|
|
1079
|
+
</div>
|
|
1080
|
+
<div id="backup-status" class="model-test-result"></div>
|
|
1081
|
+
</div>
|
|
1082
|
+
</section>
|
|
1083
|
+
|
|
1084
|
+
<!-- 手动备份 -->
|
|
1085
|
+
<section class="settings-section">
|
|
1086
|
+
<div class="settings-section-title">
|
|
1087
|
+
<span data-i18n="settings.backup.manualTitle">Manual Backup</span>
|
|
1088
|
+
</div>
|
|
1089
|
+
|
|
1090
|
+
<div class="backup-actions">
|
|
1091
|
+
<button id="btn-backup-now" class="btn-settings-action" data-i18n="settings.backup.runNow">Download backup</button>
|
|
1092
|
+
<label class="btn-settings-action btn-settings-action--file">
|
|
1093
|
+
<span data-i18n="settings.backup.restoreBtn">Restore backup</span>
|
|
1094
|
+
<input type="file" id="input-backup-restore" accept=".tar.gz,.gz" style="display:none">
|
|
1095
|
+
</label>
|
|
1096
|
+
<span id="backup-manual-status" class="model-test-result"></span>
|
|
1097
|
+
</div>
|
|
1098
|
+
</section>
|
|
1099
|
+
|
|
1100
|
+
</div><!-- end Tab: Data -->
|
|
1101
|
+
|
|
1061
1102
|
<!-- ══ Tab: About ══ -->
|
|
1062
1103
|
<div class="settings-tab-content" data-tab-content="about" style="display:none">
|
|
1063
1104
|
<!-- Version section -->
|