openclacky 1.3.1 → 1.3.3
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 +44 -0
- data/Dockerfile +3 -0
- data/README.md +1 -1
- data/README_JA.md +237 -0
- data/lib/clacky/agent/session_serializer.rb +65 -11
- data/lib/clacky/agent/time_machine.rb +247 -26
- data/lib/clacky/agent.rb +12 -1
- data/lib/clacky/agent_config.rb +14 -2
- data/lib/clacky/brand_config.rb +1 -1
- data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
- data/lib/clacky/default_agents/coding/profile.yml +3 -0
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
- data/lib/clacky/default_skills/media-gen/SKILL.md +30 -6
- data/lib/clacky/media/openai_compat.rb +64 -1
- data/lib/clacky/media/output_dir.rb +43 -0
- data/lib/clacky/message_history.rb +9 -0
- data/lib/clacky/server/channel/channel_manager.rb +26 -0
- data/lib/clacky/server/git_panel.rb +115 -0
- data/lib/clacky/server/http_server.rb +521 -13
- data/lib/clacky/server/server_master.rb +6 -4
- data/lib/clacky/utils/environment_detector.rb +16 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +512 -60
- data/lib/clacky/web/app.js +30 -7
- data/lib/clacky/web/components/code-editor.js +197 -0
- data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
- data/lib/clacky/web/core/aside.js +112 -0
- data/lib/clacky/web/core/ext.js +387 -0
- data/lib/clacky/web/features/backup/store.js +92 -0
- data/lib/clacky/web/features/backup/view.js +94 -0
- data/lib/clacky/web/features/billing/store.js +163 -0
- data/lib/clacky/web/{billing.js → features/billing/view.js} +134 -242
- data/lib/clacky/web/features/brand/store.js +110 -0
- data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
- data/lib/clacky/web/features/channels/store.js +103 -0
- data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
- data/lib/clacky/web/features/creator/store.js +81 -0
- data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
- data/lib/clacky/web/features/mcp/store.js +158 -0
- data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
- data/lib/clacky/web/features/model-tester/store.js +77 -0
- data/lib/clacky/web/features/model-tester/view.js +7 -0
- data/lib/clacky/web/features/profile/store.js +170 -0
- data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
- data/lib/clacky/web/features/share/store.js +145 -0
- data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
- data/lib/clacky/web/features/skills/store.js +303 -0
- data/lib/clacky/web/features/skills/view.js +550 -0
- data/lib/clacky/web/features/tasks/store.js +135 -0
- data/lib/clacky/web/features/tasks/view.js +241 -0
- data/lib/clacky/web/features/trash/store.js +242 -0
- data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
- data/lib/clacky/web/features/version/store.js +165 -0
- data/lib/clacky/web/features/version/view.js +323 -0
- data/lib/clacky/web/features/workspace/store.js +99 -0
- data/lib/clacky/web/features/workspace/view.js +305 -0
- data/lib/clacky/web/i18n.js +60 -6
- data/lib/clacky/web/index.html +117 -57
- data/lib/clacky/web/sessions.js +221 -25
- data/lib/clacky/web/settings.js +121 -25
- data/lib/clacky/web/skills.js +3 -821
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
- data/lib/clacky.rb +1 -0
- metadata +45 -20
- data/lib/clacky/web/backup.js +0 -119
- data/lib/clacky/web/model-tester.js +0 -66
- data/lib/clacky/web/tasks.js +0 -365
- data/lib/clacky/web/version.js +0 -449
- data/lib/clacky/web/workspace.js +0 -212
- /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
- /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
- /data/lib/clacky/web/{onboard.js → components/onboard.js} +0 -0
- /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
- /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// ── Workspace · view — Files tab (download artifacts) ─────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Renders the working-directory file tree as the "Files" tab in the session
|
|
4
|
+
// aside. The tab is product-positioned as "see what the AI produced and
|
|
5
|
+
// download it": clicking a file downloads it; directories expand lazily;
|
|
6
|
+
// right-click reveals in the OS file manager (desktop only).
|
|
7
|
+
//
|
|
8
|
+
// Registered as a host-owned (built-in) tab via Clacky.ext.ui.mountBuiltin so
|
|
9
|
+
// it shows for every session regardless of agent profile. All I/O goes through
|
|
10
|
+
// WorkspaceStore.
|
|
11
|
+
//
|
|
12
|
+
// Depends on: WorkspaceStore, Clacky.ext, I18n, Modal.
|
|
13
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
const WorkspaceView = (() => {
|
|
17
|
+
const t = (key) => (typeof I18n !== "undefined" ? I18n.t(key) : key);
|
|
18
|
+
|
|
19
|
+
const ICON_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
20
|
+
const ICON_FILE = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
|
|
21
|
+
const ICON_CARET = '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>';
|
|
22
|
+
|
|
23
|
+
function formatSize(bytes) {
|
|
24
|
+
if (bytes == null) return "";
|
|
25
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
26
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
27
|
+
let n = bytes / 1024, i = 0;
|
|
28
|
+
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
|
29
|
+
return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderEntries(entries) {
|
|
33
|
+
const frag = document.createDocumentFragment();
|
|
34
|
+
if (!entries.length) {
|
|
35
|
+
const empty = document.createElement("div");
|
|
36
|
+
empty.className = "wt-empty";
|
|
37
|
+
empty.textContent = t("workspace.empty");
|
|
38
|
+
frag.appendChild(empty);
|
|
39
|
+
return frag;
|
|
40
|
+
}
|
|
41
|
+
for (const entry of entries) frag.appendChild(buildNode(entry));
|
|
42
|
+
return frag;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildNode(entry) {
|
|
46
|
+
const node = document.createElement("div");
|
|
47
|
+
node.className = "wt-node";
|
|
48
|
+
|
|
49
|
+
const row = document.createElement("div");
|
|
50
|
+
row.className = "wt-row";
|
|
51
|
+
row.title = entry.name;
|
|
52
|
+
|
|
53
|
+
const caret = document.createElement("span");
|
|
54
|
+
caret.className = "wt-caret" + (entry.type === "dir" ? "" : " leaf");
|
|
55
|
+
if (entry.type === "dir") caret.innerHTML = ICON_CARET;
|
|
56
|
+
|
|
57
|
+
const icon = document.createElement("span");
|
|
58
|
+
icon.className = "wt-icon";
|
|
59
|
+
icon.innerHTML = entry.type === "dir" ? ICON_FOLDER : ICON_FILE;
|
|
60
|
+
|
|
61
|
+
const name = document.createElement("span");
|
|
62
|
+
name.className = "wt-name";
|
|
63
|
+
name.textContent = entry.name;
|
|
64
|
+
|
|
65
|
+
row.appendChild(caret);
|
|
66
|
+
row.appendChild(icon);
|
|
67
|
+
row.appendChild(name);
|
|
68
|
+
|
|
69
|
+
if (entry.type === "file") {
|
|
70
|
+
const size = document.createElement("span");
|
|
71
|
+
size.className = "wt-size";
|
|
72
|
+
size.textContent = formatSize(entry.size);
|
|
73
|
+
row.appendChild(size);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
node.appendChild(row);
|
|
77
|
+
|
|
78
|
+
if (entry.type === "dir") {
|
|
79
|
+
const children = document.createElement("div");
|
|
80
|
+
children.className = "wt-children";
|
|
81
|
+
children.style.display = "none";
|
|
82
|
+
node.appendChild(children);
|
|
83
|
+
row.addEventListener("click", () => toggleDir(entry, caret, children));
|
|
84
|
+
} else {
|
|
85
|
+
row.addEventListener("click", () => openFile(entry));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
row.addEventListener("contextmenu", (e) => {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
showContextMenu(e, entry);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showContextMenu(e, entry) {
|
|
97
|
+
closeContextMenu();
|
|
98
|
+
|
|
99
|
+
const iconFolder = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
100
|
+
const iconDownload = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7.5 11l4.5 4.5 4.5-4.5"/><path d="M5 20h14"/></svg>';
|
|
101
|
+
const iconCopy = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
102
|
+
const iconRelPath = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/><path d="M12 19l3-3"/></svg>';
|
|
103
|
+
|
|
104
|
+
const downloadItem = entry.type === "file" ? `
|
|
105
|
+
<div class="session-actions-menu-item" data-action="download">
|
|
106
|
+
<span class="session-actions-menu-icon">${iconDownload}</span>
|
|
107
|
+
<span class="session-actions-menu-label">${t("workspace.download")}</span>
|
|
108
|
+
</div>` : "";
|
|
109
|
+
|
|
110
|
+
const menu = document.createElement("div");
|
|
111
|
+
menu.className = "wt-context-menu session-context-menu";
|
|
112
|
+
menu.innerHTML = `
|
|
113
|
+
<div class="session-actions-menu-item" data-action="reveal">
|
|
114
|
+
<span class="session-actions-menu-icon">${iconFolder}</span>
|
|
115
|
+
<span class="session-actions-menu-label">${t("workspace.revealInFinder")}</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="session-actions-menu-item" data-action="copypath">
|
|
118
|
+
<span class="session-actions-menu-icon">${iconCopy}</span>
|
|
119
|
+
<span class="session-actions-menu-label">${t("workspace.copyPath")}</span>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="session-actions-menu-item" data-action="copyrelpath">
|
|
122
|
+
<span class="session-actions-menu-icon">${iconRelPath}</span>
|
|
123
|
+
<span class="session-actions-menu-label">${t("workspace.copyRelPath")}</span>
|
|
124
|
+
</div>
|
|
125
|
+
${downloadItem}
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
document.body.appendChild(menu);
|
|
129
|
+
menu.addEventListener("contextmenu", (ev) => ev.preventDefault());
|
|
130
|
+
menu.style.position = "fixed";
|
|
131
|
+
menu.style.top = e.clientY + "px";
|
|
132
|
+
menu.style.left = e.clientX + "px";
|
|
133
|
+
requestAnimationFrame(() => {
|
|
134
|
+
const r = menu.getBoundingClientRect();
|
|
135
|
+
if (r.right > window.innerWidth) menu.style.left = (window.innerWidth - r.width - 8) + "px";
|
|
136
|
+
if (r.bottom > window.innerHeight) menu.style.top = (window.innerHeight - r.height - 8) + "px";
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
menu.addEventListener("click", async (ev) => {
|
|
140
|
+
const item = ev.target.closest(".session-actions-menu-item");
|
|
141
|
+
if (!item) return;
|
|
142
|
+
closeContextMenu();
|
|
143
|
+
if (item.dataset.action === "reveal") await revealFile(entry);
|
|
144
|
+
if (item.dataset.action === "download") await downloadFile(entry);
|
|
145
|
+
if (item.dataset.action === "copypath") copyPath(entry);
|
|
146
|
+
if (item.dataset.action === "copyrelpath") copyRelPath(entry);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
document.addEventListener("click", closeContextMenu, { once: true });
|
|
151
|
+
}, 0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function closeContextMenu() {
|
|
155
|
+
const existing = document.querySelector(".wt-context-menu");
|
|
156
|
+
if (existing) existing.remove();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function copyPath(entry) {
|
|
160
|
+
const absPath = Workspace.state.workingDir.replace(/\/+$/, "") + "/" + entry.path.replace(/^\/+/, "");
|
|
161
|
+
navigator.clipboard.writeText(absPath).then(() => {
|
|
162
|
+
Modal.toast(absPath, "info");
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function copyRelPath(entry) {
|
|
167
|
+
navigator.clipboard.writeText(entry.path).then(() => {
|
|
168
|
+
Modal.toast(entry.path, "info");
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function revealFile(entry) {
|
|
173
|
+
try {
|
|
174
|
+
await Workspace.revealFile(entry);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error("reveal failed:", err);
|
|
177
|
+
if (typeof Modal !== "undefined") Modal.toast(t("workspace.revealFailed"), "error");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function toggleDir(entry, caret, children) {
|
|
182
|
+
const isOpen = caret.classList.contains("open");
|
|
183
|
+
if (isOpen) {
|
|
184
|
+
caret.classList.remove("open");
|
|
185
|
+
children.style.display = "none";
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
caret.classList.add("open");
|
|
189
|
+
children.style.display = "";
|
|
190
|
+
if (children.dataset.loaded === "1") return;
|
|
191
|
+
|
|
192
|
+
children.innerHTML = `<div class="wt-loading">${t("workspace.loading")}</div>`;
|
|
193
|
+
try {
|
|
194
|
+
const entries = await Workspace.fetchEntries(entry.path);
|
|
195
|
+
children.innerHTML = "";
|
|
196
|
+
children.appendChild(renderEntries(entries));
|
|
197
|
+
children.dataset.loaded = "1";
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error("workspace load failed:", err);
|
|
200
|
+
children.innerHTML = `<div class="wt-error">${t("workspace.error")}</div>`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function openFile(entry) {
|
|
205
|
+
const kind = CodeEditor.fileKind(entry.name);
|
|
206
|
+
if (kind === "binary") {
|
|
207
|
+
Modal.toast(t("workspace.previewUnsupported"), "info");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (kind === "image") {
|
|
211
|
+
try {
|
|
212
|
+
const blob = await Workspace.fetchFileBlob(entry);
|
|
213
|
+
const url = URL.createObjectURL(blob);
|
|
214
|
+
CodeEditor.open({ filename: entry.name, title: entry.name, kind: "image", imageUrl: url, onClose: () => URL.revokeObjectURL(url) });
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error("preview failed:", err);
|
|
217
|
+
Modal.toast(t("workspace.previewFailed"), "error");
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const text = await Workspace.fetchFileText(entry);
|
|
223
|
+
CodeEditor.open({ filename: entry.name, title: entry.name, content: text, readOnly: true });
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error("preview failed:", err);
|
|
226
|
+
Modal.toast(t("workspace.previewFailed"), "error");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function downloadFile(entry) {
|
|
231
|
+
try {
|
|
232
|
+
const blob = await Workspace.fetchFileBlob(entry);
|
|
233
|
+
const url = URL.createObjectURL(blob);
|
|
234
|
+
const a = document.createElement("a");
|
|
235
|
+
a.href = url;
|
|
236
|
+
a.download = entry.name;
|
|
237
|
+
document.body.appendChild(a);
|
|
238
|
+
a.click();
|
|
239
|
+
a.remove();
|
|
240
|
+
URL.revokeObjectURL(url);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error("download failed:", err);
|
|
243
|
+
if (typeof Modal !== "undefined") Modal.toast(t("workspace.downloadFailed"), "error");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function loadRoot(tree) {
|
|
248
|
+
if (!tree || !Workspace.state.hasSession()) return;
|
|
249
|
+
tree.innerHTML = `<div class="wt-loading">${t("workspace.loading")}</div>`;
|
|
250
|
+
try {
|
|
251
|
+
const entries = await Workspace.fetchEntries("");
|
|
252
|
+
tree.innerHTML = "";
|
|
253
|
+
tree.appendChild(renderEntries(entries));
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error("workspace load failed:", err);
|
|
256
|
+
tree.innerHTML = `<div class="wt-error">${t("workspace.error")}</div>`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Build the Files tab body for the current session.
|
|
261
|
+
function renderFilesTab(_ctx) {
|
|
262
|
+
const wrap = document.createElement("div");
|
|
263
|
+
wrap.className = "wt-panel";
|
|
264
|
+
|
|
265
|
+
const bar = document.createElement("div");
|
|
266
|
+
bar.className = "wt-bar";
|
|
267
|
+
const hint = document.createElement("span");
|
|
268
|
+
hint.className = "wt-bar-hint";
|
|
269
|
+
hint.textContent = t("workspace.contextMenuHint");
|
|
270
|
+
const refresh = document.createElement("button");
|
|
271
|
+
refresh.type = "button";
|
|
272
|
+
refresh.className = "wt-bar-btn";
|
|
273
|
+
refresh.textContent = t("workspace.refresh");
|
|
274
|
+
bar.appendChild(hint);
|
|
275
|
+
bar.appendChild(refresh);
|
|
276
|
+
|
|
277
|
+
const tree = document.createElement("div");
|
|
278
|
+
tree.className = "wt-tree";
|
|
279
|
+
tree.setAttribute("role", "tree");
|
|
280
|
+
|
|
281
|
+
refresh.addEventListener("click", () => loadRoot(tree));
|
|
282
|
+
|
|
283
|
+
wrap.appendChild(bar);
|
|
284
|
+
wrap.appendChild(tree);
|
|
285
|
+
loadRoot(tree);
|
|
286
|
+
return wrap;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { renderFilesTab };
|
|
290
|
+
})();
|
|
291
|
+
|
|
292
|
+
// Files is a built-in tab: visible for every session, after the agent-scoped
|
|
293
|
+
// panels (git/time-machine use orders 10/20).
|
|
294
|
+
if (window.Clacky && Clacky.ext) {
|
|
295
|
+
Clacky.ext.ui.mountBuiltin("session.aside", (ctx) => WorkspaceView.renderFilesTab(ctx), {
|
|
296
|
+
order: 40,
|
|
297
|
+
tab: { id: "files", label: (typeof I18n !== "undefined" ? I18n.t("workspace.title") : "Files") },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Keep the store's session context in sync (sessions.js still calls
|
|
302
|
+
// Workspace.onSession on every session switch). Rendering is driven by the
|
|
303
|
+
// slot re-render, so this only updates state.
|
|
304
|
+
Workspace.onSession = (session) => { Workspace.setSession(session); };
|
|
305
|
+
window.Workspace = Workspace;
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -69,6 +69,10 @@ const I18n = (() => {
|
|
|
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
71
|
"chat.copied": "Copied",
|
|
72
|
+
"chat.continue": "Continue",
|
|
73
|
+
"chat.edit": "Edit",
|
|
74
|
+
"chat.cancel": "Cancel",
|
|
75
|
+
"chat.send": "Send",
|
|
72
76
|
"chat.empty.title": "Start the conversation",
|
|
73
77
|
"chat.empty.subtitle": "Ask anything, or use a skill to get going.",
|
|
74
78
|
"chat.empty.tip1": "Type / to browse skills",
|
|
@@ -106,7 +110,7 @@ const I18n = (() => {
|
|
|
106
110
|
"sib.dir.loadError": "Failed to load",
|
|
107
111
|
"sib.dir.confirm": "Confirm",
|
|
108
112
|
"sib.dir.cancel": "Cancel",
|
|
109
|
-
"sib.dir.showHidden": "Show hidden files", "workspace.title": "
|
|
113
|
+
"sib.dir.showHidden": "Show hidden files", "workspace.title": "Files",
|
|
110
114
|
"workspace.expand": "Open workspace",
|
|
111
115
|
"workspace.collapse": "Collapse workspace",
|
|
112
116
|
"workspace.refresh": "Refresh",
|
|
@@ -114,6 +118,16 @@ const I18n = (() => {
|
|
|
114
118
|
"workspace.loading": "Loading…",
|
|
115
119
|
"workspace.error": "Failed to load files",
|
|
116
120
|
"workspace.downloadFailed": "Download failed",
|
|
121
|
+
"workspace.previewFailed": "Failed to preview file",
|
|
122
|
+
"workspace.previewUnsupported": "Cannot preview this file type",
|
|
123
|
+
"workspace.download": "Download",
|
|
124
|
+
"workspace.contextMenuHint": "Right-click for more options",
|
|
125
|
+
"workspace.copyPath": "Copy Path",
|
|
126
|
+
"workspace.copyRelPath": "Copy Relative Path",
|
|
127
|
+
"aside.collapse": "Collapse panel",
|
|
128
|
+
"aside.expand": "Open panel",
|
|
129
|
+
"workspace.revealInFinder": "Reveal in Finder",
|
|
130
|
+
"workspace.revealFailed": "Failed to reveal file",
|
|
117
131
|
"sib.model.tooltip": "Click to switch model",
|
|
118
132
|
"sib.model.tooltip.busy": "Model switching is disabled while the agent is responding",
|
|
119
133
|
"sib.variant.header": "Quick switch",
|
|
@@ -159,6 +173,9 @@ const I18n = (() => {
|
|
|
159
173
|
"modal.no": "No",
|
|
160
174
|
"modal.ok": "OK",
|
|
161
175
|
"modal.cancel": "Cancel",
|
|
176
|
+
"modal.close": "Close",
|
|
177
|
+
"modal.save": "Save",
|
|
178
|
+
"modal.saving": "Saving…",
|
|
162
179
|
|
|
163
180
|
// ── Auth ──
|
|
164
181
|
"auth.accessKeyRequired": "Access key required:",
|
|
@@ -340,8 +357,11 @@ const I18n = (() => {
|
|
|
340
357
|
// Per-tab curate actions
|
|
341
358
|
"profile.soul.curateHint": "Not quite right? Let the assistant curate this through a short conversation.",
|
|
342
359
|
"profile.soul.curateBtn": "Have the assistant curate this",
|
|
360
|
+
"profile.soul.editBtn": "Edit directly",
|
|
343
361
|
"profile.user.curateHint": "Changed jobs? Picked up new interests? Let the assistant update your profile.",
|
|
344
362
|
"profile.user.curateBtn": "Have the assistant update this",
|
|
363
|
+
"profile.user.editBtn": "Edit directly",
|
|
364
|
+
|
|
345
365
|
"profile.curateFail": "Could not start the curation session",
|
|
346
366
|
"profile.curateName.soul": "Curate soul",
|
|
347
367
|
"profile.curateName.user": "Curate profile",
|
|
@@ -533,7 +553,7 @@ const I18n = (() => {
|
|
|
533
553
|
"settings.models.badge.default": "Default",
|
|
534
554
|
"settings.models.badge.lite": "Lite",
|
|
535
555
|
"settings.media.title": "Secondary Models",
|
|
536
|
-
"settings.media.desc": "
|
|
556
|
+
"settings.media.desc": "Image / video / audio / vision models (optional)",
|
|
537
557
|
"settings.media.loading": "Loading…",
|
|
538
558
|
"settings.media.error": "Failed to load: {{msg}}",
|
|
539
559
|
"settings.media.kind.image": "Image",
|
|
@@ -566,6 +586,13 @@ const I18n = (() => {
|
|
|
566
586
|
"settings.media.apiKey.required": "API key required",
|
|
567
587
|
"settings.media.model.required": "Model name required",
|
|
568
588
|
"settings.media.baseUrl.required": "Base URL required",
|
|
589
|
+
"settings.media.output_dir.desc": "Where generated images, videos and audio are saved (optional)",
|
|
590
|
+
"settings.media.output_dir.browse": "Browse…",
|
|
591
|
+
"settings.media.output_dir.picker": "Select Media Output Directory",
|
|
592
|
+
"settings.media.output_dir.clear": "Clear",
|
|
593
|
+
"settings.media.output_dir.saved": "Saved",
|
|
594
|
+
"settings.media.output_dir.cleared": "Cleared",
|
|
595
|
+
"settings.media.output_dir.invalid": "Invalid directory",
|
|
569
596
|
"settings.models.field.quicksetup": "Quick Setup",
|
|
570
597
|
"settings.models.field.model": "Model",
|
|
571
598
|
"settings.models.field.baseurl": "Base URL",
|
|
@@ -640,7 +667,7 @@ const I18n = (() => {
|
|
|
640
667
|
"settings.backup.includeSessions": "Include session history (larger archive)",
|
|
641
668
|
"settings.backup.autoLabel": "Automatic backup",
|
|
642
669
|
"settings.backup.autoHint": "Daily at 03:00, keeps the latest 7",
|
|
643
|
-
"settings.backup.runNow": "Download backup",
|
|
670
|
+
"settings.backup.runNow": "Download backup (full snapshot)",
|
|
644
671
|
"settings.backup.running": "Preparing backup…",
|
|
645
672
|
"settings.backup.downloaded": "Backup downloaded.",
|
|
646
673
|
"settings.backup.lastOk": "Last backup: {{time}}",
|
|
@@ -954,6 +981,10 @@ const I18n = (() => {
|
|
|
954
981
|
"chat.retry": "重试",
|
|
955
982
|
"chat.copy": "复制",
|
|
956
983
|
"chat.copied": "已复制",
|
|
984
|
+
"chat.continue": "继续",
|
|
985
|
+
"chat.edit": "编辑",
|
|
986
|
+
"chat.cancel": "取消",
|
|
987
|
+
"chat.send": "发送",
|
|
957
988
|
"chat.empty.title": "开始新会话",
|
|
958
989
|
"chat.empty.subtitle": "直接提问,或用一个 Skill 启动。",
|
|
959
990
|
"chat.empty.tip1": "输入 / 浏览 Skill",
|
|
@@ -991,7 +1022,7 @@ const I18n = (() => {
|
|
|
991
1022
|
"sib.dir.loadError": "加载失败",
|
|
992
1023
|
"sib.dir.confirm": "确认",
|
|
993
1024
|
"sib.dir.cancel": "取消",
|
|
994
|
-
"sib.dir.showHidden": "显示隐藏文件", "workspace.title": "
|
|
1025
|
+
"sib.dir.showHidden": "显示隐藏文件", "workspace.title": "文件",
|
|
995
1026
|
"workspace.expand": "打开工作区",
|
|
996
1027
|
"workspace.collapse": "收起工作区",
|
|
997
1028
|
"workspace.refresh": "刷新",
|
|
@@ -999,6 +1030,16 @@ const I18n = (() => {
|
|
|
999
1030
|
"workspace.loading": "加载中…",
|
|
1000
1031
|
"workspace.error": "加载文件失败",
|
|
1001
1032
|
"workspace.downloadFailed": "下载失败",
|
|
1033
|
+
"workspace.previewFailed": "文件预览失败",
|
|
1034
|
+
"workspace.previewUnsupported": "无法预览此类型文件",
|
|
1035
|
+
"workspace.download": "下载",
|
|
1036
|
+
"workspace.contextMenuHint": "右键显示更多菜单",
|
|
1037
|
+
"workspace.copyPath": "复制路径",
|
|
1038
|
+
"workspace.copyRelPath": "复制相对路径",
|
|
1039
|
+
"aside.collapse": "收起面板",
|
|
1040
|
+
"aside.expand": "打开面板",
|
|
1041
|
+
"workspace.revealInFinder": "打开所在文件夹",
|
|
1042
|
+
"workspace.revealFailed": "无法打开文件位置",
|
|
1002
1043
|
"sib.model.tooltip": "点击切换模型",
|
|
1003
1044
|
"sib.model.tooltip.busy": "Agent 回复中,暂时无法切换模型",
|
|
1004
1045
|
"sib.variant.header": "快速切换",
|
|
@@ -1042,6 +1083,9 @@ const I18n = (() => {
|
|
|
1042
1083
|
"modal.no": "取消",
|
|
1043
1084
|
"modal.ok": "确定",
|
|
1044
1085
|
"modal.cancel": "取消",
|
|
1086
|
+
"modal.close": "关闭",
|
|
1087
|
+
"modal.save": "保存",
|
|
1088
|
+
"modal.saving": "保存中…",
|
|
1045
1089
|
|
|
1046
1090
|
// ── Auth ──
|
|
1047
1091
|
"auth.accessKeyRequired": "请输入访问密钥:",
|
|
@@ -1225,8 +1269,11 @@ const I18n = (() => {
|
|
|
1225
1269
|
// Per-tab curate actions
|
|
1226
1270
|
"profile.soul.curateHint": "感觉不太对?让助理通过一段简短对话帮你调整。",
|
|
1227
1271
|
"profile.soul.curateBtn": "让助理整理性格",
|
|
1272
|
+
"profile.soul.editBtn": "直接编辑",
|
|
1228
1273
|
"profile.user.curateHint": "换工作了?有新兴趣了?让助理帮你更新主人档案。",
|
|
1229
1274
|
"profile.user.curateBtn": "让助理更新档案",
|
|
1275
|
+
"profile.user.editBtn": "直接编辑",
|
|
1276
|
+
|
|
1230
1277
|
"profile.curateFail": "无法启动整理会话",
|
|
1231
1278
|
"profile.curateName.soul": "整理性格",
|
|
1232
1279
|
"profile.curateName.user": "整理主人档案",
|
|
@@ -1417,7 +1464,7 @@ const I18n = (() => {
|
|
|
1417
1464
|
"settings.models.badge.default": "默认",
|
|
1418
1465
|
"settings.models.badge.lite": "轻量",
|
|
1419
1466
|
"settings.media.title": "配置副模型",
|
|
1420
|
-
"settings.media.desc": "
|
|
1467
|
+
"settings.media.desc": "图片生成 / 视频生成 / 音频生成 / 视觉理解(可选)",
|
|
1421
1468
|
"settings.media.loading": "加载中…",
|
|
1422
1469
|
"settings.media.error": "加载失败:{{msg}}",
|
|
1423
1470
|
"settings.media.kind.image": "图片生成",
|
|
@@ -1450,6 +1497,13 @@ const I18n = (() => {
|
|
|
1450
1497
|
"settings.media.apiKey.required": "请填写 API Key",
|
|
1451
1498
|
"settings.media.model.required": "请填写模型名称",
|
|
1452
1499
|
"settings.media.baseUrl.required": "请填写 Base URL",
|
|
1500
|
+
"settings.media.output_dir.desc": "生成的图片、视频、音频文件保存位置(可选)",
|
|
1501
|
+
"settings.media.output_dir.browse": "选择目录…",
|
|
1502
|
+
"settings.media.output_dir.picker": "选择媒体输出目录",
|
|
1503
|
+
"settings.media.output_dir.clear": "清除",
|
|
1504
|
+
"settings.media.output_dir.saved": "已保存",
|
|
1505
|
+
"settings.media.output_dir.cleared": "已清除",
|
|
1506
|
+
"settings.media.output_dir.invalid": "目录无效",
|
|
1453
1507
|
"settings.models.field.quicksetup": "快速配置",
|
|
1454
1508
|
"settings.models.field.model": "Model",
|
|
1455
1509
|
"settings.models.field.baseurl": "Base URL",
|
|
@@ -1524,7 +1578,7 @@ const I18n = (() => {
|
|
|
1524
1578
|
"settings.backup.includeSessions": "包含会话历史(归档更大)",
|
|
1525
1579
|
"settings.backup.autoLabel": "自动备份",
|
|
1526
1580
|
"settings.backup.autoHint": "每天 03:00 自动备份,保留最近 7 份",
|
|
1527
|
-
"settings.backup.runNow": "
|
|
1581
|
+
"settings.backup.runNow": "下载备份(完整快照)",
|
|
1528
1582
|
"settings.backup.running": "正在准备备份…",
|
|
1529
1583
|
"settings.backup.downloaded": "备份已下载。",
|
|
1530
1584
|
"settings.backup.lastOk": "上次备份:{{time}}",
|