openclacky 1.3.0 → 1.3.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 +14 -0
- data/lib/clacky/providers.rb +1 -1
- data/lib/clacky/server/http_server.rb +11 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +146 -31
- data/lib/clacky/web/app.js +51 -0
- data/lib/clacky/web/billing.js +132 -6
- data/lib/clacky/web/i18n.js +8 -0
- data/lib/clacky/web/index.html +1 -0
- data/lib/clacky/web/onboard.js +8 -1
- data/lib/clacky/web/sessions.js +11 -0
- data/lib/clacky/web/skills.js +33 -0
- metadata +4 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a39f825dd6192443ca9f0d30139e20a4b7fe366f9415d721c9a2401bfed1dc4a
|
|
4
|
+
data.tar.gz: f825a682fad6a33983e6a582a52d5a9378b7bd29d118b5a2fe1d5c26dc7b3195
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62cf37fe9eb083438853faeaf8f28a4579300d51607d2fbd8eef9caba69dc82d4b2c8dc98ae70f666500c7913b47325b0a6b58a5f3043b7139e8265b12035525
|
|
7
|
+
data.tar.gz: 706a5237c4aae5092068c91dfbc6c3cb7b1a93cc27b6878cbf32f043de5ec0c038321228d626efb3b2db744e8b81b4d6889fc518ee8c2495496714e628bf7a29
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.1] - 2026-06-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Delete skills directly from the Web UI
|
|
12
|
+
|
|
13
|
+
### Improved
|
|
14
|
+
- Sidebar is now draggable to resize, with width persisted across sessions
|
|
15
|
+
- Sidebar scrollbar only appears while scrolling for a cleaner look
|
|
16
|
+
- Billing page UI polish and mobile adaptation
|
|
17
|
+
- Default openclacky image model switched to Nano Banana 2
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Onboarding device-login window no longer gets blocked as a popup
|
|
21
|
+
|
|
8
22
|
## [1.3.0] - 2026-06-17
|
|
9
23
|
|
|
10
24
|
### Added
|
data/lib/clacky/providers.rb
CHANGED
|
@@ -59,7 +59,7 @@ module Clacky
|
|
|
59
59
|
"or-gemini-3-1-flash-image" => "Nano Banana 2",
|
|
60
60
|
"or-gpt-image-2" => "GPT Image 2"
|
|
61
61
|
},
|
|
62
|
-
"default_image_model" => "or-
|
|
62
|
+
"default_image_model" => "or-gemini-3-1-flash-image",
|
|
63
63
|
# Video generation models served by the openclacky gateway, which
|
|
64
64
|
# routes them to Vertex AI Veo (async predictLongRunning under the
|
|
65
65
|
# hood; the gateway hides the polling and returns the MP4 inline).
|
|
@@ -627,6 +627,9 @@ module Clacky
|
|
|
627
627
|
elsif method == "PATCH" && path.match?(%r{^/api/skills/[^/]+/toggle$})
|
|
628
628
|
name = URI.decode_www_form_component(path.sub("/api/skills/", "").sub("/toggle", ""))
|
|
629
629
|
api_toggle_skill(name, req, res)
|
|
630
|
+
elsif method == "DELETE" && path.match?(%r{^/api/skills/[^/]+$})
|
|
631
|
+
name = URI.decode_www_form_component(path.sub("/api/skills/", ""))
|
|
632
|
+
api_delete_skill(name, res)
|
|
630
633
|
elsif method == "POST" && path.match?(%r{^/api/brand/skills/[^/]+/install$})
|
|
631
634
|
slug = URI.decode_www_form_component(path.sub("/api/brand/skills/", "").sub("/install", ""))
|
|
632
635
|
api_brand_skill_install(slug, req, res)
|
|
@@ -3463,6 +3466,14 @@ module Clacky
|
|
|
3463
3466
|
json_response(res, 422, { error: e.message })
|
|
3464
3467
|
end
|
|
3465
3468
|
|
|
3469
|
+
private def api_delete_skill(name, res)
|
|
3470
|
+
skill = @skill_loader[name]
|
|
3471
|
+
return json_response(res, 404, { error: "Skill not found: #{name}" }) unless skill
|
|
3472
|
+
|
|
3473
|
+
FileUtils.rm_rf(skill.directory)
|
|
3474
|
+
json_response(res, 200, { ok: true })
|
|
3475
|
+
end
|
|
3476
|
+
|
|
3466
3477
|
# POST /api/my-skills/:name/publish
|
|
3467
3478
|
# GET /api/creator/skills
|
|
3468
3479
|
# Returns two separate groups:
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -250,7 +250,7 @@ html { font-size: 16px; }
|
|
|
250
250
|
.skel-label { height: 0.75rem; width: 4rem; margin-top: 0.375rem; }
|
|
251
251
|
.skel-block { height: 9rem; width: 100%; }
|
|
252
252
|
.skel-block-sm { height: 5rem; width: 100%; }
|
|
253
|
-
.skel-heatmap { height:
|
|
253
|
+
.skel-heatmap { height: 5rem; width: 178px; }
|
|
254
254
|
|
|
255
255
|
|
|
256
256
|
|
|
@@ -375,7 +375,7 @@ body {
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
.header-logo-img {
|
|
378
|
-
height:
|
|
378
|
+
height: 2rem;
|
|
379
379
|
object-fit: contain;
|
|
380
380
|
display: block;
|
|
381
381
|
flex-shrink: 0;
|
|
@@ -498,10 +498,6 @@ body {
|
|
|
498
498
|
.theme-toggle-btn:active {
|
|
499
499
|
background: var(--color-bg-hover);
|
|
500
500
|
}
|
|
501
|
-
/* Sound-notification toggle shares .theme-toggle-btn; highlight when ON. */
|
|
502
|
-
#notify-toggle-header.notify-on:hover {
|
|
503
|
-
color: var(--color-accent-primary, var(--color-text-primary));
|
|
504
|
-
}
|
|
505
501
|
|
|
506
502
|
/* ── Content Row (Sidebar + Main) ───────────────────────────────────────── */
|
|
507
503
|
#app > aside,
|
|
@@ -530,8 +526,9 @@ body {
|
|
|
530
526
|
/* The sidebar sits on the outer frame (bg-primary) so it's visibly a layer
|
|
531
527
|
BEHIND the chat surface (bg-secondary). Subtle right border separates them. */
|
|
532
528
|
#sidebar {
|
|
533
|
-
width: 15rem;
|
|
534
|
-
|
|
529
|
+
--sidebar-width: 15rem;
|
|
530
|
+
width: var(--sidebar-width);
|
|
531
|
+
min-width: var(--sidebar-width);
|
|
535
532
|
background: var(--color-bg-primary);
|
|
536
533
|
border-right: 1px solid var(--color-border-primary);
|
|
537
534
|
display: flex;
|
|
@@ -540,9 +537,22 @@ body {
|
|
|
540
537
|
height: 100%;
|
|
541
538
|
flex-shrink: 0;
|
|
542
539
|
margin-left: 0;
|
|
540
|
+
position: relative;
|
|
543
541
|
}
|
|
544
542
|
#sidebar.hidden {
|
|
545
|
-
margin-left: -
|
|
543
|
+
margin-left: calc(-1 * var(--sidebar-width));
|
|
544
|
+
}
|
|
545
|
+
#sidebar-resize-handle {
|
|
546
|
+
position: absolute;
|
|
547
|
+
top: 0;
|
|
548
|
+
right: -3px;
|
|
549
|
+
bottom: 0;
|
|
550
|
+
width: 6px;
|
|
551
|
+
cursor: col-resize;
|
|
552
|
+
z-index: 10;
|
|
553
|
+
}
|
|
554
|
+
#sidebar:has(#sidebar-resize-handle.active) {
|
|
555
|
+
transition: background-color var(--transition-base);
|
|
546
556
|
}
|
|
547
557
|
#sidebar-header {
|
|
548
558
|
display: flex;
|
|
@@ -578,6 +588,22 @@ body {
|
|
|
578
588
|
flex-direction: column;
|
|
579
589
|
min-height: 0;
|
|
580
590
|
padding: 0 6px 8px;
|
|
591
|
+
margin-right: -1px;
|
|
592
|
+
scrollbar-width: thin;
|
|
593
|
+
scrollbar-color: transparent transparent;
|
|
594
|
+
}
|
|
595
|
+
#sidebar-list.is-scrolling {
|
|
596
|
+
scrollbar-color: var(--color-border-secondary) transparent;
|
|
597
|
+
}
|
|
598
|
+
#sidebar-list::-webkit-scrollbar { width: 4px; }
|
|
599
|
+
#sidebar-list::-webkit-scrollbar-track { background: transparent; }
|
|
600
|
+
#sidebar-list::-webkit-scrollbar-thumb {
|
|
601
|
+
background: transparent;
|
|
602
|
+
border-radius: 4px;
|
|
603
|
+
transition: background 0.4s;
|
|
604
|
+
}
|
|
605
|
+
#sidebar-list.is-scrolling::-webkit-scrollbar-thumb {
|
|
606
|
+
background: var(--color-border-secondary);
|
|
581
607
|
}
|
|
582
608
|
#session-list { padding: 4px 0 0; min-height: 6.75rem; }
|
|
583
609
|
|
|
@@ -598,10 +624,6 @@ body {
|
|
|
598
624
|
background: var(--color-bg-primary);
|
|
599
625
|
z-index: 10;
|
|
600
626
|
}
|
|
601
|
-
.sidebar-divider:first-child {
|
|
602
|
-
margin-top: 0;
|
|
603
|
-
padding-top: 18px;
|
|
604
|
-
}
|
|
605
627
|
.sidebar-divider span {
|
|
606
628
|
white-space: nowrap;
|
|
607
629
|
flex: 0 0 auto;
|
|
@@ -2128,7 +2150,7 @@ body {
|
|
|
2128
2150
|
.msg-user .msg-time { color: var(--color-text-secondary); right: 0; left: auto; padding-right: 0.25rem; }
|
|
2129
2151
|
.msg-assistant .msg-time { color: var(--color-text-secondary); left: 0; right: auto; padding-left: 0.25rem; }
|
|
2130
2152
|
|
|
2131
|
-
.msg-user { background: var(--color-accent-soft); color: var(--color-text-primary); align-self: flex-end; white-space: pre-wrap;
|
|
2153
|
+
.msg-user { background: var(--color-accent-soft); color: var(--color-text-primary); align-self: flex-end; white-space: pre-wrap; }
|
|
2132
2154
|
[data-theme="dark"] .msg-user { background: var(--color-accent-soft); }
|
|
2133
2155
|
.msg-assistant { background: var(--color-bg-tertiary); border: 1px solid var(--color-border-primary); align-self: flex-start; }
|
|
2134
2156
|
|
|
@@ -2777,11 +2799,11 @@ body {
|
|
|
2777
2799
|
#session-info-bar {
|
|
2778
2800
|
display: flex;
|
|
2779
2801
|
align-items: center;
|
|
2780
|
-
gap:
|
|
2802
|
+
gap: 0.375rem;
|
|
2781
2803
|
flex-wrap: wrap;
|
|
2782
|
-
padding:
|
|
2783
|
-
margin-left:
|
|
2784
|
-
margin-right:
|
|
2804
|
+
padding: 0.375rem 0.75rem;
|
|
2805
|
+
margin-left: 1.5rem;
|
|
2806
|
+
margin-right: 1.5rem;
|
|
2785
2807
|
background: transparent;
|
|
2786
2808
|
border-top: none;
|
|
2787
2809
|
font-size: 0.6875rem;
|
|
@@ -6541,6 +6563,24 @@ body {
|
|
|
6541
6563
|
background: var(--color-success-bg);
|
|
6542
6564
|
}
|
|
6543
6565
|
|
|
6566
|
+
/* ── Delete button ────────────────────────────────────────────────────────── */
|
|
6567
|
+
.btn-skill-delete {
|
|
6568
|
+
background: transparent;
|
|
6569
|
+
border: 1px solid transparent;
|
|
6570
|
+
border-radius: 6px;
|
|
6571
|
+
color: var(--color-text-tertiary);
|
|
6572
|
+
cursor: pointer;
|
|
6573
|
+
padding: 0.25rem;
|
|
6574
|
+
display: inline-flex;
|
|
6575
|
+
align-items: center;
|
|
6576
|
+
transition: color .15s, border-color .15s, background .15s;
|
|
6577
|
+
}
|
|
6578
|
+
.btn-skill-delete:hover {
|
|
6579
|
+
color: var(--color-error, #c0392b);
|
|
6580
|
+
border-color: var(--color-error-border, #f5c6c6);
|
|
6581
|
+
background: var(--color-error-bg, #fff0f0);
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6544
6584
|
/* ── Skills sidebar section ──────────────────────────────────────────────── */
|
|
6545
6585
|
#skill-list-items { padding: 0 0.5rem 0.5rem; display: flex; flex-direction: column; gap: 2px; }
|
|
6546
6586
|
|
|
@@ -9712,9 +9752,6 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9712
9752
|
border-radius: 12px;
|
|
9713
9753
|
transition: border-color 0.15s ease;
|
|
9714
9754
|
}
|
|
9715
|
-
.billing-stat-card:hover {
|
|
9716
|
-
border-color: color-mix(in srgb, var(--color-accent-primary) 40%, transparent);
|
|
9717
|
-
}
|
|
9718
9755
|
.billing-stat-primary {
|
|
9719
9756
|
border-color: color-mix(in srgb, var(--color-accent-primary) 35%, transparent);
|
|
9720
9757
|
}
|
|
@@ -9762,7 +9799,12 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9762
9799
|
}
|
|
9763
9800
|
|
|
9764
9801
|
/* ── Heatmap ─────────────────────────────────────────────────────────── */
|
|
9765
|
-
.billing-heatmap-row {
|
|
9802
|
+
.billing-heatmap-row {
|
|
9803
|
+
display: grid;
|
|
9804
|
+
grid-template-columns: auto 1fr;
|
|
9805
|
+
gap: 1rem;
|
|
9806
|
+
width: 100%;
|
|
9807
|
+
}
|
|
9766
9808
|
.billing-heat-dow-row {
|
|
9767
9809
|
display: grid;
|
|
9768
9810
|
grid-template-columns: repeat(7, 28px);
|
|
@@ -9790,11 +9832,11 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9790
9832
|
}
|
|
9791
9833
|
.billing-heat-cell.is-empty { background: transparent; }
|
|
9792
9834
|
.billing-heat-cell[data-level="0"] { background: var(--color-border-primary); }
|
|
9793
|
-
.billing-heat-cell[data-level="1"] { background:
|
|
9794
|
-
.billing-heat-cell[data-level="2"] { background:
|
|
9795
|
-
.billing-heat-cell[data-level="3"] { background:
|
|
9796
|
-
.billing-heat-cell[data-level="4"] { background:
|
|
9797
|
-
.billing-heat-cell[data-level="5"] { background:
|
|
9835
|
+
.billing-heat-cell[data-level="1"] { background: color-mix(in srgb, var(--color-accent-primary) 20%, var(--color-bg-secondary)); }
|
|
9836
|
+
.billing-heat-cell[data-level="2"] { background: color-mix(in srgb, var(--color-accent-primary) 40%, var(--color-bg-secondary)); }
|
|
9837
|
+
.billing-heat-cell[data-level="3"] { background: color-mix(in srgb, var(--color-accent-primary) 60%, var(--color-bg-secondary)); }
|
|
9838
|
+
.billing-heat-cell[data-level="4"] { background: color-mix(in srgb, var(--color-accent-primary) 80%, var(--color-bg-secondary)); }
|
|
9839
|
+
.billing-heat-cell[data-level="5"] { background: var(--color-accent-primary); }
|
|
9798
9840
|
.billing-heat-legend {
|
|
9799
9841
|
display: flex;
|
|
9800
9842
|
align-items: center;
|
|
@@ -9808,6 +9850,63 @@ body.setup-mode[data-theme="dark"] {
|
|
|
9808
9850
|
aspect-ratio: auto;
|
|
9809
9851
|
}
|
|
9810
9852
|
|
|
9853
|
+
/* ── Heatmap + Trend two-column row ──────────────────────────────────── */
|
|
9854
|
+
.billing-heatmap-card,
|
|
9855
|
+
.billing-trend-card {
|
|
9856
|
+
min-width: 0;
|
|
9857
|
+
min-height: 140px;
|
|
9858
|
+
}
|
|
9859
|
+
|
|
9860
|
+
/* ── Cost Trend Line Chart ───────────────────────────────────────────── */
|
|
9861
|
+
.billing-trend-total {
|
|
9862
|
+
font-size: 0.75rem;
|
|
9863
|
+
font-weight: 600;
|
|
9864
|
+
color: var(--color-accent-primary);
|
|
9865
|
+
}
|
|
9866
|
+
.billing-trend-chart {
|
|
9867
|
+
flex: 1;
|
|
9868
|
+
min-height: 0;
|
|
9869
|
+
}
|
|
9870
|
+
.billing-trend-svg {
|
|
9871
|
+
width: 100%;
|
|
9872
|
+
height: 100%;
|
|
9873
|
+
overflow: visible;
|
|
9874
|
+
}
|
|
9875
|
+
.billing-trend-grid-line {
|
|
9876
|
+
stroke: var(--color-border-primary);
|
|
9877
|
+
stroke-width: 1;
|
|
9878
|
+
}
|
|
9879
|
+
.billing-trend-y-label {
|
|
9880
|
+
font-size: 9px;
|
|
9881
|
+
fill: var(--color-text-secondary);
|
|
9882
|
+
text-anchor: end;
|
|
9883
|
+
}
|
|
9884
|
+
.billing-trend-x-label {
|
|
9885
|
+
font-size: 9px;
|
|
9886
|
+
fill: var(--color-text-secondary);
|
|
9887
|
+
text-anchor: middle;
|
|
9888
|
+
}
|
|
9889
|
+
.billing-trend-line {
|
|
9890
|
+
stroke: var(--color-accent-primary);
|
|
9891
|
+
stroke-width: 1.5;
|
|
9892
|
+
stroke-linecap: round;
|
|
9893
|
+
stroke-linejoin: round;
|
|
9894
|
+
}
|
|
9895
|
+
.billing-trend-area {
|
|
9896
|
+
fill: url(#billing-trend-grad);
|
|
9897
|
+
}
|
|
9898
|
+
.billing-trend-dot {
|
|
9899
|
+
fill: var(--color-accent-primary);
|
|
9900
|
+
stroke: var(--color-bg-secondary);
|
|
9901
|
+
stroke-width: 2;
|
|
9902
|
+
cursor: pointer;
|
|
9903
|
+
opacity: 0;
|
|
9904
|
+
transition: opacity 0.15s;
|
|
9905
|
+
}
|
|
9906
|
+
.billing-trend-svg:hover .billing-trend-dot {
|
|
9907
|
+
opacity: 1;
|
|
9908
|
+
}
|
|
9909
|
+
|
|
9811
9910
|
/* ── Chart Card base ─────────────────────────────────────────────────── */
|
|
9812
9911
|
.billing-chart-row { width: 100%; }
|
|
9813
9912
|
.billing-chart-card {
|
|
@@ -10002,6 +10101,8 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10002
10101
|
border: 1px solid var(--color-border-primary);
|
|
10003
10102
|
border-radius: 12px;
|
|
10004
10103
|
padding: 1.25rem;
|
|
10104
|
+
height: 16rem;
|
|
10105
|
+
overflow-y: auto;
|
|
10005
10106
|
}
|
|
10006
10107
|
.billing-section h3 {
|
|
10007
10108
|
font-size: 0.9375rem;
|
|
@@ -10103,6 +10204,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10103
10204
|
height: 100%;
|
|
10104
10205
|
background: var(--color-accent-primary);
|
|
10105
10206
|
border-radius: 3px;
|
|
10207
|
+
min-width: 8px;
|
|
10106
10208
|
}
|
|
10107
10209
|
.billing-model-cost {
|
|
10108
10210
|
font-size: 0.8125rem;
|
|
@@ -10603,6 +10705,9 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10603
10705
|
text-align: right;
|
|
10604
10706
|
color: var(--color-text-primary);
|
|
10605
10707
|
}
|
|
10708
|
+
.billing-cell-total {
|
|
10709
|
+
color: var(--color-accent-primary);
|
|
10710
|
+
}
|
|
10606
10711
|
.billing-cell-hit {
|
|
10607
10712
|
color: #3b82f6;
|
|
10608
10713
|
}
|
|
@@ -11180,6 +11285,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11180
11285
|
transform: translateX(-100%);
|
|
11181
11286
|
margin-left: 0 !important;
|
|
11182
11287
|
}
|
|
11288
|
+
#sidebar-resize-handle { display: none; }
|
|
11183
11289
|
|
|
11184
11290
|
/* Overlay backdrop */
|
|
11185
11291
|
#sidebar-overlay {
|
|
@@ -11219,7 +11325,9 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11219
11325
|
|
|
11220
11326
|
/* Session info bar: single-line, no hover-expand, font smaller */
|
|
11221
11327
|
#session-info-bar {
|
|
11222
|
-
padding: 0.1875rem 0.
|
|
11328
|
+
padding: 0.1875rem 0.5rem;
|
|
11329
|
+
margin-left: 0.75rem;
|
|
11330
|
+
margin-right: 0.75rem;
|
|
11223
11331
|
font-size: 0.625rem;
|
|
11224
11332
|
overflow: hidden;
|
|
11225
11333
|
white-space: nowrap;
|
|
@@ -11246,9 +11354,14 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11246
11354
|
margin: 0 0.25rem;
|
|
11247
11355
|
}
|
|
11248
11356
|
|
|
11357
|
+
/* Tighten input area outer padding on mobile */
|
|
11358
|
+
#input-area {
|
|
11359
|
+
padding: 0px 12px 12px;
|
|
11360
|
+
}
|
|
11361
|
+
|
|
11249
11362
|
/* Input bar: proportional spacing, touch-friendly */
|
|
11250
11363
|
#input-bar {
|
|
11251
|
-
padding: 0.
|
|
11364
|
+
padding: 0.375rem 0.5rem;
|
|
11252
11365
|
gap: 0.25rem;
|
|
11253
11366
|
}
|
|
11254
11367
|
|
|
@@ -11264,12 +11377,12 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11264
11377
|
/* Textarea: prevent iOS auto-zoom (must be ≥1rem) */
|
|
11265
11378
|
#user-input {
|
|
11266
11379
|
font-size: 1rem;
|
|
11267
|
-
padding: 0.
|
|
11380
|
+
padding: 0.1875rem 0.375rem;
|
|
11268
11381
|
}
|
|
11269
11382
|
|
|
11270
11383
|
/* Send button: bigger tap target */
|
|
11271
11384
|
#btn-send, #btn-interrupt {
|
|
11272
|
-
padding: 0.
|
|
11385
|
+
padding: 0.3125rem 0.75rem;
|
|
11273
11386
|
font-size: 0.875rem;
|
|
11274
11387
|
}
|
|
11275
11388
|
|
|
@@ -11434,6 +11547,8 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11434
11547
|
#trash-body { padding: 1rem 1rem 5rem; }
|
|
11435
11548
|
#creator-body { padding: 1rem 1rem 5rem; }
|
|
11436
11549
|
|
|
11550
|
+
.billing-heatmap-row { grid-template-columns: 1fr; }
|
|
11551
|
+
|
|
11437
11552
|
/* ── MCP page ── */
|
|
11438
11553
|
#mcp-panel {
|
|
11439
11554
|
overflow: visible;
|
data/lib/clacky/web/app.js
CHANGED
|
@@ -513,6 +513,57 @@ if ($("btn-toggle-sidebar")) {
|
|
|
513
513
|
// Tap overlay to close sidebar on mobile
|
|
514
514
|
$("sidebar-overlay").addEventListener("click", _closeSidebar);
|
|
515
515
|
|
|
516
|
+
// ── Sidebar resize ──────────────────────────────────────────────────────
|
|
517
|
+
(function _initSidebarResize() {
|
|
518
|
+
const sidebar = $("sidebar");
|
|
519
|
+
const handle = $("sidebar-resize-handle");
|
|
520
|
+
if (!sidebar || !handle) return;
|
|
521
|
+
|
|
522
|
+
const MIN_W = 12; // rem
|
|
523
|
+
const MAX_W = 32; // rem
|
|
524
|
+
const baseFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
525
|
+
|
|
526
|
+
let startX = 0;
|
|
527
|
+
let startW = 0;
|
|
528
|
+
|
|
529
|
+
// Restore saved width
|
|
530
|
+
const saved = localStorage.getItem("sidebar-width");
|
|
531
|
+
if (saved) {
|
|
532
|
+
const w = parseFloat(saved);
|
|
533
|
+
if (w >= MIN_W && w <= MAX_W) {
|
|
534
|
+
sidebar.style.setProperty("--sidebar-width", w + "rem");
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function _getWidth() {
|
|
539
|
+
return parseFloat(getComputedStyle(sidebar).getPropertyValue("--sidebar-width"));
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
handle.addEventListener("mousedown", (e) => {
|
|
543
|
+
e.preventDefault();
|
|
544
|
+
startX = e.clientX;
|
|
545
|
+
startW = _getWidth();
|
|
546
|
+
handle.classList.add("active");
|
|
547
|
+
document.body.style.cursor = "col-resize";
|
|
548
|
+
document.body.style.userSelect = "none";
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
document.addEventListener("mousemove", (e) => {
|
|
552
|
+
if (!handle.classList.contains("active")) return;
|
|
553
|
+
const dx = (e.clientX - startX) / baseFontSize;
|
|
554
|
+
const newW = Math.min(MAX_W, Math.max(MIN_W, startW + dx));
|
|
555
|
+
sidebar.style.setProperty("--sidebar-width", newW + "rem");
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
document.addEventListener("mouseup", () => {
|
|
559
|
+
if (!handle.classList.contains("active")) return;
|
|
560
|
+
handle.classList.remove("active");
|
|
561
|
+
document.body.style.cursor = "";
|
|
562
|
+
document.body.style.userSelect = "";
|
|
563
|
+
localStorage.setItem("sidebar-width", _getWidth());
|
|
564
|
+
});
|
|
565
|
+
})();
|
|
566
|
+
|
|
516
567
|
// On mobile: start with sidebar hidden
|
|
517
568
|
if (_isMobile()) _closeSidebar();
|
|
518
569
|
|
data/lib/clacky/web/billing.js
CHANGED
|
@@ -124,9 +124,12 @@ const Billing = (() => {
|
|
|
124
124
|
`).join("")}
|
|
125
125
|
</div>
|
|
126
126
|
<div class="billing-heatmap-row">
|
|
127
|
-
<div class="billing-chart-card billing-
|
|
127
|
+
<div class="billing-chart-card billing-heatmap-card">
|
|
128
128
|
<div class="skel skel-heatmap"></div>
|
|
129
129
|
</div>
|
|
130
|
+
<div class="billing-chart-card billing-trend-card">
|
|
131
|
+
<div class="skel skel-block-sm"></div>
|
|
132
|
+
</div>
|
|
130
133
|
</div>
|
|
131
134
|
<div class="billing-bottom-grid">
|
|
132
135
|
<div class="billing-section"><div class="skel skel-block"></div></div>
|
|
@@ -160,9 +163,12 @@ const Billing = (() => {
|
|
|
160
163
|
`).join("")}
|
|
161
164
|
</div>
|
|
162
165
|
<div class="billing-heatmap-row">
|
|
163
|
-
<div class="billing-chart-card billing-
|
|
166
|
+
<div class="billing-chart-card billing-heatmap-card">
|
|
164
167
|
<div class="skel skel-heatmap"></div>
|
|
165
168
|
</div>
|
|
169
|
+
<div class="billing-chart-card billing-trend-card">
|
|
170
|
+
<div class="skel skel-block-sm"></div>
|
|
171
|
+
</div>
|
|
166
172
|
</div>
|
|
167
173
|
<div class="billing-bottom-grid">
|
|
168
174
|
<div class="billing-section"><div class="skel skel-block"></div></div>
|
|
@@ -185,7 +191,7 @@ const Billing = (() => {
|
|
|
185
191
|
// Model filter options (使用完整模型列表)
|
|
186
192
|
const models = _allModels.length > 0 ? _allModels : (_summary.by_model ? Object.keys(_summary.by_model) : []);
|
|
187
193
|
const modelOptions = [`<option value="all">${I18n.t("billing.allModels") || "All Models"}</option>`]
|
|
188
|
-
.concat(models.map(m => `<option value="${_esc(m)}" ${m === _currentModel ? "selected" : ""}>${_esc(m)}</option>`))
|
|
194
|
+
.concat(models.filter(m => m).map(m => `<option value="${_esc(m)}" ${m === _currentModel ? "selected" : ""}>${_esc(m)}</option>`))
|
|
189
195
|
.join("");
|
|
190
196
|
|
|
191
197
|
container.innerHTML = `
|
|
@@ -214,7 +220,7 @@ const Billing = (() => {
|
|
|
214
220
|
</div>
|
|
215
221
|
|
|
216
222
|
<div class="billing-stats-row">
|
|
217
|
-
<div class="billing-stat-card
|
|
223
|
+
<div class="billing-stat-card">
|
|
218
224
|
<div class="billing-stat-icon billing-stat-icon-cost">
|
|
219
225
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="10" cy="10" r="8" stroke="currentColor" stroke-width="1.5"/><path d="M10 6v1m0 6v1M7.5 10a2.5 2.5 0 0 0 2.5 2.5c1.38 0 2.5-.56 2.5-1.25S11.38 10 10 10c-1.38 0-2.5-.56-2.5-1.25S8.62 7.5 10 7.5A2.5 2.5 0 0 1 12.5 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
220
226
|
</div>
|
|
@@ -254,6 +260,7 @@ const Billing = (() => {
|
|
|
254
260
|
|
|
255
261
|
<div class="billing-heatmap-row">
|
|
256
262
|
${_renderHeatmap()}
|
|
263
|
+
${_renderCostTrend()}
|
|
257
264
|
</div>
|
|
258
265
|
|
|
259
266
|
<div class="billing-bottom-grid">
|
|
@@ -294,6 +301,7 @@ const Billing = (() => {
|
|
|
294
301
|
// Bind chart tooltip handlers
|
|
295
302
|
_bindChartTooltip();
|
|
296
303
|
_bindHeatmapTooltip();
|
|
304
|
+
_bindTrendTooltip();
|
|
297
305
|
}
|
|
298
306
|
|
|
299
307
|
// Builds the per-period scorecard numbers from a raw summary object, using
|
|
@@ -445,6 +453,41 @@ const Billing = (() => {
|
|
|
445
453
|
});
|
|
446
454
|
}
|
|
447
455
|
|
|
456
|
+
function _bindTrendTooltip() {
|
|
457
|
+
const svg = document.querySelector(".billing-trend-svg");
|
|
458
|
+
const tooltip = document.getElementById("billing-tooltip");
|
|
459
|
+
if (!svg || !tooltip) return;
|
|
460
|
+
|
|
461
|
+
svg.addEventListener("mousemove", (e) => {
|
|
462
|
+
const dot = e.target.closest(".billing-trend-dot");
|
|
463
|
+
if (!dot) {
|
|
464
|
+
tooltip.style.display = "none";
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
tooltip.innerHTML = `
|
|
468
|
+
<div class="tooltip-header">
|
|
469
|
+
<span class="tooltip-date">${dot.dataset.date}</span>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="tooltip-row">
|
|
472
|
+
<span class="tooltip-label">${I18n.t("billing.cost") || "Cost"}</span>
|
|
473
|
+
<span class="tooltip-value">${dot.dataset.cost}</span>
|
|
474
|
+
</div>
|
|
475
|
+
`;
|
|
476
|
+
tooltip.style.display = "block";
|
|
477
|
+
tooltip.style.visibility = "hidden";
|
|
478
|
+
const rect = tooltip.getBoundingClientRect();
|
|
479
|
+
const ovf = e.clientX + 15 + rect.width - window.innerWidth;
|
|
480
|
+
tooltip.style.left = ovf > 0 ? `${e.clientX - 15 - rect.width}px` : `${e.clientX + 15}px`;
|
|
481
|
+
tooltip.style.top = `${e.clientY - 10}px`;
|
|
482
|
+
tooltip.style.visibility = "visible";
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
svg.addEventListener("mouseleave", () => {
|
|
486
|
+
tooltip.style.display = "none";
|
|
487
|
+
tooltip.style.visibility = "";
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
448
491
|
function _bindClearHandlers() {
|
|
449
492
|
const clearBtn = document.getElementById("billing-clear-btn");
|
|
450
493
|
const clearPopup = document.getElementById("billing-clear-popup");
|
|
@@ -600,6 +643,7 @@ const Billing = (() => {
|
|
|
600
643
|
}
|
|
601
644
|
|
|
602
645
|
const entries = Object.entries(_summary.by_model)
|
|
646
|
+
.filter(([_, data]) => (typeof data === "object" ? data.cost : data) > 0)
|
|
603
647
|
.sort((a, b) => (b[1].cost || b[1]) - (a[1].cost || a[1]));
|
|
604
648
|
|
|
605
649
|
const totalCost = entries.reduce((sum, [, data]) => sum + (typeof data === "object" ? data.cost : data), 0) || 1;
|
|
@@ -653,7 +697,7 @@ const Billing = (() => {
|
|
|
653
697
|
const dowHeader = dowLabels.map(l => `<span class="billing-heat-dow">${_esc(l)}</span>`).join("");
|
|
654
698
|
|
|
655
699
|
return `
|
|
656
|
-
<div class="billing-chart-card billing-
|
|
700
|
+
<div class="billing-chart-card billing-heatmap-card">
|
|
657
701
|
<div class="billing-chart-header">
|
|
658
702
|
<h4>${I18n.t("billing.heatmap.title") || "Activity"}</h4>
|
|
659
703
|
<div class="billing-heat-legend">
|
|
@@ -672,6 +716,88 @@ const Billing = (() => {
|
|
|
672
716
|
`;
|
|
673
717
|
}
|
|
674
718
|
|
|
719
|
+
function _renderCostTrend() {
|
|
720
|
+
if (!_daily || _daily.length < 2) {
|
|
721
|
+
return `<div class="billing-chart-card billing-trend-card"><div class="billing-chart-empty">${I18n.t("billing.noData") || "No data available"}</div></div>`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const days = _daily.slice(-30);
|
|
725
|
+
const costs = days.map(d => _convertCost(d.cost || 0));
|
|
726
|
+
const maxCost = Math.max(...costs, 0.0001);
|
|
727
|
+
const minCost = Math.min(...costs);
|
|
728
|
+
|
|
729
|
+
const pad = { top: 20, right: 16, bottom: 22, left: 48 };
|
|
730
|
+
const w = 400;
|
|
731
|
+
const h = 140;
|
|
732
|
+
const plotW = w - pad.left - pad.right;
|
|
733
|
+
const plotH = h - pad.top - pad.bottom;
|
|
734
|
+
|
|
735
|
+
const range = maxCost - minCost || 1;
|
|
736
|
+
const xStep = days.length > 1 ? plotW / (days.length - 1) : plotW;
|
|
737
|
+
const points = costs.map((c, i) => {
|
|
738
|
+
const x = pad.left + i * xStep;
|
|
739
|
+
const y = pad.top + plotH - ((c - minCost) / range) * plotH;
|
|
740
|
+
return `${x},${y}`;
|
|
741
|
+
}).join(" ");
|
|
742
|
+
|
|
743
|
+
const areaPoints = costs.length > 0
|
|
744
|
+
? `${pad.left},${pad.top + plotH} ${points} ${pad.left + (costs.length - 1) * xStep},${pad.top + plotH}`
|
|
745
|
+
: "";
|
|
746
|
+
|
|
747
|
+
const yTicks = 4;
|
|
748
|
+
const yLabels = Array.from({ length: yTicks + 1 }, (_, i) => {
|
|
749
|
+
const val = minCost + (range / yTicks) * i;
|
|
750
|
+
const y = pad.top + plotH - ((val - minCost) / range) * plotH;
|
|
751
|
+
return { val, y };
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const showEvery = days.length > 20 ? 10 : days.length > 10 ? 5 : days.length > 5 ? 3 : 1;
|
|
755
|
+
const xLabels = [];
|
|
756
|
+
let lastX = -50;
|
|
757
|
+
days.forEach((d, i) => {
|
|
758
|
+
if (i % showEvery !== 0 && i !== days.length - 1) return;
|
|
759
|
+
const x = pad.left + i * xStep;
|
|
760
|
+
if (x - lastX < 40) return;
|
|
761
|
+
lastX = x;
|
|
762
|
+
xLabels.push({ date: d.date.slice(5), x });
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
const currencySymbol = _getCurrencySymbol();
|
|
766
|
+
|
|
767
|
+
return `
|
|
768
|
+
<div class="billing-chart-card billing-trend-card">
|
|
769
|
+
<div class="billing-chart-header">
|
|
770
|
+
<h4>${I18n.t("billing.costTrend") || "Cost Trend"}</h4>
|
|
771
|
+
<span class="billing-trend-total">${currencySymbol}${_formatCost(costs.reduce((a, b) => a + b, 0))}</span>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="billing-trend-chart">
|
|
774
|
+
<svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="xMidYMid meet" class="billing-trend-svg">
|
|
775
|
+
${yLabels.map(l => `
|
|
776
|
+
<line x1="${pad.left}" y1="${l.y}" x2="${w - pad.right}" y2="${l.y}" class="billing-trend-grid-line" />
|
|
777
|
+
<text x="${pad.left - 6}" y="${l.y + 4}" class="billing-trend-y-label">${currencySymbol}${_formatCost(l.val)}</text>
|
|
778
|
+
`).join("")}
|
|
779
|
+
${xLabels.map(l => `
|
|
780
|
+
<text x="${l.x}" y="${h - 4}" class="billing-trend-x-label">${l.date}</text>
|
|
781
|
+
`).join("")}
|
|
782
|
+
<defs>
|
|
783
|
+
<linearGradient id="billing-trend-grad" x1="0" y1="0" x2="0" y2="1">
|
|
784
|
+
<stop offset="0%" stop-color="#4f46e5" stop-opacity="0.15" />
|
|
785
|
+
<stop offset="100%" stop-color="#4f46e5" stop-opacity="0.02" />
|
|
786
|
+
</linearGradient>
|
|
787
|
+
</defs>
|
|
788
|
+
<polygon points="${areaPoints}" fill="url(#billing-trend-grad)" class="billing-trend-area" />
|
|
789
|
+
<polyline points="${points}" fill="none" class="billing-trend-line" />
|
|
790
|
+
${costs.map((c, i) => {
|
|
791
|
+
const cx = pad.left + i * xStep;
|
|
792
|
+
const cy = pad.top + plotH - ((c - minCost) / range) * plotH;
|
|
793
|
+
return `<circle cx="${cx}" cy="${cy}" r="3" class="billing-trend-dot" data-date="${days[i].date}" data-cost="${currencySymbol}${_formatCost(c)}" />`;
|
|
794
|
+
}).join("")}
|
|
795
|
+
</svg>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
`;
|
|
799
|
+
}
|
|
800
|
+
|
|
675
801
|
function _renderCombinedChart() {
|
|
676
802
|
if (!_daily || _daily.length === 0) {
|
|
677
803
|
return `<div class="billing-chart-card billing-chart-wide"><div class="billing-chart-empty">${I18n.t("billing.noData") || "No data available"}</div></div>`;
|
|
@@ -782,7 +908,7 @@ const Billing = (() => {
|
|
|
782
908
|
<span class="billing-cell-main">${_esc(displayName)}</span>
|
|
783
909
|
<span class="billing-cell-sub">${requests} ${I18n.t("billing.requests") || "req"} · ${_esc(models)}</span>
|
|
784
910
|
</div>
|
|
785
|
-
<div class="billing-cell billing-cell-number">${_formatCompact(totalTokens)}</div>
|
|
911
|
+
<div class="billing-cell billing-cell-number billing-cell-total">${_formatCompact(totalTokens)}</div>
|
|
786
912
|
<div class="billing-cell billing-cell-number billing-cell-hit">${_formatCompact(cacheHit)}</div>
|
|
787
913
|
<div class="billing-cell billing-cell-number billing-cell-miss">${_formatCompact(cacheMiss)}</div>
|
|
788
914
|
<div class="billing-cell billing-cell-number">${_formatCompact(completionTokens)}</div>
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -434,6 +434,10 @@ const I18n = (() => {
|
|
|
434
434
|
"skills.toggle.disableDesc": "AI can auto-invoke. Disable to prevent auto-triggering (manual use still available)",
|
|
435
435
|
"skills.toggle.enableDesc": "AI cannot auto-invoke. Enable to allow auto-triggering",
|
|
436
436
|
"skills.toggleError": "Error: ",
|
|
437
|
+
"skills.deleteConfirm": "Are you sure you want to delete \"{{name}}\"? This cannot be undone.",
|
|
438
|
+
"skills.deleteError": "Delete failed.",
|
|
439
|
+
"skills.deleted": "Skill \"{{name}}\" deleted.",
|
|
440
|
+
"skills.btn.delete": "Delete skill",
|
|
437
441
|
"skills.ac.empty": "No skills available",
|
|
438
442
|
"skills.upload.uploading": "Uploading…",
|
|
439
443
|
"skills.upload.uploaded": "Uploaded",
|
|
@@ -1314,6 +1318,10 @@ const I18n = (() => {
|
|
|
1314
1318
|
"skills.toggle.disableDesc": "AI 可自动调用。关闭后 AI 不会主动触发(手动使用仍可用)",
|
|
1315
1319
|
"skills.toggle.enableDesc": "AI 不会自动调用。开启后允许 AI 主动触发",
|
|
1316
1320
|
"skills.toggleError": "错误:",
|
|
1321
|
+
"skills.deleteConfirm": "确定要删除 \"{{name}}\" 吗?此操作不可撤销。",
|
|
1322
|
+
"skills.deleteError": "删除失败。",
|
|
1323
|
+
"skills.deleted": "技能 \"{{name}}\" 已删除。",
|
|
1324
|
+
"skills.btn.delete": "删除技能",
|
|
1317
1325
|
"skills.ac.empty": "暂无可用技能",
|
|
1318
1326
|
"skills.upload.uploading": "上传中…",
|
|
1319
1327
|
"skills.upload.uploaded": "已上传",
|
data/lib/clacky/web/index.html
CHANGED
data/lib/clacky/web/onboard.js
CHANGED
|
@@ -423,6 +423,8 @@ const Onboard = (() => {
|
|
|
423
423
|
const zh = _selectedLang === "zh";
|
|
424
424
|
_setDeviceError("");
|
|
425
425
|
|
|
426
|
+
const w = window.open("about:blank", "_blank");
|
|
427
|
+
|
|
426
428
|
let data;
|
|
427
429
|
try {
|
|
428
430
|
const res = await fetch("/api/onboard/device/start", { method: "POST" });
|
|
@@ -432,6 +434,7 @@ const Onboard = (() => {
|
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
if (!data || !data.ok) {
|
|
437
|
+
if (w && !w.closed) w.close();
|
|
435
438
|
_setDeviceError((data && data.error) || (zh ? "无法发起登录,请稍后重试。" : "Could not start login. Please try again."));
|
|
436
439
|
return;
|
|
437
440
|
}
|
|
@@ -443,7 +446,11 @@ const Onboard = (() => {
|
|
|
443
446
|
if (link && url) link.href = url;
|
|
444
447
|
|
|
445
448
|
_showDevicePending(true);
|
|
446
|
-
if (
|
|
449
|
+
if (w && !w.closed) {
|
|
450
|
+
w.location.href = url;
|
|
451
|
+
} else {
|
|
452
|
+
window.open(url, "_blank");
|
|
453
|
+
}
|
|
447
454
|
|
|
448
455
|
_devicePolling = true;
|
|
449
456
|
_pollDevice(data.device_code, (data.interval || 5) * 1000);
|
data/lib/clacky/web/sessions.js
CHANGED
|
@@ -5235,3 +5235,14 @@ document.addEventListener("langchange", () => {
|
|
|
5235
5235
|
document.addEventListener("currencychange", () => {
|
|
5236
5236
|
if (Sessions._lastSession) Sessions.updateInfoBar(Sessions._lastSession);
|
|
5237
5237
|
});
|
|
5238
|
+
|
|
5239
|
+
(function () {
|
|
5240
|
+
const sidebarList = document.getElementById("sidebar-list");
|
|
5241
|
+
if (!sidebarList) return;
|
|
5242
|
+
let scrollTimer = null;
|
|
5243
|
+
sidebarList.addEventListener("scroll", () => {
|
|
5244
|
+
sidebarList.classList.add("is-scrolling");
|
|
5245
|
+
clearTimeout(scrollTimer);
|
|
5246
|
+
scrollTimer = setTimeout(() => sidebarList.classList.remove("is-scrolling"), 1000);
|
|
5247
|
+
}, { passive: true });
|
|
5248
|
+
})();
|
data/lib/clacky/web/skills.js
CHANGED
|
@@ -336,6 +336,15 @@ const Skills = (() => {
|
|
|
336
336
|
? ""
|
|
337
337
|
: `<button class="btn-skill-use" data-name="${escapeHtml(skill.name)}">${I18n.t("skills.btn.use")}</button>`;
|
|
338
338
|
|
|
339
|
+
// Delete button only for custom (non-system, non-brand) skills
|
|
340
|
+
const deleteButtonHtml = isSystem
|
|
341
|
+
? ""
|
|
342
|
+
: `<button class="btn-skill-delete" data-name="${escapeHtml(skill.name)}" title="${I18n.t("skills.btn.delete")}">
|
|
343
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
344
|
+
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
345
|
+
</svg>
|
|
346
|
+
</button>`;
|
|
347
|
+
|
|
339
348
|
card.innerHTML = `
|
|
340
349
|
<div class="skill-card-main">
|
|
341
350
|
<div class="skill-card-info">
|
|
@@ -353,6 +362,7 @@ const Skills = (() => {
|
|
|
353
362
|
<span class="skill-toggle-track"></span>
|
|
354
363
|
</label>
|
|
355
364
|
${useButtonHtml}
|
|
365
|
+
${deleteButtonHtml}
|
|
356
366
|
</div>
|
|
357
367
|
</div>
|
|
358
368
|
${errorNoticeHtml}`;
|
|
@@ -385,6 +395,15 @@ const Skills = (() => {
|
|
|
385
395
|
useBtn.addEventListener("click", () => _useInstalledSkill(skill.name));
|
|
386
396
|
}
|
|
387
397
|
|
|
398
|
+
// Bind delete button event
|
|
399
|
+
const deleteBtn = card.querySelector(".btn-skill-delete");
|
|
400
|
+
if (deleteBtn) {
|
|
401
|
+
deleteBtn.addEventListener("click", (e) => {
|
|
402
|
+
e.stopPropagation();
|
|
403
|
+
Skills.delete(skill.name);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
388
407
|
return card;
|
|
389
408
|
}
|
|
390
409
|
|
|
@@ -579,6 +598,20 @@ const Skills = (() => {
|
|
|
579
598
|
}
|
|
580
599
|
},
|
|
581
600
|
|
|
601
|
+
/** Delete a skill by name. Prompts confirmation, then DELETE to server. */
|
|
602
|
+
async delete(name) {
|
|
603
|
+
if (!confirm(I18n.t("skills.deleteConfirm", { name }))) return;
|
|
604
|
+
try {
|
|
605
|
+
const res = await fetch(`/api/skills/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
606
|
+
const data = await res.json();
|
|
607
|
+
if (!res.ok) { alert(data.error || I18n.t("skills.deleteError")); return; }
|
|
608
|
+
Modal.toast(I18n.t("skills.deleted", { name }), "success");
|
|
609
|
+
await Skills.load();
|
|
610
|
+
} catch (e) {
|
|
611
|
+
console.error("[Skills] delete failed", e);
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
|
|
582
615
|
/** Switch the Skills panel to the brand-skills tab.
|
|
583
616
|
* Called externally (e.g. from settings.js after license activation) to
|
|
584
617
|
* guide the user directly to the Brand Skills download page.
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openclacky
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- windy
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: faraday
|
|
@@ -270,8 +269,8 @@ email:
|
|
|
270
269
|
- yafei@dao42.com
|
|
271
270
|
executables:
|
|
272
271
|
- clacky
|
|
273
|
-
- openclacky
|
|
274
272
|
- clarky
|
|
273
|
+
- openclacky
|
|
275
274
|
extensions: []
|
|
276
275
|
extra_rdoc_files: []
|
|
277
276
|
files:
|
|
@@ -632,7 +631,6 @@ metadata:
|
|
|
632
631
|
homepage_uri: https://github.com/clacky-ai/openclacky
|
|
633
632
|
source_code_uri: https://github.com/clacky-ai/openclacky
|
|
634
633
|
changelog_uri: https://github.com/clacky-ai/openclacky/blob/main/CHANGELOG.md
|
|
635
|
-
post_install_message:
|
|
636
634
|
rdoc_options: []
|
|
637
635
|
require_paths:
|
|
638
636
|
- lib
|
|
@@ -650,8 +648,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
650
648
|
- !ruby/object:Gem::Version
|
|
651
649
|
version: '0'
|
|
652
650
|
requirements: []
|
|
653
|
-
rubygems_version: 3.
|
|
654
|
-
signing_key:
|
|
651
|
+
rubygems_version: 3.6.9
|
|
655
652
|
specification_version: 4
|
|
656
653
|
summary: The most Token-efficient open-source AI Agent — BYOK, Skill-driven, IM-integrated.
|
|
657
654
|
test_files: []
|