openclacky 0.9.32 → 0.9.34
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 +22 -0
- data/lib/clacky/agent/llm_caller.rb +11 -12
- data/lib/clacky/agent/skill_auto_creator.rb +16 -21
- data/lib/clacky/agent/skill_manager.rb +18 -21
- data/lib/clacky/agent/skill_reflector.rb +16 -24
- data/lib/clacky/agent/system_prompt_builder.rb +5 -0
- data/lib/clacky/agent.rb +45 -19
- data/lib/clacky/client.rb +47 -16
- data/lib/clacky/server/http_server.rb +116 -12
- data/lib/clacky/server/session_registry.rb +7 -0
- data/lib/clacky/server/web_ui_controller.rb +6 -0
- data/lib/clacky/skill.rb +5 -0
- data/lib/clacky/skill_loader.rb +2 -10
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +383 -124
- data/lib/clacky/web/app.js +233 -115
- data/lib/clacky/web/i18n.js +42 -0
- data/lib/clacky/web/index.html +86 -32
- data/lib/clacky/web/sessions.js +349 -30
- data/lib/clacky/web/settings.js +76 -2
- metadata +1 -1
data/lib/clacky/web/app.js
CHANGED
|
@@ -274,7 +274,40 @@ const Modal = (() => {
|
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
/** Show a text input prompt dialog. Returns a Promise<string|null>. */
|
|
278
|
+
function prompt(message, defaultValue = "") {
|
|
279
|
+
return new Promise(resolve => {
|
|
280
|
+
$("prompt-modal-message").textContent = message;
|
|
281
|
+
const input = $("prompt-modal-input");
|
|
282
|
+
input.value = defaultValue;
|
|
283
|
+
$("prompt-modal-overlay").style.display = "flex";
|
|
284
|
+
|
|
285
|
+
// Auto-focus and select all text
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
input.focus();
|
|
288
|
+
input.select();
|
|
289
|
+
}, 50);
|
|
290
|
+
|
|
291
|
+
const cleanup = (result) => {
|
|
292
|
+
$("prompt-modal-overlay").style.display = "none";
|
|
293
|
+
$("prompt-modal-ok").onclick = null;
|
|
294
|
+
$("prompt-modal-cancel").onclick = null;
|
|
295
|
+
input.onkeydown = null;
|
|
296
|
+
resolve(result);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
$("prompt-modal-ok").onclick = () => cleanup(input.value.trim() || null);
|
|
300
|
+
$("prompt-modal-cancel").onclick = () => cleanup(null);
|
|
301
|
+
|
|
302
|
+
// Support Enter to confirm, Escape to cancel
|
|
303
|
+
input.onkeydown = (e) => {
|
|
304
|
+
if (e.key === "Enter") cleanup(input.value.trim() || null);
|
|
305
|
+
if (e.key === "Escape") cleanup(null);
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { confirm, prompt };
|
|
278
311
|
})();
|
|
279
312
|
|
|
280
313
|
// ── Confirmation modal ────────────────────────────────────────────────────
|
|
@@ -391,8 +424,13 @@ WS.onEvent(ev => {
|
|
|
391
424
|
// Update chat title in case session was renamed
|
|
392
425
|
$("chat-title").textContent = current?.name || "";
|
|
393
426
|
}
|
|
394
|
-
// When a session finishes, refresh tasks and skills
|
|
395
|
-
if (patch.status === "idle") {
|
|
427
|
+
// When a session finishes, refresh tasks and skills, and clear any progress state
|
|
428
|
+
if (patch.status === "idle") {
|
|
429
|
+
Tasks.load();
|
|
430
|
+
Skills.load();
|
|
431
|
+
// Clear progress state for this session (even if not currently active)
|
|
432
|
+
Sessions.clearProgress(sid);
|
|
433
|
+
}
|
|
396
434
|
break;
|
|
397
435
|
}
|
|
398
436
|
|
|
@@ -825,10 +863,8 @@ function _mobileCloseSidebar() {
|
|
|
825
863
|
}
|
|
826
864
|
|
|
827
865
|
// ── New session split button [+ ▾] ────────────────────────────────────────
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
// dropdown items: click → create session with chosen agent_profile
|
|
831
|
-
// btn-new-project-entry: click → open inline new-project form
|
|
866
|
+
// Main button: quick create (like before)
|
|
867
|
+
// Arrow button: show dropdown with "Advanced Options..." to open modal
|
|
832
868
|
if ($("btn-new-session-inline")) {
|
|
833
869
|
$("btn-new-session-inline").addEventListener("click", () => Sessions.create("general"));
|
|
834
870
|
}
|
|
@@ -839,21 +875,13 @@ if ($("btn-new-session-arrow")) {
|
|
|
839
875
|
if (dd) dd.hidden = !dd.hidden;
|
|
840
876
|
});
|
|
841
877
|
}
|
|
842
|
-
// Dropdown item
|
|
843
|
-
document.
|
|
844
|
-
|
|
878
|
+
// Dropdown item: Advanced Options → open modal
|
|
879
|
+
document.addEventListener("click", (e) => {
|
|
880
|
+
if (e.target && e.target.id === "btn-new-session-modal") {
|
|
845
881
|
e.stopPropagation();
|
|
846
882
|
$("new-session-dropdown").hidden = true;
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (item.id === "btn-new-project-entry") {
|
|
850
|
-
NewProjectForm.open();
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
const profile = item.dataset.profile || "general";
|
|
855
|
-
Sessions.create(profile);
|
|
856
|
-
});
|
|
883
|
+
Sessions.openNewSessionModal();
|
|
884
|
+
}
|
|
857
885
|
});
|
|
858
886
|
// Close dropdown when clicking elsewhere
|
|
859
887
|
document.addEventListener("click", () => {
|
|
@@ -863,103 +891,28 @@ document.addEventListener("click", () => {
|
|
|
863
891
|
|
|
864
892
|
$("btn-welcome-new").addEventListener("click", () => Sessions.create("general"));
|
|
865
893
|
|
|
866
|
-
// ── New
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
// Generate default dir like ~/clacky_projects/my-project
|
|
870
|
-
return "~/clacky_projects/my-project";
|
|
871
|
-
},
|
|
872
|
-
|
|
873
|
-
open() {
|
|
874
|
-
const panel = $("new-project-form");
|
|
875
|
-
const input = $("new-project-dir");
|
|
876
|
-
if (!panel) return;
|
|
877
|
-
// Set default directory value
|
|
878
|
-
if (input && !input.value) input.value = this._defaultDir();
|
|
879
|
-
// Animate open
|
|
880
|
-
panel.hidden = false;
|
|
881
|
-
requestAnimationFrame(() => panel.classList.add("panel--open"));
|
|
882
|
-
// Focus input and select the "my-project" part so user can rename easily
|
|
883
|
-
if (input) {
|
|
884
|
-
input.focus();
|
|
885
|
-
const val = input.value;
|
|
886
|
-
const lastSlash = val.lastIndexOf("/");
|
|
887
|
-
if (lastSlash >= 0) {
|
|
888
|
-
input.setSelectionRange(lastSlash + 1, val.length);
|
|
889
|
-
} else {
|
|
890
|
-
input.select();
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
|
|
895
|
-
close() {
|
|
896
|
-
const panel = $("new-project-form");
|
|
897
|
-
if (!panel) return;
|
|
898
|
-
panel.classList.remove("panel--open");
|
|
899
|
-
setTimeout(() => { panel.hidden = true; }, 200);
|
|
900
|
-
},
|
|
901
|
-
|
|
902
|
-
async submit() {
|
|
903
|
-
const input = $("new-project-dir");
|
|
904
|
-
const createBtn = $("btn-new-project-create");
|
|
905
|
-
const dir = input ? input.value.trim() : "";
|
|
906
|
-
if (!dir) { input && input.focus(); return; }
|
|
907
|
-
|
|
908
|
-
// Derive session name from directory basename
|
|
909
|
-
const basename = dir.replace(/\/$/, "").split("/").pop() || "Project";
|
|
910
|
-
const name = basename;
|
|
911
|
-
|
|
912
|
-
if (createBtn) createBtn.disabled = true;
|
|
913
|
-
|
|
914
|
-
const res = await fetch("/api/sessions", {
|
|
915
|
-
method: "POST",
|
|
916
|
-
headers: { "Content-Type": "application/json" },
|
|
917
|
-
body: JSON.stringify({
|
|
918
|
-
name,
|
|
919
|
-
agent_profile: "coding",
|
|
920
|
-
source: "manual",
|
|
921
|
-
working_dir: dir
|
|
922
|
-
})
|
|
923
|
-
});
|
|
924
|
-
const data = await res.json();
|
|
925
|
-
|
|
926
|
-
if (createBtn) createBtn.disabled = false;
|
|
927
|
-
|
|
928
|
-
if (!res.ok) {
|
|
929
|
-
const msg = data.error || "unknown error";
|
|
930
|
-
const friendly = res.status === 409
|
|
931
|
-
? I18n.t("sessions.dirNotEmpty")
|
|
932
|
-
: I18n.t("sessions.createError") + msg;
|
|
933
|
-
alert(friendly);
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const session = data.session;
|
|
938
|
-
if (!session) return;
|
|
939
|
-
|
|
940
|
-
// Reset form for next use
|
|
941
|
-
if (input) input.value = "";
|
|
942
|
-
this.close();
|
|
943
|
-
|
|
944
|
-
Sessions.add(session);
|
|
945
|
-
Sessions.renderList();
|
|
946
|
-
Sessions.select(session.id);
|
|
947
|
-
// After subscribe is confirmed, auto-send /new to trigger the new-project skill
|
|
948
|
-
Sessions.setPendingMessage(session.id, "/new");
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
|
|
952
|
-
if ($("btn-new-project-cancel")) {
|
|
953
|
-
$("btn-new-project-cancel").addEventListener("click", () => NewProjectForm.close());
|
|
894
|
+
// ── New Session Modal event handlers ───────────────────────────────────────
|
|
895
|
+
if ($("new-session-modal-close")) {
|
|
896
|
+
$("new-session-modal-close").addEventListener("click", () => Sessions.closeNewSessionModal());
|
|
954
897
|
}
|
|
955
|
-
if ($("
|
|
956
|
-
$("
|
|
898
|
+
if ($("new-session-cancel")) {
|
|
899
|
+
$("new-session-cancel").addEventListener("click", () => Sessions.closeNewSessionModal());
|
|
957
900
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
901
|
+
if ($("new-session-create")) {
|
|
902
|
+
$("new-session-create").addEventListener("click", () => Sessions.createFromModal());
|
|
903
|
+
}
|
|
904
|
+
// Close modal when clicking overlay
|
|
905
|
+
if ($("new-session-modal")) {
|
|
906
|
+
$("new-session-modal").addEventListener("click", (e) => {
|
|
907
|
+
if (e.target.id === "new-session-modal") {
|
|
908
|
+
Sessions.closeNewSessionModal();
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
// Browse button (placeholder - actual file browsing would need native integration)
|
|
913
|
+
if ($("new-session-browse-btn")) {
|
|
914
|
+
$("new-session-browse-btn").addEventListener("click", () => {
|
|
915
|
+
alert("Tip: Enter your desired path directly, e.g., ~/clacky_workspace/my-project");
|
|
963
916
|
});
|
|
964
917
|
}
|
|
965
918
|
|
|
@@ -1596,3 +1549,168 @@ window.bootAfterBrand = async function() {
|
|
|
1596
1549
|
}
|
|
1597
1550
|
});
|
|
1598
1551
|
})();
|
|
1552
|
+
|
|
1553
|
+
// ── Session Info Bar Model Switcher ───────────────────────────────────────
|
|
1554
|
+
(function() {
|
|
1555
|
+
let _isOpen = false;
|
|
1556
|
+
|
|
1557
|
+
// Toggle model dropdown when clicking on model name
|
|
1558
|
+
document.addEventListener("click", async (e) => {
|
|
1559
|
+
const modelEl = e.target.closest("#sib-model");
|
|
1560
|
+
if (modelEl) {
|
|
1561
|
+
e.stopPropagation();
|
|
1562
|
+
const dropdown = $("sib-model-dropdown");
|
|
1563
|
+
if (!dropdown) return;
|
|
1564
|
+
|
|
1565
|
+
if (_isOpen) {
|
|
1566
|
+
dropdown.style.display = "none";
|
|
1567
|
+
_isOpen = false;
|
|
1568
|
+
} else {
|
|
1569
|
+
await _populateModelDropdown(modelEl.dataset.sessionId, modelEl.textContent.trim());
|
|
1570
|
+
|
|
1571
|
+
// Calculate position relative to the model element (fixed positioning)
|
|
1572
|
+
const rect = modelEl.getBoundingClientRect();
|
|
1573
|
+
dropdown.style.left = `${rect.left + rect.width / 2}px`;
|
|
1574
|
+
dropdown.style.top = `${rect.top - 6}px`; // 6px above the element
|
|
1575
|
+
dropdown.style.transform = "translate(-50%, -100%)"; // Center horizontally, move up by its own height
|
|
1576
|
+
|
|
1577
|
+
dropdown.style.display = "block";
|
|
1578
|
+
_isOpen = true;
|
|
1579
|
+
}
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// Close dropdown when clicking outside
|
|
1584
|
+
if (_isOpen && !e.target.closest(".sib-model-dropdown")) {
|
|
1585
|
+
const dropdown = $("sib-model-dropdown");
|
|
1586
|
+
if (dropdown) dropdown.style.display = "none";
|
|
1587
|
+
_isOpen = false;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// Populate dropdown with available models
|
|
1592
|
+
async function _populateModelDropdown(sessionId, currentModel) {
|
|
1593
|
+
const dropdown = $("sib-model-dropdown");
|
|
1594
|
+
if (!dropdown) return;
|
|
1595
|
+
|
|
1596
|
+
try {
|
|
1597
|
+
console.log("[Model Switcher] Fetching /api/config...");
|
|
1598
|
+
const res = await fetch("/api/config");
|
|
1599
|
+
const data = await res.json();
|
|
1600
|
+
console.log("[Model Switcher] Received data:", data);
|
|
1601
|
+
const models = data.models || [];
|
|
1602
|
+
console.log("[Model Switcher] Models count:", models.length);
|
|
1603
|
+
|
|
1604
|
+
if (models.length === 0) {
|
|
1605
|
+
dropdown.innerHTML = '<div style="padding:12px;text-align:center;color:var(--color-text-secondary);font-size:11px;">No models configured</div>';
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
dropdown.innerHTML = "";
|
|
1610
|
+
|
|
1611
|
+
models.forEach(m => {
|
|
1612
|
+
console.log("[Model Switcher] Adding model:", m.model, "current:", currentModel);
|
|
1613
|
+
const opt = document.createElement("div");
|
|
1614
|
+
opt.className = "sib-model-option";
|
|
1615
|
+
if (m.model === currentModel) opt.classList.add("current");
|
|
1616
|
+
|
|
1617
|
+
const modelName = document.createElement("span");
|
|
1618
|
+
modelName.textContent = m.model;
|
|
1619
|
+
opt.appendChild(modelName);
|
|
1620
|
+
|
|
1621
|
+
if (m.type === "default" || m.type === "lite") {
|
|
1622
|
+
const badge = document.createElement("span");
|
|
1623
|
+
badge.className = `model-badge ${m.type}`;
|
|
1624
|
+
badge.textContent = m.type;
|
|
1625
|
+
opt.appendChild(badge);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
opt.addEventListener("click", () => _switchModel(sessionId, m.model));
|
|
1629
|
+
dropdown.appendChild(opt);
|
|
1630
|
+
});
|
|
1631
|
+
console.log("[Model Switcher] Dropdown populated, children count:", dropdown.children.length);
|
|
1632
|
+
} catch (e) {
|
|
1633
|
+
console.error("Failed to load models:", e);
|
|
1634
|
+
dropdown.innerHTML = '<div style="padding:12px;text-align:center;color:var(--color-error);font-size:11px;">Error loading models</div>';
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// Switch session model via API
|
|
1639
|
+
async function _switchModel(sessionId, newModel) {
|
|
1640
|
+
const dropdown = $("sib-model-dropdown");
|
|
1641
|
+
if (dropdown) {
|
|
1642
|
+
dropdown.style.display = "none";
|
|
1643
|
+
_isOpen = false;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
try {
|
|
1647
|
+
const res = await fetch(`/api/sessions/${sessionId}/model`, {
|
|
1648
|
+
method: "PATCH",
|
|
1649
|
+
headers: { "Content-Type": "application/json" },
|
|
1650
|
+
body: JSON.stringify({ model: newModel })
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
const data = await res.json();
|
|
1654
|
+
|
|
1655
|
+
if (!res.ok) {
|
|
1656
|
+
throw new Error(data.error || "Unknown error");
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// Update UI optimistically (will be confirmed by session_update broadcast)
|
|
1660
|
+
const sibModel = $("sib-model");
|
|
1661
|
+
if (sibModel) sibModel.textContent = newModel;
|
|
1662
|
+
|
|
1663
|
+
console.log(`Switched session ${sessionId} to model ${newModel}`);
|
|
1664
|
+
} catch (e) {
|
|
1665
|
+
console.error("Failed to switch model:", e);
|
|
1666
|
+
alert("Failed to switch model: " + e.message);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
})();
|
|
1670
|
+
|
|
1671
|
+
// ── Session Info Bar Working Directory Switcher ───────────────────────────
|
|
1672
|
+
(function() {
|
|
1673
|
+
// Handle click on working directory
|
|
1674
|
+
document.addEventListener("click", async (e) => {
|
|
1675
|
+
const dirEl = e.target.closest("#sib-dir");
|
|
1676
|
+
if (dirEl) {
|
|
1677
|
+
e.stopPropagation();
|
|
1678
|
+
const sessionId = dirEl.dataset.sessionId;
|
|
1679
|
+
const currentDir = dirEl.title.replace(" (click to change)", "");
|
|
1680
|
+
|
|
1681
|
+
const newDir = await Modal.prompt("Change working directory:", currentDir);
|
|
1682
|
+
if (newDir && newDir !== currentDir) {
|
|
1683
|
+
_changeWorkingDirectory(sessionId, newDir);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
// Change working directory via backend API
|
|
1689
|
+
async function _changeWorkingDirectory(sessionId, newDir) {
|
|
1690
|
+
try {
|
|
1691
|
+
const res = await fetch(`/api/sessions/${sessionId}/working_dir`, {
|
|
1692
|
+
method: "PATCH",
|
|
1693
|
+
headers: { "Content-Type": "application/json" },
|
|
1694
|
+
body: JSON.stringify({ working_dir: newDir })
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
const data = await res.json();
|
|
1698
|
+
|
|
1699
|
+
if (!res.ok) {
|
|
1700
|
+
throw new Error(data.error || "Unknown error");
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Update UI optimistically (will be confirmed by session_update broadcast)
|
|
1704
|
+
const sibDir = $("sib-dir");
|
|
1705
|
+
if (sibDir) {
|
|
1706
|
+
sibDir.textContent = newDir;
|
|
1707
|
+
sibDir.title = newDir + " (click to change)";
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
console.log(`Changed session ${sessionId} directory to ${newDir}`);
|
|
1711
|
+
} catch (e) {
|
|
1712
|
+
console.error("Failed to change directory:", e);
|
|
1713
|
+
alert("Failed to change directory: " + e.message);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
})();
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -43,6 +43,7 @@ const I18n = (() => {
|
|
|
43
43
|
"chat.interrupted": "Interrupted.",
|
|
44
44
|
"chat.feedback_hint": "Or type your own answer below ↓",
|
|
45
45
|
"chat.newMessageHint": "New messages ↓",
|
|
46
|
+
"chat.retry": "Retry",
|
|
46
47
|
|
|
47
48
|
// ── Session list ──
|
|
48
49
|
"sessions.empty": "No sessions yet",
|
|
@@ -58,6 +59,11 @@ const I18n = (() => {
|
|
|
58
59
|
"sessions.badge.coding": "Coding",
|
|
59
60
|
"sessions.badge.setup": "Setup",
|
|
60
61
|
"sessions.newSession": "+ New Session",
|
|
62
|
+
"sessions.actions.pin": "Pin",
|
|
63
|
+
"sessions.actions.unpin": "Unpin",
|
|
64
|
+
"sessions.actions.rename": "Rename",
|
|
65
|
+
"sessions.actions.delete": "Delete",
|
|
66
|
+
"sessions.newSessionAdvanced": "More Options",
|
|
61
67
|
"sessions.loadMore": "Load more sessions",
|
|
62
68
|
"sessions.loadingMore": "Loading…",
|
|
63
69
|
"sessions.search.placeholder": "Search sessions…",
|
|
@@ -71,6 +77,21 @@ const I18n = (() => {
|
|
|
71
77
|
// ── Modal ──
|
|
72
78
|
"modal.yes": "Yes",
|
|
73
79
|
"modal.no": "No",
|
|
80
|
+
"modal.ok": "OK",
|
|
81
|
+
"modal.cancel": "Cancel",
|
|
82
|
+
|
|
83
|
+
// ── New Session Modal ──
|
|
84
|
+
"sessions.modal.title": "Create New Session",
|
|
85
|
+
"sessions.modal.agent": "Agent",
|
|
86
|
+
"sessions.modal.agent.general": "General",
|
|
87
|
+
"sessions.modal.agent.coding": "Coding",
|
|
88
|
+
"sessions.modal.name": "Session Name",
|
|
89
|
+
"sessions.modal.name.placeholder": "Leave empty to auto-generate",
|
|
90
|
+
"sessions.modal.model": "Model",
|
|
91
|
+
"sessions.modal.directory": "Working Directory",
|
|
92
|
+
"sessions.modal.directory.placeholder": "~/clacky_workspace",
|
|
93
|
+
"sessions.modal.initProject": "Initialize full-stack project (/new)",
|
|
94
|
+
"sessions.modal.create": "Create Session",
|
|
74
95
|
|
|
75
96
|
// ── Offline banner ──
|
|
76
97
|
"offline.banner": "Server disconnected — reconnecting…",
|
|
@@ -381,6 +402,7 @@ const I18n = (() => {
|
|
|
381
402
|
"chat.interrupted": "已中断。",
|
|
382
403
|
"chat.feedback_hint": "或在下方输入框自由作答 ↓",
|
|
383
404
|
"chat.newMessageHint": "有新消息 ↓",
|
|
405
|
+
"chat.retry": "重试",
|
|
384
406
|
|
|
385
407
|
// ── Session list ──
|
|
386
408
|
"sessions.empty": "暂无对话",
|
|
@@ -396,7 +418,12 @@ const I18n = (() => {
|
|
|
396
418
|
"sessions.badge.coding": "Coding",
|
|
397
419
|
"sessions.badge.setup": "配置",
|
|
398
420
|
"sessions.newSession": "+ 新会话",
|
|
421
|
+
"sessions.newSessionAdvanced": "更多选项",
|
|
399
422
|
"sessions.loadMore": "加载更多会话",
|
|
423
|
+
"sessions.actions.pin": "置顶",
|
|
424
|
+
"sessions.actions.unpin": "取消置顶",
|
|
425
|
+
"sessions.actions.rename": "重命名",
|
|
426
|
+
"sessions.actions.delete": "删除",
|
|
400
427
|
"sessions.loadingMore": "加载中…",
|
|
401
428
|
"sessions.search.placeholder": "搜索会话…",
|
|
402
429
|
"sessions.search.typeAll": "全部类型",
|
|
@@ -409,6 +436,21 @@ const I18n = (() => {
|
|
|
409
436
|
// ── Modal ──
|
|
410
437
|
"modal.yes": "确认",
|
|
411
438
|
"modal.no": "取消",
|
|
439
|
+
"modal.ok": "确定",
|
|
440
|
+
"modal.cancel": "取消",
|
|
441
|
+
|
|
442
|
+
// ── New Session Modal ──
|
|
443
|
+
"sessions.modal.title": "创建新会话",
|
|
444
|
+
"sessions.modal.agent": "助手类型",
|
|
445
|
+
"sessions.modal.agent.general": "通用",
|
|
446
|
+
"sessions.modal.agent.coding": "编程",
|
|
447
|
+
"sessions.modal.name": "会话名称",
|
|
448
|
+
"sessions.modal.name.placeholder": "留空自动生成",
|
|
449
|
+
"sessions.modal.model": "模型",
|
|
450
|
+
"sessions.modal.directory": "工作目录",
|
|
451
|
+
"sessions.modal.directory.placeholder": "~/clacky_workspace",
|
|
452
|
+
"sessions.modal.initProject": "初始化全栈项目 (/new)",
|
|
453
|
+
"sessions.modal.create": "创建会话",
|
|
412
454
|
|
|
413
455
|
// ── Offline banner ──
|
|
414
456
|
"offline.banner": "服务已断开,正在重连…",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -68,13 +68,15 @@
|
|
|
68
68
|
</button>
|
|
69
69
|
<div class="btn-split-wrap">
|
|
70
70
|
<button id="btn-new-session-inline" class="btn-split-main" title="New Session" data-i18n="sessions.newSession">+ New Session</button>
|
|
71
|
-
<button id="btn-new-session-arrow" class="btn-split-arrow" title="
|
|
71
|
+
<button id="btn-new-session-arrow" class="btn-split-arrow" title="Options">▾</button>
|
|
72
72
|
<!-- Dropdown -->
|
|
73
73
|
<div id="new-session-dropdown" class="new-session-dropdown" hidden>
|
|
74
|
-
<div class="dropdown-item" data-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
<div class="dropdown-item" id="btn-new-session-modal" data-i18n="sessions.newSessionAdvanced">
|
|
75
|
+
<svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: -1px; margin-right: 5px; opacity: 0.6;">
|
|
76
|
+
<path d="M8 0a.5.5 0 01.5.5v2a.5.5 0 01-1 0v-2A.5.5 0 018 0zm0 13a.5.5 0 01.5.5v2a.5.5 0 01-1 0v-2A.5.5 0 018 13zm8-5a.5.5 0 01-.5.5h-2a.5.5 0 010-1h2a.5.5 0 01.5.5zM3 8a.5.5 0 01-.5.5h-2a.5.5 0 010-1h2A.5.5 0 013 8zm10.657-5.657a.5.5 0 010 .707l-1.414 1.415a.5.5 0 11-.707-.708l1.414-1.414a.5.5 0 01.707 0zm-9.193 9.193a.5.5 0 010 .707L3.05 13.657a.5.5 0 01-.707-.707l1.414-1.414a.5.5 0 01.707 0zm9.193 2.121a.5.5 0 01-.707 0l-1.414-1.414a.5.5 0 01.707-.707l1.414 1.414a.5.5 0 010 .707zM4.464 4.465a.5.5 0 01-.707 0L2.343 3.05a.5.5 0 11.707-.707l1.414 1.414a.5.5 0 010 .708z"/>
|
|
77
|
+
</svg>
|
|
78
|
+
<span data-i18n="sessions.newSessionAdvanced">More Options</span>
|
|
79
|
+
</div>
|
|
78
80
|
</div>
|
|
79
81
|
</div>
|
|
80
82
|
</div>
|
|
@@ -109,23 +111,6 @@
|
|
|
109
111
|
</div>
|
|
110
112
|
</div>
|
|
111
113
|
|
|
112
|
-
<!-- New Project inline form (hidden by default, toggled by "New Project" dropdown item) -->
|
|
113
|
-
<div id="new-project-form" class="new-project-panel" hidden>
|
|
114
|
-
<div class="new-project-card">
|
|
115
|
-
<div class="new-project-title">🚀 New Project</div>
|
|
116
|
-
<div class="new-project-field">
|
|
117
|
-
<label class="new-project-label" for="new-project-dir">Working Directory</label>
|
|
118
|
-
<input id="new-project-dir" type="text" class="new-project-input"
|
|
119
|
-
placeholder="~/clacky_projects/my-project"
|
|
120
|
-
autocomplete="off" spellcheck="false" />
|
|
121
|
-
</div>
|
|
122
|
-
<div class="new-project-actions">
|
|
123
|
-
<button id="btn-new-project-cancel" class="btn-new-project-cancel">Cancel</button>
|
|
124
|
-
<button id="btn-new-project-create" class="btn-new-project-create">Create →</button>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
114
|
<!-- Unified session list (all sources + profiles, newest first) -->
|
|
130
115
|
<div id="session-list"></div>
|
|
131
116
|
<!-- Load more button rendered dynamically by Sessions.renderList() -->
|
|
@@ -271,20 +256,26 @@
|
|
|
271
256
|
</div>
|
|
272
257
|
<!-- Session info bar — mirrors CLI bottom status bar -->
|
|
273
258
|
<div id="session-info-bar" style="display:none">
|
|
274
|
-
<!--
|
|
259
|
+
<!-- Order: status | id | dir | model | mode | tasks | cost -->
|
|
260
|
+
<!-- Pattern: element + separator-after-element (except last) -->
|
|
275
261
|
<span id="sib-status"></span>
|
|
276
|
-
<span class="sib-sep sib-sep-
|
|
262
|
+
<span class="sib-sep sib-sep-after-status">│</span>
|
|
277
263
|
<span id="sib-id"></span>
|
|
278
|
-
<span
|
|
279
|
-
<span
|
|
280
|
-
<span
|
|
281
|
-
|
|
264
|
+
<span class="sib-sep sib-sep-after-id">│</span>
|
|
265
|
+
<span id="sib-dir" title="Click to change directory"></span>
|
|
266
|
+
<span class="sib-sep sib-sep-after-dir">│</span>
|
|
267
|
+
<span id="sib-model-wrap">
|
|
268
|
+
<span id="sib-model" class="sib-model-clickable" title="Click to switch model"></span>
|
|
269
|
+
<div id="sib-model-dropdown" class="sib-model-dropdown" style="display:none"></div>
|
|
270
|
+
</span>
|
|
271
|
+
<span class="sib-sep sib-sep-after-model">│</span>
|
|
272
|
+
<!-- Detail fields: mode, tasks, cost -->
|
|
282
273
|
<span class="sib-detail">
|
|
283
|
-
<span
|
|
284
|
-
<span
|
|
285
|
-
<span id="sib-mode-wrap"><span class="sib-sep">│</span><span id="sib-mode"></span></span>
|
|
286
|
-
<span class="sib-sep sib-sep-before-tasks">│</span>
|
|
274
|
+
<span id="sib-mode"></span>
|
|
275
|
+
<span class="sib-sep sib-sep-after-mode">│</span>
|
|
287
276
|
<span id="sib-tasks"></span>
|
|
277
|
+
<span class="sib-sep sib-sep-after-tasks">│</span>
|
|
278
|
+
<span id="sib-cost"></span>
|
|
288
279
|
</span>
|
|
289
280
|
</div>
|
|
290
281
|
|
|
@@ -718,6 +709,57 @@
|
|
|
718
709
|
</div>
|
|
719
710
|
</div>
|
|
720
711
|
|
|
712
|
+
<!-- ── NEW SESSION MODAL ─────────────────────────────────────────────────── -->
|
|
713
|
+
<div id="new-session-modal" class="modal-overlay" style="display:none">
|
|
714
|
+
<div class="modal-box new-session-modal">
|
|
715
|
+
<div class="modal-header">
|
|
716
|
+
<h3 class="modal-title" data-i18n="sessions.modal.title">Create New Session</h3>
|
|
717
|
+
<button id="new-session-modal-close" class="modal-close-btn" title="Close">×</button>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="modal-body">
|
|
720
|
+
<div class="modal-field">
|
|
721
|
+
<label class="modal-label" data-i18n="sessions.modal.agent">Agent</label>
|
|
722
|
+
<select id="new-session-agent" class="modal-select">
|
|
723
|
+
<option value="general" data-i18n="sessions.modal.agent.general">General</option>
|
|
724
|
+
<option value="coding" data-i18n="sessions.modal.agent.coding">Coding</option>
|
|
725
|
+
</select>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<div class="modal-field">
|
|
729
|
+
<label class="modal-label" data-i18n="sessions.modal.name">Session Name</label>
|
|
730
|
+
<input id="new-session-name" type="text" class="modal-input"
|
|
731
|
+
data-i18n-placeholder="sessions.modal.name.placeholder"
|
|
732
|
+
placeholder="Leave empty to auto-generate">
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="modal-field">
|
|
736
|
+
<label class="modal-label" data-i18n="sessions.modal.model">Model</label>
|
|
737
|
+
<select id="new-session-model" class="modal-select">
|
|
738
|
+
<!-- Populated dynamically from configured models -->
|
|
739
|
+
</select>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
<div class="modal-field">
|
|
743
|
+
<label class="modal-label" data-i18n="sessions.modal.directory">Working Directory</label>
|
|
744
|
+
<input id="new-session-directory" type="text" class="modal-input"
|
|
745
|
+
data-i18n-placeholder="sessions.modal.directory.placeholder"
|
|
746
|
+
placeholder="~/workspace/my-project">
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
<div id="new-session-init-project-field" class="modal-field-checkbox" style="display:none">
|
|
750
|
+
<label class="modal-checkbox-label">
|
|
751
|
+
<input id="new-session-init-project" type="checkbox" class="modal-checkbox">
|
|
752
|
+
<span data-i18n="sessions.modal.initProject">Initialize full-stack project (/new)</span>
|
|
753
|
+
</label>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
<div class="modal-footer">
|
|
757
|
+
<button id="new-session-create" class="btn-primary" data-i18n="sessions.modal.create">Create Session</button>
|
|
758
|
+
<button id="new-session-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
721
763
|
<!-- ── CONFIRMATION MODAL ────────────────────────────────────────────────── -->
|
|
722
764
|
<div id="modal-overlay" class="modal-overlay" style="display:none">
|
|
723
765
|
<div class="modal-box sm">
|
|
@@ -729,6 +771,18 @@
|
|
|
729
771
|
</div>
|
|
730
772
|
</div>
|
|
731
773
|
|
|
774
|
+
<!-- Prompt modal for text input -->
|
|
775
|
+
<div id="prompt-modal-overlay" class="modal-overlay" style="display:none">
|
|
776
|
+
<div class="modal-box sm">
|
|
777
|
+
<div id="prompt-modal-message" style="font-size:14px;line-height:1.6;margin-bottom:12px"></div>
|
|
778
|
+
<input type="text" id="prompt-modal-input" class="prompt-modal-input" autocomplete="off" spellcheck="false">
|
|
779
|
+
<div class="modal-actions">
|
|
780
|
+
<button id="prompt-modal-ok" class="btn-primary" data-i18n="modal.ok">OK</button>
|
|
781
|
+
<button id="prompt-modal-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
|
|
732
786
|
|
|
733
787
|
|
|
734
788
|
<script src="/marked.min.js"></script>
|