openclacky 1.3.2 → 1.3.4

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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/Dockerfile +3 -0
  4. data/README.md +1 -1
  5. data/README_JA.md +237 -0
  6. data/docs/rich_ui_guide.md +277 -0
  7. data/docs/rich_ui_refactor_plan.md +396 -0
  8. data/lib/clacky/agent/llm_caller.rb +10 -4
  9. data/lib/clacky/agent/session_serializer.rb +52 -7
  10. data/lib/clacky/agent/time_machine.rb +247 -26
  11. data/lib/clacky/agent.rb +15 -3
  12. data/lib/clacky/api_extension.rb +262 -0
  13. data/lib/clacky/api_extension_loader.rb +156 -0
  14. data/lib/clacky/cli.rb +93 -3
  15. data/lib/clacky/client.rb +38 -13
  16. data/lib/clacky/default_agents/_panels/git/panel.js +201 -0
  17. data/lib/clacky/default_agents/_panels/time_machine/panel.js +640 -0
  18. data/lib/clacky/default_agents/coding/profile.yml +3 -0
  19. data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
  20. data/lib/clacky/default_skills/cron-task-creator/SKILL.md +1 -1
  21. data/lib/clacky/default_skills/extend-openclacky/SKILL.md +6 -4
  22. data/lib/clacky/default_skills/media-gen/SKILL.md +37 -10
  23. data/lib/clacky/idle_compression_timer.rb +3 -1
  24. data/lib/clacky/locales/en.rb +26 -0
  25. data/lib/clacky/locales/i18n.rb +26 -0
  26. data/lib/clacky/locales/zh.rb +26 -0
  27. data/lib/clacky/media/openai_compat.rb +64 -1
  28. data/lib/clacky/message_history.rb +9 -0
  29. data/lib/clacky/rich_ui/components/base_component.rb +50 -0
  30. data/lib/clacky/rich_ui/components/dialogs/approval_dialog.rb +142 -0
  31. data/lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb +106 -0
  32. data/lib/clacky/rich_ui/components/dialogs/form_dialog.rb +128 -0
  33. data/lib/clacky/rich_ui/components/sidebar.rb +119 -0
  34. data/lib/clacky/rich_ui/components/sidebar_panels.rb +134 -0
  35. data/lib/clacky/rich_ui/components/status_view.rb +58 -0
  36. data/lib/clacky/rich_ui/components/thinking_live_view.rb +79 -0
  37. data/lib/clacky/rich_ui/entry_tracker.rb +56 -0
  38. data/lib/clacky/rich_ui/layout_adapter.rb +16 -0
  39. data/lib/clacky/rich_ui/progress_handle_adapter.rb +24 -0
  40. data/lib/clacky/rich_ui/rich_ui_controller.rb +868 -0
  41. data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +184 -0
  42. data/lib/clacky/rich_ui/view_renderer.rb +291 -0
  43. data/lib/clacky/rich_ui.rb +57 -0
  44. data/lib/clacky/rich_ui_controller.rb +3 -1549
  45. data/lib/clacky/server/api_extension_dispatcher.rb +120 -0
  46. data/lib/clacky/server/channel/channel_manager.rb +26 -0
  47. data/lib/clacky/server/git_panel.rb +115 -0
  48. data/lib/clacky/server/http_server.rb +547 -15
  49. data/lib/clacky/server/server_master.rb +6 -4
  50. data/lib/clacky/server/session_registry.rb +1 -1
  51. data/lib/clacky/shell_hook_loader.rb +1 -1
  52. data/lib/clacky/tools/edit.rb +14 -2
  53. data/lib/clacky/ui2/ui_controller.rb +7 -0
  54. data/lib/clacky/version.rb +1 -1
  55. data/lib/clacky/web/app.css +511 -101
  56. data/lib/clacky/web/app.js +95 -14
  57. data/lib/clacky/web/components/code-editor.js +197 -0
  58. data/lib/clacky/web/{notify.js → components/notify.js} +1 -1
  59. data/lib/clacky/web/{onboard.js → components/onboard.js} +18 -2
  60. data/lib/clacky/web/core/aside.js +117 -0
  61. data/lib/clacky/web/core/ext.js +387 -0
  62. data/lib/clacky/web/features/backup/store.js +92 -0
  63. data/lib/clacky/web/features/backup/view.js +94 -0
  64. data/lib/clacky/web/features/billing/store.js +163 -0
  65. data/lib/clacky/web/{billing.js → features/billing/view.js} +132 -240
  66. data/lib/clacky/web/features/brand/store.js +110 -0
  67. data/lib/clacky/web/{brand.js → features/brand/view.js} +49 -199
  68. data/lib/clacky/web/features/channels/store.js +103 -0
  69. data/lib/clacky/web/{channels.js → features/channels/view.js} +50 -127
  70. data/lib/clacky/web/features/creator/store.js +81 -0
  71. data/lib/clacky/web/{creator.js → features/creator/view.js} +53 -102
  72. data/lib/clacky/web/features/mcp/store.js +158 -0
  73. data/lib/clacky/web/{mcp.js → features/mcp/view.js} +57 -134
  74. data/lib/clacky/web/features/model-tester/store.js +77 -0
  75. data/lib/clacky/web/features/model-tester/view.js +7 -0
  76. data/lib/clacky/web/features/profile/store.js +170 -0
  77. data/lib/clacky/web/{profile.js → features/profile/view.js} +94 -144
  78. data/lib/clacky/web/features/share/store.js +145 -0
  79. data/lib/clacky/web/{share.js → features/share/view.js} +66 -202
  80. data/lib/clacky/web/features/skills/store.js +331 -0
  81. data/lib/clacky/web/features/skills/view.js +581 -0
  82. data/lib/clacky/web/features/tasks/store.js +135 -0
  83. data/lib/clacky/web/features/tasks/view.js +241 -0
  84. data/lib/clacky/web/features/trash/store.js +242 -0
  85. data/lib/clacky/web/{trash.js → features/trash/view.js} +102 -293
  86. data/lib/clacky/web/features/version/store.js +165 -0
  87. data/lib/clacky/web/features/version/view.js +323 -0
  88. data/lib/clacky/web/features/workspace/store.js +99 -0
  89. data/lib/clacky/web/features/workspace/view.js +305 -0
  90. data/lib/clacky/web/i18n.js +68 -6
  91. data/lib/clacky/web/index.html +113 -62
  92. data/lib/clacky/web/sessions.js +493 -39
  93. data/lib/clacky/web/settings.js +143 -49
  94. data/lib/clacky/web/skills.js +3 -863
  95. data/lib/clacky/web/vendor/codemirror/codemirror.min.js +29 -0
  96. data/lib/clacky/web/ws-dispatcher.js +7 -3
  97. data/lib/clacky.rb +17 -1
  98. metadata +81 -20
  99. data/lib/clacky/web/backup.js +0 -119
  100. data/lib/clacky/web/model-tester.js +0 -66
  101. data/lib/clacky/web/tasks.js +0 -373
  102. data/lib/clacky/web/version.js +0 -449
  103. data/lib/clacky/web/workspace.js +0 -316
  104. /data/lib/clacky/web/{notify.mp3 → assets/notify.mp3} +0 -0
  105. /data/lib/clacky/web/{datepicker.js → components/datepicker.js} +0 -0
  106. /data/lib/clacky/web/{sidebar.js → components/sidebar.js} +0 -0
  107. /data/lib/clacky/web/{marked.min.js → vendor/marked/marked.min.js} +0 -0
