openclacky 1.2.18 → 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 +35 -0
- data/lib/clacky/agent/time_machine.rb +256 -74
- data/lib/clacky/agent/tool_executor.rb +12 -0
- data/lib/clacky/agent.rb +15 -20
- data/lib/clacky/agent_config.rb +18 -0
- data/lib/clacky/cli.rb +55 -3
- data/lib/clacky/default_skills/media-gen/SKILL.md +172 -5
- data/lib/clacky/media/base.rb +93 -0
- data/lib/clacky/media/gemini.rb +10 -0
- data/lib/clacky/media/generator.rb +57 -0
- data/lib/clacky/media/openai_compat.rb +160 -0
- data/lib/clacky/message_history.rb +12 -7
- data/lib/clacky/providers.rb +29 -1
- data/lib/clacky/rich_ui_controller.rb +3 -1
- data/lib/clacky/server/backup_manager.rb +200 -0
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +10 -2
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +68 -15
- data/lib/clacky/server/channel/channel_manager.rb +65 -50
- data/lib/clacky/server/http_server.rb +356 -14
- data/lib/clacky/server/scheduler.rb +19 -0
- data/lib/clacky/server/session_registry.rb +8 -4
- data/lib/clacky/session_manager.rb +40 -2
- data/lib/clacky/tools/trash_manager.rb +14 -0
- data/lib/clacky/ui2/components/command_suggestions.rb +1 -0
- data/lib/clacky/ui2/components/modal_component.rb +34 -7
- data/lib/clacky/ui2/ui_controller.rb +150 -19
- data/lib/clacky/utils/file_processor.rb +75 -4
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +2283 -1277
- data/lib/clacky/web/app.js +73 -1
- data/lib/clacky/web/backup.js +119 -0
- data/lib/clacky/web/billing.js +224 -11
- data/lib/clacky/web/channels.js +81 -11
- data/lib/clacky/web/design-sample.css +247 -0
- data/lib/clacky/web/design-sample.html +127 -0
- data/lib/clacky/web/favicon.svg +16 -0
- data/lib/clacky/web/i18n.js +167 -31
- data/lib/clacky/web/index.html +176 -55
- data/lib/clacky/web/logo_nav_dark.png +0 -0
- data/lib/clacky/web/onboard.js +121 -28
- data/lib/clacky/web/sessions.js +447 -192
- data/lib/clacky/web/settings.js +21 -1
- data/lib/clacky/web/skills.js +34 -1
- data/lib/clacky/web/tasks.js +129 -61
- data/lib/clacky/web/utils.js +72 -0
- data/lib/clacky/web/ws-dispatcher.js +6 -0
- data/lib/clacky.rb +1 -0
- metadata +9 -8
- data/lib/clacky/server/channel/group_message_buffer.rb +0 -53
data/lib/clacky/web/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title id="page-title">{{BRAND_NAME}}</title>
|
|
7
|
-
<link rel="icon" href="/favicon.
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
8
8
|
<link rel="apple-touch-icon" href="/apple-touch-icon-180.png">
|
|
9
9
|
<link rel="stylesheet" href="/vendor/katex/katex.min.css">
|
|
10
10
|
<link rel="stylesheet" href="/vendor/hljs/hljs-theme.css">
|
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
title="Creator / Owner" data-i18n-title="header.owner.tooltip">OWNER</button>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
|
+
<div id="header-center">
|
|
47
|
+
<button id="header-cmdbar" type="button" title="Search sessions">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
|
49
|
+
<span class="cmdbar-ph" data-i18n="header.cmdbar.placeholder">Search sessions…</span>
|
|
50
|
+
<span class="cmdbar-kbd">⌘K</span>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
46
53
|
<div id="header-right">
|
|
47
54
|
<button id="share-toggle-header" class="theme-toggle-btn" data-i18n-title="share.tooltip" title="Share">
|
|
48
55
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-sm">
|
|
@@ -62,6 +69,42 @@
|
|
|
62
69
|
</div>
|
|
63
70
|
</header>
|
|
64
71
|
|
|
72
|
+
<!-- ── Command-palette search overlay (⌘K / top cmdbar) ─────────────────── -->
|
|
73
|
+
<!-- Search lives here, decoupled from the sidebar list: results render in
|
|
74
|
+
#session-search-results and the left session list is never replaced. -->
|
|
75
|
+
<div id="session-search-overlay" class="cmd-palette-overlay" hidden>
|
|
76
|
+
<div class="cmd-palette" role="dialog" aria-modal="true" aria-label="Search sessions">
|
|
77
|
+
<div class="search-panel-card">
|
|
78
|
+
<!-- Input row -->
|
|
79
|
+
<div class="search-input-row">
|
|
80
|
+
<span class="search-icon">🔍</span>
|
|
81
|
+
<input id="session-search-q" type="text" class="search-input"
|
|
82
|
+
data-i18n-placeholder="sessions.search.placeholder" placeholder="Search sessions…"
|
|
83
|
+
autocomplete="off" />
|
|
84
|
+
<button id="btn-search-q-clear" class="btn-search-q-clear" aria-label="Clear text" hidden>✕</button>
|
|
85
|
+
<button id="btn-session-search-close" class="cmd-palette-esc" type="button" aria-label="Close">ESC</button>
|
|
86
|
+
</div>
|
|
87
|
+
<!-- Filter row -->
|
|
88
|
+
<div class="search-filter-row">
|
|
89
|
+
<select id="session-search-type" class="search-select">
|
|
90
|
+
<option value="" data-i18n="sessions.search.typeAll">All types</option>
|
|
91
|
+
<option value="manual" data-i18n="sessions.search.typeManual">Default</option>
|
|
92
|
+
<option value="cron" data-i18n="sessions.search.typeCron">Scheduled</option>
|
|
93
|
+
<option value="channel" data-i18n="sessions.search.typeChannel">Channel</option>
|
|
94
|
+
<option value="setup" data-i18n="sessions.search.typeSetup">Setup</option>
|
|
95
|
+
<option value="coding" data-i18n="sessions.search.typeCoding">Coding</option>
|
|
96
|
+
</select>
|
|
97
|
+
<div class="search-date-wrap">
|
|
98
|
+
<button id="session-search-date" class="search-date datepicker-trigger" type="button" data-value="" data-i18n="sessions.search.datePlaceholder"></button>
|
|
99
|
+
</div>
|
|
100
|
+
<button id="btn-search-clear-all" class="btn-search-clear-all" aria-label="Clear filters" hidden>✕</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<!-- Results render here (independent from the sidebar session list) -->
|
|
104
|
+
<div id="session-search-results" class="cmd-palette-results"></div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
65
108
|
<!-- ── Sidebar overlay (mobile only) ───────────────────────────────────── -->
|
|
66
109
|
<div id="sidebar-overlay"></div>
|
|
67
110
|
|
|
@@ -74,17 +117,10 @@
|
|
|
74
117
|
<div id="sidebar-list">
|
|
75
118
|
<!-- Chat Group -->
|
|
76
119
|
<div id="chat-section">
|
|
77
|
-
<!-- Header: "Sessions" label +
|
|
120
|
+
<!-- Header: "Sessions" label + [+ ▾] split button -->
|
|
78
121
|
<div class="sidebar-divider">
|
|
79
122
|
<span data-i18n="sidebar.chat">Sessions</span>
|
|
80
123
|
<div class="sidebar-divider-actions">
|
|
81
|
-
<!-- Magnifier toggle (shown when ≥10 sessions, managed by JS) -->
|
|
82
|
-
<button id="btn-session-search-toggle" class="btn-icon-sm" title="Search sessions" style="display:none" aria-label="Search sessions">
|
|
83
|
-
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
84
|
-
<circle cx="6.5" cy="6.5" r="4.5" stroke="currentColor" stroke-width="1.6"/>
|
|
85
|
-
<line x1="10.3" y1="10.3" x2="14" y2="14" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
|
86
|
-
</svg>
|
|
87
|
-
</button>
|
|
88
124
|
<div class="btn-split-wrap">
|
|
89
125
|
<button id="btn-new-session-inline" class="btn-split-main" title="New Session" data-i18n="sessions.newSession">+ New Session</button>
|
|
90
126
|
<button id="btn-new-session-arrow" class="btn-split-arrow" title="Options">▾</button>
|
|
@@ -101,35 +137,6 @@
|
|
|
101
137
|
</div>
|
|
102
138
|
</div>
|
|
103
139
|
|
|
104
|
-
<!-- Search panel (hidden by default, toggled by magnifier button) -->
|
|
105
|
-
<div id="session-search-bar" class="session-search-panel" hidden>
|
|
106
|
-
<div class="search-panel-card">
|
|
107
|
-
<!-- Input row -->
|
|
108
|
-
<div class="search-input-row">
|
|
109
|
-
<span class="search-icon">🔍</span>
|
|
110
|
-
<input id="session-search-q" type="text" class="search-input"
|
|
111
|
-
data-i18n-placeholder="sessions.search.placeholder" placeholder="Search sessions…"
|
|
112
|
-
autocomplete="off" />
|
|
113
|
-
<button id="btn-search-q-clear" class="btn-search-q-clear" aria-label="Clear text" hidden>✕</button>
|
|
114
|
-
</div>
|
|
115
|
-
<!-- Filter row -->
|
|
116
|
-
<div class="search-filter-row">
|
|
117
|
-
<select id="session-search-type" class="search-select">
|
|
118
|
-
<option value="" data-i18n="sessions.search.typeAll">All types</option>
|
|
119
|
-
<option value="manual" data-i18n="sessions.search.typeManual">Default</option>
|
|
120
|
-
<option value="cron" data-i18n="sessions.search.typeCron">Scheduled</option>
|
|
121
|
-
<option value="channel" data-i18n="sessions.search.typeChannel">Channel</option>
|
|
122
|
-
<option value="setup" data-i18n="sessions.search.typeSetup">Setup</option>
|
|
123
|
-
<option value="coding" data-i18n="sessions.search.typeCoding">Coding</option>
|
|
124
|
-
</select>
|
|
125
|
-
<div class="search-date-wrap">
|
|
126
|
-
<button id="session-search-date" class="search-date datepicker-trigger" type="button" data-value="" data-i18n="sessions.search.datePlaceholder"></button>
|
|
127
|
-
</div>
|
|
128
|
-
<button id="btn-search-clear-all" class="btn-search-clear-all" aria-label="Clear filters" hidden>✕</button>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
140
|
<!-- Cron view header (hidden by default, shown when viewing cron sessions) -->
|
|
134
141
|
<div id="cron-view-header" class="sidebar-divider" style="display:none">
|
|
135
142
|
<button id="btn-cron-back" class="btn-icon-sm" title="Back" aria-label="Back to session list">
|
|
@@ -284,6 +291,7 @@
|
|
|
284
291
|
</span>
|
|
285
292
|
</div>
|
|
286
293
|
</div>
|
|
294
|
+
<div id="sidebar-resize-handle"></div>
|
|
287
295
|
</aside>
|
|
288
296
|
|
|
289
297
|
<!-- ── MAIN ─────────────────────────────────────────────────────────── -->
|
|
@@ -295,10 +303,30 @@
|
|
|
295
303
|
|
|
296
304
|
<!-- Welcome screen -->
|
|
297
305
|
<div id="welcome" class="centered">
|
|
298
|
-
<div class="
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
<div class="ce-mark">
|
|
307
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
|
308
|
+
<path d="M12 2l2.4 6.6L21 11l-6.6 2.4L12 20l-2.4-6.6L3 11l6.6-2.4z"/>
|
|
309
|
+
</svg>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="ce-head">
|
|
312
|
+
<h2 id="welcome-title" class="ce-title" data-i18n="welcome.title" data-i18n-vars="brand={{BRAND_NAME}}">What should we build?</h2>
|
|
313
|
+
<p class="ce-sub" data-i18n="welcome.body">Your agent is standing by. Pick a starting point or just type.</p>
|
|
314
|
+
</div>
|
|
315
|
+
<div class="chips">
|
|
316
|
+
<button class="chip" data-welcome-prompt="welcome.chip.code.prompt">
|
|
317
|
+
<span class="ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M16 18l6-6-6-6M8 6l-6 6 6 6"/></svg></span>
|
|
318
|
+
<span class="ct-wrap"><span class="ct" data-i18n="welcome.chip.code.title">Write code</span><span class="cd" data-i18n="welcome.chip.code.desc">build, refactor, debug</span></span>
|
|
319
|
+
</button>
|
|
320
|
+
<button class="chip" data-welcome-prompt="welcome.chip.task.prompt">
|
|
321
|
+
<span class="ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span>
|
|
322
|
+
<span class="ct-wrap"><span class="ct" data-i18n="welcome.chip.task.title">Automate a task</span><span class="cd" data-i18n="welcome.chip.task.desc">schedule, run, repeat</span></span>
|
|
323
|
+
</button>
|
|
324
|
+
<button class="chip" data-welcome-prompt="welcome.chip.research.prompt">
|
|
325
|
+
<span class="ci"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5V6a2 2 0 0 1 2-2h12v16H6a2 2 0 0 1-2-2z"/><path d="M8 7h7"/></svg></span>
|
|
326
|
+
<span class="ct-wrap"><span class="ct" data-i18n="welcome.chip.research.title">Research</span><span class="cd" data-i18n="welcome.chip.research.desc">search, read, summarize</span></span>
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
<button id="btn-welcome-new" class="ce-blank" data-i18n="welcome.btn">Start a blank session</button>
|
|
302
330
|
</div>
|
|
303
331
|
|
|
304
332
|
<!-- Scheduled Tasks list panel (shown when user clicks "Scheduled Tasks") -->
|
|
@@ -406,9 +434,9 @@
|
|
|
406
434
|
</button>
|
|
407
435
|
<textarea id="user-input" rows="1"
|
|
408
436
|
data-i18n-placeholder="chat.input.placeholder"
|
|
409
|
-
placeholder="Message… (Enter to send, Shift
|
|
410
|
-
<button id="btn-send"
|
|
411
|
-
<button id="btn-interrupt" style="display:none" title="Stop"></button>
|
|
437
|
+
placeholder="Message… (Enter to send, Shift-Enter for newline)"></textarea>
|
|
438
|
+
<button id="btn-send" title="Send message"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg></button>
|
|
439
|
+
<button id="btn-interrupt" style="display:none" title="Stop"><svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="1.5"/></svg></button>
|
|
412
440
|
</div>
|
|
413
441
|
</div>
|
|
414
442
|
</div><!-- /#chat-main -->
|
|
@@ -777,7 +805,7 @@
|
|
|
777
805
|
<!-- Models section -->
|
|
778
806
|
<section class="settings-section">
|
|
779
807
|
<div class="settings-section-title">
|
|
780
|
-
<span data-i18n="settings.models.title">
|
|
808
|
+
<span data-i18n="settings.models.title">Primary Model</span>
|
|
781
809
|
<button id="btn-add-model" class="btn-settings-add" data-i18n="settings.models.add">+ Add Model</button>
|
|
782
810
|
</div>
|
|
783
811
|
<div id="model-cards"></div>
|
|
@@ -786,10 +814,10 @@
|
|
|
786
814
|
<!-- Media generation section -->
|
|
787
815
|
<section class="settings-section" id="media-section">
|
|
788
816
|
<div class="settings-section-title">
|
|
789
|
-
<span data-i18n="settings.media.title">
|
|
817
|
+
<span data-i18n="settings.media.title">Secondary Models</span>
|
|
790
818
|
</div>
|
|
791
819
|
<div class="settings-section-desc" data-i18n="settings.media.desc">
|
|
792
|
-
|
|
820
|
+
Optional. Image / video / audio / vision models.
|
|
793
821
|
</div>
|
|
794
822
|
<div id="media-rows"></div>
|
|
795
823
|
</section>
|
|
@@ -898,6 +926,33 @@
|
|
|
898
926
|
</div>
|
|
899
927
|
</section>
|
|
900
928
|
|
|
929
|
+
<!-- Backup section -->
|
|
930
|
+
<section class="settings-section" id="backup-section">
|
|
931
|
+
<div class="settings-section-title">
|
|
932
|
+
<span data-i18n="settings.backup.title">Backup</span>
|
|
933
|
+
</div>
|
|
934
|
+
<p class="settings-section-desc" data-i18n="settings.backup.desc">Back up your ~/.clacky directory (config, skills, memories, tasks, sessions). Regenerable caches and logs are excluded.</p>
|
|
935
|
+
|
|
936
|
+
<div class="backup-auto-row">
|
|
937
|
+
<label class="toggle-switch">
|
|
938
|
+
<input type="checkbox" id="backup-auto-toggle">
|
|
939
|
+
<span class="toggle-slider"></span>
|
|
940
|
+
</label>
|
|
941
|
+
<span class="backup-auto-label" data-i18n="settings.backup.autoLabel">Automatic backup</span>
|
|
942
|
+
<span class="backup-auto-hint" data-i18n="settings.backup.autoHint">Daily at 03:00, keeps the latest 7</span>
|
|
943
|
+
</div>
|
|
944
|
+
|
|
945
|
+
<label class="backup-option">
|
|
946
|
+
<input type="checkbox" id="backup-include-sessions">
|
|
947
|
+
<span data-i18n="settings.backup.includeSessions">Include session history (larger archive)</span>
|
|
948
|
+
</label>
|
|
949
|
+
|
|
950
|
+
<div class="backup-actions">
|
|
951
|
+
<button id="btn-backup-now" class="btn-settings-action" data-i18n="settings.backup.runNow">💾 Download backup</button>
|
|
952
|
+
<span id="backup-status" class="model-test-result"></span>
|
|
953
|
+
</div>
|
|
954
|
+
</section>
|
|
955
|
+
|
|
901
956
|
<!-- Brand & License section -->
|
|
902
957
|
<section class="settings-section" id="brand-license-section">
|
|
903
958
|
<div class="settings-section-title">
|
|
@@ -1153,6 +1208,63 @@
|
|
|
1153
1208
|
<div id="setup-phase-key" style="display:none">
|
|
1154
1209
|
<p class="setup-phase-label" data-i18n="onboard.key.title">Connect your AI model</p>
|
|
1155
1210
|
|
|
1211
|
+
<!-- Primary path: one-click device login -->
|
|
1212
|
+
<div id="setup-device-block">
|
|
1213
|
+
<div id="setup-device-card" class="setup-device-card">
|
|
1214
|
+
<div class="setup-device-card-head">
|
|
1215
|
+
<span class="setup-device-card-title" data-i18n="onboard.device.card.title">OpenClacky AI Keys</span>
|
|
1216
|
+
<span class="setup-device-card-badge" data-i18n="provider.recommended">Recommended</span>
|
|
1217
|
+
</div>
|
|
1218
|
+
<p class="setup-device-card-lead" data-i18n="onboard.device.card.lead">Sign up and get $1 free credit · no subscription · pay as you go</p>
|
|
1219
|
+
<ul class="setup-device-card-points">
|
|
1220
|
+
<li data-i18n="onboard.device.card.point1">One key for all frontier models — Claude, Gemini, DeepSeek & more</li>
|
|
1221
|
+
<li data-i18n="onboard.device.card.point2">Same pricing as official APIs, switch models instantly</li>
|
|
1222
|
+
</ul>
|
|
1223
|
+
<button id="setup-btn-device-login" class="setup-submit-btn setup-device-btn">
|
|
1224
|
+
<span data-i18n="onboard.device.btn">Get started free →</span>
|
|
1225
|
+
</button>
|
|
1226
|
+
</div>
|
|
1227
|
+
|
|
1228
|
+
<!-- Pending state: shown while waiting for browser approval -->
|
|
1229
|
+
<div id="setup-device-pending" style="display:none">
|
|
1230
|
+
<div class="setup-device-spinner"></div>
|
|
1231
|
+
<p class="setup-device-pending-text" data-i18n="onboard.device.pending">Waiting for confirmation in your browser…</p>
|
|
1232
|
+
<p class="setup-device-code">
|
|
1233
|
+
<span data-i18n="onboard.device.code">Verification code:</span>
|
|
1234
|
+
<strong id="setup-device-usercode">—</strong>
|
|
1235
|
+
</p>
|
|
1236
|
+
<a id="setup-device-link" href="#" target="_blank" rel="noopener" class="setup-device-reopen" data-i18n="onboard.device.reopen">Open browser again →</a>
|
|
1237
|
+
<button id="setup-device-cancel" class="setup-back-btn" data-i18n="onboard.device.cancel">Cancel</button>
|
|
1238
|
+
</div>
|
|
1239
|
+
|
|
1240
|
+
<!-- Success state: shown after approval, before launching onboard session -->
|
|
1241
|
+
<div id="setup-device-success" style="display:none" class="setup-device-success">
|
|
1242
|
+
<div class="setup-device-success-head">
|
|
1243
|
+
<span class="setup-device-success-icon">✓</span>
|
|
1244
|
+
<span class="setup-device-success-title" data-i18n="onboard.device.success.title">You're in!</span>
|
|
1245
|
+
</div>
|
|
1246
|
+
<p class="setup-device-success-lead" data-i18n="onboard.device.success.lead">Your account is connected. Here's what you got:</p>
|
|
1247
|
+
<ul class="setup-device-success-points">
|
|
1248
|
+
<li>
|
|
1249
|
+
<strong data-i18n="onboard.device.success.credit.label">Free trial credit:</strong>
|
|
1250
|
+
<span data-i18n="onboard.device.success.credit.value">$1.00 (one-time, on us)</span>
|
|
1251
|
+
</li>
|
|
1252
|
+
<li>
|
|
1253
|
+
<strong data-i18n="onboard.device.success.model.label">Trial model:</strong>
|
|
1254
|
+
<span id="setup-device-success-model">or-gemini-3-5-flash</span>
|
|
1255
|
+
</li>
|
|
1256
|
+
<li class="setup-device-success-note" data-i18n="onboard.device.success.note">During the trial only Gemini 3.5 Flash is available — top up any amount to unlock all frontier models on this same key.</li>
|
|
1257
|
+
</ul>
|
|
1258
|
+
<button id="setup-btn-device-continue" class="setup-submit-btn" data-i18n="onboard.device.success.btn">Start using Clacky →</button>
|
|
1259
|
+
</div>
|
|
1260
|
+
|
|
1261
|
+
<div id="setup-device-error" class="setup-test-result" style="min-height:0;"></div>
|
|
1262
|
+
</div>
|
|
1263
|
+
|
|
1264
|
+
<!-- Secondary path: manual key entry, collapsed by default -->
|
|
1265
|
+
<details id="setup-manual-details">
|
|
1266
|
+
<summary id="setup-manual-summary" data-i18n="onboard.manual.toggle">Configure manually with your own API key</summary>
|
|
1267
|
+
|
|
1156
1268
|
<div class="setup-field">
|
|
1157
1269
|
<label class="setup-label" data-i18n="onboard.key.provider">Provider</label>
|
|
1158
1270
|
<div class="custom-select-wrapper" id="setup-provider-wrapper">
|
|
@@ -1166,7 +1278,6 @@
|
|
|
1166
1278
|
<div class="custom-select-option" data-value="" data-i18n="onboard.key.provider.placeholder">— Choose provider —</div>
|
|
1167
1279
|
</div>
|
|
1168
1280
|
</div>
|
|
1169
|
-
<div id="setup-provider-promo" class="provider-promo-hint"></div>
|
|
1170
1281
|
</div>
|
|
1171
1282
|
|
|
1172
1283
|
<div class="setup-field">
|
|
@@ -1218,6 +1329,7 @@
|
|
|
1218
1329
|
<button id="setup-btn-back" class="setup-back-btn" data-i18n="onboard.key.btn.back">← Back</button>
|
|
1219
1330
|
<button id="setup-btn-test" class="setup-submit-btn" data-i18n="onboard.key.btn.test">Test & Continue →</button>
|
|
1220
1331
|
</div>
|
|
1332
|
+
</details>
|
|
1221
1333
|
</div>
|
|
1222
1334
|
|
|
1223
1335
|
</div>
|
|
@@ -1254,9 +1366,14 @@
|
|
|
1254
1366
|
|
|
1255
1367
|
<div class="modal-field">
|
|
1256
1368
|
<label class="modal-label" data-i18n="sessions.modal.directory">Working Directory</label>
|
|
1257
|
-
<
|
|
1258
|
-
|
|
1259
|
-
|
|
1369
|
+
<div class="modal-input-row">
|
|
1370
|
+
<input id="new-session-directory" type="text" class="modal-input"
|
|
1371
|
+
data-i18n-placeholder="sessions.modal.directory.placeholder"
|
|
1372
|
+
placeholder="~/workspace/my-project">
|
|
1373
|
+
<button id="new-session-browse-btn" type="button" class="modal-browse-btn" data-i18n-title="sessions.modal.directory.browse" title="Browse…" aria-label="Browse">
|
|
1374
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
1375
|
+
</button>
|
|
1376
|
+
</div>
|
|
1260
1377
|
</div>
|
|
1261
1378
|
|
|
1262
1379
|
<div id="new-session-init-project-field" class="modal-field-checkbox" style="display:none">
|
|
@@ -1302,7 +1419,9 @@
|
|
|
1302
1419
|
<!-- Rename Session Modal -->
|
|
1303
1420
|
<div id="rename-modal-overlay" class="modal-overlay" style="display:none">
|
|
1304
1421
|
<div class="modal-box sm">
|
|
1305
|
-
<div class="modal-
|
|
1422
|
+
<div class="modal-header">
|
|
1423
|
+
<h3 class="modal-title" data-i18n="sessions.actions.rename">Rename</h3>
|
|
1424
|
+
</div>
|
|
1306
1425
|
<div class="modal-body">
|
|
1307
1426
|
<div class="modal-field">
|
|
1308
1427
|
<label class="modal-label" for="rename-modal-input">
|
|
@@ -1312,14 +1431,14 @@
|
|
|
1312
1431
|
<input type="text" id="rename-modal-input" class="modal-input" autocomplete="off" spellcheck="false">
|
|
1313
1432
|
</div>
|
|
1314
1433
|
</div>
|
|
1315
|
-
<div class="modal-
|
|
1434
|
+
<div class="modal-footer">
|
|
1316
1435
|
<button id="rename-modal-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
|
|
1317
1436
|
<button id="rename-modal-save" class="btn-primary" data-i18n="modal.ok">OK</button>
|
|
1318
1437
|
</div>
|
|
1319
1438
|
</div>
|
|
1320
1439
|
</div>
|
|
1321
1440
|
|
|
1322
|
-
|
|
1441
|
+
<div id="tooltip" style="display:none"></div>
|
|
1323
1442
|
|
|
1324
1443
|
<script src="/marked.min.js"></script>
|
|
1325
1444
|
<script src="/vendor/hljs/highlight.min.js"></script>
|
|
@@ -1339,6 +1458,7 @@
|
|
|
1339
1458
|
<script src="/skills.js"></script>
|
|
1340
1459
|
<script src="/channels.js"></script>
|
|
1341
1460
|
<script src="/mcp.js"></script>
|
|
1461
|
+
<script src="/backup.js"></script>
|
|
1342
1462
|
<script src="/model-tester.js"></script>
|
|
1343
1463
|
<script src="/settings.js"></script>
|
|
1344
1464
|
<script src="/billing.js"></script>
|
|
@@ -1352,5 +1472,6 @@
|
|
|
1352
1472
|
<script src="/vendor/qrcode/qrcode.min.js"></script>
|
|
1353
1473
|
<script src="/share.js"></script>
|
|
1354
1474
|
<script src="/app.js"></script>
|
|
1475
|
+
<script>Tooltip.init();</script>
|
|
1355
1476
|
</body>
|
|
1356
1477
|
</html>
|
|
Binary file
|
data/lib/clacky/web/onboard.js
CHANGED
|
@@ -132,9 +132,6 @@ const Onboard = (() => {
|
|
|
132
132
|
_renderProviderOptions();
|
|
133
133
|
// Bind event listeners only once (delegation-based, safe to skip on re-entry)
|
|
134
134
|
_bindCustomDropdown();
|
|
135
|
-
// Show promo hint by default (no provider selected on initial entry)
|
|
136
|
-
const promoHint = $("setup-provider-promo");
|
|
137
|
-
if (promoHint && !promoHint.classList.contains("visible")) _showPromoHint(promoHint);
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
function _renderProviderOptions() {
|
|
@@ -249,18 +246,6 @@ const Onboard = (() => {
|
|
|
249
246
|
});
|
|
250
247
|
}
|
|
251
248
|
|
|
252
|
-
function _showPromoHint(promoHint) {
|
|
253
|
-
const items = [
|
|
254
|
-
I18n.t("provider.promo.openclacky.1"),
|
|
255
|
-
I18n.t("provider.promo.openclacky.2"),
|
|
256
|
-
I18n.t("provider.promo.openclacky.3"),
|
|
257
|
-
];
|
|
258
|
-
const title = `<div class="promo-title">${I18n.t("provider.promo.openclacky.title")}</div>`;
|
|
259
|
-
const body = items.map(s => `<div class="promo-item"><span class="promo-icon">✦</span>${s}</div>`).join("");
|
|
260
|
-
promoHint.innerHTML = `<div class="promo-inner">${title}${body}</div>`;
|
|
261
|
-
promoHint.classList.add("visible");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
249
|
function _bindCustomDropdown() {
|
|
265
250
|
if (_dropdownBound) return; // listeners already attached
|
|
266
251
|
_dropdownBound = true;
|
|
@@ -292,7 +277,6 @@ const Onboard = (() => {
|
|
|
292
277
|
trigger.classList.remove("open");
|
|
293
278
|
|
|
294
279
|
const getApiKeyLink = $("setup-get-apikey-link");
|
|
295
|
-
const promoHint = $("setup-provider-promo");
|
|
296
280
|
if (value === "__custom__") {
|
|
297
281
|
// Custom: clear presets so the user can fill in their own values
|
|
298
282
|
$("setup-model").value = "";
|
|
@@ -300,7 +284,6 @@ const Onboard = (() => {
|
|
|
300
284
|
_updateSetupModelDropdown([]);
|
|
301
285
|
_updateSetupBaseUrlDropdown(null);
|
|
302
286
|
if (getApiKeyLink) getApiKeyLink.style.display = "none";
|
|
303
|
-
if (promoHint) promoHint.classList.remove("visible");
|
|
304
287
|
} else if (value) {
|
|
305
288
|
const preset = _providers.find(p => p.id === value);
|
|
306
289
|
if (preset) {
|
|
@@ -316,18 +299,8 @@ const Onboard = (() => {
|
|
|
316
299
|
getApiKeyLink.style.display = "none";
|
|
317
300
|
}
|
|
318
301
|
}
|
|
319
|
-
// Show promo hint for openclacky, hide for others
|
|
320
|
-
if (promoHint) {
|
|
321
|
-
if (value === "openclacky") {
|
|
322
|
-
_showPromoHint(promoHint);
|
|
323
|
-
} else {
|
|
324
|
-
promoHint.classList.remove("visible");
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
302
|
} else {
|
|
328
303
|
if (getApiKeyLink) getApiKeyLink.style.display = "none";
|
|
329
|
-
// Show promo hint when no provider selected (default state)
|
|
330
|
-
if (promoHint) _showPromoHint(promoHint);
|
|
331
304
|
}
|
|
332
305
|
});
|
|
333
306
|
|
|
@@ -398,6 +371,126 @@ const Onboard = (() => {
|
|
|
398
371
|
$("setup-btn-back").addEventListener("click", () => {
|
|
399
372
|
_showSetupStep("lang");
|
|
400
373
|
});
|
|
374
|
+
|
|
375
|
+
_bindDeviceStep();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Device-authorization login (primary onboarding path) ──────────────────
|
|
379
|
+
let _devicePolling = false;
|
|
380
|
+
|
|
381
|
+
function _bindDeviceStep() {
|
|
382
|
+
const btn = $("setup-btn-device-login");
|
|
383
|
+
if (btn) btn.addEventListener("click", _startDeviceLogin);
|
|
384
|
+
|
|
385
|
+
const cancel = $("setup-device-cancel");
|
|
386
|
+
if (cancel) cancel.addEventListener("click", () => {
|
|
387
|
+
_devicePolling = false;
|
|
388
|
+
_showDevicePending(false);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const continueBtn = $("setup-btn-device-continue");
|
|
392
|
+
if (continueBtn) continueBtn.addEventListener("click", () => {
|
|
393
|
+
_launchOnboardSession();
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function _showDevicePending(on) {
|
|
398
|
+
const pending = $("setup-device-pending");
|
|
399
|
+
const card = $("setup-device-card");
|
|
400
|
+
if (pending) pending.style.display = on ? "" : "none";
|
|
401
|
+
if (card) card.style.display = on ? "none" : "";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function _showDeviceSuccess(model) {
|
|
405
|
+
const pending = $("setup-device-pending");
|
|
406
|
+
const card = $("setup-device-card");
|
|
407
|
+
const success = $("setup-device-success");
|
|
408
|
+
const modelEl = $("setup-device-success-model");
|
|
409
|
+
if (pending) pending.style.display = "none";
|
|
410
|
+
if (card) card.style.display = "none";
|
|
411
|
+
if (success) success.style.display = "";
|
|
412
|
+
if (modelEl && model) modelEl.textContent = model;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function _setDeviceError(msg) {
|
|
416
|
+
const el = $("setup-device-error");
|
|
417
|
+
if (!el) return;
|
|
418
|
+
el.textContent = msg ? "✗ " + msg : "";
|
|
419
|
+
el.className = "setup-test-result" + (msg ? " result-fail" : "");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function _startDeviceLogin() {
|
|
423
|
+
const zh = _selectedLang === "zh";
|
|
424
|
+
_setDeviceError("");
|
|
425
|
+
|
|
426
|
+
const w = window.open("about:blank", "_blank");
|
|
427
|
+
|
|
428
|
+
let data;
|
|
429
|
+
try {
|
|
430
|
+
const res = await fetch("/api/onboard/device/start", { method: "POST" });
|
|
431
|
+
data = await res.json();
|
|
432
|
+
} catch (_) {
|
|
433
|
+
data = null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!data || !data.ok) {
|
|
437
|
+
if (w && !w.closed) w.close();
|
|
438
|
+
_setDeviceError((data && data.error) || (zh ? "无法发起登录,请稍后重试。" : "Could not start login. Please try again."));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const url = data.verification_uri_complete || data.verification_uri;
|
|
443
|
+
const codeEl = $("setup-device-usercode");
|
|
444
|
+
if (codeEl) codeEl.textContent = data.user_code || "—";
|
|
445
|
+
const link = $("setup-device-link");
|
|
446
|
+
if (link && url) link.href = url;
|
|
447
|
+
|
|
448
|
+
_showDevicePending(true);
|
|
449
|
+
if (w && !w.closed) {
|
|
450
|
+
w.location.href = url;
|
|
451
|
+
} else {
|
|
452
|
+
window.open(url, "_blank");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
_devicePolling = true;
|
|
456
|
+
_pollDevice(data.device_code, (data.interval || 5) * 1000);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function _pollDevice(deviceCode, intervalMs) {
|
|
460
|
+
const zh = _selectedLang === "zh";
|
|
461
|
+
|
|
462
|
+
while (_devicePolling) {
|
|
463
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
464
|
+
if (!_devicePolling) return;
|
|
465
|
+
|
|
466
|
+
let data;
|
|
467
|
+
try {
|
|
468
|
+
const res = await fetch("/api/onboard/device/poll", {
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers: { "Content-Type": "application/json" },
|
|
471
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
472
|
+
});
|
|
473
|
+
data = await res.json();
|
|
474
|
+
} catch (_) {
|
|
475
|
+
continue; // transient network error — keep polling
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (data.status === "approved") {
|
|
479
|
+
_devicePolling = false;
|
|
480
|
+
_showDeviceSuccess(data.default_model);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (data.status === "pending") continue;
|
|
484
|
+
|
|
485
|
+
// denied / expired / consumed / error
|
|
486
|
+
_devicePolling = false;
|
|
487
|
+
_showDevicePending(false);
|
|
488
|
+
const msg = data.status === "denied"
|
|
489
|
+
? (zh ? "授权已被拒绝。" : "Authorization was denied.")
|
|
490
|
+
: (zh ? "授权已过期,请重新登录。" : "Authorization expired. Please try again.");
|
|
491
|
+
_setDeviceError(data.error || msg);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
401
494
|
}
|
|
402
495
|
|
|
403
496
|
async function _testAndSave() {
|
|
@@ -469,7 +562,7 @@ const Onboard = (() => {
|
|
|
469
562
|
const res = await fetch("/api/sessions", {
|
|
470
563
|
method: "POST",
|
|
471
564
|
headers: { "Content-Type": "application/json" },
|
|
472
|
-
body: JSON.stringify({ name: "
|
|
565
|
+
body: JSON.stringify({ name: "Onboard", source: "setup" })
|
|
473
566
|
});
|
|
474
567
|
const data = await res.json();
|
|
475
568
|
const session = data.session;
|