openclacky 1.4.1 → 1.5.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 +32 -0
- data/lib/clacky/agent/llm_caller.rb +3 -3
- data/lib/clacky/agent/session_serializer.rb +39 -5
- data/lib/clacky/brand_config.rb +49 -7
- data/lib/clacky/client.rb +22 -16
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +135 -84
- data/lib/clacky/default_extensions/ext-studio/ext.yml +2 -4
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +522 -0
- data/lib/clacky/extension/api_extension.rb +5 -6
- data/lib/clacky/extension/loader.rb +16 -2
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/mcp/http_transport.rb +1 -1
- data/lib/clacky/providers.rb +109 -1
- data/lib/clacky/server/http_server.rb +115 -19
- data/lib/clacky/server/session_registry.rb +9 -0
- data/lib/clacky/utils/model_pricing.rb +36 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +321 -32
- data/lib/clacky/web/app.js +29 -6
- data/lib/clacky/web/components/onboard.js +7 -3
- data/lib/clacky/web/features/extensions/store.js +54 -2
- data/lib/clacky/web/features/extensions/view.js +40 -5
- data/lib/clacky/web/features/new-session/store.js +8 -4
- data/lib/clacky/web/features/version/view.js +5 -1
- data/lib/clacky/web/i18n.js +52 -18
- data/lib/clacky/web/index.html +51 -5
- data/lib/clacky/web/sessions.js +40 -11
- data/lib/clacky/web/settings.js +22 -3
- data/lib/clacky/web/theme.js +27 -58
- data/scripts/build/src/uninstall.sh.cc +1 -1
- metadata +2 -5
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +0 -71
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +0 -73
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +0 -65
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +0 -106
data/lib/clacky/web/app.css
CHANGED
|
@@ -152,6 +152,163 @@
|
|
|
152
152
|
--shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.48), 0 4px 8px rgba(0, 0, 0, 0.32);
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/* ── Dim theme (mid-dark, softer than pure dark) ─────────────────────────
|
|
156
|
+
Design stance: desaturated dark grey, less eye strain than near-black.
|
|
157
|
+
Accent is the same run-time variable — not touched here. */
|
|
158
|
+
[data-theme="dim"] {
|
|
159
|
+
--color-bg-primary: #21262d; /* sidebar / shell — deep slate */
|
|
160
|
+
--color-bg-secondary: #2b3038; /* main content — slightly lighter */
|
|
161
|
+
--color-bg-tertiary: #2b3038;
|
|
162
|
+
--color-bg-hover: #333b44;
|
|
163
|
+
--color-bg-input: #2b3038;
|
|
164
|
+
--color-bg-card: #2b3038;
|
|
165
|
+
--color-bg-overlay: rgba(0, 0, 0, 0.52);
|
|
166
|
+
|
|
167
|
+
--color-border-primary: #363d47;
|
|
168
|
+
--color-border-secondary: #2e353f;
|
|
169
|
+
--color-border-strong: #4a5260;
|
|
170
|
+
|
|
171
|
+
--color-text-primary: #cdd9e5;
|
|
172
|
+
--color-text-secondary: #8b98a8;
|
|
173
|
+
--color-text-tertiary: #5d6875;
|
|
174
|
+
--color-text-muted: #4a535e;
|
|
175
|
+
--color-text-inverse: #ffffff;
|
|
176
|
+
--live: #3ed47e;
|
|
177
|
+
|
|
178
|
+
--color-accent-primary: #818cf8;
|
|
179
|
+
--color-accent-hover: #6366f1;
|
|
180
|
+
--color-accent-soft: color-mix(in srgb, var(--color-accent-primary) 14%, transparent);
|
|
181
|
+
|
|
182
|
+
--color-success: #34d399;
|
|
183
|
+
--color-success-bg: #0d2a22;
|
|
184
|
+
--color-success-border: #1a4033;
|
|
185
|
+
|
|
186
|
+
--color-error: #f87171;
|
|
187
|
+
--color-error-bg: #2d1818;
|
|
188
|
+
--color-error-border: #4d2020;
|
|
189
|
+
|
|
190
|
+
--color-warning: #fbbf24;
|
|
191
|
+
--color-warning-bg: #2d1f00;
|
|
192
|
+
--color-warning-border: #4d3200;
|
|
193
|
+
|
|
194
|
+
--color-info: #38bdf8;
|
|
195
|
+
--color-secondary: #34d399;
|
|
196
|
+
--color-accent-alt: #fbbf24;
|
|
197
|
+
|
|
198
|
+
--color-button-primary: var(--color-accent-primary);
|
|
199
|
+
--color-button-primary-hover: var(--color-accent-hover);
|
|
200
|
+
--color-button-primary-text: #ffffff;
|
|
201
|
+
|
|
202
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.28);
|
|
203
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.30), 0 1px 2px rgba(0, 0, 0, 0.22);
|
|
204
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35), 0 2px 4px rgba(0, 0, 0, 0.20);
|
|
205
|
+
--shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.44), 0 4px 8px rgba(0, 0, 0, 0.28);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ── Warm theme (warm off-white, sepia feel) ─────────────────────────────
|
|
209
|
+
Design stance: warm paper tones, easier on eyes than cold white. */
|
|
210
|
+
[data-theme="warm"] {
|
|
211
|
+
--color-bg-primary: #f5f0e8; /* warm parchment */
|
|
212
|
+
--color-bg-secondary: #fdf8f0; /* warmer white */
|
|
213
|
+
--color-bg-tertiary: #fdf8f0;
|
|
214
|
+
--color-bg-hover: #ece5d8;
|
|
215
|
+
--color-bg-input: #fdf8f0;
|
|
216
|
+
--color-bg-card: #fdf8f0;
|
|
217
|
+
--color-bg-overlay: rgba(30, 20, 10, 0.40);
|
|
218
|
+
|
|
219
|
+
--color-border-primary: #e0d6c4;
|
|
220
|
+
--color-border-secondary: #ede6d6;
|
|
221
|
+
--color-border-strong: #c8bba8;
|
|
222
|
+
|
|
223
|
+
--color-text-primary: #2c2416;
|
|
224
|
+
--color-text-secondary: #5c4e38;
|
|
225
|
+
--color-text-tertiary: #8a7a62;
|
|
226
|
+
--color-text-muted: #a89880;
|
|
227
|
+
--color-text-inverse: #ffffff;
|
|
228
|
+
--live: #2e9e5b;
|
|
229
|
+
|
|
230
|
+
--color-accent-primary: #4f46e5;
|
|
231
|
+
--color-accent-hover: #4338ca;
|
|
232
|
+
--color-accent-soft: color-mix(in srgb, var(--color-accent-primary) 10%, transparent);
|
|
233
|
+
|
|
234
|
+
--color-success: #10b981;
|
|
235
|
+
--color-success-bg: #f0fdf4;
|
|
236
|
+
--color-success-border: #bbf7d0;
|
|
237
|
+
|
|
238
|
+
--color-error: #ef4444;
|
|
239
|
+
--color-error-bg: #fef2f2;
|
|
240
|
+
--color-error-border: #fecaca;
|
|
241
|
+
|
|
242
|
+
--color-warning: #f59e0b;
|
|
243
|
+
--color-warning-bg: #fffbeb;
|
|
244
|
+
--color-warning-border: #fcd34d;
|
|
245
|
+
|
|
246
|
+
--color-info: #0ea5e9;
|
|
247
|
+
--color-secondary: #10b981;
|
|
248
|
+
--color-accent-alt: #f59e0b;
|
|
249
|
+
|
|
250
|
+
--color-button-primary: var(--color-accent-primary);
|
|
251
|
+
--color-button-primary-hover: var(--color-accent-hover);
|
|
252
|
+
--color-button-primary-text: #ffffff;
|
|
253
|
+
|
|
254
|
+
--shadow-xs: 0 1px 2px rgba(30, 20, 10, 0.05);
|
|
255
|
+
--shadow-sm: 0 1px 3px rgba(30, 20, 10, 0.07), 0 1px 2px rgba(30, 20, 10, 0.05);
|
|
256
|
+
--shadow-md: 0 4px 12px rgba(30, 20, 10, 0.09), 0 2px 4px rgba(30, 20, 10, 0.05);
|
|
257
|
+
--shadow-lg: 0 12px 28px rgba(30, 20, 10, 0.13), 0 4px 8px rgba(30, 20, 10, 0.07);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* ── Blue-dark theme (VS Code / Apifox style blue-grey dark) ────────────
|
|
261
|
+
Design stance: blue-tinted dark surfaces, familiar dev-tool aesthetic. */
|
|
262
|
+
[data-theme="bluedark"] {
|
|
263
|
+
--color-bg-primary: #1e2228; /* sidebar / shell — blue-grey dark */
|
|
264
|
+
--color-bg-secondary: #252b33; /* main content — slightly lighter */
|
|
265
|
+
--color-bg-tertiary: #252b33;
|
|
266
|
+
--color-bg-hover: #2e3540;
|
|
267
|
+
--color-bg-input: #252b33;
|
|
268
|
+
--color-bg-card: #252b33;
|
|
269
|
+
--color-bg-overlay: rgba(0, 0, 0, 0.60);
|
|
270
|
+
|
|
271
|
+
--color-border-primary: #30373f;
|
|
272
|
+
--color-border-secondary: #282f38;
|
|
273
|
+
--color-border-strong: #44515f;
|
|
274
|
+
|
|
275
|
+
--color-text-primary: #d4dbe6;
|
|
276
|
+
--color-text-secondary: #8b97a8;
|
|
277
|
+
--color-text-tertiary: #5a6678;
|
|
278
|
+
--color-text-muted: #44505e;
|
|
279
|
+
--color-text-inverse: #ffffff;
|
|
280
|
+
--live: #3ed47e;
|
|
281
|
+
|
|
282
|
+
--color-accent-primary: #818cf8;
|
|
283
|
+
--color-accent-hover: #6366f1;
|
|
284
|
+
--color-accent-soft: color-mix(in srgb, var(--color-accent-primary) 14%, transparent);
|
|
285
|
+
|
|
286
|
+
--color-success: #34d399;
|
|
287
|
+
--color-success-bg: #0d2a22;
|
|
288
|
+
--color-success-border: #1a4033;
|
|
289
|
+
|
|
290
|
+
--color-error: #f87171;
|
|
291
|
+
--color-error-bg: #2d1818;
|
|
292
|
+
--color-error-border: #4d2020;
|
|
293
|
+
|
|
294
|
+
--color-warning: #fbbf24;
|
|
295
|
+
--color-warning-bg: #2d1f00;
|
|
296
|
+
--color-warning-border: #4d3200;
|
|
297
|
+
|
|
298
|
+
--color-info: #38bdf8;
|
|
299
|
+
--color-secondary: #34d399;
|
|
300
|
+
--color-accent-alt: #fbbf24;
|
|
301
|
+
|
|
302
|
+
--color-button-primary: var(--color-accent-primary);
|
|
303
|
+
--color-button-primary-hover: var(--color-accent-hover);
|
|
304
|
+
--color-button-primary-text: #ffffff;
|
|
305
|
+
|
|
306
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.40);
|
|
307
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.42), 0 1px 2px rgba(0, 0, 0, 0.32);
|
|
308
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.48), 0 2px 4px rgba(0, 0, 0, 0.30);
|
|
309
|
+
--shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.60), 0 4px 8px rgba(0, 0, 0, 0.40);
|
|
310
|
+
}
|
|
311
|
+
|
|
155
312
|
/* ── Font Size Presets ────────────────────────────────────────────────────
|
|
156
313
|
Controlled via html[data-font-size]: small / medium / large.
|
|
157
314
|
Mechanism: change root font-size, which 1rem inherits from. Every CSS
|
|
@@ -240,6 +397,33 @@ html { font-size: 16px; }
|
|
|
240
397
|
background-size: 200% 100%;
|
|
241
398
|
animation: skel-shimmer 1.8s infinite linear;
|
|
242
399
|
}
|
|
400
|
+
[data-theme="dim"] .skel {
|
|
401
|
+
background: linear-gradient(90deg,
|
|
402
|
+
#2e343c 25%,
|
|
403
|
+
#3c444e 50%,
|
|
404
|
+
#2e343c 75%
|
|
405
|
+
);
|
|
406
|
+
background-size: 200% 100%;
|
|
407
|
+
animation: skel-shimmer 1.8s infinite linear;
|
|
408
|
+
}
|
|
409
|
+
[data-theme="warm"] .skel {
|
|
410
|
+
background: linear-gradient(90deg,
|
|
411
|
+
#ece5d8 25%,
|
|
412
|
+
#ddd5c5 50%,
|
|
413
|
+
#ece5d8 75%
|
|
414
|
+
);
|
|
415
|
+
background-size: 200% 100%;
|
|
416
|
+
animation: skel-shimmer 1.8s infinite linear;
|
|
417
|
+
}
|
|
418
|
+
[data-theme="bluedark"] .skel {
|
|
419
|
+
background: linear-gradient(90deg,
|
|
420
|
+
#272e38 25%,
|
|
421
|
+
#333d4a 50%,
|
|
422
|
+
#272e38 75%
|
|
423
|
+
);
|
|
424
|
+
background-size: 200% 100%;
|
|
425
|
+
animation: skel-shimmer 1.8s infinite linear;
|
|
426
|
+
}
|
|
243
427
|
/* Generic size helpers */
|
|
244
428
|
.skel-icon { width: 2rem; height: 2rem; border-radius: 50%; flex-shrink: 0; }
|
|
245
429
|
.skel-title { height: 1.25rem; width: 6rem; }
|
|
@@ -266,8 +450,10 @@ body {
|
|
|
266
450
|
display: flex;
|
|
267
451
|
overflow: hidden;
|
|
268
452
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
453
|
+
padding: 0 8px 8px;
|
|
454
|
+
box-sizing: border-box;
|
|
269
455
|
}
|
|
270
|
-
#app { display: flex; flex-direction: column; width: 100%; height:
|
|
456
|
+
#app { display: flex; flex-direction: column; width: 100%; height: 100%; overflow: hidden; }
|
|
271
457
|
|
|
272
458
|
/* ── Offline Banner ───────────────────────────────────────────────────── */
|
|
273
459
|
#offline-banner {
|
|
@@ -291,6 +477,8 @@ body {
|
|
|
291
477
|
display: flex;
|
|
292
478
|
flex: 1;
|
|
293
479
|
overflow: hidden;
|
|
480
|
+
border: 1px solid var(--color-border-primary);
|
|
481
|
+
border-radius: 8px;
|
|
294
482
|
}
|
|
295
483
|
|
|
296
484
|
/* ── Top Header ───────────────────────────────────────────────────────── */
|
|
@@ -298,7 +486,6 @@ body {
|
|
|
298
486
|
height: 3rem;
|
|
299
487
|
min-height: 3rem;
|
|
300
488
|
background: var(--color-bg-primary);
|
|
301
|
-
border-bottom: 1px solid var(--color-border-primary);
|
|
302
489
|
display: flex;
|
|
303
490
|
align-items: center;
|
|
304
491
|
justify-content: space-between;
|
|
@@ -754,7 +941,7 @@ body {
|
|
|
754
941
|
transition: background 0.12s, color 0.12s;
|
|
755
942
|
}
|
|
756
943
|
.btn-split-menu-item:hover {
|
|
757
|
-
background: var(--color-bg-
|
|
944
|
+
background: var(--color-bg-hover);
|
|
758
945
|
color: var(--color-text-primary);
|
|
759
946
|
}
|
|
760
947
|
|
|
@@ -2452,7 +2639,7 @@ body {
|
|
|
2452
2639
|
flex-shrink: 0;
|
|
2453
2640
|
margin-bottom: -9px;
|
|
2454
2641
|
display: flex;
|
|
2455
|
-
padding-inline:
|
|
2642
|
+
padding-inline: 24px;
|
|
2456
2643
|
}
|
|
2457
2644
|
.session-composer > * {
|
|
2458
2645
|
justify-content: flex-start !important;
|
|
@@ -2543,7 +2730,7 @@ body {
|
|
|
2543
2730
|
Designed to be quiet and low-contrast so it guides without distracting. */
|
|
2544
2731
|
.chat-empty-hint {
|
|
2545
2732
|
margin: auto; /* vertical + horizontal centering inside flex column */
|
|
2546
|
-
max-width:
|
|
2733
|
+
max-width: 30rem;
|
|
2547
2734
|
padding: 1.5rem 1rem;
|
|
2548
2735
|
display: flex;
|
|
2549
2736
|
flex-direction: column;
|
|
@@ -2552,47 +2739,43 @@ body {
|
|
|
2552
2739
|
color: var(--color-text-secondary);
|
|
2553
2740
|
user-select: none;
|
|
2554
2741
|
pointer-events: none; /* purely decorative — don't block clicks/scroll */
|
|
2555
|
-
opacity: 0.85;
|
|
2556
2742
|
}
|
|
2557
2743
|
.chat-empty-hint .chat-empty-icon {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2744
|
+
display: flex;
|
|
2745
|
+
align-items: center;
|
|
2746
|
+
justify-content: center;
|
|
2747
|
+
width: 4rem;
|
|
2748
|
+
height: 4rem;
|
|
2749
|
+
margin-bottom: 1.125rem;
|
|
2750
|
+
border-radius: 50%;
|
|
2751
|
+
color: var(--color-accent-primary);
|
|
2752
|
+
background: color-mix(in srgb, var(--color-accent-primary) 10%, transparent);
|
|
2561
2753
|
}
|
|
2562
2754
|
.chat-empty-hint .chat-empty-title {
|
|
2563
2755
|
color: var(--color-text-primary);
|
|
2564
|
-
font-size:
|
|
2565
|
-
font-weight:
|
|
2566
|
-
|
|
2756
|
+
font-size: 1.25rem;
|
|
2757
|
+
font-weight: 500;
|
|
2758
|
+
line-height: 1.3;
|
|
2759
|
+
margin-bottom: 0.5rem;
|
|
2567
2760
|
}
|
|
2568
2761
|
.chat-empty-hint .chat-empty-subtitle {
|
|
2569
|
-
color: var(--color-text-
|
|
2570
|
-
font-size: 0.
|
|
2571
|
-
|
|
2572
|
-
opacity: 0.85;
|
|
2762
|
+
color: var(--color-text-tertiary);
|
|
2763
|
+
font-size: 0.875rem;
|
|
2764
|
+
line-height: 1.5;
|
|
2573
2765
|
}
|
|
2574
2766
|
.chat-empty-hint .chat-empty-tips {
|
|
2575
2767
|
list-style: none;
|
|
2576
2768
|
padding: 0;
|
|
2577
|
-
margin: 0;
|
|
2769
|
+
margin: 1.5rem 0 0;
|
|
2578
2770
|
display: flex;
|
|
2579
2771
|
flex-direction: column;
|
|
2580
|
-
gap: 0.
|
|
2581
|
-
font-size: 0.
|
|
2772
|
+
gap: 0.5rem;
|
|
2773
|
+
font-size: 0.8125rem;
|
|
2582
2774
|
color: var(--color-text-tertiary);
|
|
2583
2775
|
}
|
|
2584
2776
|
.chat-empty-hint .chat-empty-tips li {
|
|
2585
|
-
position: relative;
|
|
2586
|
-
padding-left: 0.875rem;
|
|
2587
2777
|
line-height: 1.5;
|
|
2588
2778
|
}
|
|
2589
|
-
.chat-empty-hint .chat-empty-tips li::before {
|
|
2590
|
-
content: "·";
|
|
2591
|
-
position: absolute;
|
|
2592
|
-
left: 0.25rem;
|
|
2593
|
-
top: -1px;
|
|
2594
|
-
opacity: 0.6;
|
|
2595
|
-
}
|
|
2596
2779
|
|
|
2597
2780
|
/* New message notification banner */
|
|
2598
2781
|
.new-message-banner {
|
|
@@ -3311,7 +3494,7 @@ body {
|
|
|
3311
3494
|
max-height: 18rem;
|
|
3312
3495
|
overflow-y: auto;
|
|
3313
3496
|
}
|
|
3314
|
-
.tool-item-status {
|
|
3497
|
+
.tool-item-status { font-size: 0.6875rem; flex-shrink: 0; }
|
|
3315
3498
|
.tool-item-status.ok { color: var(--color-success); }
|
|
3316
3499
|
.tool-item-status.err { color: var(--color-error); }
|
|
3317
3500
|
.tool-item-status.running { color: var(--live); animation: pulse 1.6s ease-in-out infinite; }
|
|
@@ -3558,7 +3741,7 @@ body {
|
|
|
3558
3741
|
opacity: 0.45;
|
|
3559
3742
|
}
|
|
3560
3743
|
[data-theme="dark"] #sib-id {
|
|
3561
|
-
opacity:
|
|
3744
|
+
opacity: 1;
|
|
3562
3745
|
}
|
|
3563
3746
|
[data-theme="dark"] #sib-dir {
|
|
3564
3747
|
opacity: 0.85;
|
|
@@ -3570,7 +3753,7 @@ body {
|
|
|
3570
3753
|
}
|
|
3571
3754
|
|
|
3572
3755
|
#sib-id {
|
|
3573
|
-
opacity:
|
|
3756
|
+
opacity: 1;
|
|
3574
3757
|
font-size: 0.625rem;
|
|
3575
3758
|
letter-spacing: 0.03em;
|
|
3576
3759
|
flex-shrink: 0;
|
|
@@ -3585,7 +3768,6 @@ body {
|
|
|
3585
3768
|
}
|
|
3586
3769
|
.sib-id-clickable {
|
|
3587
3770
|
cursor: pointer;
|
|
3588
|
-
padding: 1px 0.25rem;
|
|
3589
3771
|
border-radius: 3px;
|
|
3590
3772
|
transition: all 0.15s ease;
|
|
3591
3773
|
}
|
|
@@ -11774,6 +11956,113 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11774
11956
|
.swatch-sunrise-orange { background: #F59E0B; }
|
|
11775
11957
|
.swatch-rose-violet { background: #8B5CF6; }
|
|
11776
11958
|
.swatch-coral-red { background: #EF4444; }
|
|
11959
|
+
.swatch-sky-blue { background: #38BDF8; }
|
|
11960
|
+
.swatch-hot-pink { background: #EC4899; }
|
|
11961
|
+
.swatch-warm-brown { background: #92745A; }
|
|
11962
|
+
.swatch-slate-gray { background: #6B7280; }
|
|
11963
|
+
|
|
11964
|
+
/* ── Background Theme Cards ─────────────────────────────────────────── */
|
|
11965
|
+
.settings-bg-theme-cards {
|
|
11966
|
+
display: flex;
|
|
11967
|
+
flex-wrap: wrap;
|
|
11968
|
+
gap: 0.75rem;
|
|
11969
|
+
padding: 0.25rem 0;
|
|
11970
|
+
}
|
|
11971
|
+
|
|
11972
|
+
.settings-bg-theme-card {
|
|
11973
|
+
display: flex;
|
|
11974
|
+
flex-direction: column;
|
|
11975
|
+
align-items: center;
|
|
11976
|
+
gap: 0.375rem;
|
|
11977
|
+
cursor: pointer;
|
|
11978
|
+
border: none;
|
|
11979
|
+
background: transparent;
|
|
11980
|
+
padding: 0;
|
|
11981
|
+
flex-shrink: 0;
|
|
11982
|
+
}
|
|
11983
|
+
|
|
11984
|
+
/* The thumbnail preview box */
|
|
11985
|
+
.settings-bg-theme-card .bg-card-preview {
|
|
11986
|
+
width: 4.5rem;
|
|
11987
|
+
height: 3rem;
|
|
11988
|
+
border-radius: 0.5rem;
|
|
11989
|
+
border: 2px solid var(--color-border-primary);
|
|
11990
|
+
position: relative;
|
|
11991
|
+
overflow: hidden;
|
|
11992
|
+
transition: border-color 0.15s, transform 0.15s;
|
|
11993
|
+
display: flex;
|
|
11994
|
+
flex-direction: row;
|
|
11995
|
+
}
|
|
11996
|
+
|
|
11997
|
+
.settings-bg-theme-card:hover .bg-card-preview {
|
|
11998
|
+
border-color: var(--color-border-strong);
|
|
11999
|
+
transform: translateY(-1px);
|
|
12000
|
+
}
|
|
12001
|
+
|
|
12002
|
+
.settings-bg-theme-card.active .bg-card-preview {
|
|
12003
|
+
border-color: var(--color-accent-primary);
|
|
12004
|
+
box-shadow: 0 0 0 2px var(--color-accent-soft);
|
|
12005
|
+
}
|
|
12006
|
+
|
|
12007
|
+
/* Active checkmark badge */
|
|
12008
|
+
.settings-bg-theme-card.active .bg-card-preview::after {
|
|
12009
|
+
content: "✓";
|
|
12010
|
+
position: absolute;
|
|
12011
|
+
top: 0.2rem;
|
|
12012
|
+
right: 0.3rem;
|
|
12013
|
+
font-size: 0.625rem;
|
|
12014
|
+
font-weight: 700;
|
|
12015
|
+
color: white;
|
|
12016
|
+
background: var(--color-accent-primary);
|
|
12017
|
+
border-radius: 50%;
|
|
12018
|
+
width: 0.875rem;
|
|
12019
|
+
height: 0.875rem;
|
|
12020
|
+
display: flex;
|
|
12021
|
+
align-items: center;
|
|
12022
|
+
justify-content: center;
|
|
12023
|
+
line-height: 1;
|
|
12024
|
+
}
|
|
12025
|
+
|
|
12026
|
+
/* Sidebar strip (left 35%) */
|
|
12027
|
+
.settings-bg-theme-card .bg-card-sidebar {
|
|
12028
|
+
width: 35%;
|
|
12029
|
+
height: 100%;
|
|
12030
|
+
border-radius: 0;
|
|
12031
|
+
}
|
|
12032
|
+
|
|
12033
|
+
/* Main area (right 65%) */
|
|
12034
|
+
.settings-bg-theme-card .bg-card-main {
|
|
12035
|
+
width: 65%;
|
|
12036
|
+
height: 100%;
|
|
12037
|
+
}
|
|
12038
|
+
|
|
12039
|
+
/* Per-theme colors */
|
|
12040
|
+
.bg-theme-light .bg-card-sidebar { background: #f5f5f3; }
|
|
12041
|
+
.bg-theme-light .bg-card-main { background: #ffffff; }
|
|
12042
|
+
|
|
12043
|
+
.bg-theme-dark .bg-card-sidebar { background: #101114; }
|
|
12044
|
+
.bg-theme-dark .bg-card-main { background: #16181d; }
|
|
12045
|
+
|
|
12046
|
+
.bg-theme-dim .bg-card-sidebar { background: #21262d; }
|
|
12047
|
+
.bg-theme-dim .bg-card-main { background: #2b3038; }
|
|
12048
|
+
|
|
12049
|
+
.bg-theme-warm .bg-card-sidebar { background: #f5f0e8; }
|
|
12050
|
+
.bg-theme-warm .bg-card-main { background: #fdf8f0; }
|
|
12051
|
+
|
|
12052
|
+
.bg-theme-black .bg-card-sidebar { background: #1e2228; }
|
|
12053
|
+
.bg-theme-black .bg-card-main { background: #252b33; }
|
|
12054
|
+
|
|
12055
|
+
/* Label under card */
|
|
12056
|
+
.settings-bg-theme-card .bg-card-label {
|
|
12057
|
+
font-size: 0.6875rem;
|
|
12058
|
+
color: var(--color-text-secondary);
|
|
12059
|
+
white-space: nowrap;
|
|
12060
|
+
}
|
|
12061
|
+
|
|
12062
|
+
.settings-bg-theme-card.active .bg-card-label {
|
|
12063
|
+
color: var(--color-accent-primary);
|
|
12064
|
+
font-weight: 600;
|
|
12065
|
+
}
|
|
11777
12066
|
|
|
11778
12067
|
/* ── Sessions List ───────────────────────────────────────────────────── */
|
|
11779
12068
|
.billing-sessions-row {
|
data/lib/clacky/web/app.js
CHANGED
|
@@ -436,7 +436,34 @@ const Modal = (() => {
|
|
|
436
436
|
});
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
/**
|
|
439
|
+
/**
|
|
440
|
+
* Like confirm(), but shows an extra opt-in checkbox (unchecked by default).
|
|
441
|
+
* Resolves { ok, checked } so the caller can act on both the decision and
|
|
442
|
+
* the checkbox state.
|
|
443
|
+
*/
|
|
444
|
+
function confirmWithCheckbox(message, checkboxLabel) {
|
|
445
|
+
return new Promise(resolve => {
|
|
446
|
+
const overlay = $("modal-overlay");
|
|
447
|
+
$("modal-message").textContent = message;
|
|
448
|
+
$("modal-skip-text").textContent = checkboxLabel;
|
|
449
|
+
$("modal-skip-cb").checked = false;
|
|
450
|
+
$("modal-skip-label").style.display = "flex";
|
|
451
|
+
if (overlay.parentNode !== document.body || overlay.nextSibling) {
|
|
452
|
+
document.body.appendChild(overlay);
|
|
453
|
+
}
|
|
454
|
+
overlay.style.display = "flex";
|
|
455
|
+
|
|
456
|
+
const cleanup = (ok) => {
|
|
457
|
+
overlay.style.display = "none";
|
|
458
|
+
$("modal-skip-label").style.display = "none";
|
|
459
|
+
$("modal-yes").onclick = null;
|
|
460
|
+
$("modal-no").onclick = null;
|
|
461
|
+
resolve({ ok, checked: ok && $("modal-skip-cb").checked });
|
|
462
|
+
};
|
|
463
|
+
$("modal-yes").onclick = () => cleanup(true);
|
|
464
|
+
$("modal-no").onclick = () => cleanup(false);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
440
467
|
function prompt(message, defaultValue = "") {
|
|
441
468
|
return new Promise(resolve => {
|
|
442
469
|
const overlay = $("prompt-modal-overlay");
|
|
@@ -530,7 +557,7 @@ const Modal = (() => {
|
|
|
530
557
|
});
|
|
531
558
|
}
|
|
532
559
|
|
|
533
|
-
return { confirm, confirmOnce, prompt, rename };
|
|
560
|
+
return { confirm, confirmOnce, confirmWithCheckbox, prompt, rename };
|
|
534
561
|
})();
|
|
535
562
|
|
|
536
563
|
Clacky.Modal = Modal;
|
|
@@ -714,10 +741,6 @@ window.mobileCloseSidebar = _mobileCloseSidebar;
|
|
|
714
741
|
|
|
715
742
|
// ── Theme / session-scoped message panel bindings ──────────────────────────
|
|
716
743
|
|
|
717
|
-
// Theme toggle in header
|
|
718
|
-
if ($("theme-toggle-header")) {
|
|
719
|
-
$("theme-toggle-header").addEventListener("click", () => Theme.toggle());
|
|
720
|
-
}
|
|
721
744
|
// btn-delete-session, #messages scroll-to-top (load history), and btn-interrupt
|
|
722
745
|
// moved to sessions.js (_initMessageHistory in Sessions.init()).
|
|
723
746
|
|
|
@@ -162,16 +162,20 @@ const Onboard = (() => {
|
|
|
162
162
|
dropdown.appendChild(placeholder);
|
|
163
163
|
|
|
164
164
|
_providers.forEach(p => {
|
|
165
|
+
// Prefer i18n key (localised); fall back to shipped English `name`.
|
|
166
|
+
const translated = p.name_key ? I18n.t(p.name_key) : null;
|
|
167
|
+
const displayName = (translated && translated !== p.name_key) ? translated : p.name;
|
|
168
|
+
|
|
165
169
|
const opt = document.createElement("div");
|
|
166
170
|
opt.className = "custom-select-option";
|
|
167
171
|
opt.dataset.value = p.id;
|
|
168
|
-
opt.dataset.label =
|
|
172
|
+
opt.dataset.label = displayName;
|
|
169
173
|
if (p.id === "openclacky") {
|
|
170
174
|
const nameSpan = document.createElement("span");
|
|
171
|
-
nameSpan.textContent =
|
|
175
|
+
nameSpan.textContent = displayName;
|
|
172
176
|
opt.innerHTML = nameSpan.outerHTML + ` <span class="provider-badge-recommended">${I18n.t("provider.recommended")}</span>`;
|
|
173
177
|
} else {
|
|
174
|
-
opt.textContent =
|
|
178
|
+
opt.textContent = displayName;
|
|
175
179
|
}
|
|
176
180
|
dropdown.appendChild(opt);
|
|
177
181
|
});
|
|
@@ -24,6 +24,7 @@ const ExtensionsStore = (() => {
|
|
|
24
24
|
let _query = ""; // current search text
|
|
25
25
|
let _sort = "downloads"; // "newest" | "updated" | "downloads"
|
|
26
26
|
let _filterInstalled = false; // when true, show only installed extensions
|
|
27
|
+
let _filterBrand = false; // when true, show only brand-private extensions
|
|
27
28
|
let _loading = false;
|
|
28
29
|
let _error = null; // soft warning when the store is unreachable
|
|
29
30
|
let _detail = null; // currently opened extension detail, or null
|
|
@@ -53,6 +54,7 @@ const ExtensionsStore = (() => {
|
|
|
53
54
|
get query() { return _query; },
|
|
54
55
|
get sort() { return _sort; },
|
|
55
56
|
get filterInstalled() { return _filterInstalled; },
|
|
57
|
+
get filterBrand() { return _filterBrand; },
|
|
56
58
|
get loading() { return _loading; },
|
|
57
59
|
get error() { return _error; },
|
|
58
60
|
get detail() { return _detail; },
|
|
@@ -67,6 +69,7 @@ const ExtensionsStore = (() => {
|
|
|
67
69
|
/** Fetch the catalog from the server for the current query + sort. */
|
|
68
70
|
async load() {
|
|
69
71
|
if (_filterInstalled) return;
|
|
72
|
+
if (_filterBrand) return Extensions.loadBrandExtensions();
|
|
70
73
|
_loading = true;
|
|
71
74
|
_error = null;
|
|
72
75
|
_emit("extensions:loading");
|
|
@@ -97,18 +100,22 @@ const ExtensionsStore = (() => {
|
|
|
97
100
|
setQuery(query) {
|
|
98
101
|
_query = (query || "").trim();
|
|
99
102
|
if (_filterInstalled) return Extensions.setFilterInstalled(true);
|
|
103
|
+
if (_filterBrand) return Extensions.loadBrandExtensions();
|
|
100
104
|
return Extensions.load();
|
|
101
105
|
},
|
|
102
106
|
|
|
103
107
|
/** Set the sort order and reload. */
|
|
104
108
|
setSort(sort) {
|
|
105
109
|
_sort = sort || "downloads";
|
|
110
|
+
if (_filterBrand) return Extensions.loadBrandExtensions();
|
|
111
|
+
if (_filterInstalled) return Extensions.setFilterInstalled(true);
|
|
106
112
|
return Extensions.load();
|
|
107
113
|
},
|
|
108
114
|
|
|
109
115
|
/** Toggle the "installed only" filter — fetches from local store when enabled. */
|
|
110
116
|
async setFilterInstalled(onlyInstalled) {
|
|
111
117
|
_filterInstalled = !!onlyInstalled;
|
|
118
|
+
_filterBrand = false;
|
|
112
119
|
if (_filterInstalled) {
|
|
113
120
|
_loading = true;
|
|
114
121
|
_emit("extensions:loading");
|
|
@@ -132,6 +139,40 @@ const ExtensionsStore = (() => {
|
|
|
132
139
|
}
|
|
133
140
|
},
|
|
134
141
|
|
|
142
|
+
/** Toggle the "brand only" filter — fetches brand-private extensions. */
|
|
143
|
+
async setFilterBrand(onlyBrand) {
|
|
144
|
+
_filterBrand = !!onlyBrand;
|
|
145
|
+
_filterInstalled = false;
|
|
146
|
+
if (_filterBrand) {
|
|
147
|
+
return Extensions.loadBrandExtensions();
|
|
148
|
+
} else {
|
|
149
|
+
return Extensions.load();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/** Fetch brand-private extensions from /api/store/extensions/brand. */
|
|
154
|
+
async loadBrandExtensions() {
|
|
155
|
+
_loading = true;
|
|
156
|
+
_error = null;
|
|
157
|
+
_emit("extensions:loading");
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetch("/api/store/extensions/brand");
|
|
160
|
+
const data = await res.json();
|
|
161
|
+
_extensions = data.extensions || [];
|
|
162
|
+
_error = data.warning || null;
|
|
163
|
+
_loading = false;
|
|
164
|
+
_emit("extensions:changed", { extensions: _extensions, warning: _error });
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error("[Extensions] loadBrandExtensions failed", e);
|
|
167
|
+
_extensions = [];
|
|
168
|
+
_error = I18n.t("extensions.loadFailed");
|
|
169
|
+
_loading = false;
|
|
170
|
+
_emit("extensions:error", { network: true });
|
|
171
|
+
} finally {
|
|
172
|
+
_loading = false;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
135
176
|
/** Open the detail view for one extension (fetches contributes + versions). */
|
|
136
177
|
async loadDetail(id) {
|
|
137
178
|
if (!id) return;
|
|
@@ -159,6 +200,17 @@ const ExtensionsStore = (() => {
|
|
|
159
200
|
}
|
|
160
201
|
},
|
|
161
202
|
|
|
203
|
+
/** Fetch /api/brand/status and return { branded: bool }. */
|
|
204
|
+
async fetchBrandStatus() {
|
|
205
|
+
try {
|
|
206
|
+
const res = await fetch("/api/brand/status");
|
|
207
|
+
const data = await res.json();
|
|
208
|
+
return data;
|
|
209
|
+
} catch (_e) {
|
|
210
|
+
return { branded: false };
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
162
214
|
/** Close the detail view. */
|
|
163
215
|
closeDetail() {
|
|
164
216
|
_detail = null;
|
|
@@ -218,13 +270,13 @@ const ExtensionsStore = (() => {
|
|
|
218
270
|
},
|
|
219
271
|
|
|
220
272
|
/** Remove an installed extension, then return to the list. */
|
|
221
|
-
async uninstall(id) {
|
|
273
|
+
async uninstall(id, purgeData = false) {
|
|
222
274
|
if (!id) return;
|
|
223
275
|
try {
|
|
224
276
|
const res = await fetch("/api/store/extension", {
|
|
225
277
|
method: "DELETE",
|
|
226
278
|
headers: { "Content-Type": "application/json" },
|
|
227
|
-
body: JSON.stringify({ id }),
|
|
279
|
+
body: JSON.stringify({ id, purge_data: purgeData }),
|
|
228
280
|
});
|
|
229
281
|
const data = await res.json();
|
|
230
282
|
if (!res.ok || !data.ok) throw new Error(data.error || "uninstall failed");
|