openclacky 1.3.2 → 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 +28 -0
- data/Dockerfile +3 -0
- data/README.md +1 -1
- data/README_JA.md +237 -0
- data/lib/clacky/agent/session_serializer.rb +49 -5
- 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/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 +497 -12
- data/lib/clacky/server/server_master.rb +6 -4
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +473 -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} +132 -240
- 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 +56 -6
- data/lib/clacky/web/index.html +117 -58
- data/lib/clacky/web/sessions.js +221 -25
- data/lib/clacky/web/settings.js +118 -22
- data/lib/clacky/web/skills.js +3 -863
- 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 -373
- data/lib/clacky/web/version.js +0 -449
- data/lib/clacky/web/workspace.js +0 -316
- /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
data/lib/clacky/web/settings.js
CHANGED
|
@@ -17,6 +17,7 @@ const Settings = (() => {
|
|
|
17
17
|
function open() {
|
|
18
18
|
_load();
|
|
19
19
|
_loadMedia();
|
|
20
|
+
_initMediaOutputDir();
|
|
20
21
|
_loadBrand();
|
|
21
22
|
_loadBrowserStatus();
|
|
22
23
|
_initNetworkSettings();
|
|
@@ -96,6 +97,10 @@ const Settings = (() => {
|
|
|
96
97
|
<span class="model-card-grid-name">${_esc(displayName)}</span>
|
|
97
98
|
${isDefault ? `<span class="badge badge-default">${I18n.t("settings.models.badge.default")}</span>` : ""}
|
|
98
99
|
${isLite ? `<span class="badge badge-lite">${I18n.t("settings.models.badge.lite")}</span>` : ""}
|
|
100
|
+
${!isDefault ? `<button class="btn-card-grid-action btn-card-grid-action-primary" data-index="${index}" data-action="default" style="margin-left:auto">
|
|
101
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
102
|
+
<span>${I18n.t("settings.models.btn.setDefault")}</span>
|
|
103
|
+
</button>` : ""}
|
|
99
104
|
</div>
|
|
100
105
|
<div class="model-card-grid-provider">${_esc(providerName)}</div>
|
|
101
106
|
${model.api_key_masked ? `<div class="model-card-grid-model">${_esc(model.api_key_masked)}</div>` : ""}
|
|
@@ -120,11 +125,7 @@ const Settings = (() => {
|
|
|
120
125
|
</div>
|
|
121
126
|
</div>
|
|
122
127
|
<div class="model-card-grid-footer">
|
|
123
|
-
${
|
|
124
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
125
|
-
<span>${I18n.t("settings.models.btn.setDefault")}</span>
|
|
126
|
-
</button>` : `<span></span>`}
|
|
127
|
-
${websiteUrl ? `<a class="model-card-grid-link" href="${_esc(websiteUrl)}" target="_blank" rel="noopener noreferrer">
|
|
128
|
+
${websiteUrl ? `<a class="model-card-grid-link" href="${_esc(websiteUrl)}" target="_blank" rel="noopener noreferrer" style="margin-left:auto">
|
|
128
129
|
${I18n.t("settings.models.link.topUp")}
|
|
129
130
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17L17 7"/><path d="M8 7h9v9"/></svg>
|
|
130
131
|
</a>` : ""}
|
|
@@ -169,7 +170,14 @@ const Settings = (() => {
|
|
|
169
170
|
document.getElementById("model-modal-baseurl").value = model.base_url || "";
|
|
170
171
|
document.getElementById("model-modal-apikey").value = model.api_key_masked || "";
|
|
171
172
|
document.getElementById("model-modal-default-field").style.display = "";
|
|
172
|
-
|
|
173
|
+
// Lock the checkbox when this is the only configured model: the system
|
|
174
|
+
// must always have one default (backend re-promotes on save), so
|
|
175
|
+
// unchecking would be a silent no-op. Force-checked + disabled makes
|
|
176
|
+
// the constraint visible without any extra copy.
|
|
177
|
+
const setDefaultCb = document.getElementById("model-modal-set-default");
|
|
178
|
+
const isOnlyModel = _models.length === 1;
|
|
179
|
+
setDefaultCb.checked = isOnlyModel ? true : (model.type === "default");
|
|
180
|
+
setDefaultCb.disabled = isOnlyModel;
|
|
173
181
|
|
|
174
182
|
// Set provider dropdown value
|
|
175
183
|
const matched = _findProviderByBaseUrl(model.base_url);
|
|
@@ -190,6 +198,9 @@ const Settings = (() => {
|
|
|
190
198
|
// Default to checked for new models — most users want their first/new
|
|
191
199
|
// model to take over as the default.
|
|
192
200
|
document.getElementById("model-modal-set-default").checked = true;
|
|
201
|
+
// Reset disabled flag in case the previous open was edit-mode on the
|
|
202
|
+
// sole-model lock path.
|
|
203
|
+
document.getElementById("model-modal-set-default").disabled = false;
|
|
193
204
|
|
|
194
205
|
// Reset provider dropdown
|
|
195
206
|
_modalSelectedProviderId = null;
|
|
@@ -1261,6 +1272,89 @@ const Settings = (() => {
|
|
|
1261
1272
|
}
|
|
1262
1273
|
}
|
|
1263
1274
|
|
|
1275
|
+
// ── Media output directory ────────────────────────────────────────────────────
|
|
1276
|
+
//
|
|
1277
|
+
// Single user-facing override for where /api/media/* writes generated files.
|
|
1278
|
+
// Mirrors the proxy_url section above (same field-input + save/clear pair)
|
|
1279
|
+
// because the data shape is identical (one optional string). Resolution
|
|
1280
|
+
// priority lives server-side in Clacky::Media::OutputDir.resolve:
|
|
1281
|
+
// per-call output_dir → media_output_dir (this setting) → default_working_dir → Dir.pwd
|
|
1282
|
+
|
|
1283
|
+
async function _initMediaOutputDir() {
|
|
1284
|
+
const input = document.getElementById("settings-media-output-dir");
|
|
1285
|
+
const browseBtn = document.getElementById("btn-browse-media-output-dir");
|
|
1286
|
+
const clearBtn = document.getElementById("btn-clear-media-output-dir");
|
|
1287
|
+
const status = document.getElementById("settings-media-output-dir-status");
|
|
1288
|
+
if (!input || !browseBtn) return;
|
|
1289
|
+
|
|
1290
|
+
try {
|
|
1291
|
+
const res = await fetch("/api/config/media-output-dir");
|
|
1292
|
+
const data = await res.json();
|
|
1293
|
+
if (data.ok) {
|
|
1294
|
+
input.value = data.value || "";
|
|
1295
|
+
// Show the system fallback as a placeholder hint so the user sees
|
|
1296
|
+
// where files would land if they leave the field blank.
|
|
1297
|
+
if (data.default) input.placeholder = data.default;
|
|
1298
|
+
}
|
|
1299
|
+
} catch (_) { /* non-critical */ }
|
|
1300
|
+
|
|
1301
|
+
async function _patchMediaOutputDir(value, successKey) {
|
|
1302
|
+
status.textContent = "";
|
|
1303
|
+
status.className = "model-test-result";
|
|
1304
|
+
try {
|
|
1305
|
+
const res = await fetch("/api/config/media-output-dir", {
|
|
1306
|
+
method: "PATCH",
|
|
1307
|
+
headers: { "Content-Type": "application/json" },
|
|
1308
|
+
body: JSON.stringify({ value: value })
|
|
1309
|
+
});
|
|
1310
|
+
const data = await res.json();
|
|
1311
|
+
if (data.ok) {
|
|
1312
|
+
status.textContent = I18n.t(successKey);
|
|
1313
|
+
status.className = "model-test-result success";
|
|
1314
|
+
// Auto-hide the toast after a short delay so it doesn't linger
|
|
1315
|
+
// forever (looks like a stuck banner). 2s is enough to read.
|
|
1316
|
+
clearTimeout(_patchMediaOutputDir._hideTimer);
|
|
1317
|
+
_patchMediaOutputDir._hideTimer = setTimeout(() => {
|
|
1318
|
+
status.textContent = "";
|
|
1319
|
+
status.className = "model-test-result";
|
|
1320
|
+
}, 2000);
|
|
1321
|
+
// Server may have expanded `~` or normalized the path — reflect
|
|
1322
|
+
// the canonical value back into the input so the user sees what
|
|
1323
|
+
// was actually persisted.
|
|
1324
|
+
input.value = data.value || "";
|
|
1325
|
+
} else {
|
|
1326
|
+
status.textContent = data.error || I18n.t("settings.media.output_dir.invalid");
|
|
1327
|
+
status.className = "model-test-result error";
|
|
1328
|
+
}
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
status.textContent = e.message || I18n.t("settings.media.output_dir.invalid");
|
|
1331
|
+
status.className = "model-test-result error";
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (!browseBtn.dataset.bound) {
|
|
1336
|
+
browseBtn.dataset.bound = "1";
|
|
1337
|
+
browseBtn.addEventListener("click", async () => {
|
|
1338
|
+
// Reuse the global directory picker in session-less mode (browses
|
|
1339
|
+
// the real filesystem via /api/dirs). Picker resolves to an absolute
|
|
1340
|
+
// path on confirm, or null on cancel.
|
|
1341
|
+
const start = (input.value || "").trim();
|
|
1342
|
+
const picked = await window.openDirectoryPicker(start, null, I18n.t("settings.media.output_dir.picker"));
|
|
1343
|
+
if (!picked) return;
|
|
1344
|
+
// Persist immediately — no separate Save click needed.
|
|
1345
|
+
await _patchMediaOutputDir(picked, "settings.media.output_dir.saved");
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if (clearBtn && !clearBtn.dataset.bound) {
|
|
1350
|
+
clearBtn.dataset.bound = "1";
|
|
1351
|
+
clearBtn.addEventListener("click", () => {
|
|
1352
|
+
input.value = "";
|
|
1353
|
+
_patchMediaOutputDir("", "settings.media.output_dir.cleared");
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1264
1358
|
// ── Brand & License ───────────────────────────────────────────────────────────
|
|
1265
1359
|
|
|
1266
1360
|
// Whether the server was started with --brand-test (relaxed key validation).
|
|
@@ -1463,22 +1557,24 @@ const Settings = (() => {
|
|
|
1463
1557
|
// ── Init ──────────────────────────────────────────────────────────────────────
|
|
1464
1558
|
|
|
1465
1559
|
function _initTabs() {
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
tabs
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1560
|
+
const bar = document.getElementById("settings-tabs");
|
|
1561
|
+
if (!bar) return;
|
|
1562
|
+
|
|
1563
|
+
// Delegated so extension tabs (mounted into the settings.tabs slot after
|
|
1564
|
+
// this runs) switch correctly without re-binding.
|
|
1565
|
+
bar.addEventListener("click", (e) => {
|
|
1566
|
+
const tab = e.target.closest(".settings-tab");
|
|
1567
|
+
if (!tab || !bar.contains(tab)) return;
|
|
1568
|
+
const targetTab = tab.dataset.tab;
|
|
1569
|
+
if (!targetTab) return;
|
|
1570
|
+
|
|
1571
|
+
document.querySelectorAll("#settings-tabs .settings-tab").forEach(t =>
|
|
1572
|
+
t.classList.toggle("active", t.dataset.tab === targetTab));
|
|
1573
|
+
|
|
1574
|
+
document.querySelectorAll("#settings-body .settings-tab-content").forEach(c => {
|
|
1575
|
+
const isActive = c.dataset.tabContent === targetTab;
|
|
1576
|
+
c.classList.toggle("active", isActive);
|
|
1577
|
+
c.style.display = isActive ? "" : "none";
|
|
1482
1578
|
});
|
|
1483
1579
|
});
|
|
1484
1580
|
}
|