openclacky 1.5.21 → 1.5.22
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 +61 -0
- data/lib/clacky/billing/platform_billing.rb +3 -0
- data/lib/clacky/default_extensions/git/panels/git/view.js +1911 -398
- data/lib/clacky/default_extensions/preview/panels/preview/view.js +199 -34
- data/lib/clacky/providers.rb +43 -17
- data/lib/clacky/search_config.rb +33 -11
- data/lib/clacky/server/git_panel.rb +360 -34
- data/lib/clacky/server/http_server.rb +145 -47
- data/lib/clacky/server/preview.rb +118 -17
- data/lib/clacky/server/scheduler.rb +81 -2
- data/lib/clacky/server/web_ui_controller.rb +35 -27
- data/lib/clacky/session_manager.rb +52 -5
- data/lib/clacky/utils/mac_app_detector.rb +8 -7
- data/lib/clacky/utils/model_pricing.rb +60 -2
- data/lib/clacky/utils/windows_app_detector.rb +3 -1
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +650 -96
- data/lib/clacky/web/app.js +59 -6
- data/lib/clacky/web/auth.js +1 -1
- data/lib/clacky/web/components/composer.js +107 -8
- data/lib/clacky/web/components/image-zoom.js +165 -0
- data/lib/clacky/web/components/model-picker.js +6 -4
- data/lib/clacky/web/components/notify.js +2 -2
- data/lib/clacky/web/core/aside.js +60 -3
- data/lib/clacky/web/features/billing/view.js +2 -2
- data/lib/clacky/web/features/channels/view.js +5 -2
- data/lib/clacky/web/features/extensions/view.js +2 -2
- data/lib/clacky/web/features/new-session/store.js +79 -2
- data/lib/clacky/web/features/new-session/view.js +496 -58
- data/lib/clacky/web/features/skills/store.js +1 -1
- data/lib/clacky/web/features/tasks/view.js +162 -51
- data/lib/clacky/web/features/workspace/store.js +13 -6
- data/lib/clacky/web/features/workspace/view.js +153 -163
- data/lib/clacky/web/i18n.js +238 -56
- data/lib/clacky/web/index.html +173 -157
- data/lib/clacky/web/projects.js +37 -4
- data/lib/clacky/web/sessions.js +181 -105
- data/lib/clacky/web/settings.js +26 -8
- data/lib/clacky/web/skills.js +2 -2
- data/lib/clacky/web/theme.js +17 -2
- data/lib/clacky/web/utils.js +17 -2
- data/lib/clacky/web/weixin-qr.html +2 -1
- data/lib/clacky/web/ws-dispatcher.js +8 -29
- metadata +2 -1
data/lib/clacky/web/settings.js
CHANGED
|
@@ -30,6 +30,9 @@ const Settings = (() => {
|
|
|
30
30
|
// Last successfully activated tab. Dynamic extension tabs may be rebuilt
|
|
31
31
|
// while Settings is hidden, so the DOM alone cannot retain this state.
|
|
32
32
|
let _activeTab = "models";
|
|
33
|
+
// Masked web-search keys per provider. Module-level because the provider
|
|
34
|
+
// select and save handlers outlive the initializer that first bound them.
|
|
35
|
+
let _searchKeysMasked = {};
|
|
33
36
|
|
|
34
37
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
35
38
|
|
|
@@ -272,16 +275,20 @@ const Settings = (() => {
|
|
|
272
275
|
|
|
273
276
|
card.innerHTML = `
|
|
274
277
|
<div class="model-card-grid-info">
|
|
275
|
-
<div class="model-card-grid-
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
<div class="model-card-grid-head">
|
|
279
|
+
<div class="model-card-grid-head-text">
|
|
280
|
+
<div class="model-card-grid-name-row">
|
|
281
|
+
<span class="model-card-grid-name">${_esc(displayName)}</span>
|
|
282
|
+
${isDefault ? `<span class="badge badge-default">${I18n.t("settings.models.badge.default")}</span>` : ""}
|
|
283
|
+
${isLite ? `<span class="badge badge-lite">${I18n.t("settings.models.badge.lite")}</span>` : ""}
|
|
284
|
+
</div>
|
|
285
|
+
<div class="model-card-grid-provider">${_esc(providerName)}</div>
|
|
286
|
+
</div>
|
|
287
|
+
${!isDefault ? `<button class="btn-card-grid-action btn-card-grid-action-primary" data-index="${index}" data-action="default">
|
|
280
288
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
281
289
|
<span>${I18n.t("settings.models.btn.setDefault")}</span>
|
|
282
290
|
</button>` : ""}
|
|
283
291
|
</div>
|
|
284
|
-
<div class="model-card-grid-provider">${_esc(providerName)}</div>
|
|
285
292
|
${model.api_key_masked ? `<div class="model-card-grid-model">${_esc(model.api_key_masked)}</div>` : ""}
|
|
286
293
|
${remark ? `<div class="model-card-grid-remark" title="${_esc(remark)}">
|
|
287
294
|
<span class="model-card-grid-remark-label">${I18n.t("settings.models.field.remark")}</span>
|
|
@@ -320,6 +327,7 @@ const Settings = (() => {
|
|
|
320
327
|
</div>
|
|
321
328
|
`;
|
|
322
329
|
|
|
330
|
+
card.querySelector(".model-card-grid-head").prepend(ModelPicker.vendorBadge(displayName));
|
|
323
331
|
container.appendChild(card);
|
|
324
332
|
_bindCompactCardEvents(card, index);
|
|
325
333
|
}
|
|
@@ -1788,7 +1796,10 @@ const Settings = (() => {
|
|
|
1788
1796
|
select = CustomSelect.init({
|
|
1789
1797
|
trigger: trigger,
|
|
1790
1798
|
dropdown: dropdown,
|
|
1791
|
-
onSelect: function () {
|
|
1799
|
+
onSelect: function (value) {
|
|
1800
|
+
if (keyInput) keyInput.value = _searchKeysMasked[value] || "";
|
|
1801
|
+
_syncKeyVisibility();
|
|
1802
|
+
}
|
|
1792
1803
|
});
|
|
1793
1804
|
}
|
|
1794
1805
|
|
|
@@ -1861,8 +1872,9 @@ const Settings = (() => {
|
|
|
1861
1872
|
`<div class="custom-select-option" data-value="">${I18n.t("settings.search.builtin")}</div>` +
|
|
1862
1873
|
deepseekRow +
|
|
1863
1874
|
rows;
|
|
1875
|
+
_searchKeysMasked = data.keys_masked || {};
|
|
1864
1876
|
if (select) select.setValue(data.provider || "");
|
|
1865
|
-
keyInput.value = data.
|
|
1877
|
+
keyInput.value = _searchKeysMasked[data.provider] || "";
|
|
1866
1878
|
}
|
|
1867
1879
|
} catch (_) { /* non-critical */ }
|
|
1868
1880
|
_syncKeyVisibility();
|
|
@@ -1896,6 +1908,7 @@ const Settings = (() => {
|
|
|
1896
1908
|
});
|
|
1897
1909
|
const data = await res.json();
|
|
1898
1910
|
if (data.ok) {
|
|
1911
|
+
_searchKeysMasked = data.keys_masked || {};
|
|
1899
1912
|
Modal.toast(I18n.t("settings.search.saved"), "success");
|
|
1900
1913
|
} else {
|
|
1901
1914
|
_showStatus(data.error || I18n.t("settings.search.failed"), false);
|
|
@@ -2342,6 +2355,11 @@ const Settings = (() => {
|
|
|
2342
2355
|
btn.classList.toggle("active", btn.dataset.lang === I18n.lang());
|
|
2343
2356
|
btn.addEventListener("click", () => {
|
|
2344
2357
|
I18n.setLang(btn.dataset.lang);
|
|
2358
|
+
// applyAll only rewrites static attributes; the info bar composes its
|
|
2359
|
+
// tooltips in JS, so refresh them once for the new language.
|
|
2360
|
+
if (typeof Sessions !== "undefined" && Sessions._lastSession) {
|
|
2361
|
+
Sessions.updateInfoBar(Sessions._lastSession);
|
|
2362
|
+
}
|
|
2345
2363
|
document.querySelectorAll("#language-section .settings-lang-btn").forEach(b =>
|
|
2346
2364
|
b.classList.toggle("active", b.dataset.lang === I18n.lang())
|
|
2347
2365
|
);
|
data/lib/clacky/web/skills.js
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// Depends on: Sessions (sendMessage), Skills (createInSession, toggleImportBar),
|
|
19
19
|
// I18n, global $ helper.
|
|
20
20
|
// ─────────────────────────────────────────────────────────────────────────
|
|
21
|
-
const SKILL_AC_SHOW_SYSTEM_KEY = "skill-ac-show-system";
|
|
22
|
-
const SKILL_AC_MRU_KEY = "skill-ac-mru";
|
|
21
|
+
const SKILL_AC_SHOW_SYSTEM_KEY = "clacky-skill-ac-show-system";
|
|
22
|
+
const SKILL_AC_MRU_KEY = "clacky-skill-ac-mru";
|
|
23
23
|
|
|
24
24
|
const SkillAC = (() => {
|
|
25
25
|
let _initialized = false;
|
data/lib/clacky/web/theme.js
CHANGED
|
@@ -28,6 +28,21 @@ const Theme = (() => {
|
|
|
28
28
|
window.dispatchEvent(new CustomEvent("clacky-theme-change", { detail: { theme } }));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Per-element transitions have different durations, so a live switch would
|
|
32
|
+
// repaint regions out of step; freeze them and cross-fade the page as one.
|
|
33
|
+
function _switchTo(theme) {
|
|
34
|
+
const root = document.documentElement;
|
|
35
|
+
const swap = () => {
|
|
36
|
+
root.classList.add("theme-switching");
|
|
37
|
+
_applyAttr(theme);
|
|
38
|
+
void root.offsetWidth;
|
|
39
|
+
requestAnimationFrame(() => root.classList.remove("theme-switching"));
|
|
40
|
+
};
|
|
41
|
+
const reduceMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
42
|
+
if (document.startViewTransition && !reduceMotion) document.startViewTransition(swap);
|
|
43
|
+
else swap();
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
// Update active state on all .settings-bg-theme-card buttons.
|
|
32
47
|
function _syncBgCards() {
|
|
33
48
|
const effective = current();
|
|
@@ -45,7 +60,7 @@ const Theme = (() => {
|
|
|
45
60
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
46
61
|
const onChange = () => {
|
|
47
62
|
if (!localStorage.getItem(STORAGE_KEY)) {
|
|
48
|
-
|
|
63
|
+
_switchTo(_systemTheme());
|
|
49
64
|
}
|
|
50
65
|
};
|
|
51
66
|
if (mq.addEventListener) mq.addEventListener("change", onChange);
|
|
@@ -61,7 +76,7 @@ const Theme = (() => {
|
|
|
61
76
|
} else {
|
|
62
77
|
localStorage.setItem(STORAGE_KEY, theme);
|
|
63
78
|
}
|
|
64
|
-
|
|
79
|
+
_switchTo(theme);
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
// Keep applyBg as alias for backward compat (settings.js calls it)
|
data/lib/clacky/web/utils.js
CHANGED
|
@@ -133,6 +133,10 @@ const Tooltip = (() => {
|
|
|
133
133
|
tip.textContent = text;
|
|
134
134
|
tip.setAttribute("data-pos", pos);
|
|
135
135
|
tip.style.display = "block";
|
|
136
|
+
// Measure from the left edge: leaving the previous anchor's `left` in place
|
|
137
|
+
// shrinks the available width, so a tip next to the right edge wraps early.
|
|
138
|
+
tip.style.left = "0px";
|
|
139
|
+
tip.style.top = "0px";
|
|
136
140
|
|
|
137
141
|
const r = anchor.getBoundingClientRect();
|
|
138
142
|
const tw = tip.offsetWidth;
|
|
@@ -168,16 +172,27 @@ const Tooltip = (() => {
|
|
|
168
172
|
_hideTimer = setTimeout(() => { tip.style.display = "none"; }, 120);
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
// Anchor that was clicked; stays silent until the pointer leaves it so the
|
|
176
|
+
// tip does not cover the menu or popover the click just opened.
|
|
177
|
+
let _suppressed = null;
|
|
178
|
+
|
|
171
179
|
function init() {
|
|
172
180
|
document.addEventListener("mouseover", (e) => {
|
|
173
181
|
const anchor = e.target.closest("[data-tooltip]");
|
|
174
|
-
if (anchor) show(anchor);
|
|
182
|
+
if (anchor && anchor !== _suppressed) show(anchor);
|
|
175
183
|
});
|
|
176
184
|
document.addEventListener("mouseout", (e) => {
|
|
177
185
|
const anchor = e.target.closest("[data-tooltip]");
|
|
178
186
|
if (!anchor) return;
|
|
179
|
-
if (
|
|
187
|
+
if (anchor.contains(e.relatedTarget)) return;
|
|
188
|
+
if (anchor === _suppressed) _suppressed = null;
|
|
189
|
+
hide();
|
|
180
190
|
});
|
|
191
|
+
document.addEventListener("pointerdown", (e) => {
|
|
192
|
+
const anchor = e.target.closest && e.target.closest("[data-tooltip]");
|
|
193
|
+
_suppressed = anchor || null;
|
|
194
|
+
hide();
|
|
195
|
+
}, true);
|
|
181
196
|
}
|
|
182
197
|
|
|
183
198
|
return { init, show, hide };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh">
|
|
2
|
+
<html lang="zh" translate="no">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="google" content="notranslate">
|
|
6
7
|
<title>微信扫码登录</title>
|
|
7
8
|
<style>
|
|
8
9
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -393,7 +393,6 @@ function _dispatchEvent(ev) {
|
|
|
393
393
|
Skills.load();
|
|
394
394
|
// Clear progress state for this session (even if not currently active)
|
|
395
395
|
Sessions.clearProgress(sid);
|
|
396
|
-
if (patch.status === "idle") Sessions.markDone(sid);
|
|
397
396
|
Sessions.renderList();
|
|
398
397
|
}
|
|
399
398
|
break;
|
|
@@ -405,6 +404,13 @@ function _dispatchEvent(ev) {
|
|
|
405
404
|
// module decides whether the user is looking at that session.
|
|
406
405
|
case "task_finished":
|
|
407
406
|
if (typeof Notify !== "undefined") Notify.onTaskFinished(ev.session_id, ev.awaiting_feedback);
|
|
407
|
+
// Only a real finish lights the done dot; idle snapshots (moves, renames, compression) must not.
|
|
408
|
+
if (!ev.awaiting_feedback) {
|
|
409
|
+
Sessions.markDone(ev.session_id);
|
|
410
|
+
Sessions.renderList();
|
|
411
|
+
Projects.renderSection();
|
|
412
|
+
}
|
|
413
|
+
if (ev.complete) Sessions.markDoneSummary(ev.session_id, ev.complete);
|
|
408
414
|
break;
|
|
409
415
|
|
|
410
416
|
case "session_renamed": {
|
|
@@ -633,34 +639,7 @@ function _dispatchEvent(ev) {
|
|
|
633
639
|
Sessions.clearProgress();
|
|
634
640
|
Sessions.collapseToolGroup();
|
|
635
641
|
_closeAllPhases("incomplete"); // safety net: missed phase_end
|
|
636
|
-
|
|
637
|
-
const costSource = ev.cost_source;
|
|
638
|
-
const symbol = typeof Billing !== "undefined" ? Billing.getCurrencySymbol() : "$";
|
|
639
|
-
const rawCost = ev.cost || 0;
|
|
640
|
-
const cost = typeof Billing !== "undefined" ? Billing.convertCost(rawCost) : rawCost;
|
|
641
|
-
const costDisplay = (!costSource || costSource === "estimated")
|
|
642
|
-
? "N/A"
|
|
643
|
-
: `${symbol}${cost.toFixed(4)}`;
|
|
644
|
-
let mainLine = I18n.t("chat.done", { n: ev.iterations, cost: costDisplay });
|
|
645
|
-
if (typeof ev.duration === "number" && ev.duration > 0) {
|
|
646
|
-
mainLine += I18n.t("chat.done.duration", { duration: ev.duration.toFixed(1) });
|
|
647
|
-
}
|
|
648
|
-
let cacheLine = null;
|
|
649
|
-
const cs = ev.cache_stats;
|
|
650
|
-
const total = cs && (cs.total_requests || cs["total_requests"]);
|
|
651
|
-
const hits = cs && (cs.cache_hit_requests || cs["cache_hit_requests"]);
|
|
652
|
-
const cachedTokens = cs && (cs.cache_read_input_tokens || cs["cache_read_input_tokens"]);
|
|
653
|
-
if (total && total > 0 && cachedTokens && cachedTokens > 0) {
|
|
654
|
-
const rate = ((hits / total) * 100).toFixed(1);
|
|
655
|
-
const tokensFmt = cachedTokens >= 1000
|
|
656
|
-
? `${(cachedTokens / 1000).toFixed(1)}k`
|
|
657
|
-
: `${cachedTokens}`;
|
|
658
|
-
cacheLine = I18n.t("chat.done.cache", {
|
|
659
|
-
rate, hits, total: total, tokens: tokensFmt
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
Sessions.appendInfo(`✓ ${mainLine}`, cacheLine);
|
|
663
|
-
}
|
|
642
|
+
Sessions.appendCompleteSummary(ev);
|
|
664
643
|
if (typeof Share !== "undefined") Share.maybePromptOnComplete();
|
|
665
644
|
break;
|
|
666
645
|
|
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.5.
|
|
4
|
+
version: 1.5.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
@@ -688,6 +688,7 @@ files:
|
|
|
688
688
|
- lib/clacky/web/components/composer.js
|
|
689
689
|
- lib/clacky/web/components/custom-select.js
|
|
690
690
|
- lib/clacky/web/components/datepicker.js
|
|
691
|
+
- lib/clacky/web/components/image-zoom.js
|
|
691
692
|
- lib/clacky/web/components/mentions.js
|
|
692
693
|
- lib/clacky/web/components/model-picker.js
|
|
693
694
|
- lib/clacky/web/components/notify.js
|