@@ -217,7 +217,7 @@ WS.onEvent(ev => {
217
217
  const pendingMsg = Sessions.takePendingMessage();
218
218
  if (pendingMsg && pendingMsg.session_id === ev.session_id) {
219
219
  Sessions.appendMsg("user", escapeHtml(pendingMsg.content), { time: new Date() });
220
- WS.send({ type: "message", session_id: pendingMsg.session_id, content: pendingMsg.content });
220
+ WS.send({ type: "message", session_id: pendingMsg.session_id, content: pendingMsg.content, lang: I18n.lang() });
221
221
  }
222
222
  break;
223
223
  }
@@ -298,8 +298,12 @@ WS.onEvent(ev => {
298
298
 
299
299
  // ── Chat messages ──────────────────────────────────────────────────
300
300
  case "history_user_message":
301
- // Emitted only during history replay never from live WS.
302
- // Rendered by Sessions._fetchHistory; nothing to do here.
301
+ // Emitted by show_user_message before agent.run; stamp the authoritative
302
+ // created_at onto the optimistically-rendered bubble and register it in
303
+ // the dedup set so the subsequent history fetch skips this round.
304
+ if (ev.session_id === Sessions.activeId && ev.created_at) {
305
+ Sessions.stampLastUserBubble(ev.created_at);
306
+ }
303
307
  break;
304
308
 
305
309
  case "assistant_message":
data/lib/clacky.rb CHANGED
@@ -68,6 +68,7 @@ require_relative "clacky/message_format/bedrock"
68
68
  require_relative "clacky/bedrock_stream_aggregator"
69
69
  require_relative "clacky/openai_stream_aggregator"
70
70
  require_relative "clacky/anthropic_stream_aggregator"
71
+ require_relative "clacky/locales/i18n"
71
72
  require_relative "clacky/client"
72
73
  require_relative "clacky/skill"
73
74
  require_relative "clacky/skill_loader"
@@ -144,10 +145,25 @@ require_relative "clacky/cli"
144
145
  require_relative "clacky/patch_loader"
145
146
  Clacky::PatchLoader.load_all
146
147
 
148
+ # HTTP API extension layer: define the base class + loader + dispatcher.
149
+ # Loading of user extensions is deferred to HttpServer#start so the host
150
+ # process is fully initialized before extension handlers can resolve helpers
151
+ # like session_manager or agent_config.
152
+ require_relative "clacky/api_extension"
153
+ require_relative "clacky/api_extension_loader"
154
+ require_relative "clacky/server/api_extension_dispatcher"
155
+
147
156
  module Clacky
148
157
  class AgentInterrupted < Exception; end # Inherit from Exception to bypass rescue StandardError
149
158
  class AgentError < StandardError; end
150
- class BadRequestError < AgentError; end # 400 errors — our request was malformed, history should be rolled back
159
+ class BadRequestError < AgentError
160
+ attr_reader :display_message
161
+
162
+ def initialize(message, display_message: nil)
163
+ super(message)
164
+ @display_message = display_message
165
+ end
166
+ end
151
167
  class InsufficientCreditError < AgentError
152
168
  attr_reader :error_code, :provider_id
153
169
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
@@ -245,6 +245,20 @@ dependencies:
245
245
  - - "<"
246
246
  - !ruby/object:Gem::Version
247
247
  version: '5.0'
248
+ - !ruby/object:Gem::Dependency
249
+ name: ruby_rich
250
+ requirement: !ruby/object:Gem::Requirement
251
+ requirements:
252
+ - - "~>"
253
+ - !ruby/object:Gem::Version
254
+ version: 0.5.2
255
+ type: :runtime
256
+ prerelease: false
257
+ version_requirements: !ruby/object:Gem::Requirement
258
+ requirements:
259
+ - - "~>"
260
+ - !ruby/object:Gem::Version
261
+ version: 0.5.2
248
262
  - !ruby/object:Gem::Dependency
249
263
  name: chunky_png
250
264
  requirement: !ruby/object:Gem::Requirement
@@ -288,6 +302,7 @@ files:
288
302
  - LICENSE.txt
289
303
  - README.md
290
304
  - README_CN.md
305
+ - README_JA.md
291
306
  - ROADMAP.md
292
307
  - Rakefile
293
308
  - benchmark/fixtures/sample_project/Gemfile
@@ -312,6 +327,8 @@ files:
312
327
  - docs/engineering-article.md
313
328
  - docs/mcp-architecture.md
314
329
  - docs/mcp.example.json
330
+ - docs/rich_ui_guide.md
331
+ - docs/rich_ui_refactor_plan.md
315
332
  - docs/session-skill-invocation.md
316
333
  - docs/time_machine_design.md
317
334
  - docs/ui2-architecture.md
@@ -338,6 +355,8 @@ files:
338
355
  - lib/clacky/agent_config.rb
339
356
  - lib/clacky/agent_profile.rb
340
357
  - lib/clacky/anthropic_stream_aggregator.rb
358
+ - lib/clacky/api_extension.rb
359
+ - lib/clacky/api_extension_loader.rb
341
360
  - lib/clacky/banner.rb
342
361
  - lib/clacky/bedrock_stream_aggregator.rb
343
362
  - lib/clacky/billing/billing_record.rb
@@ -348,9 +367,12 @@ files:
348
367
  - lib/clacky/client.rb
349
368
  - lib/clacky/default_agents/SOUL.md
350
369
  - lib/clacky/default_agents/USER.md
370
+ - lib/clacky/default_agents/_panels/git/panel.js
371
+ - lib/clacky/default_agents/_panels/time_machine/panel.js
351
372
  - lib/clacky/default_agents/base_prompt.md
352
373
  - lib/clacky/default_agents/coding/profile.yml
353
374
  - lib/clacky/default_agents/coding/system_prompt.md
375
+ - lib/clacky/default_agents/coding/webui/.gitkeep
354
376
  - lib/clacky/default_agents/general/profile.yml
355
377
  - lib/clacky/default_agents/general/system_prompt.md
356
378
  - lib/clacky/default_parsers/doc_parser.rb
@@ -408,6 +430,9 @@ files:
408
430
  - lib/clacky/default_skills/skill-creator/scripts/validate_skill_frontmatter.rb
409
431
  - lib/clacky/idle_compression_timer.rb
410
432
  - lib/clacky/json_ui_controller.rb
433
+ - lib/clacky/locales/en.rb
434
+ - lib/clacky/locales/i18n.rb
435
+ - lib/clacky/locales/zh.rb
411
436
  - lib/clacky/mcp/client.rb
412
437
  - lib/clacky/mcp/http_transport.rb
413
438
  - lib/clacky/mcp/registry.rb
@@ -430,7 +455,23 @@ files:
430
455
  - lib/clacky/platform_http_client.rb
431
456
  - lib/clacky/providers.rb
432
457
  - lib/clacky/proxy_config.rb
458
+ - lib/clacky/rich_ui.rb
459
+ - lib/clacky/rich_ui/components/base_component.rb
460
+ - lib/clacky/rich_ui/components/dialogs/approval_dialog.rb
461
+ - lib/clacky/rich_ui/components/dialogs/config_menu_dialog.rb
462
+ - lib/clacky/rich_ui/components/dialogs/form_dialog.rb
463
+ - lib/clacky/rich_ui/components/sidebar.rb
464
+ - lib/clacky/rich_ui/components/sidebar_panels.rb
465
+ - lib/clacky/rich_ui/components/status_view.rb
466
+ - lib/clacky/rich_ui/components/thinking_live_view.rb
467
+ - lib/clacky/rich_ui/entry_tracker.rb
468
+ - lib/clacky/rich_ui/layout_adapter.rb
469
+ - lib/clacky/rich_ui/progress_handle_adapter.rb
470
+ - lib/clacky/rich_ui/rich_ui_controller.rb
471
+ - lib/clacky/rich_ui/shell/rich_agent_shell.rb
472
+ - lib/clacky/rich_ui/view_renderer.rb
433
473
  - lib/clacky/rich_ui_controller.rb
474
+ - lib/clacky/server/api_extension_dispatcher.rb
434
475
  - lib/clacky/server/backup_manager.rb
435
476
  - lib/clacky/server/browser_manager.rb
436
477
  - lib/clacky/server/channel.rb
@@ -459,6 +500,7 @@ files:
459
500
  - lib/clacky/server/channel/user_adapter_loader.rb
460
501
  - lib/clacky/server/discover.rb
461
502
  - lib/clacky/server/epipe_safe_io.rb
503
+ - lib/clacky/server/git_panel.rb
462
504
  - lib/clacky/server/http_server.rb
463
505
  - lib/clacky/server/scheduler.rb
464
506
  - lib/clacky/server/server_master.rb
@@ -538,38 +580,58 @@ files:
538
580
  - lib/clacky/web/app.css
539
581
  - lib/clacky/web/app.js
540
582
  - lib/clacky/web/apple-touch-icon-180.png
583
+ - lib/clacky/web/assets/notify.mp3
541
584
  - lib/clacky/web/auth.js
542
- - lib/clacky/web/backup.js
543
- - lib/clacky/web/billing.js
544
- - lib/clacky/web/brand.js
545
- - lib/clacky/web/channels.js
546
- - lib/clacky/web/creator.js
547
- - lib/clacky/web/datepicker.js
585
+ - lib/clacky/web/components/code-editor.js
586
+ - lib/clacky/web/components/datepicker.js
587
+ - lib/clacky/web/components/notify.js
588
+ - lib/clacky/web/components/onboard.js
589
+ - lib/clacky/web/components/sidebar.js
590
+ - lib/clacky/web/core/aside.js
591
+ - lib/clacky/web/core/ext.js
548
592
  - lib/clacky/web/design-sample.css
549
593
  - lib/clacky/web/design-sample.html
550
594
  - lib/clacky/web/favicon.ico
551
595
  - lib/clacky/web/favicon.svg
596
+ - lib/clacky/web/features/backup/store.js
597
+ - lib/clacky/web/features/backup/view.js
598
+ - lib/clacky/web/features/billing/store.js
599
+ - lib/clacky/web/features/billing/view.js
600
+ - lib/clacky/web/features/brand/store.js
601
+ - lib/clacky/web/features/brand/view.js
602
+ - lib/clacky/web/features/channels/store.js
603
+ - lib/clacky/web/features/channels/view.js
604
+ - lib/clacky/web/features/creator/store.js
605
+ - lib/clacky/web/features/creator/view.js
606
+ - lib/clacky/web/features/mcp/store.js
607
+ - lib/clacky/web/features/mcp/view.js
608
+ - lib/clacky/web/features/model-tester/store.js
609
+ - lib/clacky/web/features/model-tester/view.js
610
+ - lib/clacky/web/features/profile/store.js
611
+ - lib/clacky/web/features/profile/view.js
612
+ - lib/clacky/web/features/share/store.js
613
+ - lib/clacky/web/features/share/view.js
614
+ - lib/clacky/web/features/skills/store.js
615
+ - lib/clacky/web/features/skills/view.js
616
+ - lib/clacky/web/features/tasks/store.js
617
+ - lib/clacky/web/features/tasks/view.js
618
+ - lib/clacky/web/features/trash/store.js
619
+ - lib/clacky/web/features/trash/view.js
620
+ - lib/clacky/web/features/version/store.js
621
+ - lib/clacky/web/features/version/view.js
622
+ - lib/clacky/web/features/workspace/store.js
623
+ - lib/clacky/web/features/workspace/view.js
552
624
  - lib/clacky/web/i18n.js
553
625
  - lib/clacky/web/icon-dark.svg
554
626
  - lib/clacky/web/icon.svg
555
627
  - lib/clacky/web/index.html
556
628
  - lib/clacky/web/logo_nav_dark.png
557
- - lib/clacky/web/marked.min.js
558
- - lib/clacky/web/mcp.js
559
- - lib/clacky/web/model-tester.js
560
- - lib/clacky/web/notify.js
561
- - lib/clacky/web/notify.mp3
562
- - lib/clacky/web/onboard.js
563
- - lib/clacky/web/profile.js
564
629
  - lib/clacky/web/sessions.js
565
630
  - lib/clacky/web/settings.js
566
- - lib/clacky/web/share.js
567
- - lib/clacky/web/sidebar.js
568
631
  - lib/clacky/web/skills.js
569
- - lib/clacky/web/tasks.js
570
632
  - lib/clacky/web/theme.js
571
- - lib/clacky/web/trash.js
572
633
  - lib/clacky/web/utils.js
634
+ - lib/clacky/web/vendor/codemirror/codemirror.min.js
573
635
  - lib/clacky/web/vendor/hljs/highlight.min.js
574
636
  - lib/clacky/web/vendor/hljs/hljs-theme.css
575
637
  - lib/clacky/web/vendor/katex/auto-render.min.js
@@ -595,10 +657,9 @@ files:
595
657
  - lib/clacky/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2
596
658
  - lib/clacky/web/vendor/katex/katex.min.css
597
659
  - lib/clacky/web/vendor/katex/katex.min.js
660
+ - lib/clacky/web/vendor/marked/marked.min.js
598
661
  - lib/clacky/web/vendor/qrcode/qrcode.min.js
599
- - lib/clacky/web/version.js
600
662
  - lib/clacky/web/weixin-qr.html
601
- - lib/clacky/web/workspace.js
602
663
  - lib/clacky/web/ws-dispatcher.js
603
664
  - lib/clacky/web/ws.js
604
665
  - scripts/build/build.sh
@@ -1,119 +0,0 @@
1
- // backup.js — Backup settings panel (Settings → General → Backup).
2
- //
3
- // Talks to /api/backup/{status,config,download}. Lets the user toggle automatic
4
- // backups (handled by the server-side Scheduler — fixed daily at 03:00, keeps 7)
5
- // and download a one-off archive directly to the browser.
6
-
7
- const Backup = (() => {
8
-
9
- let _status = null;
10
- let _saving = false;
11
-
12
- async function load() {
13
- try {
14
- const res = await fetch("/api/backup/status");
15
- _status = await res.json();
16
- _render();
17
- } catch (e) {
18
- // Backup section is non-critical; fail quietly.
19
- }
20
- }
21
-
22
- function _render() {
23
- if (!_status) return;
24
- const cfg = _status.config || {};
25
-
26
- const incl = $("backup-include-sessions");
27
- if (incl) incl.checked = cfg.include_sessions !== false;
28
-
29
- const autoToggle = $("backup-auto-toggle");
30
- if (autoToggle) autoToggle.checked = !!cfg.enabled;
31
-
32
- _renderLastRun(cfg);
33
- }
34
-
35
- function _renderLastRun(cfg) {
36
- const el = $("backup-status");
37
- if (!el) return;
38
- if (!cfg.last_run_at) { el.textContent = ""; el.className = "model-test-result"; return; }
39
- if (cfg.last_status === "error") {
40
- el.textContent = I18n.t("settings.backup.lastError", { msg: cfg.last_error || "" });
41
- el.className = "model-test-result error";
42
- } else {
43
- el.textContent = I18n.t("settings.backup.lastOk", { time: _fmtDate(cfg.last_run_at) });
44
- el.className = "model-test-result success";
45
- }
46
- }
47
-
48
- async function _downloadNow() {
49
- const btn = $("btn-backup-now");
50
- const el = $("backup-status");
51
- if (btn) btn.disabled = true;
52
- if (el) { el.textContent = I18n.t("settings.backup.running"); el.className = "model-test-result"; }
53
- try {
54
- const res = await fetch("/api/backup/download");
55
- if (!res.ok) {
56
- let msg = "failed";
57
- try { msg = (await res.json()).error || msg; } catch (e) {}
58
- throw new Error(msg);
59
- }
60
- const blob = await res.blob();
61
- const cd = res.headers.get("Content-Disposition") || "";
62
- const m = cd.match(/filename="?([^"]+)"?/);
63
- const name = (m && m[1]) || "clacky-backup.tar.gz";
64
- const url = URL.createObjectURL(blob);
65
- const a = document.createElement("a");
66
- a.href = url;
67
- a.download = name;
68
- document.body.appendChild(a);
69
- a.click();
70
- a.remove();
71
- URL.revokeObjectURL(url);
72
- if (el) { el.textContent = I18n.t("settings.backup.downloaded"); el.className = "model-test-result success"; }
73
- } catch (e) {
74
- if (el) { el.textContent = I18n.t("settings.backup.lastError", { msg: e.message }); el.className = "model-test-result error"; }
75
- } finally {
76
- if (btn) btn.disabled = false;
77
- }
78
- }
79
-
80
- async function _saveConfig(patch) {
81
- if (_saving) return;
82
- _saving = true;
83
- try {
84
- const res = await fetch("/api/backup/config", {
85
- method: "PATCH",
86
- headers: { "Content-Type": "application/json" },
87
- body: JSON.stringify(patch)
88
- });
89
- const data = await res.json();
90
- if (data.ok) { _status = data.status; _render(); }
91
- } catch (e) {
92
- // ignore — next load will resync
93
- } finally {
94
- _saving = false;
95
- }
96
- }
97
-
98
- function _fmtDate(iso) {
99
- if (!iso) return "";
100
- try { return new Date(iso).toLocaleString(); } catch (e) { return iso; }
101
- }
102
-
103
- function _bind() {
104
- const btn = $("btn-backup-now");
105
- if (btn) btn.addEventListener("click", _downloadNow);
106
-
107
- const autoToggle = $("backup-auto-toggle");
108
- if (autoToggle) autoToggle.addEventListener("change", () => _saveConfig({ enabled: autoToggle.checked }));
109
-
110
- const incl = $("backup-include-sessions");
111
- if (incl) incl.addEventListener("change", () => _saveConfig({ include_sessions: incl.checked }));
112
- }
113
-
114
- document.addEventListener("DOMContentLoaded", _bind);
115
-
116
- return { load };
117
- })();
118
-
119
- window.Backup = Backup;
@@ -1,66 +0,0 @@
1
- // Shared helpers for the model config UI flows.
2
- // Used by both the onboarding wizard and the settings model modal.
3
- window.ModelTester = (function () {
4
- // Test a model connection.
5
- // Returns one of:
6
- // { ok: true, base_url, message } — connected, no rewrite
7
- // { ok: true, base_url, message, rewrote: true } — connected, base_url auto-corrected (/v1 appended)
8
- // { ok: false, message } — failed (server-reported or network)
9
- async function testConnection({ model, base_url, api_key, anthropic_format, index, id } = {}) {
10
- const body = { model, base_url, api_key };
11
- if (typeof id === "string" && id) body.id = id;
12
- if (typeof index === "number") body.index = index;
13
- if (anthropic_format) body.anthropic_format = true;
14
-
15
- let data;
16
- try {
17
- const res = await fetch("/api/config/test", {
18
- method: "POST",
19
- headers: { "Content-Type": "application/json" },
20
- body: JSON.stringify(body)
21
- });
22
- data = await res.json();
23
- } catch (e) {
24
- return { ok: false, message: e.message };
25
- }
26
-
27
- if (!data.ok) {
28
- const msg = data.message || "";
29
- const code = data.error_code || "";
30
- if (code === "insufficient_credit") {
31
- return { ok: false, message: I18n.t("error.insufficient_credit"), error_code: code };
32
- }
33
- return { ok: false, message: msg, error_code: code };
34
- }
35
-
36
- if (data.effective_base_url && data.effective_base_url !== base_url) {
37
- return { ok: true, base_url: data.effective_base_url, message: data.message || "", rewrote: true };
38
- }
39
- return { ok: true, base_url, message: data.message || "" };
40
- }
41
-
42
- // Persist a model config (create or update).
43
- // existingId === null/undefined → POST /api/config/models (create).
44
- // existingId === string → PATCH /api/config/models/:id (update).
45
- // Returns { ok: bool, error? }.
46
- async function saveModel(payload, { existingId } = {}) {
47
- const url = existingId
48
- ? `/api/config/models/${encodeURIComponent(existingId)}`
49
- : "/api/config/models";
50
- const method = existingId ? "PATCH" : "POST";
51
-
52
- try {
53
- const res = await fetch(url, {
54
- method,
55
- headers: { "Content-Type": "application/json" },
56
- body: JSON.stringify(payload)
57
- });
58
- const data = await res.json();
59
- return data.ok ? { ok: true } : { ok: false, error: data.error || "" };
60
- } catch (e) {
61
- return { ok: false, error: e.message };
62
- }
63
- }
64
-
65
- return { testConnection, saveModel };
66
- })();