openclacky 1.1.0 → 1.1.2
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 +37 -0
- data/README.md +28 -7
- data/lib/clacky/agent/llm_caller.rb +23 -1
- data/lib/clacky/agent/session_serializer.rb +6 -1
- data/lib/clacky/agent/skill_manager.rb +18 -5
- data/lib/clacky/agent.rb +14 -5
- data/lib/clacky/anthropic_stream_aggregator.rb +135 -0
- data/lib/clacky/bedrock_stream_aggregator.rb +137 -0
- data/lib/clacky/brand_config.rb +68 -15
- data/lib/clacky/cli.rb +18 -19
- data/lib/clacky/client.rb +146 -17
- data/lib/clacky/default_skills/onboard/SKILL.md +6 -2
- data/lib/clacky/default_skills/onboard/scripts/import_external_skills.rb +50 -6
- data/lib/clacky/openai_stream_aggregator.rb +130 -0
- data/lib/clacky/server/channel/adapters/weixin/adapter.rb +169 -6
- data/lib/clacky/server/channel/channel_ui_controller.rb +6 -0
- data/lib/clacky/server/http_server.rb +9 -3
- data/lib/clacky/server/web_ui_controller.rb +8 -4
- data/lib/clacky/tools/terminal.rb +11 -0
- data/lib/clacky/ui2/components/input_area.rb +10 -1
- data/lib/clacky/ui2/components/todo_area.rb +22 -2
- data/lib/clacky/ui2/layout_manager.rb +70 -14
- data/lib/clacky/ui2/progress_handle.rb +86 -15
- data/lib/clacky/ui2/ui_controller.rb +47 -7
- data/lib/clacky/utils/logger.rb +7 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +6 -4
- data/lib/clacky/web/i18n.js +21 -6
- data/lib/clacky/web/index.html +8 -6
- data/lib/clacky/web/sessions.js +171 -58
- data/lib/clacky/web/vendor/katex/auto-render.min.js +1 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- data/lib/clacky/web/vendor/katex/katex.min.css +1 -0
- data/lib/clacky/web/vendor/katex/katex.min.js +1 -0
- data/lib/clacky/web/ws-dispatcher.js +19 -4
- data/lib/clacky.rb +3 -0
- data/scripts/build/src/install.sh.cc +15 -5
- data/scripts/install.ps1 +14 -3
- data/scripts/install.sh +15 -5
- metadata +28 -2
|
@@ -71,7 +71,7 @@ module Clacky
|
|
|
71
71
|
class ProgressHandle
|
|
72
72
|
# Default tick interval (seconds). Matches the old global spinner
|
|
73
73
|
# cadence. Tests may pass a smaller interval for speed.
|
|
74
|
-
DEFAULT_TICK_INTERVAL = 0.
|
|
74
|
+
DEFAULT_TICK_INTERVAL = 0.25
|
|
75
75
|
|
|
76
76
|
# Style hint for the renderer. The owner decides what colors to use;
|
|
77
77
|
# the handle only forwards the hint as part of the frame metadata
|
|
@@ -93,6 +93,12 @@ module Clacky
|
|
|
93
93
|
# frame would be visual noise.
|
|
94
94
|
FAST_FINISH_THRESHOLD_SECONDS = 2
|
|
95
95
|
|
|
96
|
+
# Show "Thinking for Ns" once the gap since the last LLM stream
|
|
97
|
+
# chunk reaches this many seconds. Bedrock often pauses 5–18s
|
|
98
|
+
# while generating large content blocks (long tool_use JSON in
|
|
99
|
+
# particular); without this hint users assume the agent is stuck.
|
|
100
|
+
IDLE_HINT_THRESHOLD_SECONDS = 2
|
|
101
|
+
|
|
96
102
|
# @param owner [#register_progress, #unregister_progress, #render_frame]
|
|
97
103
|
# @param message [String] Initial progress message.
|
|
98
104
|
# @param style [Symbol] :primary or :quiet (see VALID_STYLES).
|
|
@@ -122,6 +128,7 @@ module Clacky
|
|
|
122
128
|
@ticker = nil
|
|
123
129
|
@state = :fresh # :fresh → :running → :closed
|
|
124
130
|
@metadata = {}
|
|
131
|
+
@last_chunk_at = nil
|
|
125
132
|
@monitor = Monitor.new
|
|
126
133
|
end
|
|
127
134
|
|
|
@@ -133,9 +140,10 @@ module Clacky
|
|
|
133
140
|
@monitor.synchronize do
|
|
134
141
|
return self unless @state == :fresh
|
|
135
142
|
|
|
136
|
-
@state
|
|
137
|
-
@start_time
|
|
138
|
-
@
|
|
143
|
+
@state = :running
|
|
144
|
+
@start_time = @clock.call
|
|
145
|
+
@last_chunk_at = @start_time
|
|
146
|
+
@entry_id = @owner.register_progress(self)
|
|
139
147
|
end
|
|
140
148
|
|
|
141
149
|
# Fire one initial frame synchronously so the user sees the
|
|
@@ -156,9 +164,11 @@ module Clacky
|
|
|
156
164
|
@monitor.synchronize do
|
|
157
165
|
return if @state != :running
|
|
158
166
|
@message = message.to_s if message
|
|
159
|
-
|
|
167
|
+
if metadata
|
|
168
|
+
@metadata = metadata
|
|
169
|
+
@last_chunk_at = @clock.call
|
|
170
|
+
end
|
|
160
171
|
end
|
|
161
|
-
render_now
|
|
162
172
|
end
|
|
163
173
|
|
|
164
174
|
# Stop the ticker, render one final frame, and unregister from the
|
|
@@ -203,7 +213,7 @@ module Clacky
|
|
|
203
213
|
# +render_frame+ and is responsible for writing it into the entry.
|
|
204
214
|
def current_frame
|
|
205
215
|
@monitor.synchronize do
|
|
206
|
-
compose_frame(@message, elapsed_seconds, @metadata)
|
|
216
|
+
compose_frame(@message, elapsed_seconds, @metadata, idle_seconds)
|
|
207
217
|
end
|
|
208
218
|
end
|
|
209
219
|
|
|
@@ -225,6 +235,15 @@ module Clacky
|
|
|
225
235
|
render_now
|
|
226
236
|
end
|
|
227
237
|
|
|
238
|
+
# Like __reattach_entry! but skips the render_now hop. Used by the
|
|
239
|
+
# owner when it has just painted a frame into the new entry itself
|
|
240
|
+
# (e.g. while rotating the handle to remain at the buffer tail) and
|
|
241
|
+
# is still inside its own synchronization — calling render_now there
|
|
242
|
+
# would re-enter the owner's mutex.
|
|
243
|
+
def __rebind_entry!(new_entry_id)
|
|
244
|
+
@monitor.synchronize { @entry_id = new_entry_id }
|
|
245
|
+
end
|
|
246
|
+
|
|
228
247
|
# Test hook: force a synchronous render regardless of tick cadence.
|
|
229
248
|
def __force_render!
|
|
230
249
|
render_now
|
|
@@ -269,16 +288,68 @@ module Clacky
|
|
|
269
288
|
(@clock.call - @start_time).to_i
|
|
270
289
|
end
|
|
271
290
|
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
291
|
+
# Seconds since the last metadata update (i.e. the last LLM stream
|
|
292
|
+
# chunk that carried token info). Used to surface "Thinking for Ns"
|
|
293
|
+
# in the live frame so users can see the agent isn't stuck even
|
|
294
|
+
# when token counts plateau during long Bedrock content blocks.
|
|
295
|
+
private def idle_seconds
|
|
296
|
+
return 0 unless @last_chunk_at
|
|
297
|
+
(@clock.call - @last_chunk_at).to_i
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Live-frame format:
|
|
301
|
+
# "<message>… (<elapsed>s · ↓N tokens · reasoning…)"
|
|
302
|
+
# The "reasoning" tail only appears once tokens have started
|
|
303
|
+
# streaming AND the gap since the last chunk reaches the threshold
|
|
304
|
+
# — signalling the model is between tool_use blocks doing extended
|
|
305
|
+
# thinking. No seconds shown there to avoid duplicating elapsed;
|
|
306
|
+
# animated dots (1→2→3) provide the "still alive" cue.
|
|
307
|
+
private def compose_frame(message, elapsed, metadata, idle = 0)
|
|
308
|
+
head = message.to_s
|
|
277
309
|
if metadata && (attempt = metadata[:attempt]) && (total = metadata[:total])
|
|
278
|
-
|
|
310
|
+
head = "#{head} [#{attempt}/#{total}]"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
token_part = metadata && format_token_progress(metadata)
|
|
314
|
+
|
|
315
|
+
suffix_parts = []
|
|
316
|
+
suffix_parts << "#{elapsed}s" if elapsed > 0
|
|
317
|
+
suffix_parts << token_part if token_part
|
|
318
|
+
if token_part && idle >= IDLE_HINT_THRESHOLD_SECONDS
|
|
319
|
+
suffix_parts << "reasoning #{spinner_frame} "
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
return "#{head}…" if suffix_parts.empty?
|
|
323
|
+
"#{head}… (#{suffix_parts.join(" · ")})"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
327
|
+
SPINNER_INTERVAL_MS = 250
|
|
328
|
+
|
|
329
|
+
private def spinner_frame
|
|
330
|
+
ms = (@clock.call.to_f * 1000).to_i
|
|
331
|
+
SPINNER_FRAMES[(ms / SPINNER_INTERVAL_MS) % SPINNER_FRAMES.length]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Render LLM streaming token counts as "↑1.2k ↓234 tokens".
|
|
335
|
+
# When input_tokens is unknown (e.g. OpenAI-compat streaming where
|
|
336
|
+
# prompt_tokens only arrives in the final frame), shows "↑—" so the
|
|
337
|
+
# column doesn't flicker between absent / present.
|
|
338
|
+
private def format_token_progress(metadata)
|
|
339
|
+
output = metadata[:output_tokens]
|
|
340
|
+
return nil if output.nil? || output.to_i <= 0
|
|
341
|
+
"↓ #{compact_count(output.to_i)} tokens"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
private def compact_count(n)
|
|
345
|
+
return n.to_s if n < 1000
|
|
346
|
+
if n < 1_000_000
|
|
347
|
+
k = n / 1000.0
|
|
348
|
+
k >= 10 ? "#{k.to_i}k" : "%.1fk" % k
|
|
349
|
+
else
|
|
350
|
+
m = n / 1_000_000.0
|
|
351
|
+
m >= 10 ? "#{m.to_i}M" : "%.1fM" % m
|
|
279
352
|
end
|
|
280
|
-
head = parts.join(" ")
|
|
281
|
-
elapsed > 0 ? "#{head}… (#{elapsed}s)" : "#{head}…"
|
|
282
353
|
end
|
|
283
354
|
|
|
284
355
|
# Final frame (used by +finish+). Same as +compose_frame+ but we
|
|
@@ -49,6 +49,7 @@ module Clacky
|
|
|
49
49
|
@time_machine_callback = nil
|
|
50
50
|
@tasks_count = 0
|
|
51
51
|
@total_cost = 0.0
|
|
52
|
+
@session_id = nil
|
|
52
53
|
@last_diff_lines = nil
|
|
53
54
|
|
|
54
55
|
# ── Progress subsystem (v2: owned handles, stacked) ──────────────
|
|
@@ -73,6 +74,7 @@ module Clacky
|
|
|
73
74
|
|
|
74
75
|
# Set session bar data before initializing screen
|
|
75
76
|
@input_area.update_sessionbar(
|
|
77
|
+
session_id: @session_id,
|
|
76
78
|
working_dir: @config[:working_dir],
|
|
77
79
|
mode: @config[:mode],
|
|
78
80
|
model: @config[:model],
|
|
@@ -109,10 +111,13 @@ module Clacky
|
|
|
109
111
|
# @param cost_source [Symbol, nil] :api / :price / :default (optional)
|
|
110
112
|
# @param status [String] Workspace status ('idle' or 'working') (optional)
|
|
111
113
|
# @param latency [Hash, nil] Latency metrics; accepted but not displayed in the TUI.
|
|
112
|
-
|
|
114
|
+
# @param session_id [String, nil] Full session id; rendered as first 8 chars (parity with WebUI).
|
|
115
|
+
def update_sessionbar(tasks: nil, cost: nil, cost_source: nil, status: nil, latency: nil, session_id: nil)
|
|
113
116
|
@tasks_count = tasks if tasks
|
|
114
117
|
@total_cost = cost if cost
|
|
118
|
+
@session_id = session_id if session_id
|
|
115
119
|
@input_area.update_sessionbar(
|
|
120
|
+
session_id: @session_id,
|
|
116
121
|
working_dir: @config[:working_dir],
|
|
117
122
|
mode: @config[:mode],
|
|
118
123
|
model: @config[:model],
|
|
@@ -193,9 +198,35 @@ module Clacky
|
|
|
193
198
|
@input_area.set_agent(agent, agent_profile)
|
|
194
199
|
end
|
|
195
200
|
|
|
196
|
-
# Append output to the output area
|
|
197
|
-
#
|
|
201
|
+
# Append output to the output area.
|
|
202
|
+
#
|
|
203
|
+
# If a progress indicator is currently active (somewhere in the
|
|
204
|
+
# buffer), rotate it to the tail after the append: business content
|
|
205
|
+
# ends up above, the spinner stays at the bottom. Without this,
|
|
206
|
+
# every subsequent ticker tick on a non-tail progress entry would
|
|
207
|
+
# trigger a full output repaint (visible flicker) and the visual
|
|
208
|
+
# order would have business messages appearing below the spinner.
|
|
198
209
|
def append_output(content)
|
|
210
|
+
@progress_mutex.synchronize do
|
|
211
|
+
top = @progress_stack.last
|
|
212
|
+
if top && top.entry_id
|
|
213
|
+
@layout.remove_entry(top.entry_id)
|
|
214
|
+
top.__detach_entry!
|
|
215
|
+
new_id = @layout.append_output(content)
|
|
216
|
+
progress_id = @layout.append_output(render_for(top))
|
|
217
|
+
top.__rebind_entry!(progress_id)
|
|
218
|
+
new_id
|
|
219
|
+
else
|
|
220
|
+
@layout.append_output(content)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Internal append that bypasses the progress-rotation logic and the
|
|
226
|
+
# @progress_mutex. Used by register_progress / unregister_progress,
|
|
227
|
+
# which already hold the mutex and are themselves placing a fresh
|
|
228
|
+
# progress entry at the tail.
|
|
229
|
+
private def append_output_unlocked(content)
|
|
199
230
|
@layout.append_output(content)
|
|
200
231
|
end
|
|
201
232
|
|
|
@@ -481,6 +512,8 @@ module Clacky
|
|
|
481
512
|
|
|
482
513
|
# Clear user tip when agent stops working
|
|
483
514
|
@input_area.clear_user_tip
|
|
515
|
+
# Hide todo area while idle (data preserved, restored on next work)
|
|
516
|
+
@layout.hide_todos
|
|
484
517
|
@layout.render_input
|
|
485
518
|
|
|
486
519
|
# Don't show completion message if awaiting user feedback
|
|
@@ -597,7 +630,7 @@ module Clacky
|
|
|
597
630
|
end
|
|
598
631
|
|
|
599
632
|
@progress_stack.push(handle)
|
|
600
|
-
entry_id =
|
|
633
|
+
entry_id = append_output_unlocked(render_for(handle))
|
|
601
634
|
recompute_sessionbar_status
|
|
602
635
|
entry_id
|
|
603
636
|
end
|
|
@@ -623,7 +656,7 @@ module Clacky
|
|
|
623
656
|
# Restore the new top, if any: allocate a fresh entry and let it
|
|
624
657
|
# resume rendering from where it left off.
|
|
625
658
|
if (restored = @progress_stack.last)
|
|
626
|
-
new_id =
|
|
659
|
+
new_id = append_output_unlocked(render_for(restored))
|
|
627
660
|
restored.__reattach_entry!(new_id)
|
|
628
661
|
end
|
|
629
662
|
|
|
@@ -826,6 +859,8 @@ module Clacky
|
|
|
826
859
|
@last_sessionbar_status = 'idle'
|
|
827
860
|
# Clear user tip when agent stops working
|
|
828
861
|
@input_area.clear_user_tip
|
|
862
|
+
# Hide todo area while idle (data preserved, restored on next work)
|
|
863
|
+
@layout.hide_todos
|
|
829
864
|
@layout.render_input
|
|
830
865
|
end
|
|
831
866
|
|
|
@@ -848,6 +883,8 @@ module Clacky
|
|
|
848
883
|
# Set workspace status to working (called when agent starts working)
|
|
849
884
|
def set_working_status
|
|
850
885
|
update_sessionbar(status: 'working')
|
|
886
|
+
# Restore todo area if it was hidden during idle
|
|
887
|
+
@layout.show_todos
|
|
851
888
|
# Show a random user tip with 40% probability when agent starts working
|
|
852
889
|
@input_area.show_user_tip(probability: 0.4)
|
|
853
890
|
@layout.render_input
|
|
@@ -1281,6 +1318,7 @@ module Clacky
|
|
|
1281
1318
|
|
|
1282
1319
|
# Update session bar data (will be rendered by request_confirmation's render_all)
|
|
1283
1320
|
@input_area.update_sessionbar(
|
|
1321
|
+
session_id: @session_id,
|
|
1284
1322
|
working_dir: @config[:working_dir],
|
|
1285
1323
|
mode: @config[:mode],
|
|
1286
1324
|
model: @config[:model],
|
|
@@ -1346,8 +1384,10 @@ module Clacky
|
|
|
1346
1384
|
# Add action buttons
|
|
1347
1385
|
choices << { name: "─" * 50, disabled: true }
|
|
1348
1386
|
choices << { name: "[+] Add New Model", value: { action: :add } }
|
|
1349
|
-
|
|
1350
|
-
|
|
1387
|
+
if current_config.models.length > 0
|
|
1388
|
+
choices << { name: "[*] Edit Current Model", value: { action: :edit } }
|
|
1389
|
+
choices << { name: "[-] Delete Model", value: { action: :delete } } if current_config.models.length > 1
|
|
1390
|
+
end
|
|
1351
1391
|
choices << { name: "[X] Close", value: { action: :close } }
|
|
1352
1392
|
|
|
1353
1393
|
# Show menu
|
data/lib/clacky/utils/logger.rb
CHANGED
|
@@ -35,6 +35,13 @@ module Clacky
|
|
|
35
35
|
@console ||= false
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# Path of the log file currently being written to (today's file).
|
|
39
|
+
# File may not exist yet if no log has been emitted today — callers
|
|
40
|
+
# should check File.exist? before reading.
|
|
41
|
+
def current_log_file
|
|
42
|
+
log_file_path(Time.now)
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
# Log at DEBUG level.
|
|
39
46
|
def debug(message, **context)
|
|
40
47
|
write_log(:debug, message, context)
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/app.css
CHANGED
|
@@ -943,9 +943,9 @@ body {
|
|
|
943
943
|
align-self: center;
|
|
944
944
|
}
|
|
945
945
|
.session-item:hover .session-actions-btn { display: flex; }
|
|
946
|
-
.session-actions-btn:hover {
|
|
947
|
-
background: var(--color-border-primary);
|
|
948
|
-
color: var(--color-text-primary);
|
|
946
|
+
.session-actions-btn:hover {
|
|
947
|
+
background: var(--color-border-primary);
|
|
948
|
+
color: var(--color-text-primary);
|
|
949
949
|
}
|
|
950
950
|
|
|
951
951
|
/* Pin icon in session name */
|
|
@@ -1808,6 +1808,8 @@ body {
|
|
|
1808
1808
|
.msg-assistant em { font-style: italic; color: var(--color-text-secondary); }
|
|
1809
1809
|
.msg-tool { background: var(--color-bg-primary); border: 1px solid var(--color-border-primary); font-family: monospace; font-size: 12px; color: var(--color-text-secondary); align-self: flex-start; }
|
|
1810
1810
|
.msg-info { color: var(--color-text-secondary); font-size: 12px; align-self: center; font-style: italic; }
|
|
1811
|
+
.msg-info.msg-info-main { font-style: normal; }
|
|
1812
|
+
.msg-info-sub { color: var(--color-text-secondary); font-size: 11px; align-self: center; opacity: 0.7; margin-top: -27px; }
|
|
1811
1813
|
|
|
1812
1814
|
/* ── Feedback request card ──────────────────────────────────────────────── */
|
|
1813
1815
|
.feedback-card {
|
|
@@ -1926,7 +1928,7 @@ body {
|
|
|
1926
1928
|
}
|
|
1927
1929
|
.msg-success { color: var(--color-success); align-self: flex-start; font-size: 13px; }
|
|
1928
1930
|
.tool-name { color: var(--color-warning); font-weight: 600; }
|
|
1929
|
-
.progress-msg { color: var(--color-accent-primary); font-size: 12px; align-self: center;
|
|
1931
|
+
.progress-msg { color: var(--color-accent-primary); font-size: 12px; align-self: center; }
|
|
1930
1932
|
|
|
1931
1933
|
/* ── Token usage line ────────────────────────────────────────────────────── */
|
|
1932
1934
|
.token-usage-line {
|
data/lib/clacky/web/i18n.js
CHANGED
|
@@ -37,6 +37,7 @@ const I18n = (() => {
|
|
|
37
37
|
"chat.status.running": "running",
|
|
38
38
|
"chat.status.error": "error",
|
|
39
39
|
"chat.input.placeholder": "Message… (Enter to send, Shift+Enter for newline)",
|
|
40
|
+
"chat.input.placeholderRunning": "AI is working — you can still send extra info anytime...",
|
|
40
41
|
"chat.btn.send": "Send",
|
|
41
42
|
"chat.thinking": "Thinking…",
|
|
42
43
|
"chat.retrying": "Retrying",
|
|
@@ -44,6 +45,8 @@ const I18n = (() => {
|
|
|
44
45
|
"chat.history_start": "No more history",
|
|
45
46
|
"chat.image_expired": "Expired",
|
|
46
47
|
"chat.done": "Done — {{n}} iteration(s), {{cost}}",
|
|
48
|
+
"chat.done.duration": " · {{duration}}s",
|
|
49
|
+
"chat.done.cache": "Cache hit {{rate}}% ({{hits}}/{{total}}) · {{tokens}} tokens reused",
|
|
47
50
|
"chat.interrupted": "Interrupted.",
|
|
48
51
|
"chat.feedback_hint": "Or type your own answer below ↓",
|
|
49
52
|
"chat.newMessageHint": "New messages ↓",
|
|
@@ -66,11 +69,15 @@ const I18n = (() => {
|
|
|
66
69
|
"sessions.deleteTitle": "Delete session",
|
|
67
70
|
"sessions.createError": "Error: ",
|
|
68
71
|
"sessions.dirNotEmpty": "Directory already exists and is not empty.",
|
|
69
|
-
"sessions.export.tooltip": "Download session files (session.json + chunks) for debugging",
|
|
72
|
+
"sessions.export.tooltip": "Download session files (session.json + chunks + today's log) for debugging",
|
|
70
73
|
"sessions.export.failed": "Failed to download session",
|
|
71
74
|
"sessions.actions.tooltip": "Click for session actions",
|
|
72
75
|
"sessions.actions.download": "Download session files",
|
|
73
76
|
"sessions.actions.downloadHint": "for debugging",
|
|
77
|
+
"sib.dir.tooltip": "Click to change directory",
|
|
78
|
+
"sib.dir.changePrompt": "Change working directory:",
|
|
79
|
+
"sib.model.tooltip": "Click to switch model",
|
|
80
|
+
"sib.signal.tooltip": "Recent LLM latency",
|
|
74
81
|
"sessions.thinking": "Thinking…",
|
|
75
82
|
"sessions.default_name": "Session {{n}}",
|
|
76
83
|
"sessions.badge.cron": "Auto",
|
|
@@ -449,8 +456,8 @@ const I18n = (() => {
|
|
|
449
456
|
"settings.brand.label.qrHint": "Scan with your phone camera",
|
|
450
457
|
"settings.brand.btn.change": "Change Serial Number",
|
|
451
458
|
"settings.brand.btn.unbind": "Unbind License",
|
|
452
|
-
"settings.brand.confirmRebind": "
|
|
453
|
-
"settings.brand.confirmUnbind": "
|
|
459
|
+
"settings.brand.confirmRebind": "Enter a new serial number to rebind. If it belongs to the same brand, your installed brand skills are preserved; switching to a different brand will remove them. Continue?",
|
|
460
|
+
"settings.brand.confirmUnbind": "Unbind this license? All installed brand skills will be removed, and this device will no longer have access to branded features.",
|
|
454
461
|
"settings.brand.unbindSuccess": "License unbound successfully.",
|
|
455
462
|
"settings.brand.unbindFailed": "Failed to unbind license. Please try again.",
|
|
456
463
|
"settings.brand.badge.active": "Active",
|
|
@@ -558,6 +565,7 @@ const I18n = (() => {
|
|
|
558
565
|
"chat.status.running": "运行中",
|
|
559
566
|
"chat.status.error": "出错",
|
|
560
567
|
"chat.input.placeholder": "输入消息…(Enter 发送,Shift+Enter 换行)",
|
|
568
|
+
"chat.input.placeholderRunning": "AI 正在工作,你仍然可以随时补充新信息给它...",
|
|
561
569
|
"chat.btn.send": "发送",
|
|
562
570
|
"chat.thinking": "思考中…",
|
|
563
571
|
"chat.retrying": "正在重试",
|
|
@@ -565,6 +573,8 @@ const I18n = (() => {
|
|
|
565
573
|
"chat.history_start": "没有更多历史了",
|
|
566
574
|
"chat.image_expired": "已过期",
|
|
567
575
|
"chat.done": "完成 — {{n}} 步,{{cost}}",
|
|
576
|
+
"chat.done.duration": " · {{duration}}s",
|
|
577
|
+
"chat.done.cache": "缓存命中 {{rate}}% ({{hits}}/{{total}}) · 复用 {{tokens}} tokens",
|
|
568
578
|
"chat.interrupted": "已中断。",
|
|
569
579
|
"chat.feedback_hint": "或在下方输入框自由作答 ↓",
|
|
570
580
|
"chat.newMessageHint": "有新消息 ↓",
|
|
@@ -585,11 +595,15 @@ const I18n = (() => {
|
|
|
585
595
|
"sessions.deleteTitle": "删除对话",
|
|
586
596
|
"sessions.createError": "错误:",
|
|
587
597
|
"sessions.dirNotEmpty": "该目录已存在且不为空,请换一个目录名。",
|
|
588
|
-
"sessions.export.tooltip": "下载会话文件(session.json +
|
|
598
|
+
"sessions.export.tooltip": "下载会话文件(session.json + 归档片段 + 当天日志),用于调试",
|
|
589
599
|
"sessions.export.failed": "下载会话文件失败",
|
|
590
600
|
"sessions.actions.tooltip": "点击查看会话操作",
|
|
591
601
|
"sessions.actions.download": "下载会话文件",
|
|
592
602
|
"sessions.actions.downloadHint": "用于调试",
|
|
603
|
+
"sib.dir.tooltip": "点击切换工作目录",
|
|
604
|
+
"sib.dir.changePrompt": "切换工作目录:",
|
|
605
|
+
"sib.model.tooltip": "点击切换模型",
|
|
606
|
+
"sib.signal.tooltip": "最近一次 LLM 响应延迟",
|
|
593
607
|
"sessions.thinking": "思考中…",
|
|
594
608
|
"sessions.default_name": "对话 {{n}}",
|
|
595
609
|
"sessions.badge.cron": "定时",
|
|
@@ -612,6 +626,7 @@ const I18n = (() => {
|
|
|
612
626
|
"sessions.search.typeCoding": "Coding",
|
|
613
627
|
"sessions.search.typeSetup": "配置",
|
|
614
628
|
"sessions.search.datePlaceholder": "日期",
|
|
629
|
+
"modal.yes": "确定",
|
|
615
630
|
"modal.no": "取消",
|
|
616
631
|
"modal.ok": "确定",
|
|
617
632
|
"modal.cancel": "取消",
|
|
@@ -964,8 +979,8 @@ const I18n = (() => {
|
|
|
964
979
|
"settings.brand.label.qrHint": "使用手机扫描二维码",
|
|
965
980
|
"settings.brand.btn.change": "更换序列号",
|
|
966
981
|
"settings.brand.btn.unbind": "解绑授权",
|
|
967
|
-
"settings.brand.confirmRebind": "
|
|
968
|
-
"settings.brand.confirmUnbind": "
|
|
982
|
+
"settings.brand.confirmRebind": "请输入新序列号以重新绑定。若属于同一品牌,已安装的品牌技能将保留;切换为其他品牌时才会清理。是否继续?",
|
|
983
|
+
"settings.brand.confirmUnbind": "确定要解绑此授权吗?所有已安装的品牌技能将被删除,本设备将无法再访问品牌功能。",
|
|
969
984
|
"settings.brand.unbindSuccess": "授权解绑成功。",
|
|
970
985
|
"settings.brand.unbindFailed": "解绑授权失败,请重试。",
|
|
971
986
|
"settings.brand.badge.active": "已激活",
|
data/lib/clacky/web/index.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title id="page-title">{{BRAND_NAME}}</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/icon.svg">
|
|
8
|
+
<link rel="stylesheet" href="/vendor/katex/katex.min.css">
|
|
8
9
|
<link rel="stylesheet" href="/app.css">
|
|
9
10
|
<script>
|
|
10
11
|
// Inline theme init — must run before CSS renders to prevent flash of wrong theme.
|
|
@@ -300,10 +301,10 @@
|
|
|
300
301
|
<div id="sib-actions-dropdown" class="sib-actions-dropdown" style="display:none" role="menu"></div>
|
|
301
302
|
</span>
|
|
302
303
|
<span class="sib-sep sib-sep-after-id">│</span>
|
|
303
|
-
<span id="sib-dir" title="Click to change directory"></span>
|
|
304
|
+
<span id="sib-dir" data-i18n-title="sib.dir.tooltip" title="Click to change directory"></span>
|
|
304
305
|
<span class="sib-sep sib-sep-after-dir">│</span>
|
|
305
306
|
<span id="sib-model-wrap">
|
|
306
|
-
<span id="sib-model" class="sib-model-clickable" title="Click to switch model"></span>
|
|
307
|
+
<span id="sib-model" class="sib-model-clickable" data-i18n-title="sib.model.tooltip" title="Click to switch model"></span>
|
|
307
308
|
<div id="sib-model-dropdown" class="sib-model-dropdown" style="display:none"></div>
|
|
308
309
|
</span>
|
|
309
310
|
<span class="sib-sep sib-sep-after-model">│</span>
|
|
@@ -311,7 +312,7 @@
|
|
|
311
312
|
call completes (see updateInfoBar / Sessions.renderSignalBars). Click
|
|
312
313
|
opens a mini benchmark panel (see Step 3/4 — not yet implemented). -->
|
|
313
314
|
<span id="sib-signal-wrap" style="display:none">
|
|
314
|
-
<span id="sib-signal" class="sib-signal-clickable" title="Recent LLM latency">
|
|
315
|
+
<span id="sib-signal" class="sib-signal-clickable" data-i18n-title="sib.signal.tooltip" title="Recent LLM latency">
|
|
315
316
|
<span class="sig-bars" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
|
|
316
317
|
<span class="sig-text"></span>
|
|
317
318
|
</span>
|
|
@@ -905,7 +906,6 @@
|
|
|
905
906
|
<div class="modal-box new-session-modal">
|
|
906
907
|
<div class="modal-header">
|
|
907
908
|
<h3 class="modal-title" data-i18n="sessions.modal.title">Create New Session</h3>
|
|
908
|
-
<button id="new-session-modal-close" class="modal-close-btn" title="Close">×</button>
|
|
909
909
|
</div>
|
|
910
910
|
<div class="modal-body">
|
|
911
911
|
<div class="modal-field">
|
|
@@ -945,8 +945,8 @@
|
|
|
945
945
|
</div>
|
|
946
946
|
</div>
|
|
947
947
|
<div class="modal-footer">
|
|
948
|
-
<button id="new-session-create" class="btn-primary" data-i18n="sessions.modal.create">Create Session</button>
|
|
949
948
|
<button id="new-session-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
|
|
949
|
+
<button id="new-session-create" class="btn-primary" data-i18n="sessions.modal.create">Create Session</button>
|
|
950
950
|
</div>
|
|
951
951
|
</div>
|
|
952
952
|
</div>
|
|
@@ -968,8 +968,8 @@
|
|
|
968
968
|
<div id="prompt-modal-message" style="font-size:14px;line-height:1.6;margin-bottom:12px"></div>
|
|
969
969
|
<input type="text" id="prompt-modal-input" class="prompt-modal-input" autocomplete="off" spellcheck="false">
|
|
970
970
|
<div class="modal-actions">
|
|
971
|
-
<button id="prompt-modal-ok" class="btn-primary" data-i18n="modal.ok">OK</button>
|
|
972
971
|
<button id="prompt-modal-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
|
|
972
|
+
<button id="prompt-modal-ok" class="btn-primary" data-i18n="modal.ok">OK</button>
|
|
973
973
|
</div>
|
|
974
974
|
</div>
|
|
975
975
|
</div>
|
|
@@ -997,6 +997,8 @@
|
|
|
997
997
|
|
|
998
998
|
|
|
999
999
|
<script src="/marked.min.js"></script>
|
|
1000
|
+
<script src="/vendor/katex/katex.min.js"></script>
|
|
1001
|
+
<script src="/vendor/katex/auto-render.min.js"></script>
|
|
1000
1002
|
<script src="/i18n.js"></script>
|
|
1001
1003
|
<script src="/auth.js"></script>
|
|
1002
1004
|
<script src="/theme.js"></script>
|