openclacky 1.3.4 → 1.3.6
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 +39 -0
- data/lib/clacky/agent/fake_tool_call_detector.rb +52 -0
- data/lib/clacky/agent/message_compressor.rb +32 -8
- data/lib/clacky/agent/message_compressor_helper.rb +113 -12
- data/lib/clacky/agent/session_serializer.rb +3 -2
- data/lib/clacky/agent/tool_executor.rb +0 -12
- data/lib/clacky/agent.rb +74 -12
- data/lib/clacky/api_extension.rb +81 -0
- data/lib/clacky/api_extension_loader.rb +13 -1
- data/lib/clacky/cli.rb +0 -1
- data/lib/clacky/client.rb +14 -17
- data/lib/clacky/default_agents/_panels/time_machine/panel.js +22 -0
- data/lib/clacky/default_agents/base_prompt.md +1 -0
- data/lib/clacky/default_extensions/meeting/handler.rb +331 -0
- data/lib/clacky/default_extensions/meeting/meeting.js +790 -0
- data/lib/clacky/default_extensions/meeting/meta.yml +3 -0
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +44 -0
- data/lib/clacky/default_skills/media-gen/SKILL.md +63 -0
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +114 -0
- data/lib/clacky/json_ui_controller.rb +1 -1
- data/lib/clacky/media/base.rb +60 -0
- data/lib/clacky/media/dashscope.rb +385 -21
- data/lib/clacky/media/gemini.rb +9 -0
- data/lib/clacky/media/generator.rb +52 -0
- data/lib/clacky/media/openai_compat.rb +166 -0
- data/lib/clacky/null_ui_controller.rb +13 -0
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/providers.rb +50 -2
- data/lib/clacky/rich_ui/rich_ui_controller.rb +1 -1
- data/lib/clacky/server/channel/channel_ui_controller.rb +1 -1
- data/lib/clacky/server/http_server.rb +260 -122
- data/lib/clacky/server/session_registry.rb +54 -3
- data/lib/clacky/server/web_ui_controller.rb +3 -2
- data/lib/clacky/session_manager.rb +35 -4
- data/lib/clacky/skill_loader.rb +14 -2
- data/lib/clacky/tools/terminal/output_cleaner.rb +1 -3
- data/lib/clacky/tools/terminal.rb +0 -43
- data/lib/clacky/ui2/components/modal_component.rb +1 -1
- data/lib/clacky/ui2/layout_manager.rb +0 -5
- data/lib/clacky/ui2/progress_handle.rb +0 -3
- data/lib/clacky/ui2/ui_controller.rb +140 -40
- data/lib/clacky/ui_interface.rb +10 -1
- data/lib/clacky/utils/encoding.rb +25 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +199 -23
- data/lib/clacky/web/components/onboard.js +1 -14
- data/lib/clacky/web/components/sidebar.js +1 -3
- data/lib/clacky/web/features/backup/store.js +23 -0
- data/lib/clacky/web/features/backup/view.js +49 -22
- data/lib/clacky/web/features/brand/view.js +8 -5
- data/lib/clacky/web/features/channels/store.js +1 -20
- data/lib/clacky/web/features/mcp/store.js +1 -20
- data/lib/clacky/web/features/profile/store.js +1 -13
- data/lib/clacky/web/features/profile/view.js +16 -4
- data/lib/clacky/web/features/skills/store.js +6 -21
- data/lib/clacky/web/features/tasks/view.js +77 -28
- data/lib/clacky/web/features/version/store.js +2 -0
- data/lib/clacky/web/i18n.js +46 -3
- data/lib/clacky/web/index.html +67 -26
- data/lib/clacky/web/sessions.js +177 -87
- data/lib/clacky/web/settings.js +34 -2
- data/lib/clacky/web/ws-dispatcher.js +12 -4
- data/lib/clacky.rb +12 -5
- metadata +8 -1
data/lib/clacky/web/app.css
CHANGED
|
@@ -650,38 +650,38 @@ body {
|
|
|
650
650
|
position: relative;
|
|
651
651
|
display: flex;
|
|
652
652
|
align-items: center;
|
|
653
|
-
border-radius:
|
|
653
|
+
border-radius: 6px;
|
|
654
654
|
overflow: visible;
|
|
655
655
|
}
|
|
656
656
|
.btn-split-main {
|
|
657
|
-
height: 1.
|
|
658
|
-
padding: 0 0.
|
|
657
|
+
height: 1.5rem;
|
|
658
|
+
padding: 0 0.625rem;
|
|
659
659
|
border: none;
|
|
660
|
-
border-radius:
|
|
660
|
+
border-radius: 6px 0 0 6px;
|
|
661
661
|
background: var(--color-accent-primary);
|
|
662
662
|
color: #fff;
|
|
663
|
-
font-size: 0.
|
|
663
|
+
font-size: 0.75rem;
|
|
664
664
|
font-weight: 500;
|
|
665
665
|
white-space: nowrap;
|
|
666
666
|
line-height: 1;
|
|
667
667
|
cursor: pointer;
|
|
668
|
-
transition: background 0.15s,
|
|
668
|
+
transition: background 0.15s, box-shadow 0.15s;
|
|
669
669
|
}
|
|
670
670
|
.btn-split-main:hover {
|
|
671
671
|
background: var(--color-button-primary-hover);
|
|
672
|
-
box-shadow: 0 2px
|
|
672
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
|
|
673
673
|
}
|
|
674
674
|
.btn-split-arrow {
|
|
675
|
-
height: 1.
|
|
676
|
-
padding: 0 0.
|
|
675
|
+
height: 1.5rem;
|
|
676
|
+
padding: 0 0.35rem;
|
|
677
677
|
border: none;
|
|
678
678
|
border-left: 1px solid rgba(255,255,255,0.3);
|
|
679
|
-
border-radius: 0
|
|
679
|
+
border-radius: 0 6px 6px 0;
|
|
680
680
|
background: var(--color-accent-primary);
|
|
681
681
|
color: #fff;
|
|
682
|
-
font-size: 0.
|
|
682
|
+
font-size: 0.75rem;
|
|
683
683
|
cursor: pointer;
|
|
684
|
-
transition: background 0.15s
|
|
684
|
+
transition: background 0.15s;
|
|
685
685
|
}
|
|
686
686
|
.btn-split-arrow:hover {
|
|
687
687
|
background: var(--color-button-primary-hover);
|
|
@@ -690,7 +690,7 @@ body {
|
|
|
690
690
|
/* ── Dropdown menu ───────────────────────────────────────────────────────── */
|
|
691
691
|
.new-session-dropdown {
|
|
692
692
|
position: absolute;
|
|
693
|
-
top:
|
|
693
|
+
top: 100%;
|
|
694
694
|
right: 0;
|
|
695
695
|
min-width: 11.25rem;
|
|
696
696
|
background: var(--color-bg-card);
|
|
@@ -698,13 +698,13 @@ body {
|
|
|
698
698
|
border-radius: 7px;
|
|
699
699
|
box-shadow: var(--shadow-md);
|
|
700
700
|
z-index: 200;
|
|
701
|
-
padding: 0.
|
|
701
|
+
padding: 0.375rem 0 0.25rem;
|
|
702
702
|
overflow: hidden;
|
|
703
703
|
}
|
|
704
704
|
.new-session-dropdown[hidden] { display: none; }
|
|
705
705
|
.dropdown-item {
|
|
706
|
-
padding: 0.
|
|
707
|
-
font-size: 0.
|
|
706
|
+
padding: 0.4rem 0.875rem;
|
|
707
|
+
font-size: 0.8125rem;
|
|
708
708
|
color: var(--color-text-secondary);
|
|
709
709
|
cursor: pointer;
|
|
710
710
|
white-space: nowrap;
|
|
@@ -1799,7 +1799,7 @@ body {
|
|
|
1799
1799
|
white-space: nowrap;
|
|
1800
1800
|
transition: background .12s;
|
|
1801
1801
|
}
|
|
1802
|
-
.task-run-btn:hover { background: var(--color-
|
|
1802
|
+
.task-run-btn:hover { background: var(--color-button-primary-hover); }
|
|
1803
1803
|
.task-action-btn {
|
|
1804
1804
|
display: inline-flex;
|
|
1805
1805
|
align-items: center;
|
|
@@ -2456,6 +2456,36 @@ body {
|
|
|
2456
2456
|
.msg-copy-btn { opacity: 0.65; }
|
|
2457
2457
|
}
|
|
2458
2458
|
|
|
2459
|
+
/* ── Inline "start" button on unchecked task-list items ──────────────────── */
|
|
2460
|
+
.msg-assistant li.task-list-item { position: relative; }
|
|
2461
|
+
.msg-todo-spawn {
|
|
2462
|
+
display: inline-flex;
|
|
2463
|
+
align-items: center;
|
|
2464
|
+
margin-left: 0.5rem;
|
|
2465
|
+
padding: 0.05rem 0.5rem;
|
|
2466
|
+
font-size: 0.75rem;
|
|
2467
|
+
line-height: 1.4;
|
|
2468
|
+
border: 1px solid var(--color-border-primary);
|
|
2469
|
+
border-radius: 5px;
|
|
2470
|
+
background: var(--color-bg-primary);
|
|
2471
|
+
color: var(--color-text-secondary);
|
|
2472
|
+
cursor: pointer;
|
|
2473
|
+
opacity: 0;
|
|
2474
|
+
vertical-align: middle;
|
|
2475
|
+
transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
|
2476
|
+
}
|
|
2477
|
+
.msg-assistant:hover .msg-todo-spawn,
|
|
2478
|
+
.msg-todo-spawn:focus-visible { opacity: 1; }
|
|
2479
|
+
.msg-todo-spawn:hover {
|
|
2480
|
+
color: var(--color-text-primary);
|
|
2481
|
+
background: var(--color-bg-tertiary);
|
|
2482
|
+
border-color: var(--color-border-secondary);
|
|
2483
|
+
}
|
|
2484
|
+
.msg-todo-spawn:disabled { opacity: 0.5; cursor: default; }
|
|
2485
|
+
@media (hover: none) {
|
|
2486
|
+
.msg-todo-spawn { opacity: 0.7; }
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2459
2489
|
/* ── Markdown rendering inside assistant messages ────────────────────────── */
|
|
2460
2490
|
.msg-assistant p:last-child { margin-bottom: 0; }
|
|
2461
2491
|
.msg-assistant h1, .msg-assistant h2, .msg-assistant h3,
|
|
@@ -2690,7 +2720,7 @@ body {
|
|
|
2690
2720
|
align-self: flex-start;
|
|
2691
2721
|
display: flex;
|
|
2692
2722
|
flex-direction: column;
|
|
2693
|
-
gap: 0.
|
|
2723
|
+
gap: 0.5rem;
|
|
2694
2724
|
}
|
|
2695
2725
|
.msg-error .retry-btn {
|
|
2696
2726
|
align-self: flex-start;
|
|
@@ -2721,6 +2751,56 @@ body {
|
|
|
2721
2751
|
.msg-error a:hover {
|
|
2722
2752
|
opacity: 0.8;
|
|
2723
2753
|
}
|
|
2754
|
+
.error-raw-detail {
|
|
2755
|
+
margin-top: -0.125rem;
|
|
2756
|
+
font-size: 0.75rem;
|
|
2757
|
+
align-self: flex-start;
|
|
2758
|
+
max-width: 100%;
|
|
2759
|
+
}
|
|
2760
|
+
.error-raw-detail summary {
|
|
2761
|
+
cursor: pointer;
|
|
2762
|
+
font-size: 0.7rem;
|
|
2763
|
+
font-weight: 500;
|
|
2764
|
+
user-select: none;
|
|
2765
|
+
color: var(--color-error);
|
|
2766
|
+
opacity: 0.6;
|
|
2767
|
+
list-style: none;
|
|
2768
|
+
display: inline-flex;
|
|
2769
|
+
align-items: center;
|
|
2770
|
+
gap: 0.25rem;
|
|
2771
|
+
transition: opacity 0.15s;
|
|
2772
|
+
}
|
|
2773
|
+
.error-raw-detail summary::-webkit-details-marker { display: none; }
|
|
2774
|
+
.error-raw-detail summary::before {
|
|
2775
|
+
content: "";
|
|
2776
|
+
display: inline-block;
|
|
2777
|
+
width: 0;
|
|
2778
|
+
height: 0;
|
|
2779
|
+
border-top: 4px solid transparent;
|
|
2780
|
+
border-bottom: 4px solid transparent;
|
|
2781
|
+
border-left: 5px solid currentColor;
|
|
2782
|
+
transition: transform 0.15s;
|
|
2783
|
+
flex-shrink: 0;
|
|
2784
|
+
}
|
|
2785
|
+
.error-raw-detail[open] summary::before {
|
|
2786
|
+
transform: rotate(90deg);
|
|
2787
|
+
}
|
|
2788
|
+
.error-raw-detail summary:hover {
|
|
2789
|
+
opacity: 0.9;
|
|
2790
|
+
}
|
|
2791
|
+
.error-raw-detail pre {
|
|
2792
|
+
margin: 0.375rem 0 0;
|
|
2793
|
+
padding: 0.5rem 0.625rem;
|
|
2794
|
+
background: rgba(255, 59, 48, 0.05);
|
|
2795
|
+
border: 1px solid rgba(255, 59, 48, 0.12);
|
|
2796
|
+
border-radius: 5px;
|
|
2797
|
+
font-family: var(--font-mono, monospace);
|
|
2798
|
+
font-size: 0.7rem;
|
|
2799
|
+
line-height: 1.5;
|
|
2800
|
+
white-space: pre-wrap;
|
|
2801
|
+
word-break: break-all;
|
|
2802
|
+
color: var(--color-text-secondary);
|
|
2803
|
+
}
|
|
2724
2804
|
.msg-success { color: var(--color-success); align-self: flex-start; font-size: 0.8125rem; }
|
|
2725
2805
|
.tool-name { color: var(--color-warning); font-weight: 600; }
|
|
2726
2806
|
.progress-msg { color: var(--color-accent-primary); font-size: 0.75rem; align-self: center; }
|
|
@@ -4406,6 +4486,7 @@ body {
|
|
|
4406
4486
|
border-radius: 10px;
|
|
4407
4487
|
padding: 0.625rem 0.875rem;
|
|
4408
4488
|
background: var(--color-bg-secondary);
|
|
4489
|
+
margin-bottom: 0.625rem;
|
|
4409
4490
|
}
|
|
4410
4491
|
.backup-auto-card .backup-auto-row {
|
|
4411
4492
|
margin: 0.25rem 0;
|
|
@@ -4437,6 +4518,43 @@ body {
|
|
|
4437
4518
|
font-size: 0.75rem;
|
|
4438
4519
|
color: var(--color-text-secondary);
|
|
4439
4520
|
}
|
|
4521
|
+
.backup-dest-row {
|
|
4522
|
+
display: flex;
|
|
4523
|
+
align-items: center;
|
|
4524
|
+
gap: 0.5rem;
|
|
4525
|
+
margin: 0.625rem 0 0.25rem;
|
|
4526
|
+
padding-top: 0.625rem;
|
|
4527
|
+
border-top: 1px solid var(--color-border-primary);
|
|
4528
|
+
}
|
|
4529
|
+
.backup-dest-label {
|
|
4530
|
+
font-size: 0.78125rem;
|
|
4531
|
+
color: var(--color-text-secondary);
|
|
4532
|
+
white-space: nowrap;
|
|
4533
|
+
flex-shrink: 0;
|
|
4534
|
+
}
|
|
4535
|
+
.backup-dest-path {
|
|
4536
|
+
font-size: 0.78125rem;
|
|
4537
|
+
color: var(--color-text-primary);
|
|
4538
|
+
font-family: var(--font-mono, monospace);
|
|
4539
|
+
overflow: hidden;
|
|
4540
|
+
text-overflow: ellipsis;
|
|
4541
|
+
white-space: nowrap;
|
|
4542
|
+
}
|
|
4543
|
+
.btn-settings-link {
|
|
4544
|
+
flex-shrink: 0;
|
|
4545
|
+
background: transparent;
|
|
4546
|
+
border: 1px solid var(--color-border-primary);
|
|
4547
|
+
border-radius: 6px;
|
|
4548
|
+
color: var(--color-accent-primary);
|
|
4549
|
+
font-size: 0.75rem;
|
|
4550
|
+
padding: 0.25rem 0.625rem;
|
|
4551
|
+
cursor: pointer;
|
|
4552
|
+
white-space: nowrap;
|
|
4553
|
+
}
|
|
4554
|
+
.btn-settings-link:hover {
|
|
4555
|
+
background: var(--color-bg-hover);
|
|
4556
|
+
border-color: var(--color-accent-primary);
|
|
4557
|
+
}
|
|
4440
4558
|
.backup-actions {
|
|
4441
4559
|
display: flex;
|
|
4442
4560
|
align-items: center;
|
|
@@ -4444,6 +4562,21 @@ body {
|
|
|
4444
4562
|
flex-wrap: wrap;
|
|
4445
4563
|
}
|
|
4446
4564
|
|
|
4565
|
+
.backup-config-row {
|
|
4566
|
+
margin-top: 0.5rem;
|
|
4567
|
+
padding-top: 0.75rem;
|
|
4568
|
+
border-top: 1px solid var(--color-border);
|
|
4569
|
+
}
|
|
4570
|
+
.btn-settings-action--file {
|
|
4571
|
+
cursor: pointer;
|
|
4572
|
+
}
|
|
4573
|
+
.backup-actions-sep {
|
|
4574
|
+
width: 1px;
|
|
4575
|
+
height: 1.25rem;
|
|
4576
|
+
background: var(--color-border);
|
|
4577
|
+
flex-shrink: 0;
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4447
4580
|
.settings-network-url {
|
|
4448
4581
|
display: flex;
|
|
4449
4582
|
flex-direction: column;
|
|
@@ -6929,10 +7062,10 @@ body {
|
|
|
6929
7062
|
background: var(--color-bg-hover);
|
|
6930
7063
|
}
|
|
6931
7064
|
.code-editor-body .cm-activeLine {
|
|
6932
|
-
background: var(--color-bg-hover);
|
|
7065
|
+
background: color-mix(in srgb, var(--color-bg-hover) 60%, transparent);
|
|
6933
7066
|
}
|
|
6934
7067
|
.code-editor-body .cm-selectionBackground {
|
|
6935
|
-
background: color-mix(in srgb, var(--color-accent-primary)
|
|
7068
|
+
background: color-mix(in srgb, var(--color-accent-primary) 40%, transparent) !important;
|
|
6936
7069
|
}
|
|
6937
7070
|
.code-editor-footer {
|
|
6938
7071
|
display: flex;
|
|
@@ -7468,7 +7601,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
7468
7601
|
cursor: pointer;
|
|
7469
7602
|
transition: background .15s, opacity .15s;
|
|
7470
7603
|
}
|
|
7471
|
-
.setup-submit-btn:hover { background:
|
|
7604
|
+
.setup-submit-btn:hover { background: var(--color-button-primary-hover); }
|
|
7472
7605
|
.setup-submit-btn:disabled { opacity: .5; cursor: default; }
|
|
7473
7606
|
|
|
7474
7607
|
/* ── Onboard panel ───────────────────────────────────────────────────────── */
|
|
@@ -10046,7 +10179,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10046
10179
|
border-right: 1px solid var(--color-border-primary);
|
|
10047
10180
|
}
|
|
10048
10181
|
.billing-period-btn:hover {
|
|
10049
|
-
background: var(--color-bg-
|
|
10182
|
+
background: var(--color-bg-hover);
|
|
10050
10183
|
color: var(--color-text-primary);
|
|
10051
10184
|
}
|
|
10052
10185
|
.billing-period-btn.active {
|
|
@@ -10906,7 +11039,7 @@ body.setup-mode[data-theme="dark"] {
|
|
|
10906
11039
|
white-space: nowrap;
|
|
10907
11040
|
transition: opacity 0.15s ease, transform 0.05s ease;
|
|
10908
11041
|
}
|
|
10909
|
-
.btn-mcp-cta:hover {
|
|
11042
|
+
.btn-mcp-cta:hover { background: var(--color-button-primary-hover) }
|
|
10910
11043
|
.btn-mcp-cta:active { transform: scale(0.98); }
|
|
10911
11044
|
.btn-mcp-cta-large {
|
|
10912
11045
|
padding: 0.625rem 1.125rem;
|
|
@@ -11019,6 +11152,49 @@ body.setup-mode[data-theme="dark"] {
|
|
|
11019
11152
|
color: var(--color-error, #d33);
|
|
11020
11153
|
}
|
|
11021
11154
|
|
|
11155
|
+
/* ── Accent Color Swatches ───────────────────────────────────────────── */
|
|
11156
|
+
.settings-accent-swatches {
|
|
11157
|
+
display: flex;
|
|
11158
|
+
flex-wrap: wrap;
|
|
11159
|
+
gap: 0.5rem;
|
|
11160
|
+
padding: 0.25rem 0;
|
|
11161
|
+
}
|
|
11162
|
+
|
|
11163
|
+
.settings-accent-swatch {
|
|
11164
|
+
width: 2.25rem;
|
|
11165
|
+
height: 2.25rem;
|
|
11166
|
+
border-radius: 50%;
|
|
11167
|
+
border: none;
|
|
11168
|
+
cursor: pointer;
|
|
11169
|
+
padding: 0;
|
|
11170
|
+
transition: transform 0.15s;
|
|
11171
|
+
outline: none;
|
|
11172
|
+
position: relative;
|
|
11173
|
+
}
|
|
11174
|
+
|
|
11175
|
+
.settings-accent-swatch:hover {
|
|
11176
|
+
transform: scale(1.15);
|
|
11177
|
+
}
|
|
11178
|
+
|
|
11179
|
+
.settings-accent-swatch.active::after {
|
|
11180
|
+
content: "";
|
|
11181
|
+
position: absolute;
|
|
11182
|
+
width: 0.375rem;
|
|
11183
|
+
height: 0.625rem;
|
|
11184
|
+
border-right: 0.125rem solid white;
|
|
11185
|
+
border-bottom: 0.125rem solid white;
|
|
11186
|
+
top: 46%;
|
|
11187
|
+
left: 50%;
|
|
11188
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
11189
|
+
}
|
|
11190
|
+
|
|
11191
|
+
.swatch-indigo { background: #4f46e5; }
|
|
11192
|
+
.swatch-aurora-blue { background: #3B82F6; }
|
|
11193
|
+
.swatch-forest-green { background: #10B981; }
|
|
11194
|
+
.swatch-sunrise-orange { background: #F59E0B; }
|
|
11195
|
+
.swatch-rose-violet { background: #8B5CF6; }
|
|
11196
|
+
.swatch-coral-red { background: #EF4444; }
|
|
11197
|
+
|
|
11022
11198
|
/* ── Sessions List ───────────────────────────────────────────────────── */
|
|
11023
11199
|
.billing-sessions-row {
|
|
11024
11200
|
margin-top: 1rem;
|
|
@@ -575,20 +575,7 @@ const Onboard = (() => {
|
|
|
575
575
|
async function _launchOnboardSession() {
|
|
576
576
|
try {
|
|
577
577
|
await _complete();
|
|
578
|
-
|
|
579
|
-
method: "POST",
|
|
580
|
-
headers: { "Content-Type": "application/json" },
|
|
581
|
-
body: JSON.stringify({ name: "Onboard", source: "setup" })
|
|
582
|
-
});
|
|
583
|
-
const data = await res.json();
|
|
584
|
-
const session = data.session;
|
|
585
|
-
if (!session) throw new Error("No session returned");
|
|
586
|
-
|
|
587
|
-
Sessions.add(session);
|
|
588
|
-
Sessions.renderList();
|
|
589
|
-
Sessions.setPendingMessage(session.id, `/onboard lang:${_selectedLang}`);
|
|
590
|
-
Sessions.select(session.id);
|
|
591
|
-
|
|
578
|
+
await Sessions.startWith(`/onboard lang:${_selectedLang}`, { name: "Onboard", source: "setup" });
|
|
592
579
|
_bootUI();
|
|
593
580
|
} catch (_) {
|
|
594
581
|
// Fallback: just boot normally if session creation fails
|
|
@@ -13,9 +13,7 @@ const Sidebar = (() => {
|
|
|
13
13
|
function init() {
|
|
14
14
|
// Settings button toggles between "settings" and "welcome" view.
|
|
15
15
|
document.getElementById("btn-settings").addEventListener("click", () => {
|
|
16
|
-
if (Router.current
|
|
17
|
-
Router.navigate("welcome");
|
|
18
|
-
} else {
|
|
16
|
+
if (Router.current !== "settings") {
|
|
19
17
|
Router.navigate("settings");
|
|
20
18
|
}
|
|
21
19
|
});
|
|
@@ -66,6 +66,29 @@ const BackupStore = (() => {
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
|
|
69
|
+
async openFolder() {
|
|
70
|
+
try {
|
|
71
|
+
await fetch("/api/backup/open-folder", { method: "POST" });
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// Non-critical, fail quietly.
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/** Upload a tar.gz archive to restore ~/.clacky. Returns { ok, error }. */
|
|
78
|
+
async restore(arrayBuffer) {
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch("/api/backup/restore", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
83
|
+
body: arrayBuffer
|
|
84
|
+
});
|
|
85
|
+
const data = await res.json().catch(() => ({}));
|
|
86
|
+
return res.ok ? { ok: true } : { ok: false, error: data.error || "Restore failed" };
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { ok: false, error: e.message };
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
69
92
|
/** Fetch a one-off archive. Returns { ok, blob, filename, error }. */
|
|
70
93
|
async fetchArchive() {
|
|
71
94
|
try {
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
// ── Backup · view — settings rendering, toggles, download UI
|
|
2
|
-
//
|
|
3
|
-
// Renders the backup settings panel, wires the toggles + download button, and
|
|
4
|
-
// drives the file download from a blob. Reads through BackupStore.state; all
|
|
5
|
-
// I/O goes through store actions. Re-renders on store change events.
|
|
6
|
-
//
|
|
7
|
-
// Augments the `Backup` facade with load (re-exposing the store action so the
|
|
8
|
-
// existing Settings caller keeps working).
|
|
9
|
-
//
|
|
10
|
-
// Depends on: BackupStore, I18n.
|
|
11
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
1
|
+
// ── Backup · view — settings rendering, toggles, download/restore UI ──────
|
|
12
2
|
|
|
13
3
|
const BackupView = (() => {
|
|
14
4
|
const $ = (id) => document.getElementById(id);
|
|
5
|
+
let _restoreStatusEl = null;
|
|
15
6
|
|
|
16
7
|
function _fmtDate(iso) {
|
|
17
8
|
if (!iso) return "";
|
|
@@ -19,8 +10,12 @@ const BackupView = (() => {
|
|
|
19
10
|
}
|
|
20
11
|
|
|
21
12
|
function _render() {
|
|
22
|
-
const cfg
|
|
23
|
-
|
|
13
|
+
const cfg = Backup.state.config;
|
|
14
|
+
const status = Backup.state.status;
|
|
15
|
+
if (!status) return;
|
|
16
|
+
|
|
17
|
+
const autoToggle = $("backup-auto-toggle");
|
|
18
|
+
if (autoToggle) autoToggle.checked = !!cfg.enabled;
|
|
24
19
|
|
|
25
20
|
const incl = $("backup-include-sessions");
|
|
26
21
|
if (incl) {
|
|
@@ -28,8 +23,8 @@ const BackupView = (() => {
|
|
|
28
23
|
incl.disabled = !cfg.enabled;
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
26
|
+
const destPath = $("backup-dest-path");
|
|
27
|
+
if (destPath) destPath.textContent = status.dest_dir || "—";
|
|
33
28
|
|
|
34
29
|
_renderLastRun(cfg);
|
|
35
30
|
}
|
|
@@ -48,10 +43,10 @@ const BackupView = (() => {
|
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
async function _downloadNow() {
|
|
51
|
-
const btn
|
|
52
|
-
const
|
|
46
|
+
const btn = $("btn-backup-now");
|
|
47
|
+
const statusEl = $("backup-manual-status");
|
|
53
48
|
if (btn) btn.disabled = true;
|
|
54
|
-
if (
|
|
49
|
+
if (statusEl) { statusEl.textContent = I18n.t("settings.backup.downloading"); statusEl.className = "model-test-result"; }
|
|
55
50
|
|
|
56
51
|
const res = await Backup.fetchArchive();
|
|
57
52
|
if (res.ok) {
|
|
@@ -63,14 +58,32 @@ const BackupView = (() => {
|
|
|
63
58
|
a.click();
|
|
64
59
|
a.remove();
|
|
65
60
|
URL.revokeObjectURL(url);
|
|
66
|
-
if (
|
|
67
|
-
} else if (
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
if (statusEl) { statusEl.textContent = I18n.t("settings.backup.downloaded"); statusEl.className = "model-test-result success"; }
|
|
62
|
+
} else if (statusEl) {
|
|
63
|
+
statusEl.textContent = I18n.t("settings.backup.lastError", { msg: res.error });
|
|
64
|
+
statusEl.className = "model-test-result error";
|
|
70
65
|
}
|
|
71
66
|
if (btn) btn.disabled = false;
|
|
72
67
|
}
|
|
73
68
|
|
|
69
|
+
async function _restoreBackup(e) {
|
|
70
|
+
const file = e.target.files[0];
|
|
71
|
+
if (!file) return;
|
|
72
|
+
e.target.value = "";
|
|
73
|
+
|
|
74
|
+
const statusEl = $("backup-manual-status");
|
|
75
|
+
if (statusEl) { statusEl.textContent = I18n.t("settings.backup.restoring"); statusEl.className = "model-test-result"; }
|
|
76
|
+
|
|
77
|
+
const buf = await file.arrayBuffer();
|
|
78
|
+
const res = await Backup.restore(buf);
|
|
79
|
+
if (res.ok) {
|
|
80
|
+
if (statusEl) { statusEl.textContent = I18n.t("settings.backup.restoreOk"); statusEl.className = "model-test-result success"; }
|
|
81
|
+
_restoreStatusEl = statusEl;
|
|
82
|
+
} else {
|
|
83
|
+
if (statusEl) { statusEl.textContent = res.error || "Restore failed"; statusEl.className = "model-test-result error"; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
function _bind() {
|
|
75
88
|
const btn = $("btn-backup-now");
|
|
76
89
|
if (btn) btn.addEventListener("click", _downloadNow);
|
|
@@ -80,11 +93,25 @@ const BackupView = (() => {
|
|
|
80
93
|
|
|
81
94
|
const incl = $("backup-include-sessions");
|
|
82
95
|
if (incl) incl.addEventListener("change", () => Backup.saveConfig({ include_sessions: incl.checked }));
|
|
96
|
+
|
|
97
|
+
const openBtn = $("btn-backup-open-folder");
|
|
98
|
+
if (openBtn) openBtn.addEventListener("click", () => Backup.openFolder());
|
|
99
|
+
|
|
100
|
+
const restoreInput = $("input-backup-restore");
|
|
101
|
+
if (restoreInput) restoreInput.addEventListener("change", _restoreBackup);
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
function _subscribe() {
|
|
86
105
|
Backup.on("backup:changed", _render);
|
|
87
106
|
document.addEventListener("DOMContentLoaded", _bind);
|
|
107
|
+
WS.onEvent(ev => {
|
|
108
|
+
if (ev.type !== "_ws_connected" || !_restoreStatusEl) return;
|
|
109
|
+
const el = _restoreStatusEl;
|
|
110
|
+
_restoreStatusEl = null;
|
|
111
|
+
el.textContent = I18n.t("settings.backup.restartOk");
|
|
112
|
+
el.className = "model-test-result success";
|
|
113
|
+
if (typeof Settings !== "undefined") Settings.open();
|
|
114
|
+
});
|
|
88
115
|
}
|
|
89
116
|
|
|
90
117
|
return { init: _subscribe };
|
|
@@ -18,6 +18,7 @@ const BrandView = (() => {
|
|
|
18
18
|
if (!data || !data.branded) return;
|
|
19
19
|
|
|
20
20
|
if (data.needs_activation) {
|
|
21
|
+
if (data.theme_color) Settings.applyAccentColor(data.theme_color, { persist: true });
|
|
21
22
|
_showActivationBanner(data.product_name);
|
|
22
23
|
_applyHeaderLogo();
|
|
23
24
|
if (data.distribution_refresh_pending) _scheduleDistributionRefreshPoll();
|
|
@@ -153,6 +154,7 @@ const BrandView = (() => {
|
|
|
153
154
|
if (data.ok) {
|
|
154
155
|
_setResult(true, I18n.t("brand.activate.success"));
|
|
155
156
|
if (data.product_name) _applyBrandName(data.product_name);
|
|
157
|
+
if (data.theme_color) Settings.applyAccentColor(data.theme_color, { persist: true });
|
|
156
158
|
Brand.clearBrandCache();
|
|
157
159
|
_applyHeaderLogo();
|
|
158
160
|
setTimeout(_bootUI, 800);
|
|
@@ -201,13 +203,14 @@ const BrandView = (() => {
|
|
|
201
203
|
const brandWrap = document.getElementById("header-brand");
|
|
202
204
|
|
|
203
205
|
if (info.theme_color) {
|
|
204
|
-
const root = document.documentElement;
|
|
205
|
-
root.style.setProperty("--color-accent-primary", info.theme_color);
|
|
206
|
-
root.style.setProperty("--color-accent-hover", info.theme_color);
|
|
207
|
-
root.style.setProperty("--color-button-primary", info.theme_color);
|
|
208
|
-
root.style.setProperty("--color-button-primary-hover", info.theme_color);
|
|
209
206
|
const metaTheme = document.querySelector("meta[name='theme-color']");
|
|
210
207
|
if (metaTheme) metaTheme.setAttribute("content", info.theme_color);
|
|
208
|
+
}
|
|
209
|
+
const userAccent = (() => { try { return localStorage.getItem("clacky-accent-color"); } catch (_) { return null; } })();
|
|
210
|
+
if (userAccent) {
|
|
211
|
+
Settings.applyAccentColor(userAccent, { persist: false });
|
|
212
|
+
} else if (info.theme_color) {
|
|
213
|
+
Settings.applyAccentColor(info.theme_color, { persist: true });
|
|
211
214
|
} else {
|
|
212
215
|
const root = document.documentElement;
|
|
213
216
|
root.style.removeProperty("--color-accent-primary");
|
|
@@ -37,26 +37,7 @@ const ChannelsStore = (() => {
|
|
|
37
37
|
// Create a session, register it, queue a command, and navigate to it.
|
|
38
38
|
async function _sendToAgent(command, sessionName) {
|
|
39
39
|
try {
|
|
40
|
-
|
|
41
|
-
const m = s.name.match(/^Session (\d+)$/);
|
|
42
|
-
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
43
|
-
}, 0);
|
|
44
|
-
const name = sessionName || ("Session " + (maxN + 1));
|
|
45
|
-
|
|
46
|
-
const res = await fetch("/api/sessions", {
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers: { "Content-Type": "application/json" },
|
|
49
|
-
body: JSON.stringify({ name, source: "setup" }),
|
|
50
|
-
});
|
|
51
|
-
const data = await res.json();
|
|
52
|
-
if (!res.ok) throw new Error(data.error || I18n.t("channels.sessionError"));
|
|
53
|
-
const session = data.session;
|
|
54
|
-
if (!session) throw new Error(I18n.t("channels.noSession"));
|
|
55
|
-
|
|
56
|
-
Sessions.add(session);
|
|
57
|
-
Sessions.renderList();
|
|
58
|
-
Sessions.setPendingMessage(session.id, command);
|
|
59
|
-
Sessions.select(session.id);
|
|
40
|
+
await Sessions.startWith(command, { name: sessionName });
|
|
60
41
|
} catch (e) {
|
|
61
42
|
alert("Error: " + e.message);
|
|
62
43
|
}
|
|
@@ -43,26 +43,7 @@ const McpStore = (() => {
|
|
|
43
43
|
|
|
44
44
|
async function _sendToAgent(command, sessionName) {
|
|
45
45
|
try {
|
|
46
|
-
|
|
47
|
-
const m = s.name.match(/^Session (\d+)$/);
|
|
48
|
-
return m ? Math.max(max, parseInt(m[1], 10)) : max;
|
|
49
|
-
}, 0);
|
|
50
|
-
const name = sessionName || ("Session " + (maxN + 1));
|
|
51
|
-
|
|
52
|
-
const res = await fetch("/api/sessions", {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: { "Content-Type": "application/json" },
|
|
55
|
-
body: JSON.stringify({ name, source: "mcp" }),
|
|
56
|
-
});
|
|
57
|
-
const data = await res.json();
|
|
58
|
-
if (!res.ok) throw new Error(data.error || "failed to create session");
|
|
59
|
-
const session = data.session;
|
|
60
|
-
if (!session) throw new Error("no session returned");
|
|
61
|
-
|
|
62
|
-
Sessions.add(session);
|
|
63
|
-
Sessions.renderList();
|
|
64
|
-
Sessions.setPendingMessage(session.id, command);
|
|
65
|
-
Sessions.select(session.id);
|
|
46
|
+
await Sessions.startWith(command, { name: sessionName });
|
|
66
47
|
} catch (e) {
|
|
67
48
|
alert("Error: " + e.message);
|
|
68
49
|
}
|
|
@@ -64,19 +64,7 @@ const ProfileStore = (() => {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async function _openCurateSession(name, command) {
|
|
67
|
-
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: { "Content-Type": "application/json" },
|
|
70
|
-
body: JSON.stringify({ name, source: "onboard" })
|
|
71
|
-
});
|
|
72
|
-
const data = await res.json();
|
|
73
|
-
const session = data.session;
|
|
74
|
-
if (!session) throw new Error("No session returned");
|
|
75
|
-
|
|
76
|
-
Sessions.add(session);
|
|
77
|
-
Sessions.renderList();
|
|
78
|
-
Sessions.setPendingMessage(session.id, command);
|
|
79
|
-
Sessions.select(session.id);
|
|
67
|
+
await Sessions.startWith(command, { name, source: "onboard" });
|
|
80
68
|
}
|
|
81
69
|
|
|
82
70
|
const Profile = {
|