openclacky 1.3.11 → 1.4.1
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 +59 -0
- data/lib/clacky/agent/session_serializer.rb +11 -4
- data/lib/clacky/agent/skill_manager.rb +1 -0
- data/lib/clacky/agent.rb +4 -5
- data/lib/clacky/brand_config.rb +53 -3
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +202 -3
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +610 -60
- data/lib/clacky/default_skills/media-gen/SKILL.md +182 -7
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +5 -3
- data/lib/clacky/extension/packager.rb +21 -1
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/media/generator.rb +41 -0
- data/lib/clacky/media/volcengine.rb +394 -0
- data/lib/clacky/platform_http_client.rb +9 -7
- data/lib/clacky/providers.rb +4 -4
- data/lib/clacky/server/http_server.rb +67 -22
- data/lib/clacky/tools/grep.rb +6 -1
- data/lib/clacky/tools/terminal.rb +24 -8
- data/lib/clacky/ui2/layout_manager.rb +4 -0
- data/lib/clacky/ui2/screen_buffer.rb +31 -3
- data/lib/clacky/utils/environment_detector.rb +16 -9
- data/lib/clacky/utils/file_processor.rb +30 -28
- data/lib/clacky/utils/model_pricing.rb +49 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +229 -31
- data/lib/clacky/web/components/onboard.js +14 -4
- data/lib/clacky/web/features/backup/view.js +4 -3
- data/lib/clacky/web/features/extensions/store.js +2 -2
- data/lib/clacky/web/features/extensions/view.js +37 -7
- data/lib/clacky/web/features/mcp/view.js +5 -2
- data/lib/clacky/web/features/skills/view.js +61 -1
- data/lib/clacky/web/features/workspace/store.js +11 -0
- data/lib/clacky/web/features/workspace/view.js +10 -4
- data/lib/clacky/web/i18n.js +14 -2
- data/lib/clacky/web/index.html +29 -13
- data/lib/clacky/web/sessions.js +45 -8
- data/scripts/uninstall.sh +1 -1
- metadata +2 -1
|
@@ -16,6 +16,62 @@
|
|
|
16
16
|
|
|
17
17
|
const SkillsView = (() => {
|
|
18
18
|
let _domWired = false;
|
|
19
|
+
let _searchQuery = "";
|
|
20
|
+
|
|
21
|
+
// ── Search filtering ─────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
function _applySearch() {
|
|
24
|
+
const q = _searchQuery.trim().toLowerCase();
|
|
25
|
+
const activeTab = SkillsStore.state.activeTab;
|
|
26
|
+
|
|
27
|
+
if (activeTab === "my-skills") {
|
|
28
|
+
const list = $("skills-list");
|
|
29
|
+
if (!list) return;
|
|
30
|
+
list.querySelectorAll(".skill-card").forEach(card => {
|
|
31
|
+
card.style.display = (!q || card.textContent.toLowerCase().includes(q)) ? "" : "none";
|
|
32
|
+
});
|
|
33
|
+
let noResult = list.querySelector(".skills-search-empty");
|
|
34
|
+
const anyVisible = [...list.querySelectorAll(".skill-card")].some(c => c.style.display !== "none");
|
|
35
|
+
if (q && !anyVisible) {
|
|
36
|
+
if (!noResult) {
|
|
37
|
+
noResult = document.createElement("div");
|
|
38
|
+
noResult.className = "skills-search-empty";
|
|
39
|
+
noResult.textContent = I18n.t("skills.search.empty");
|
|
40
|
+
list.appendChild(noResult);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
noResult && noResult.remove();
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const list = $("brand-skills-list");
|
|
47
|
+
if (!list) return;
|
|
48
|
+
list.querySelectorAll(".brand-skill-card").forEach(card => {
|
|
49
|
+
card.style.display = (!q || card.textContent.toLowerCase().includes(q)) ? "" : "none";
|
|
50
|
+
});
|
|
51
|
+
let noResult = list.querySelector(".skills-search-empty");
|
|
52
|
+
const anyVisible = [...list.querySelectorAll(".brand-skill-card")].some(c => c.style.display !== "none");
|
|
53
|
+
if (q && !anyVisible) {
|
|
54
|
+
if (!noResult) {
|
|
55
|
+
noResult = document.createElement("div");
|
|
56
|
+
noResult.className = "skills-search-empty";
|
|
57
|
+
noResult.textContent = I18n.t("skills.search.empty");
|
|
58
|
+
list.appendChild(noResult);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
noResult && noResult.remove();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _wireSearch() {
|
|
67
|
+
const input = $("skills-search-input");
|
|
68
|
+
if (!input) return;
|
|
69
|
+
input.placeholder = I18n.t("skills.search.placeholder");
|
|
70
|
+
input.addEventListener("input", () => {
|
|
71
|
+
_searchQuery = input.value;
|
|
72
|
+
_applySearch();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
19
75
|
|
|
20
76
|
// ── My Skills rendering ──────────────────────────────────────────────────
|
|
21
77
|
|
|
@@ -45,6 +101,7 @@ const SkillsView = (() => {
|
|
|
45
101
|
console.error("[Skills] _renderSkillCard failed for skill", i, skill.name, e);
|
|
46
102
|
}
|
|
47
103
|
});
|
|
104
|
+
_applySearch();
|
|
48
105
|
}
|
|
49
106
|
|
|
50
107
|
function _renderEmptyState() {
|
|
@@ -247,6 +304,7 @@ const SkillsView = (() => {
|
|
|
247
304
|
if (freeMode && paidSkillsCount > 0) {
|
|
248
305
|
container.appendChild(_renderPaidHint(paidSkillsCount));
|
|
249
306
|
}
|
|
307
|
+
_applySearch();
|
|
250
308
|
}
|
|
251
309
|
|
|
252
310
|
function _renderPaidHint(paidSkillsCount) {
|
|
@@ -383,6 +441,8 @@ const SkillsView = (() => {
|
|
|
383
441
|
function _wireDom() {
|
|
384
442
|
if (_domWired) return;
|
|
385
443
|
|
|
444
|
+
_wireSearch();
|
|
445
|
+
|
|
386
446
|
document.querySelectorAll(".skills-tab").forEach(btn => {
|
|
387
447
|
btn.addEventListener("click", () => Skills.setActiveTab(btn.dataset.tab));
|
|
388
448
|
});
|
|
@@ -430,7 +490,7 @@ const SkillsView = (() => {
|
|
|
430
490
|
_renderBrandSkills();
|
|
431
491
|
});
|
|
432
492
|
|
|
433
|
-
Skills.on("tab:changed", (p) => _applyTab(p.tab));
|
|
493
|
+
Skills.on("tab:changed", (p) => { _applyTab(p.tab); _applySearch(); });
|
|
434
494
|
|
|
435
495
|
Skills.on("brandStatus:changed", (p) => {
|
|
436
496
|
const brandTab = $("tab-brand-skills");
|
|
@@ -72,6 +72,17 @@ const WorkspaceStore = (() => {
|
|
|
72
72
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
73
73
|
},
|
|
74
74
|
|
|
75
|
+
async displayPath(entry) {
|
|
76
|
+
const resp = await fetch("/api/file-action", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({ path: _absPath(entry.path), action: "display-path" })
|
|
80
|
+
});
|
|
81
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
82
|
+
const data = await resp.json();
|
|
83
|
+
return data.path;
|
|
84
|
+
},
|
|
85
|
+
|
|
75
86
|
async fetchFileBlob(entry) {
|
|
76
87
|
const resp = await fetch("/api/file-action", {
|
|
77
88
|
method: "POST",
|
|
@@ -156,10 +156,16 @@ const WorkspaceView = (() => {
|
|
|
156
156
|
if (existing) existing.remove();
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
function copyPath(entry) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
async function copyPath(entry) {
|
|
160
|
+
const fallback = Workspace.state.workingDir.replace(/\/+$/, "") + "/" + entry.path.replace(/^\/+/, "");
|
|
161
|
+
let target = fallback;
|
|
162
|
+
try {
|
|
163
|
+
target = await Workspace.displayPath(entry);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
target = fallback;
|
|
166
|
+
}
|
|
167
|
+
navigator.clipboard.writeText(target).then(() => {
|
|
168
|
+
Modal.toast(target, "info");
|
|
163
169
|
});
|
|
164
170
|
}
|
|
165
171
|
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -95,6 +95,8 @@ const I18n = (() => {
|
|
|
95
95
|
"sessions.deleteTitle": "Delete session",
|
|
96
96
|
"sessions.createError": "Error: ",
|
|
97
97
|
"sessions.dirNotEmpty": "Directory already exists and is not empty.",
|
|
98
|
+
"sessions.today": "Today",
|
|
99
|
+
"sessions.yesterday": "Yesterday",
|
|
98
100
|
"sessions.export.tooltip": "Download session files (session.json + chunks + today's log) for debugging",
|
|
99
101
|
"sessions.export.failed": "Failed to download session",
|
|
100
102
|
"sessions.actions.tooltip": "Click for session actions",
|
|
@@ -177,7 +179,8 @@ const I18n = (() => {
|
|
|
177
179
|
"sessions.badge.channel": "Channel",
|
|
178
180
|
"sessions.badge.coding": "Coding",
|
|
179
181
|
"sessions.badge.setup": "Setup",
|
|
180
|
-
"sessions.newSession": "
|
|
182
|
+
"sessions.newSession": "New Session",
|
|
183
|
+
"sessions.advancedOptions": "Advanced options",
|
|
181
184
|
"sessions.actions.pin": "Pin",
|
|
182
185
|
"sessions.actions.unpin": "Unpin",
|
|
183
186
|
"sessions.actions.fork": "Fork",
|
|
@@ -414,6 +417,8 @@ const I18n = (() => {
|
|
|
414
417
|
"skills.import.install": "Install",
|
|
415
418
|
"skills.tab.my": "My Skills",
|
|
416
419
|
"skills.tab.brand": "Brand Skills",
|
|
420
|
+
"skills.search.placeholder": "Search skills…",
|
|
421
|
+
"skills.search.empty": "No skills match your search.",
|
|
417
422
|
"skills.empty": "No skills loaded.",
|
|
418
423
|
"skills.empty.createBtn": "Create a new skill with /skill-creator",
|
|
419
424
|
"skills.noSkills": "No skills",
|
|
@@ -533,6 +538,7 @@ const I18n = (() => {
|
|
|
533
538
|
"extensions.unit.api": "API",
|
|
534
539
|
"extensions.unit.apis": "APIs",
|
|
535
540
|
"extensions.detail.back": "Back",
|
|
541
|
+
"extensions.detail.readme": "Description",
|
|
536
542
|
"extensions.detail.contributes": "What's inside",
|
|
537
543
|
"extensions.detail.versions": "Version history",
|
|
538
544
|
"extensions.section.agents": "Agents",
|
|
@@ -1063,6 +1069,8 @@ const I18n = (() => {
|
|
|
1063
1069
|
"sessions.deleteTitle": "删除会话",
|
|
1064
1070
|
"sessions.createError": "错误:",
|
|
1065
1071
|
"sessions.dirNotEmpty": "该目录已存在且不为空,请换一个目录名。",
|
|
1072
|
+
"sessions.today": "今天",
|
|
1073
|
+
"sessions.yesterday": "昨天",
|
|
1066
1074
|
"sessions.export.tooltip": "下载会话文件(session.json + 归档片段 + 当天日志),用于调试",
|
|
1067
1075
|
"sessions.export.failed": "下载会话文件失败",
|
|
1068
1076
|
"sessions.actions.tooltip": "点击查看会话操作",
|
|
@@ -1145,7 +1153,8 @@ const I18n = (() => {
|
|
|
1145
1153
|
"sessions.badge.channel": "频道",
|
|
1146
1154
|
"sessions.badge.coding": "Coding",
|
|
1147
1155
|
"sessions.badge.setup": "配置",
|
|
1148
|
-
"sessions.newSession": "
|
|
1156
|
+
"sessions.newSession": "新会话",
|
|
1157
|
+
"sessions.advancedOptions": "高级选项",
|
|
1149
1158
|
"sessions.loadMore": "加载更多会话",
|
|
1150
1159
|
"sessions.actions.pin": "置顶",
|
|
1151
1160
|
"sessions.actions.unpin": "取消置顶",
|
|
@@ -1381,6 +1390,8 @@ const I18n = (() => {
|
|
|
1381
1390
|
"skills.import.install": "安装",
|
|
1382
1391
|
"skills.tab.my": "我的技能",
|
|
1383
1392
|
"skills.tab.brand": "品牌技能",
|
|
1393
|
+
"skills.search.placeholder": "搜索技能…",
|
|
1394
|
+
"skills.search.empty": "没有匹配的技能。",
|
|
1384
1395
|
"skills.empty": "暂无技能。",
|
|
1385
1396
|
"skills.empty.createBtn": "用 /skill-creator 创建新技能",
|
|
1386
1397
|
"skills.noSkills": "无技能",
|
|
@@ -1500,6 +1511,7 @@ const I18n = (() => {
|
|
|
1500
1511
|
"extensions.unit.api": "个 API",
|
|
1501
1512
|
"extensions.unit.apis": "个 API",
|
|
1502
1513
|
"extensions.detail.back": "返回",
|
|
1514
|
+
"extensions.detail.readme": "使用说明",
|
|
1503
1515
|
"extensions.detail.contributes": "包含内容",
|
|
1504
1516
|
"extensions.detail.versions": "版本历史",
|
|
1505
1517
|
"extensions.section.agents": "Agent",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -46,21 +46,14 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
<div id="header-center">
|
|
48
48
|
<button id="header-cmdbar" type="button" title="Search sessions">
|
|
49
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="
|
|
49
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.3-4.3"/></svg>
|
|
50
50
|
<span class="cmdbar-ph" data-i18n="header.cmdbar.placeholder">Search sessions…</span>
|
|
51
|
-
<span class="cmdbar-kbd"
|
|
51
|
+
<span class="cmdbar-kbd">Ctrl + K</span>
|
|
52
52
|
</button>
|
|
53
53
|
</div>
|
|
54
54
|
<div id="header-right">
|
|
55
55
|
<span id="ext-slot-header-right" data-slot="header.right"></span>
|
|
56
|
-
|
|
57
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
58
|
-
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
|
59
|
-
<path d="M21 3v5h-5"/>
|
|
60
|
-
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
|
|
61
|
-
<path d="M8 16H3v5"/>
|
|
62
|
-
</svg>
|
|
63
|
-
</button>
|
|
56
|
+
|
|
64
57
|
<button id="share-toggle-header" class="theme-toggle-btn" data-i18n-title="share.tooltip" title="Share">
|
|
65
58
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
66
59
|
<circle cx="18" cy="5" r="3"/>
|
|
@@ -79,7 +72,7 @@
|
|
|
79
72
|
</div>
|
|
80
73
|
</header>
|
|
81
74
|
|
|
82
|
-
<!-- ── Command-palette search overlay (
|
|
75
|
+
<!-- ── Command-palette search overlay (platform shortcut / top cmdbar) ───── -->
|
|
83
76
|
<!-- Search lives here, decoupled from the sidebar list: results render in
|
|
84
77
|
#session-search-results and the left session list is never replaced. -->
|
|
85
78
|
<div id="session-search-overlay" class="cmd-palette-overlay" hidden>
|
|
@@ -135,7 +128,28 @@
|
|
|
135
128
|
<div class="sidebar-divider">
|
|
136
129
|
<span data-i18n="sidebar.chat">Sessions</span>
|
|
137
130
|
<div class="sidebar-divider-actions">
|
|
138
|
-
<
|
|
131
|
+
<div class="btn-split-wrap">
|
|
132
|
+
<button id="btn-new-session-inline" class="btn-split-main" title="New Session">
|
|
133
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
134
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
|
135
|
+
<line x1="12" y1="8" x2="12" y2="14" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
|
|
136
|
+
<line x1="9" y1="11" x2="15" y2="11" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
|
|
137
|
+
</svg>
|
|
138
|
+
<span data-i18n="sessions.newSession">New Session</span>
|
|
139
|
+
</button>
|
|
140
|
+
<div class="btn-split-dropdown">
|
|
141
|
+
<button id="btn-new-session-advanced" class="btn-split-arrow" aria-label="Advanced new session options">
|
|
142
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
143
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
144
|
+
</svg>
|
|
145
|
+
</button>
|
|
146
|
+
<div class="btn-split-menu">
|
|
147
|
+
<button class="btn-split-menu-item" id="btn-new-session-goto-advanced">
|
|
148
|
+
<span data-i18n="sessions.advancedOptions">Advanced options</span>
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
139
153
|
</div>
|
|
140
154
|
</div>
|
|
141
155
|
|
|
@@ -573,6 +587,8 @@
|
|
|
573
587
|
<button class="skills-tab" data-tab="brand-skills" id="tab-brand-skills" data-i18n="skills.tab.brand">Brand Skills</button>
|
|
574
588
|
</div>
|
|
575
589
|
<div class="skills-tabs-controls">
|
|
590
|
+
<!-- Search input -->
|
|
591
|
+
<input type="text" id="skills-search-input" class="skills-search" placeholder="Search skills…" data-i18n-placeholder="skills.search.placeholder" />
|
|
576
592
|
<!-- Show system skills toggle (only visible in My Skills tab) -->
|
|
577
593
|
<label class="skills-filter-toggle" id="label-show-system">
|
|
578
594
|
<input type="checkbox" id="chk-show-system-skills">
|
|
@@ -669,9 +685,9 @@
|
|
|
669
685
|
<div class="extensions-toolbar-right">
|
|
670
686
|
<input type="text" id="extensions-search-input" class="extensions-search" placeholder="Search extensions…" />
|
|
671
687
|
<select id="extensions-sort" class="extensions-sort">
|
|
688
|
+
<option value="downloads" data-i18n="extensions.sort.downloads">Most downloaded</option>
|
|
672
689
|
<option value="newest" data-i18n="extensions.sort.newest">Newest</option>
|
|
673
690
|
<option value="updated" data-i18n="extensions.sort.updated">Recently updated</option>
|
|
674
|
-
<option value="downloads" data-i18n="extensions.sort.downloads">Most downloaded</option>
|
|
675
691
|
</select>
|
|
676
692
|
</div>
|
|
677
693
|
</div>
|
data/lib/clacky/web/sessions.js
CHANGED
|
@@ -487,8 +487,8 @@ const Sessions = (() => {
|
|
|
487
487
|
// ── New session controls (split button + welcome + modal) ──────────────
|
|
488
488
|
//
|
|
489
489
|
// Wires up every button/interaction that kicks off session creation:
|
|
490
|
-
// - "+ New Session" inline split-button (quick create)
|
|
491
|
-
// - "▾" arrow button (
|
|
490
|
+
// - "+ New Session" inline split-button (quick create — directly creates a plain session)
|
|
491
|
+
// - "▾" arrow button (navigates to /#new for advanced options)
|
|
492
492
|
// - "+ New Session" big button on the welcome screen
|
|
493
493
|
// - New Session Modal: close / cancel / create / overlay click / browse
|
|
494
494
|
// - Load-more button (rendered dynamically by renderList)
|
|
@@ -497,7 +497,28 @@ const Sessions = (() => {
|
|
|
497
497
|
// we call addEventListener directly (no ?. / no `if` guards). If any is
|
|
498
498
|
// missing, it means HTML and JS drifted and we want the loud error.
|
|
499
499
|
function _initNewSessionControls() {
|
|
500
|
-
|
|
500
|
+
// Main button: directly create a plain session using the last-used agent.
|
|
501
|
+
const _btnNewInline = document.getElementById("btn-new-session-inline");
|
|
502
|
+
_btnNewInline.addEventListener("click", async () => {
|
|
503
|
+
if (_btnNewInline.disabled) return;
|
|
504
|
+
_btnNewInline.disabled = true;
|
|
505
|
+
try {
|
|
506
|
+
const session = await NewSessionStore.createSession({ existingSessions: _sessions });
|
|
507
|
+
if (!session) return;
|
|
508
|
+
NewSessionStore.reset();
|
|
509
|
+
// Add to local list immediately so renderList shows it and findOrFetch
|
|
510
|
+
// can locate it synchronously (without a round-trip to the API).
|
|
511
|
+
if (!_sessions.find(s => s.id === session.id)) {
|
|
512
|
+
_sessions.unshift(session);
|
|
513
|
+
}
|
|
514
|
+
location.hash = `session/${session.id}`;
|
|
515
|
+
} finally {
|
|
516
|
+
_btnNewInline.disabled = false;
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Arrow button: hover (CSS) shows menu. Menu item navigates to /#new on click.
|
|
521
|
+
document.getElementById("btn-new-session-goto-advanced")
|
|
501
522
|
.addEventListener("click", () => { location.hash = "#new"; });
|
|
502
523
|
|
|
503
524
|
document.addEventListener("click", (e) => {
|
|
@@ -884,8 +905,17 @@ const Sessions = (() => {
|
|
|
884
905
|
//
|
|
885
906
|
// Everything uses event delegation because some elements (e.g. the clear
|
|
886
907
|
// buttons) are re-rendered as filter state changes.
|
|
908
|
+
function _sessionSearchShortcutLabel() {
|
|
909
|
+
const uaDataPlatform = navigator.userAgentData && navigator.userAgentData.platform;
|
|
910
|
+
const platform = (uaDataPlatform || navigator.platform || "").toString();
|
|
911
|
+
return /mac/i.test(platform) ? "⌘ + K" : "Ctrl + K";
|
|
912
|
+
}
|
|
913
|
+
|
|
887
914
|
function _initSearch() {
|
|
888
|
-
|
|
915
|
+
const cmdbarKbd = document.querySelector("#header-cmdbar .cmdbar-kbd");
|
|
916
|
+
if (cmdbarKbd) cmdbarKbd.textContent = _sessionSearchShortcutLabel();
|
|
917
|
+
|
|
918
|
+
// Open the palette: top cmdbar button (or the keyboard shortcut, bound below).
|
|
889
919
|
document.addEventListener("click", (e) => {
|
|
890
920
|
if (e.target && e.target.closest("#header-cmdbar")) {
|
|
891
921
|
if (!Sessions.searchOpen) Sessions.toggleSearch();
|
|
@@ -1349,6 +1379,10 @@ const Sessions = (() => {
|
|
|
1349
1379
|
bubbleHtml += escapeHtml(ev.content || "");
|
|
1350
1380
|
el.innerHTML = bubbleHtml;
|
|
1351
1381
|
if (ev.created_at) el.dataset.createdAt = ev.created_at;
|
|
1382
|
+
// Messages archived into a compressed chunk can't be edited (the backend
|
|
1383
|
+
// truncate keys off the active in-memory history). Flag them so the edit
|
|
1384
|
+
// affordance stays hidden.
|
|
1385
|
+
if (ev.editable === false) el.dataset.editable = "false";
|
|
1352
1386
|
const wrap = document.createElement("div");
|
|
1353
1387
|
wrap.className = "msg-user-wrap";
|
|
1354
1388
|
wrap.appendChild(el);
|
|
@@ -1660,8 +1694,8 @@ const Sessions = (() => {
|
|
|
1660
1694
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
1661
1695
|
const dDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
1662
1696
|
const diffDays = Math.round((today - dDay) / 86400000);
|
|
1663
|
-
if (diffDays === 0) return
|
|
1664
|
-
if (diffDays === 1) return
|
|
1697
|
+
if (diffDays === 0) return `${I18n.t("sessions.today")} ${hhmm}`;
|
|
1698
|
+
if (diffDays === 1) return `${I18n.t("sessions.yesterday")} ${hhmm}`;
|
|
1665
1699
|
return `${d.getMonth() + 1}/${d.getDate()} ${hhmm}`;
|
|
1666
1700
|
}
|
|
1667
1701
|
|
|
@@ -1748,7 +1782,9 @@ const Sessions = (() => {
|
|
|
1748
1782
|
});
|
|
1749
1783
|
|
|
1750
1784
|
bar.appendChild(copyBtn);
|
|
1751
|
-
|
|
1785
|
+
// Skip the edit affordance for messages already archived into a compressed
|
|
1786
|
+
// chunk — editing them would silently no-op on the backend.
|
|
1787
|
+
if (el.dataset.editable !== "false") bar.appendChild(editBtn);
|
|
1752
1788
|
wrap.appendChild(bar);
|
|
1753
1789
|
}
|
|
1754
1790
|
|
|
@@ -1802,8 +1838,9 @@ const Sessions = (() => {
|
|
|
1802
1838
|
sendBtn.textContent = I18n.t("chat.send");
|
|
1803
1839
|
sendBtn.addEventListener("click", () => _submitEdit(el, textarea.value.trim()));
|
|
1804
1840
|
|
|
1841
|
+
const editIme = IME.track(textarea);
|
|
1805
1842
|
textarea.addEventListener("keydown", (e) => {
|
|
1806
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
1843
|
+
if (e.key === "Enter" && !e.shiftKey && !editIme.isComposing(e)) {
|
|
1807
1844
|
e.preventDefault();
|
|
1808
1845
|
_submitEdit(el, textarea.value.trim());
|
|
1809
1846
|
}
|
data/scripts/uninstall.sh
CHANGED
|
@@ -227,7 +227,7 @@ uninstall_gem() {
|
|
|
227
227
|
command_exists gem || return 1
|
|
228
228
|
if gem list -i openclacky >/dev/null 2>&1; then
|
|
229
229
|
print_step "Uninstalling via RubyGems..."
|
|
230
|
-
gem uninstall openclacky -x
|
|
230
|
+
gem uninstall openclacky -a -x
|
|
231
231
|
else
|
|
232
232
|
print_info "Gem 'openclacky' not found (already removed)"
|
|
233
233
|
fi
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openclacky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -480,6 +480,7 @@ files:
|
|
|
480
480
|
- lib/clacky/media/gemini.rb
|
|
481
481
|
- lib/clacky/media/generator.rb
|
|
482
482
|
- lib/clacky/media/openai_compat.rb
|
|
483
|
+
- lib/clacky/media/volcengine.rb
|
|
483
484
|
- lib/clacky/message_format/anthropic.rb
|
|
484
485
|
- lib/clacky/message_format/bedrock.rb
|
|
485
486
|
- lib/clacky/message_format/open_ai.rb
|