openclacky 1.5.7 → 1.5.8
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 +33 -0
- data/README.md +2 -2
- data/README_CN.md +1 -1
- data/README_JA.md +1 -1
- data/lib/clacky/agent.rb +2 -3
- data/lib/clacky/agent_config.rb +40 -18
- data/lib/clacky/cli.rb +0 -1
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +23 -10
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +3 -2
- data/lib/clacky/default_extensions/general/ext.yml +1 -1
- data/lib/clacky/extension/api_loader.rb +4 -11
- data/lib/clacky/extension/packager.rb +1 -1
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/media/generator.rb +3 -3
- data/lib/clacky/message_history.rb +1 -1
- data/lib/clacky/providers.rb +2 -1
- data/lib/clacky/server/http_server.rb +60 -76
- data/lib/clacky/session_manager.rb +7 -0
- data/lib/clacky/ui2/components/inline_input.rb +1 -0
- data/lib/clacky/ui2/components/input_area.rb +1 -0
- data/lib/clacky/ui2/components/modal_component.rb +0 -1
- data/lib/clacky/ui2/components/todo_area.rb +1 -0
- data/lib/clacky/ui2/line_editor.rb +1 -0
- data/lib/clacky/ui2/markdown_renderer.rb +5 -1
- data/lib/clacky/ui2/ui_controller.rb +1 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +134 -210
- data/lib/clacky/web/components/chat-navigator.js +1 -1
- data/lib/clacky/web/features/billing/store.js +3 -3
- data/lib/clacky/web/features/billing/view.js +5 -61
- data/lib/clacky/web/features/extensions/store.js +1 -1
- data/lib/clacky/web/features/extensions/view.js +28 -5
- data/lib/clacky/web/features/new-session/view.js +24 -0
- data/lib/clacky/web/i18n.js +20 -18
- data/lib/clacky/web/index.html +12 -13
- data/lib/clacky/web/projects.js +73 -282
- data/lib/clacky/web/settings.js +6 -1
- metadata +1 -1
data/lib/clacky/web/projects.js
CHANGED
|
@@ -48,30 +48,41 @@ const Projects = (() => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// ── Shared icon helper ────────────────────────────────────────────────────
|
|
51
|
-
// Reads SVG path from the picker popup DOM and forces a consistent size.
|
|
52
|
-
// Falls back to a hard-coded folder icon when the popup isn't in the DOM yet.
|
|
53
51
|
const _FOLDER_PATH = `<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"/>`;
|
|
54
52
|
const _FOLDER_OPEN_PATH = `<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/>`;
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
|
|
54
|
+
const _ICON_PATHS = {
|
|
55
|
+
folder: _FOLDER_PATH,
|
|
56
|
+
code: `<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>`,
|
|
57
|
+
book: `<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>`,
|
|
58
|
+
globe: `<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>`,
|
|
59
|
+
zap: `<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>`,
|
|
60
|
+
star: `<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>`,
|
|
61
|
+
heart: `<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>`,
|
|
62
|
+
tool: `<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>`,
|
|
63
|
+
database: `<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>`,
|
|
64
|
+
image: `<rect x="3" y="3" width="16" height="16" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>`,
|
|
65
|
+
music: `<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>`,
|
|
66
|
+
coffee: `<path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/>`,
|
|
67
|
+
briefcase: `<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>`,
|
|
68
|
+
flask: `<path d="M9 3h6"/><path d="M10 3v6l-4 8a1 1 0 0 0 .9 1.5h10.2a1 1 0 0 0 .9-1.5L14 9V3"/>`,
|
|
69
|
+
chart: `<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>`,
|
|
70
|
+
rocket: `<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>`,
|
|
71
|
+
dollar: `<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>`,
|
|
72
|
+
cpu: `<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/><line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/><line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/>`,
|
|
73
|
+
paint: `<circle cx="13.5" cy="6.5" r=".5"/><circle cx="17.5" cy="10.5" r=".5"/><circle cx="8.5" cy="7.5" r=".5"/><circle cx="6.5" cy="12.5" r=".5"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>`,
|
|
74
|
+
leaf: `<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/>`,
|
|
75
|
+
trophy: `<polyline points="8 21 8 14 4 9 4 4 20 4 20 9 16 14 16 21"/><line x1="8" y1="21" x2="16" y2="21"/><path d="M4 9H2a1 1 0 0 0-1 1 6 6 0 0 0 6 6"/><path d="M20 9h2a1 1 0 0 1 1 1 6 6 0 0 1-6 6"/>`,
|
|
76
|
+
smile: `<circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/>`,
|
|
77
|
+
map: `<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/>`,
|
|
78
|
+
flower: `<path d="M12 7.5a4.5 4.5 0 1 1 4.5 4.5M12 7.5A4.5 4.5 0 1 0 7.5 12M12 7.5V9m-4.5 3a4.5 4.5 0 1 0 4.5 4.5M7.5 12H9m7.5 0a4.5 4.5 0 1 1-4.5 4.5m4.5-4.5H15m-3 4.5V15"/><circle cx="12" cy="12" r="3"/>`,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function _getIconSvg(iconName, size = 15.5) {
|
|
57
82
|
if (iconName === "folder-open") {
|
|
58
83
|
return `<svg viewBox="0 0 24 24" width="${size}" height="${size}" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${_FOLDER_OPEN_PATH}</svg>`;
|
|
59
84
|
}
|
|
60
|
-
const
|
|
61
|
-
let inner = null;
|
|
62
|
-
if (popup) {
|
|
63
|
-
const btn = popup.querySelector(`[data-icon="${iconName}"]`);
|
|
64
|
-
if (btn) {
|
|
65
|
-
// Clone the SVG and override size attrs
|
|
66
|
-
const svg = btn.querySelector("svg");
|
|
67
|
-
if (svg) inner = svg.innerHTML;
|
|
68
|
-
}
|
|
69
|
-
if (!inner) {
|
|
70
|
-
const first = popup.querySelector("[data-icon] svg");
|
|
71
|
-
if (first) inner = first.innerHTML;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!inner) inner = _FOLDER_PATH;
|
|
85
|
+
const inner = _ICON_PATHS[iconName] || _FOLDER_PATH;
|
|
75
86
|
return `<svg viewBox="0 0 24 24" width="${size}" height="${size}" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
|
|
76
87
|
}
|
|
77
88
|
|
|
@@ -271,29 +282,11 @@ const Projects = (() => {
|
|
|
271
282
|
<path d="M12 8v8"/>
|
|
272
283
|
<path d="M8 12h8"/>
|
|
273
284
|
</svg>`;
|
|
274
|
-
newSessionBtn.addEventListener("click",
|
|
285
|
+
newSessionBtn.addEventListener("click", e => {
|
|
275
286
|
e.stopPropagation();
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
method: "POST",
|
|
280
|
-
headers: { "Content-Type": "application/json" },
|
|
281
|
-
body: JSON.stringify({
|
|
282
|
-
name,
|
|
283
|
-
agent_profile: "general",
|
|
284
|
-
source: "manual",
|
|
285
|
-
project_id: project.id,
|
|
286
|
-
}),
|
|
287
|
-
});
|
|
288
|
-
const data = await res.json();
|
|
289
|
-
if (res.ok && data.session) {
|
|
290
|
-
Sessions.add(data.session);
|
|
291
|
-
renderSection();
|
|
292
|
-
Sessions.select(data.session.id);
|
|
293
|
-
}
|
|
294
|
-
} catch (err) {
|
|
295
|
-
console.error("Failed to create session:", err);
|
|
296
|
-
}
|
|
287
|
+
const dir = project.working_dir || (NewSessionStore.state && NewSessionStore.state.defaultDir) || "";
|
|
288
|
+
NewSessionStore.updateAdvanced({ projectId: project.id, workingDir: dir });
|
|
289
|
+
Router.navigate("welcome");
|
|
297
290
|
});
|
|
298
291
|
// Actions button (⋯)
|
|
299
292
|
const actionsBtn = document.createElement("button");
|
|
@@ -304,28 +297,13 @@ const Projects = (() => {
|
|
|
304
297
|
</svg>`;
|
|
305
298
|
actionsBtn.addEventListener("click", e => {
|
|
306
299
|
e.stopPropagation();
|
|
307
|
-
_clearTooltip();
|
|
308
300
|
_showProjectMenu(project.id, actionsBtn);
|
|
309
301
|
});
|
|
310
302
|
header.appendChild(actionsBtn);
|
|
311
303
|
header.appendChild(newSessionBtn);
|
|
312
304
|
|
|
313
|
-
// Tooltip on hover — show after short delay; hide via shared _hideTimer
|
|
314
|
-
header.addEventListener("mouseenter", () => {
|
|
315
|
-
_cancelHide();
|
|
316
|
-
clearTimeout(_tooltipTimer);
|
|
317
|
-
_tooltipTimer = setTimeout(() => {
|
|
318
|
-
_showProjectTooltip(project, sessions, header);
|
|
319
|
-
}, 300);
|
|
320
|
-
});
|
|
321
|
-
header.addEventListener("mouseleave", () => {
|
|
322
|
-
clearTimeout(_tooltipTimer);
|
|
323
|
-
_scheduleHide();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
305
|
// Toggle collapse on header click — in-place DOM toggle, no full re-render
|
|
327
306
|
header.addEventListener("click", () => {
|
|
328
|
-
_clearTooltip();
|
|
329
307
|
const nowCollapsed = _collapsed.has(project.id);
|
|
330
308
|
if (nowCollapsed) {
|
|
331
309
|
_collapsed.delete(project.id);
|
|
@@ -372,156 +350,9 @@ const Projects = (() => {
|
|
|
372
350
|
});
|
|
373
351
|
}
|
|
374
352
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
let _tooltipEl = null;
|
|
378
|
-
let _tooltipTimer = null; // show delay
|
|
379
|
-
let _hideTimer = null; // hide delay — shared by header + tooltip
|
|
380
|
-
|
|
381
|
-
/** Start hide countdown. Cancelled if mouse re-enters header or tooltip. */
|
|
382
|
-
function _scheduleHide() {
|
|
383
|
-
clearTimeout(_hideTimer);
|
|
384
|
-
_hideTimer = setTimeout(_clearTooltip, 150);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/** Cancel pending hide (mouse is back on header or tooltip). */
|
|
388
|
-
function _cancelHide() {
|
|
389
|
-
clearTimeout(_hideTimer);
|
|
390
|
-
_hideTimer = null;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/** Create and position the hover tooltip for a project. */
|
|
394
|
-
function _showProjectTooltip(project, sessions, anchorEl) {
|
|
395
|
-
_clearTooltip();
|
|
396
|
-
|
|
397
|
-
const tip = document.createElement("div");
|
|
398
|
-
tip.className = "project-tooltip";
|
|
399
|
-
|
|
400
|
-
// ── Header: icon + name + session count ──────────────────────────
|
|
401
|
-
const header = document.createElement("div");
|
|
402
|
-
header.className = "project-tooltip-header";
|
|
403
|
-
|
|
404
|
-
// Project icon — use shared helper (12px, matches text size)
|
|
405
|
-
const iconWrap = document.createElement("span");
|
|
406
|
-
iconWrap.className = "project-tooltip-icon";
|
|
407
|
-
iconWrap.innerHTML = _getIconSvg(project.icon || "folder", 12);
|
|
408
|
-
if (project.color) iconWrap.style.color = project.color;
|
|
409
|
-
header.appendChild(iconWrap);
|
|
410
|
-
|
|
411
|
-
const nameEl = document.createElement("span");
|
|
412
|
-
nameEl.className = "project-tooltip-name";
|
|
413
|
-
nameEl.textContent = project.name;
|
|
414
|
-
header.appendChild(nameEl);
|
|
415
|
-
|
|
416
|
-
tip.appendChild(header);
|
|
417
|
-
|
|
418
|
-
// ── Info rows ─────────────────────────────────────────────────────
|
|
419
|
-
const rows = document.createElement("div");
|
|
420
|
-
rows.className = "project-tooltip-rows";
|
|
421
|
-
|
|
422
|
-
// Helper: build one row with icon (12px) + text
|
|
423
|
-
const addRow = (iconSvgHtml, text, titleText) => {
|
|
424
|
-
const row = document.createElement("div");
|
|
425
|
-
row.className = "project-tooltip-row";
|
|
426
|
-
|
|
427
|
-
const iconSpan = document.createElement("span");
|
|
428
|
-
iconSpan.className = "project-tooltip-row-icon";
|
|
429
|
-
iconSpan.innerHTML = iconSvgHtml;
|
|
430
|
-
row.appendChild(iconSpan);
|
|
431
|
-
|
|
432
|
-
const textSpan = document.createElement("span");
|
|
433
|
-
textSpan.className = "project-tooltip-row-text";
|
|
434
|
-
textSpan.textContent = text;
|
|
435
|
-
if (titleText) textSpan.title = titleText;
|
|
436
|
-
row.appendChild(textSpan);
|
|
437
|
-
|
|
438
|
-
rows.appendChild(row);
|
|
439
|
-
};
|
|
353
|
+
|
|
440
354
|
|
|
441
|
-
|
|
442
|
-
const chatIcon = `<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="M7.9 20A9 9 0 1 0 4 16.1L2 22z"/></svg>`;
|
|
443
|
-
|
|
444
|
-
// Sessions row
|
|
445
|
-
const sessionText = sessions.length === 0
|
|
446
|
-
? I18n.t("projects.noSessions", "No sessions")
|
|
447
|
-
: I18n.t("projects.tooltip.sessions", "{n} sessions").replace("{n}", sessions.length);
|
|
448
|
-
addRow(chatIcon, sessionText);
|
|
449
|
-
|
|
450
|
-
// Working dir row (only if set) — reuse picker folder icon at 12px
|
|
451
|
-
if (project.working_dir) {
|
|
452
|
-
const divider = document.createElement("div");
|
|
453
|
-
divider.className = "project-tooltip-divider";
|
|
454
|
-
rows.appendChild(divider);
|
|
455
|
-
|
|
456
|
-
const dirRow = document.createElement("div");
|
|
457
|
-
dirRow.className = "project-tooltip-row project-tooltip-row--clickable";
|
|
458
|
-
dirRow.title = project.working_dir;
|
|
459
|
-
|
|
460
|
-
const iconSpan = document.createElement("span");
|
|
461
|
-
iconSpan.className = "project-tooltip-row-icon";
|
|
462
|
-
iconSpan.innerHTML = _getIconSvg("folder", 12);
|
|
463
|
-
dirRow.appendChild(iconSpan);
|
|
464
|
-
|
|
465
|
-
const textSpan = document.createElement("span");
|
|
466
|
-
textSpan.className = "project-tooltip-row-text";
|
|
467
|
-
textSpan.textContent = project.working_dir;
|
|
468
|
-
dirRow.appendChild(textSpan);
|
|
469
|
-
|
|
470
|
-
// Click: reveal in Finder/Explorer
|
|
471
|
-
dirRow.addEventListener("click", () => {
|
|
472
|
-
fetch("/api/file-action", {
|
|
473
|
-
method: "POST",
|
|
474
|
-
headers: { "Content-Type": "application/json" },
|
|
475
|
-
body: JSON.stringify({ path: project.working_dir, action: "reveal" }),
|
|
476
|
-
}).catch(err => console.error("[Projects] reveal error:", err));
|
|
477
|
-
_clearTooltip();
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
rows.appendChild(dirRow);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
tip.appendChild(rows);
|
|
484
|
-
|
|
485
|
-
// ── Positioning: right side of the sidebar ───────────────────────
|
|
486
|
-
document.body.appendChild(tip);
|
|
487
|
-
_tooltipEl = tip;
|
|
488
|
-
|
|
489
|
-
// Hide when mouse leaves tooltip — use shared timer so gap crossing works
|
|
490
|
-
tip.addEventListener("mouseenter", () => _cancelHide());
|
|
491
|
-
tip.addEventListener("mouseleave", () => _scheduleHide());
|
|
492
|
-
|
|
493
|
-
const rect = anchorEl.getBoundingClientRect();
|
|
494
|
-
const tipRect = tip.getBoundingClientRect();
|
|
495
|
-
const margin = 8; // gap between sidebar and tooltip card
|
|
496
|
-
|
|
497
|
-
// Position tooltip 8px right of anchor; ::before pseudo fills the physical gap
|
|
498
|
-
let left = rect.right + margin;
|
|
499
|
-
if (left + tipRect.width > window.innerWidth - margin) {
|
|
500
|
-
left = rect.left - tipRect.width - margin;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Vertically align top with anchor row, but keep inside viewport
|
|
504
|
-
let top = rect.top;
|
|
505
|
-
if (top + tipRect.height > window.innerHeight - margin) {
|
|
506
|
-
top = window.innerHeight - tipRect.height - margin;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
tip.style.left = `${Math.max(margin, left)}px`;
|
|
510
|
-
tip.style.top = `${Math.max(margin, top)}px`;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function _clearTooltip() {
|
|
514
|
-
clearTimeout(_tooltipTimer);
|
|
515
|
-
_tooltipTimer = null;
|
|
516
|
-
clearTimeout(_hideTimer);
|
|
517
|
-
_hideTimer = null;
|
|
518
|
-
if (_tooltipEl) {
|
|
519
|
-
_tooltipEl.remove();
|
|
520
|
-
_tooltipEl = null;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/** Show a small context menu for project actions (edit / delete). */
|
|
355
|
+
/** Show a small context menu for project actions. */
|
|
525
356
|
function _showProjectMenu(projectId, anchor) {
|
|
526
357
|
// Remove any existing project menu
|
|
527
358
|
document.querySelectorAll(".project-actions-menu").forEach(m => m.remove());
|
|
@@ -529,10 +360,24 @@ const Projects = (() => {
|
|
|
529
360
|
const menu = document.createElement("div");
|
|
530
361
|
menu.className = "project-actions-menu";
|
|
531
362
|
|
|
532
|
-
|
|
363
|
+
// Lucide icons, embedded to avoid extra asset requests.
|
|
364
|
+
const iconEdit = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4z"/></svg>`;
|
|
365
|
+
const iconClearSessions = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 22-1-4"/><path d="M19 14a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2h-3a1 1 0 0 1-1-1V4a2 2 0 0 0-4 0v5a1 1 0 0 1-1 1H6a2 2 0 0 0-2 2v1a1 1 0 0 0 1 1"/><path d="M19 14H5l-1.973 6.767A1 1 0 0 0 4 22h16a1 1 0 0 0 .973-1.233z"/><path d="m8 22 1-4"/></svg>`;
|
|
366
|
+
const iconTrash = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
|
|
367
|
+
|
|
368
|
+
const addItem = (labelKey, defaultLabel, iconSvg, danger, onClick) => {
|
|
533
369
|
const item = document.createElement("button");
|
|
534
370
|
item.className = "project-actions-menu-item" + (danger ? " danger" : "");
|
|
535
|
-
|
|
371
|
+
|
|
372
|
+
const icon = document.createElement("span");
|
|
373
|
+
icon.className = "project-actions-menu-icon";
|
|
374
|
+
icon.innerHTML = iconSvg;
|
|
375
|
+
|
|
376
|
+
const label = document.createElement("span");
|
|
377
|
+
label.className = "project-actions-menu-label";
|
|
378
|
+
label.textContent = I18n.t(labelKey, defaultLabel);
|
|
379
|
+
|
|
380
|
+
item.append(icon, label);
|
|
536
381
|
item.addEventListener("click", e => {
|
|
537
382
|
e.stopPropagation();
|
|
538
383
|
menu.remove();
|
|
@@ -541,9 +386,9 @@ const Projects = (() => {
|
|
|
541
386
|
menu.appendChild(item);
|
|
542
387
|
};
|
|
543
388
|
|
|
544
|
-
addItem("projects.menu.edit", "Edit", false, () => promptEdit(projectId));
|
|
545
|
-
addItem("projects.menu.deleteSessions", "Delete Sessions", false, () => promptDeleteSessions(projectId));
|
|
546
|
-
addItem("projects.menu.delete", "Delete",
|
|
389
|
+
addItem("projects.menu.edit", "Edit Project", iconEdit, false, () => promptEdit(projectId));
|
|
390
|
+
addItem("projects.menu.deleteSessions", "Delete Sessions", iconClearSessions, false, () => promptDeleteSessions(projectId));
|
|
391
|
+
addItem("projects.menu.delete", "Delete", iconTrash, true, () => promptDelete(projectId));
|
|
547
392
|
|
|
548
393
|
// Position near anchor
|
|
549
394
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -647,6 +492,13 @@ const Projects = (() => {
|
|
|
647
492
|
*/
|
|
648
493
|
function _ensureModalDOM() {
|
|
649
494
|
if (document.getElementById("project-picker-popup")) return;
|
|
495
|
+
|
|
496
|
+
let iconsHTML = "";
|
|
497
|
+
for (const [name, path] of Object.entries(_ICON_PATHS)) {
|
|
498
|
+
const sel = name === "folder" ? " project-picker-icon-option--selected" : "";
|
|
499
|
+
iconsHTML += `<button class="project-picker-icon-option${sel}" data-icon="${name}" title="${name[0].toUpperCase()}${name.slice(1)}"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${path}</svg></button>`;
|
|
500
|
+
}
|
|
501
|
+
|
|
650
502
|
const tpl = document.createElement("div");
|
|
651
503
|
tpl.innerHTML = `
|
|
652
504
|
<div id="project-picker-popup" class="project-picker-popup" style="display:none">
|
|
@@ -665,78 +517,7 @@ const Projects = (() => {
|
|
|
665
517
|
<div class="project-picker-divider"></div>
|
|
666
518
|
<!-- Icon grid -->
|
|
667
519
|
<div class="project-picker-icons" id="project-picker-icons">
|
|
668
|
-
|
|
669
|
-
<svg viewBox="0 0 24 24" width="16" height="16" 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>
|
|
670
|
-
</button>
|
|
671
|
-
<button class="project-picker-icon-option" data-icon="code" title="Code">
|
|
672
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
673
|
-
</button>
|
|
674
|
-
<button class="project-picker-icon-option" data-icon="book" title="Book">
|
|
675
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
|
676
|
-
</button>
|
|
677
|
-
<button class="project-picker-icon-option" data-icon="globe" title="Globe">
|
|
678
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
|
679
|
-
</button>
|
|
680
|
-
<button class="project-picker-icon-option" data-icon="zap" title="Zap">
|
|
681
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
|
682
|
-
</button>
|
|
683
|
-
<button class="project-picker-icon-option" data-icon="star" title="Star">
|
|
684
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
685
|
-
</button>
|
|
686
|
-
<button class="project-picker-icon-option" data-icon="heart" title="Heart">
|
|
687
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
|
|
688
|
-
</button>
|
|
689
|
-
<button class="project-picker-icon-option" data-icon="tool" title="Tool">
|
|
690
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
|
|
691
|
-
</button>
|
|
692
|
-
<button class="project-picker-icon-option" data-icon="database" title="Database">
|
|
693
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
|
|
694
|
-
</button>
|
|
695
|
-
<button class="project-picker-icon-option" data-icon="image" title="Image">
|
|
696
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="16" height="16" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
|
|
697
|
-
</button>
|
|
698
|
-
<button class="project-picker-icon-option" data-icon="music" title="Music">
|
|
699
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
|
700
|
-
</button>
|
|
701
|
-
<button class="project-picker-icon-option" data-icon="coffee" title="Coffee">
|
|
702
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
703
|
-
</button>
|
|
704
|
-
<button class="project-picker-icon-option" data-icon="briefcase" title="Briefcase">
|
|
705
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>
|
|
706
|
-
</button>
|
|
707
|
-
<button class="project-picker-icon-option" data-icon="flask" title="Flask">
|
|
708
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6"/><path d="M10 3v6l-4 8a1 1 0 0 0 .9 1.5h10.2a1 1 0 0 0 .9-1.5L14 9V3"/></svg>
|
|
709
|
-
</button>
|
|
710
|
-
<button class="project-picker-icon-option" data-icon="chart" title="Chart">
|
|
711
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
|
|
712
|
-
</button>
|
|
713
|
-
<button class="project-picker-icon-option" data-icon="rocket" title="Rocket">
|
|
714
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>
|
|
715
|
-
</button>
|
|
716
|
-
<button class="project-picker-icon-option" data-icon="dollar" title="Dollar">
|
|
717
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
|
|
718
|
-
</button>
|
|
719
|
-
<button class="project-picker-icon-option" data-icon="cpu" title="CPU">
|
|
720
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/><line x1="20" y1="9" x2="23" y2="9"/><line x1="20" y1="14" x2="23" y2="14"/><line x1="1" y1="9" x2="4" y2="9"/><line x1="1" y1="14" x2="4" y2="14"/></svg>
|
|
721
|
-
</button>
|
|
722
|
-
<button class="project-picker-icon-option" data-icon="paint" title="Paint">
|
|
723
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5"/><circle cx="17.5" cy="10.5" r=".5"/><circle cx="8.5" cy="7.5" r=".5"/><circle cx="6.5" cy="12.5" r=".5"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>
|
|
724
|
-
</button>
|
|
725
|
-
<button class="project-picker-icon-option" data-icon="leaf" title="Leaf">
|
|
726
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/></svg>
|
|
727
|
-
</button>
|
|
728
|
-
<button class="project-picker-icon-option" data-icon="trophy" title="Trophy">
|
|
729
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="8 21 8 14 4 9 4 4 20 4 20 9 16 14 16 21"/><line x1="8" y1="21" x2="16" y2="21"/><path d="M4 9H2a1 1 0 0 0-1 1 6 6 0 0 0 6 6"/><path d="M20 9h2a1 1 0 0 1 1 1 6 6 0 0 1-6 6"/></svg>
|
|
730
|
-
</button>
|
|
731
|
-
<button class="project-picker-icon-option" data-icon="smile" title="Smile">
|
|
732
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
|
|
733
|
-
</button>
|
|
734
|
-
<button class="project-picker-icon-option" data-icon="map" title="Map">
|
|
735
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
|
|
736
|
-
</button>
|
|
737
|
-
<button class="project-picker-icon-option" data-icon="flower" title="Flower">
|
|
738
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7.5a4.5 4.5 0 1 1 4.5 4.5M12 7.5A4.5 4.5 0 1 0 7.5 12M12 7.5V9m-4.5 3a4.5 4.5 0 1 0 4.5 4.5M7.5 12H9m7.5 0a4.5 4.5 0 1 1-4.5 4.5m4.5-4.5H15m-3 4.5V15"/><circle cx="12" cy="12" r="3"/></svg>
|
|
739
|
-
</button>
|
|
520
|
+
${iconsHTML}
|
|
740
521
|
</div>
|
|
741
522
|
<!-- Done button -->
|
|
742
523
|
<div class="project-picker-footer">
|
|
@@ -775,6 +556,7 @@ const Projects = (() => {
|
|
|
775
556
|
</button>
|
|
776
557
|
</div>
|
|
777
558
|
</div>
|
|
559
|
+
<div id="create-project-folder-hint" class="create-project-folder-hint" data-i18n="projects.create.folderDefaultHint">If not set, the default working directory ~/clacky_workspace will be used</div>
|
|
778
560
|
</div>
|
|
779
561
|
</div>
|
|
780
562
|
<div class="modal-footer">
|
|
@@ -810,6 +592,7 @@ const Projects = (() => {
|
|
|
810
592
|
const folderCard = $("create-project-folder-card");
|
|
811
593
|
const folderName = $("create-project-folder-name");
|
|
812
594
|
const folderRemoveBtn = $("create-project-folder-remove");
|
|
595
|
+
const folderHint = $("create-project-folder-hint");
|
|
813
596
|
|
|
814
597
|
// State
|
|
815
598
|
let selectedColor = ""; // "" = default (accent)
|
|
@@ -859,6 +642,7 @@ const Projects = (() => {
|
|
|
859
642
|
folderCard.style.display = "none";
|
|
860
643
|
folderArea.style.cursor = "pointer";
|
|
861
644
|
}
|
|
645
|
+
folderHint.style.display = selectedDir ? "none" : "";
|
|
862
646
|
};
|
|
863
647
|
|
|
864
648
|
// Reset all state (prefill when editing an existing project)
|
|
@@ -1123,6 +907,12 @@ const Projects = (() => {
|
|
|
1123
907
|
}
|
|
1124
908
|
}
|
|
1125
909
|
|
|
910
|
+
function expand(projectId) {
|
|
911
|
+
if (!projectId) return;
|
|
912
|
+
_collapsed.delete(String(projectId));
|
|
913
|
+
_saveCollapsed();
|
|
914
|
+
}
|
|
915
|
+
|
|
1126
916
|
// ── Init ───────────────────────────────────────────────────────────────────
|
|
1127
917
|
|
|
1128
918
|
function init() {
|
|
@@ -1174,6 +964,7 @@ const Projects = (() => {
|
|
|
1174
964
|
promptEdit,
|
|
1175
965
|
promptDelete,
|
|
1176
966
|
renderSection,
|
|
967
|
+
expand,
|
|
1177
968
|
init,
|
|
1178
969
|
};
|
|
1179
970
|
})();
|
data/lib/clacky/web/settings.js
CHANGED
|
@@ -2337,7 +2337,12 @@ const Settings = (() => {
|
|
|
2337
2337
|
|
|
2338
2338
|
if (source === "custom") {
|
|
2339
2339
|
if (cur.source !== "custom" && !_mediaCustomDraft[kind]) {
|
|
2340
|
-
|
|
2340
|
+
const sc = cur.saved_custom;
|
|
2341
|
+
_mediaCustomDraft[kind] = {
|
|
2342
|
+
model: (sc && sc.model) || "",
|
|
2343
|
+
base_url: (sc && sc.base_url) || "",
|
|
2344
|
+
api_key: cur.api_key_masked || ""
|
|
2345
|
+
};
|
|
2341
2346
|
}
|
|
2342
2347
|
_mediaState[kind] = { ...cur, source: "custom" };
|
|
2343
2348
|
_renderMediaRows();
|