openclacky 1.3.3 → 1.3.4
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 +26 -0
- data/docs/rich_ui_guide.md +277 -0
- data/docs/rich_ui_refactor_plan.md +396 -0
- data/lib/clacky/agent/llm_caller.rb +10 -4
- data/lib/clacky/agent/session_serializer.rb +3 -2
- data/lib/clacky/agent.rb +3 -2
- data/lib/clacky/agent_config.rb +2 -14
- data/lib/clacky/api_extension.rb +262 -0
- data/lib/clacky/api_extension_loader.rb +156 -0
- data/lib/clacky/cli.rb +93 -3
- data/lib/clacky/client.rb +38 -13
- data/lib/clacky/default_agents/_panels/git/panel.js +1 -1
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +1 -1
- data/lib/clacky/default_skills/media-gen/SKILL.md +9 -6
- data/lib/clacky/idle_compression_timer.rb +3 -1
- data/lib/clacky/locales/en.rb +26 -0
- data/lib/clacky/locales/i18n.rb +26 -0
- data/lib/clacky/locales/zh.rb +26 -0
- data/lib/clacky/rich_ui/components/base_component.rb +50 -0
- data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
- data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
- data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
- data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
- data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
- data/lib/clacky/rich_ui/components/status_view.rb +58 -0
- data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
- data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
- data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
- data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
- data/lib/clacky/rich_ui/view_renderer.rb +291 -0
- data/lib/clacky/rich_ui.rb +57 -0
- data/lib/clacky/rich_ui_controller.rb +3 -1549
- data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
- data/lib/clacky/server/http_server.rb +150 -103
- data/lib/clacky/server/session_registry.rb +1 -1
- data/lib/clacky/shell_hook_loader.rb +1 -1
- data/lib/clacky/tools/edit.rb +14 -2
- data/lib/clacky/ui2/ui_controller.rb +7 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +56 -59
- data/lib/clacky/web/app.js +65 -7
- data/lib/clacky/web/components/onboard.js +18 -2
- data/lib/clacky/web/core/aside.js +8 -3
- data/lib/clacky/web/core/ext.js +1 -1
- data/lib/clacky/web/features/skills/store.js +30 -2
- data/lib/clacky/web/features/skills/view.js +32 -1
- data/lib/clacky/web/features/workspace/view.js +1 -1
- data/lib/clacky/web/i18n.js +32 -20
- data/lib/clacky/web/index.html +9 -17
- data/lib/clacky/web/sessions.js +286 -28
- data/lib/clacky/web/settings.js +109 -111
- data/lib/clacky/web/ws-dispatcher.js +7 -3
- data/lib/clacky.rb +17 -2
- metadata +38 -2
- data/lib/clacky/media/output_dir.rb +0 -43
data/lib/clacky/web/app.css
CHANGED
|
@@ -526,7 +526,7 @@ body {
|
|
|
526
526
|
/* The sidebar sits on the outer frame (bg-primary) so it's visibly a layer
|
|
527
527
|
BEHIND the chat surface (bg-secondary). Subtle right border separates them. */
|
|
528
528
|
#sidebar {
|
|
529
|
-
--sidebar-width:
|
|
529
|
+
--sidebar-width: 16rem;
|
|
530
530
|
width: var(--sidebar-width);
|
|
531
531
|
min-width: var(--sidebar-width);
|
|
532
532
|
background: var(--color-bg-primary);
|
|
@@ -1938,7 +1938,7 @@ body {
|
|
|
1938
1938
|
the slot has no panels for the current agent it stays empty and the whole
|
|
1939
1939
|
column collapses to zero width. */
|
|
1940
1940
|
#session-aside {
|
|
1941
|
-
--session-aside-width:
|
|
1941
|
+
--session-aside-width: 16rem;
|
|
1942
1942
|
width: var(--session-aside-width);
|
|
1943
1943
|
flex-shrink: 0;
|
|
1944
1944
|
position: relative;
|
|
@@ -1976,7 +1976,7 @@ body {
|
|
|
1976
1976
|
transition: background-color var(--transition-base);
|
|
1977
1977
|
}
|
|
1978
1978
|
.session-aside-resize:hover::after,
|
|
1979
|
-
.session-aside-resize.active::after { background:
|
|
1979
|
+
.session-aside-resize.active::after { background: transparent; }
|
|
1980
1980
|
|
|
1981
1981
|
.session-aside-collapse {
|
|
1982
1982
|
position: absolute;
|
|
@@ -2271,13 +2271,16 @@ body {
|
|
|
2271
2271
|
/* Sit above the next message in case text would otherwise clip beneath it. */
|
|
2272
2272
|
z-index: 1;
|
|
2273
2273
|
}
|
|
2274
|
-
.msg:hover .msg-time
|
|
2274
|
+
.msg:hover .msg-time,
|
|
2275
|
+
.msg-user-wrap:hover .msg-time {
|
|
2275
2276
|
opacity: 1;
|
|
2276
2277
|
transform: translateY(0);
|
|
2277
2278
|
}
|
|
2278
2279
|
/* Time color / alignment: anchor to the bubble's own side, let width be
|
|
2279
2280
|
driven by content — prevents overflow on narrow bubbles. */
|
|
2280
|
-
.msg-user
|
|
2281
|
+
/* User timestamp: appended to .msg-user-wrap (not bubble), sits below action
|
|
2282
|
+
buttons, right-aligned. */
|
|
2283
|
+
.msg-user-wrap > .msg-time { color: var(--color-text-secondary); top: auto; bottom: 0.2rem; right: 0; left: auto; padding-right: 0.25rem; }
|
|
2281
2284
|
.msg-assistant .msg-time { color: var(--color-text-secondary); left: 0; right: auto; padding-left: 0.25rem; }
|
|
2282
2285
|
|
|
2283
2286
|
.msg-user { background: var(--color-accent-soft); color: var(--color-text-primary); white-space: pre-wrap; }
|
|
@@ -2289,11 +2292,11 @@ body {
|
|
|
2289
2292
|
position: relative;
|
|
2290
2293
|
display: flex;
|
|
2291
2294
|
justify-content: flex-end;
|
|
2292
|
-
padding-bottom:
|
|
2295
|
+
padding-bottom: 3rem;
|
|
2293
2296
|
}
|
|
2294
2297
|
.msg-user-actions {
|
|
2295
2298
|
position: absolute;
|
|
2296
|
-
|
|
2299
|
+
bottom: 1.1rem;
|
|
2297
2300
|
right: 0;
|
|
2298
2301
|
display: flex;
|
|
2299
2302
|
gap: 0.125rem;
|
|
@@ -2305,6 +2308,7 @@ body {
|
|
|
2305
2308
|
.msg-user-wrap:hover .msg-user-actions { opacity: 1; pointer-events: auto; }
|
|
2306
2309
|
.msg-user-wrap:has(.msg-user.editing) { padding-bottom: 0; }
|
|
2307
2310
|
.msg-user-wrap:has(.msg-user.editing) .msg-user-actions { display: none; }
|
|
2311
|
+
.msg-user-wrap:has(.msg-user.editing) .msg-time { display: none; }
|
|
2308
2312
|
|
|
2309
2313
|
.msg-user-action-btn {
|
|
2310
2314
|
display: inline-flex;
|
|
@@ -4583,7 +4587,6 @@ body {
|
|
|
4583
4587
|
font-size: 0.8125rem;
|
|
4584
4588
|
color: var(--color-text-primary);
|
|
4585
4589
|
cursor: pointer;
|
|
4586
|
-
margin-top: 0.5rem;
|
|
4587
4590
|
}
|
|
4588
4591
|
.model-field-checkbox input[type="checkbox"] {
|
|
4589
4592
|
width: 15px;
|
|
@@ -4780,7 +4783,7 @@ body {
|
|
|
4780
4783
|
/* ── Model Cards Grid Container ─────────────────────────────────────────────── */
|
|
4781
4784
|
#model-cards {
|
|
4782
4785
|
display: grid;
|
|
4783
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
4786
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
4784
4787
|
gap: 1rem;
|
|
4785
4788
|
}
|
|
4786
4789
|
|
|
@@ -6698,24 +6701,14 @@ body {
|
|
|
6698
6701
|
white-space: nowrap;
|
|
6699
6702
|
}
|
|
6700
6703
|
.brand-skill-version.installed {
|
|
6701
|
-
background:
|
|
6702
|
-
color:
|
|
6703
|
-
border: 1px solid
|
|
6704
|
+
background: color-mix(in srgb, var(--color-accent-primary) 15%, transparent);
|
|
6705
|
+
color: var(--color-accent-primary);
|
|
6706
|
+
border: 1px solid color-mix(in srgb, var(--color-accent-primary) 35%, transparent);
|
|
6704
6707
|
}
|
|
6705
6708
|
.brand-skill-version.latest {
|
|
6706
|
-
background:
|
|
6707
|
-
color: #bfdbfe;
|
|
6708
|
-
border: 1px solid #1d4070;
|
|
6709
|
-
}
|
|
6710
|
-
[data-theme="light"] .brand-skill-version.installed {
|
|
6711
|
-
background: #eff6ff;
|
|
6712
|
-
color: #1d4ed8;
|
|
6713
|
-
border: 1px solid #bfdbfe;
|
|
6714
|
-
}
|
|
6715
|
-
[data-theme="light"] .brand-skill-version.latest {
|
|
6716
|
-
background: #1d4ed8;
|
|
6709
|
+
background: var(--color-accent-primary);
|
|
6717
6710
|
color: #ffffff;
|
|
6718
|
-
border: 1px solid
|
|
6711
|
+
border: 1px solid var(--color-accent-primary);
|
|
6719
6712
|
}
|
|
6720
6713
|
.brand-skill-update-arrow {
|
|
6721
6714
|
color: var(--color-text-secondary);
|
|
@@ -6795,17 +6788,11 @@ body {
|
|
|
6795
6788
|
border-color: var(--color-accent-primary);
|
|
6796
6789
|
}
|
|
6797
6790
|
.btn-brand-update {
|
|
6798
|
-
background:
|
|
6799
|
-
color: #bfdbfe;
|
|
6800
|
-
border: 1px solid #1d4070;
|
|
6801
|
-
}
|
|
6802
|
-
.btn-brand-update:hover { background: #1a3f85; }
|
|
6803
|
-
[data-theme="light"] .btn-brand-update {
|
|
6804
|
-
background: #1d4ed8;
|
|
6791
|
+
background: var(--color-accent-primary);
|
|
6805
6792
|
color: #ffffff;
|
|
6806
|
-
border: 1px solid
|
|
6793
|
+
border: 1px solid var(--color-accent-primary);
|
|
6807
6794
|
}
|
|
6808
|
-
|
|
6795
|
+
.btn-brand-update:hover { background: var(--color-accent-hover); }
|
|
6809
6796
|
.btn-brand-install:disabled,
|
|
6810
6797
|
.btn-brand-update:disabled {
|
|
6811
6798
|
opacity: 0.5;
|
|
@@ -6846,6 +6833,22 @@ body {
|
|
|
6846
6833
|
border-color: var(--color-error-border, #f5c6c6);
|
|
6847
6834
|
background: var(--color-error-bg, #fff0f0);
|
|
6848
6835
|
}
|
|
6836
|
+
.btn-skill-edit {
|
|
6837
|
+
background: transparent;
|
|
6838
|
+
border: 1px solid transparent;
|
|
6839
|
+
border-radius: 6px;
|
|
6840
|
+
color: var(--color-text-tertiary);
|
|
6841
|
+
cursor: pointer;
|
|
6842
|
+
padding: 0.25rem;
|
|
6843
|
+
display: inline-flex;
|
|
6844
|
+
align-items: center;
|
|
6845
|
+
transition: color .15s, border-color .15s, background .15s;
|
|
6846
|
+
}
|
|
6847
|
+
.btn-skill-edit:hover {
|
|
6848
|
+
color: var(--color-text-primary);
|
|
6849
|
+
border-color: var(--color-border-primary);
|
|
6850
|
+
background: var(--color-bg-hover);
|
|
6851
|
+
}
|
|
6849
6852
|
|
|
6850
6853
|
|
|
6851
6854
|
/* ── CodeEditor (CodeMirror 6) Modal ────────────────────────────────────── */
|
|
@@ -7396,28 +7399,35 @@ body.setup-mode[data-theme="dark"] {
|
|
|
7396
7399
|
transform: translateY(1px);
|
|
7397
7400
|
}
|
|
7398
7401
|
#setup-device-error {
|
|
7399
|
-
margin-top: 0
|
|
7402
|
+
margin-top: 0;
|
|
7403
|
+
min-height: 0;
|
|
7400
7404
|
}
|
|
7401
7405
|
#setup-device-error.result-fail { color: var(--color-error); }
|
|
7402
7406
|
|
|
7403
7407
|
/* Manual-config collapsible (secondary path) */
|
|
7404
|
-
#setup-manual-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
+
#setup-manual-toggle {
|
|
7409
|
+
display: block;
|
|
7410
|
+
width: 100%;
|
|
7411
|
+
margin-top: 0.5rem;
|
|
7412
|
+
padding: 0.5rem 0;
|
|
7413
|
+
background: none;
|
|
7414
|
+
border: none;
|
|
7408
7415
|
cursor: pointer;
|
|
7409
|
-
font-size: 0.
|
|
7416
|
+
font-size: 0.8rem;
|
|
7410
7417
|
color: var(--color-text-secondary);
|
|
7411
7418
|
text-align: center;
|
|
7412
|
-
|
|
7413
|
-
|
|
7419
|
+
text-decoration: underline;
|
|
7420
|
+
text-underline-offset: 3px;
|
|
7414
7421
|
user-select: none;
|
|
7422
|
+
transition: color 0.15s;
|
|
7415
7423
|
}
|
|
7416
|
-
#setup-manual-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7424
|
+
#setup-manual-toggle:hover {
|
|
7425
|
+
color: var(--color-text-primary);
|
|
7426
|
+
}
|
|
7427
|
+
#setup-manual-section {
|
|
7428
|
+
margin-top: 0.75rem;
|
|
7429
|
+
padding-top: 0.75rem;
|
|
7430
|
+
border-top: 1px solid var(--color-border-primary);
|
|
7421
7431
|
}
|
|
7422
7432
|
|
|
7423
7433
|
/* Bottom action row: [← Back] [Test & Continue →] */
|
|
@@ -12113,16 +12123,3 @@ body.setup-mode[data-theme="dark"] {
|
|
|
12113
12123
|
}
|
|
12114
12124
|
}
|
|
12115
12125
|
|
|
12116
|
-
/* ════ Media output directory subsection (nested under #media-section) ════ */
|
|
12117
|
-
#media-output-dir-section.settings-subsection {
|
|
12118
|
-
display: flex;
|
|
12119
|
-
flex-direction: column;
|
|
12120
|
-
gap: 0.5rem;
|
|
12121
|
-
max-width: 48rem;
|
|
12122
|
-
}
|
|
12123
|
-
#media-output-dir-section .settings-subsection-desc {
|
|
12124
|
-
font-size: 0.8125rem;
|
|
12125
|
-
color: var(--color-text-secondary);
|
|
12126
|
-
line-height: 1.5;
|
|
12127
|
-
margin: 0;
|
|
12128
|
-
}
|
data/lib/clacky/web/app.js
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
// ── DOM helper (shared by all modules loaded after this) ──────────────────
|
|
11
11
|
const $ = id => document.getElementById(id);
|
|
12
12
|
|
|
13
|
+
// ── Inject X-Lang header into every fetch request ─────────────────────────
|
|
14
|
+
const _nativeFetch = window.fetch;
|
|
15
|
+
window.fetch = function(input, init = {}) {
|
|
16
|
+
const headers = new Headers(init.headers || {});
|
|
17
|
+
if (!headers.has("X-Lang")) headers.set("X-Lang", I18n.lang());
|
|
18
|
+
return _nativeFetch.call(this, input, { ...init, headers });
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
// ── Utilities (shared) ────────────────────────────────────────────────────
|
|
14
22
|
function escapeHtml(str) {
|
|
15
23
|
return String(str)
|
|
@@ -325,13 +333,53 @@ const Modal = (() => {
|
|
|
325
333
|
/** Show a yes/no confirmation dialog. Returns a Promise<boolean>. */
|
|
326
334
|
function confirm(message) {
|
|
327
335
|
return new Promise(resolve => {
|
|
336
|
+
const overlay = $("modal-overlay");
|
|
337
|
+
$("modal-message").textContent = message;
|
|
338
|
+
$("modal-skip-label").style.display = "none";
|
|
339
|
+
$("modal-skip-cb").checked = false;
|
|
340
|
+
if (overlay.parentNode !== document.body || overlay.nextSibling) {
|
|
341
|
+
document.body.appendChild(overlay);
|
|
342
|
+
}
|
|
343
|
+
overlay.style.display = "flex";
|
|
344
|
+
|
|
345
|
+
const cleanup = (result) => {
|
|
346
|
+
overlay.style.display = "none";
|
|
347
|
+
$("modal-yes").onclick = null;
|
|
348
|
+
$("modal-no").onclick = null;
|
|
349
|
+
resolve(result);
|
|
350
|
+
};
|
|
351
|
+
$("modal-yes").onclick = () => cleanup(true);
|
|
352
|
+
$("modal-no").onclick = () => cleanup(false);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Like confirm(), but shows a "don't show again" checkbox.
|
|
358
|
+
* If localStorage[storageKey] === "1", resolves true immediately.
|
|
359
|
+
* Returns Promise<boolean>.
|
|
360
|
+
*/
|
|
361
|
+
function confirmOnce(storageKey, message, skipLabel) {
|
|
362
|
+
if (localStorage.getItem(storageKey) === "1") return Promise.resolve(true);
|
|
363
|
+
|
|
364
|
+
return new Promise(resolve => {
|
|
365
|
+
const overlay = $("modal-overlay");
|
|
328
366
|
$("modal-message").textContent = message;
|
|
329
|
-
$("modal-
|
|
367
|
+
$("modal-skip-text").textContent = skipLabel;
|
|
368
|
+
$("modal-skip-cb").checked = false;
|
|
369
|
+
$("modal-skip-label").style.display = "flex";
|
|
370
|
+
if (overlay.parentNode !== document.body || overlay.nextSibling) {
|
|
371
|
+
document.body.appendChild(overlay);
|
|
372
|
+
}
|
|
373
|
+
overlay.style.display = "flex";
|
|
330
374
|
|
|
331
375
|
const cleanup = (result) => {
|
|
332
|
-
|
|
376
|
+
overlay.style.display = "none";
|
|
377
|
+
$("modal-skip-label").style.display = "none";
|
|
333
378
|
$("modal-yes").onclick = null;
|
|
334
379
|
$("modal-no").onclick = null;
|
|
380
|
+
if (result && $("modal-skip-cb").checked) {
|
|
381
|
+
localStorage.setItem(storageKey, "1");
|
|
382
|
+
}
|
|
335
383
|
resolve(result);
|
|
336
384
|
};
|
|
337
385
|
$("modal-yes").onclick = () => cleanup(true);
|
|
@@ -342,10 +390,15 @@ const Modal = (() => {
|
|
|
342
390
|
/** Show a text input prompt dialog. Returns a Promise<string|null>. */
|
|
343
391
|
function prompt(message, defaultValue = "") {
|
|
344
392
|
return new Promise(resolve => {
|
|
393
|
+
const overlay = $("prompt-modal-overlay");
|
|
345
394
|
$("prompt-modal-message").textContent = message;
|
|
346
395
|
const input = $("prompt-modal-input");
|
|
347
396
|
input.value = defaultValue;
|
|
348
|
-
|
|
397
|
+
// Re-attach to <body> end so it stacks above dynamically-appended overlays.
|
|
398
|
+
if (overlay.parentNode !== document.body || overlay.nextSibling) {
|
|
399
|
+
document.body.appendChild(overlay);
|
|
400
|
+
}
|
|
401
|
+
overlay.style.display = "flex";
|
|
349
402
|
|
|
350
403
|
// Auto-focus and select all text
|
|
351
404
|
setTimeout(() => {
|
|
@@ -375,10 +428,15 @@ const Modal = (() => {
|
|
|
375
428
|
/** Show a rename dialog. Returns a Promise<string|null>. */
|
|
376
429
|
function rename(currentName = "") {
|
|
377
430
|
return new Promise(resolve => {
|
|
431
|
+
const overlay = $("rename-modal-overlay");
|
|
378
432
|
const input = $("rename-modal-input");
|
|
379
433
|
input.value = currentName;
|
|
380
434
|
input.classList.remove("input-error");
|
|
381
|
-
|
|
435
|
+
// Re-attach to <body> end so it stacks above dynamically-appended overlays.
|
|
436
|
+
if (overlay.parentNode !== document.body || overlay.nextSibling) {
|
|
437
|
+
document.body.appendChild(overlay);
|
|
438
|
+
}
|
|
439
|
+
overlay.style.display = "flex";
|
|
382
440
|
|
|
383
441
|
setTimeout(() => {
|
|
384
442
|
input.focus();
|
|
@@ -423,7 +481,7 @@ const Modal = (() => {
|
|
|
423
481
|
});
|
|
424
482
|
}
|
|
425
483
|
|
|
426
|
-
return { confirm, prompt, rename };
|
|
484
|
+
return { confirm, confirmOnce, prompt, rename };
|
|
427
485
|
})();
|
|
428
486
|
|
|
429
487
|
// ── Toast helper ──────────────────────────────────────────────────────────
|
|
@@ -550,7 +608,7 @@ $("sidebar-overlay").addEventListener("click", _closeSidebar);
|
|
|
550
608
|
let startW = 0;
|
|
551
609
|
|
|
552
610
|
// Restore saved width
|
|
553
|
-
const saved = localStorage.getItem("sidebar-width");
|
|
611
|
+
const saved = localStorage.getItem("clacky-sidebar-width");
|
|
554
612
|
if (saved) {
|
|
555
613
|
const w = parseFloat(saved);
|
|
556
614
|
if (w >= MIN_W && w <= MAX_W) {
|
|
@@ -583,7 +641,7 @@ $("sidebar-overlay").addEventListener("click", _closeSidebar);
|
|
|
583
641
|
handle.classList.remove("active");
|
|
584
642
|
document.body.style.cursor = "";
|
|
585
643
|
document.body.style.userSelect = "";
|
|
586
|
-
localStorage.setItem("sidebar-width", _getWidth());
|
|
644
|
+
localStorage.setItem("clacky-sidebar-width", _getWidth());
|
|
587
645
|
});
|
|
588
646
|
})();
|
|
589
647
|
|
|
@@ -110,6 +110,11 @@ const Onboard = (() => {
|
|
|
110
110
|
$("setup-phase-key").style.display = step === "key" ? "" : "none";
|
|
111
111
|
$("setup-dot-1").className = "setup-step" + (step === "lang" ? " active" : " done");
|
|
112
112
|
$("setup-dot-2").className = "setup-step" + (step === "key" ? " active" : "");
|
|
113
|
+
if (step === "key") {
|
|
114
|
+
$("setup-device-block").style.display = "";
|
|
115
|
+
$("setup-manual-toggle").style.display = "";
|
|
116
|
+
$("setup-manual-section").style.display = "none";
|
|
117
|
+
}
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
// Step 2 — API key setup
|
|
@@ -367,9 +372,20 @@ const Onboard = (() => {
|
|
|
367
372
|
|
|
368
373
|
$("setup-btn-test").addEventListener("click", _testAndSave);
|
|
369
374
|
|
|
370
|
-
|
|
375
|
+
$("setup-manual-toggle").addEventListener("click", () => {
|
|
376
|
+
$("setup-device-block").style.display = "none";
|
|
377
|
+
$("setup-manual-toggle").style.display = "none";
|
|
378
|
+
$("setup-manual-section").style.display = "";
|
|
379
|
+
});
|
|
380
|
+
|
|
371
381
|
$("setup-btn-back").addEventListener("click", () => {
|
|
372
|
-
|
|
382
|
+
if ($("setup-manual-section").style.display !== "none") {
|
|
383
|
+
$("setup-device-block").style.display = "";
|
|
384
|
+
$("setup-manual-toggle").style.display = "";
|
|
385
|
+
$("setup-manual-section").style.display = "none";
|
|
386
|
+
} else {
|
|
387
|
+
_showSetupStep("lang");
|
|
388
|
+
}
|
|
373
389
|
});
|
|
374
390
|
|
|
375
391
|
_bindDeviceStep();
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
(() => {
|
|
17
17
|
const WIDTH_KEY = "clacky.aside.width";
|
|
18
18
|
const OPEN_KEY = "clacky.aside.open";
|
|
19
|
-
const MIN_W =
|
|
19
|
+
const MIN_W = 256;
|
|
20
20
|
const MAX_W = 720;
|
|
21
21
|
|
|
22
22
|
const $ = (id) => document.getElementById(id);
|
|
@@ -31,8 +31,11 @@
|
|
|
31
31
|
const opener = $("btn-aside-open");
|
|
32
32
|
const overlay = $("workspace-overlay");
|
|
33
33
|
if (!aside) return;
|
|
34
|
-
let open =
|
|
35
|
-
try {
|
|
34
|
+
let open = false;
|
|
35
|
+
try {
|
|
36
|
+
const stored = localStorage.getItem(OPEN_KEY);
|
|
37
|
+
open = stored === null ? false : stored !== "0";
|
|
38
|
+
} catch (_e) { /* ignore */ }
|
|
36
39
|
const empty = slotEmpty();
|
|
37
40
|
aside.classList.toggle("collapsed", !open);
|
|
38
41
|
if (opener) opener.style.display = (!open && !empty) ? "" : "none";
|
|
@@ -64,6 +67,7 @@
|
|
|
64
67
|
startX = e.clientX;
|
|
65
68
|
startW = parseFloat(getComputedStyle(aside).getPropertyValue("--session-aside-width"));
|
|
66
69
|
handle.classList.add("active");
|
|
70
|
+
aside.style.transition = "none";
|
|
67
71
|
document.body.style.cursor = "col-resize";
|
|
68
72
|
document.body.style.userSelect = "none";
|
|
69
73
|
});
|
|
@@ -79,6 +83,7 @@
|
|
|
79
83
|
if (!dragging) return;
|
|
80
84
|
dragging = false;
|
|
81
85
|
handle.classList.remove("active");
|
|
86
|
+
aside.style.transition = "";
|
|
82
87
|
document.body.style.cursor = "";
|
|
83
88
|
document.body.style.userSelect = "";
|
|
84
89
|
const w = parseFloat(getComputedStyle(aside).getPropertyValue("--session-aside-width"));
|
data/lib/clacky/web/core/ext.js
CHANGED
|
@@ -313,7 +313,7 @@ Clacky.ext = (() => {
|
|
|
313
313
|
btn.type = "button";
|
|
314
314
|
btn.setAttribute("data-tab", id);
|
|
315
315
|
const label = document.createElement("span");
|
|
316
|
-
label.textContent = entry.tab.label || id;
|
|
316
|
+
label.textContent = (typeof entry.tab.label === "function" ? entry.tab.label() : entry.tab.label) || id;
|
|
317
317
|
btn.appendChild(label);
|
|
318
318
|
if (entry.tab.badge != null) _setTabBadge(btn, entry.tab.badge);
|
|
319
319
|
btn.addEventListener("click", () => activate(id));
|
|
@@ -268,6 +268,35 @@ const SkillsStore = (() => {
|
|
|
268
268
|
return _openSessionWith(message || "/skill-creator");
|
|
269
269
|
},
|
|
270
270
|
|
|
271
|
+
/** Fetch a custom skill's SKILL.md content. Returns { ok, content, path, error }. */
|
|
272
|
+
async fetchSkillContent(name) {
|
|
273
|
+
try {
|
|
274
|
+
const res = await fetch(`/api/skills/${encodeURIComponent(name)}/content`);
|
|
275
|
+
const data = await res.json();
|
|
276
|
+
if (!data.ok) throw new Error(data.error || "Load failed");
|
|
277
|
+
return { ok: true, content: data.content || "", path: data.path };
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return { ok: false, error: e.message };
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/** Update a custom skill's SKILL.md content. Returns { ok, error }. */
|
|
284
|
+
async updateSkillContent(name, content) {
|
|
285
|
+
try {
|
|
286
|
+
const res = await fetch(`/api/skills/${encodeURIComponent(name)}/content`, {
|
|
287
|
+
method: "PUT",
|
|
288
|
+
headers: { "Content-Type": "application/json" },
|
|
289
|
+
body: JSON.stringify({ content })
|
|
290
|
+
});
|
|
291
|
+
const data = await res.json();
|
|
292
|
+
if (!res.ok || !data.ok) throw new Error(data.error || "Save failed");
|
|
293
|
+
await Skills.load();
|
|
294
|
+
return { ok: true };
|
|
295
|
+
} catch (e) {
|
|
296
|
+
return { ok: false, error: e.message };
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
271
300
|
/** Import a skill: validate url/path, open a session, run /skill-add. */
|
|
272
301
|
async importSkill(url) {
|
|
273
302
|
const trimmed = (url || "").trim();
|
|
@@ -286,8 +315,7 @@ const SkillsStore = (() => {
|
|
|
286
315
|
|
|
287
316
|
// ── Cross-feature state resets (called by settings.js) ───────────────────
|
|
288
317
|
|
|
289
|
-
|
|
290
|
-
resetAfterUnbind() {
|
|
318
|
+
resetBrandState() {
|
|
291
319
|
_brandSkills = [];
|
|
292
320
|
_brandActivated = false;
|
|
293
321
|
_activeTab = "my-skills";
|
|
@@ -114,6 +114,15 @@ const SkillsView = (() => {
|
|
|
114
114
|
</svg>
|
|
115
115
|
</button>`;
|
|
116
116
|
|
|
117
|
+
const editButtonHtml = isSystem
|
|
118
|
+
? ""
|
|
119
|
+
: `<button class="btn-skill-edit" data-name="${escapeHtml(skill.name)}" title="${I18n.t("skills.btn.edit")}">
|
|
120
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
121
|
+
<path d="M12 20h9"/>
|
|
122
|
+
<path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
|
123
|
+
</svg>
|
|
124
|
+
</button>`;
|
|
125
|
+
|
|
117
126
|
card.innerHTML = `
|
|
118
127
|
<div class="skill-card-main">
|
|
119
128
|
<div class="skill-card-info">
|
|
@@ -131,6 +140,7 @@ const SkillsView = (() => {
|
|
|
131
140
|
<span class="skill-toggle-track"></span>
|
|
132
141
|
</label>
|
|
133
142
|
${useButtonHtml}
|
|
143
|
+
${editButtonHtml}
|
|
134
144
|
${deleteButtonHtml}
|
|
135
145
|
</div>
|
|
136
146
|
</div>
|
|
@@ -157,6 +167,12 @@ const SkillsView = (() => {
|
|
|
157
167
|
const useBtn = card.querySelector(".btn-skill-use");
|
|
158
168
|
if (useBtn) useBtn.addEventListener("click", () => Skills.useInstalledSkill(skill.name));
|
|
159
169
|
|
|
170
|
+
const editBtn = card.querySelector(".btn-skill-edit");
|
|
171
|
+
if (editBtn) editBtn.addEventListener("click", (e) => {
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
_editSkill(skill);
|
|
174
|
+
});
|
|
175
|
+
|
|
160
176
|
const deleteBtn = card.querySelector(".btn-skill-delete");
|
|
161
177
|
if (deleteBtn) deleteBtn.addEventListener("click", (e) => {
|
|
162
178
|
e.stopPropagation();
|
|
@@ -511,12 +527,27 @@ const SkillsView = (() => {
|
|
|
511
527
|
},
|
|
512
528
|
|
|
513
529
|
resetAfterUnbind() {
|
|
514
|
-
SkillsStore.
|
|
530
|
+
SkillsStore.resetBrandState();
|
|
515
531
|
const panel = $("skills-panel");
|
|
516
532
|
if (panel && panel.style.display !== "none") _applyTab("my-skills");
|
|
517
533
|
},
|
|
518
534
|
};
|
|
519
535
|
|
|
536
|
+
async function _editSkill(skill) {
|
|
537
|
+
const res = await Skills.fetchSkillContent(skill.name);
|
|
538
|
+
if (!res.ok) { alert(I18n.t("skills.editFail") + ": " + res.error); return; }
|
|
539
|
+
|
|
540
|
+
CodeEditor.open({
|
|
541
|
+
content: res.content,
|
|
542
|
+
title: skill.name,
|
|
543
|
+
language: "markdown",
|
|
544
|
+
onSave: async (newContent) => {
|
|
545
|
+
const r = await Skills.updateSkillContent(skill.name, newContent);
|
|
546
|
+
if (!r.ok) throw new Error(r.error);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
520
551
|
async function _doImportFromBar() {
|
|
521
552
|
const input = $("skill-import-input");
|
|
522
553
|
const bar = $("skill-import-bar");
|
|
@@ -294,7 +294,7 @@ const WorkspaceView = (() => {
|
|
|
294
294
|
if (window.Clacky && Clacky.ext) {
|
|
295
295
|
Clacky.ext.ui.mountBuiltin("session.aside", (ctx) => WorkspaceView.renderFilesTab(ctx), {
|
|
296
296
|
order: 40,
|
|
297
|
-
tab: { id: "files", label: (typeof I18n !== "undefined" ? I18n.t("workspace.title") : "Files") },
|
|
297
|
+
tab: { id: "files", label: () => (typeof I18n !== "undefined" ? I18n.t("workspace.title") : "Files") },
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
|