jinzd-ai-cli 0.4.200 → 0.4.202

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 (66) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +435 -435
  3. package/README.zh-CN.md +450 -450
  4. package/dist/agent-client-6GX6QQDU.js +0 -0
  5. package/dist/auth-FSTAKSYF.js +0 -0
  6. package/dist/{batch-5N4CXARG.js → batch-UF4EWYFD.js} +2 -2
  7. package/dist/chat-index-FDSGKWQV.js +0 -0
  8. package/dist/{chunk-JP6ESDV7.js → chunk-2NINHXEZ.js} +13 -1
  9. package/dist/{chunk-FQF6YJ6F.js → chunk-2YLYDFEX.js} +55 -14
  10. package/dist/chunk-4BKXL7SM.js +0 -0
  11. package/dist/chunk-5UR6ZOF4.js +0 -0
  12. package/dist/{chunk-HUTS2F6Y.js → chunk-75SUTNYY.js} +1 -1
  13. package/dist/chunk-A4GUGWEY.js +0 -0
  14. package/dist/chunk-BXP6YZ2P.js +0 -0
  15. package/dist/chunk-CITDVXXM.js +0 -0
  16. package/dist/chunk-CKH4KQ4E.js +0 -0
  17. package/dist/chunk-E44DTERW.js +0 -0
  18. package/dist/{chunk-JZCXULOJ.js → chunk-GOKVKOG3.js} +1 -1
  19. package/dist/chunk-HLWUDRBO.js +0 -0
  20. package/dist/chunk-HOSJZMQS.js +0 -0
  21. package/dist/{chunk-IDMCBIYZ.js → chunk-HZKC424D.js} +1 -1
  22. package/dist/chunk-IW3Q7AE5.js +0 -0
  23. package/dist/{chunk-NULOZSFA.js → chunk-JCY7RQIA.js} +1 -1
  24. package/dist/chunk-JVKAL5Q3.js +0 -0
  25. package/dist/chunk-KHYD3WXE.js +0 -0
  26. package/dist/chunk-MC34ISJU.js +0 -0
  27. package/dist/chunk-NZ4X6GUC.js +0 -0
  28. package/dist/chunk-O6UFCEUZ.js +0 -0
  29. package/dist/chunk-OJL3PY36.js +0 -0
  30. package/dist/{chunk-5SA7IACI.js → chunk-Q2MBPMTW.js} +2 -2
  31. package/dist/chunk-Q3ZUDA6S.js +0 -0
  32. package/dist/{chunk-2G62CKDL.js → chunk-SB77W6XS.js} +1 -1
  33. package/dist/chunk-SLSWPBK3.js +0 -0
  34. package/dist/chunk-T2NL5ZIA.js +0 -0
  35. package/dist/chunk-TB4W4Y4T.js +0 -0
  36. package/dist/chunk-TZQHYZKT.js +0 -0
  37. package/dist/{chunk-TBGVHRCL.js → chunk-Y6U6FMVV.js} +1 -1
  38. package/dist/{ci-2ZSBYWJE.js → ci-YOJTTP4J.js} +3 -3
  39. package/dist/{constants-GMPGZM4D.js → constants-VP7TYJL7.js} +1 -1
  40. package/dist/{doctor-cli-PG52ECBH.js → doctor-cli-PCIKXTNV.js} +5 -5
  41. package/dist/electron-server.js +67 -14
  42. package/dist/file-checkpoint-NKBHGC7L.js +0 -0
  43. package/dist/git-context-EXOEHQSF.js +0 -0
  44. package/dist/{hub-AI4ICH5E.js → hub-N2ZRAXRO.js} +1 -1
  45. package/dist/hub-server-LJ2JSKZ2.js +0 -0
  46. package/dist/index.js +17 -17
  47. package/dist/indexer-AKWMYNJI.js +0 -0
  48. package/dist/persist-L54DPLI7.js +0 -0
  49. package/dist/project-trust-NKYHL3VZ.js +0 -0
  50. package/dist/{run-tests-G2MCAQJL.js → run-tests-AXPZOPX7.js} +1 -1
  51. package/dist/{run-tests-NUWLWMHZ.js → run-tests-RNU733RF.js} +2 -2
  52. package/dist/semantic-PK7AUOJT.js +0 -0
  53. package/dist/{server-Y3XEAXTC.js → server-SLFGYGBO.js} +8 -8
  54. package/dist/{server-74PZ2PDM.js → server-YIBPZJ34.js} +28 -11
  55. package/dist/store-MWNHVGJT.js +0 -0
  56. package/dist/{task-orchestrator-IF3XCVZ7.js → task-orchestrator-FM4EXIBZ.js} +4 -4
  57. package/dist/{usage-7BAT3772.js → usage-QFW2XPF3.js} +2 -2
  58. package/dist/vector-store-BBDXB5IQ.js +0 -0
  59. package/dist/wasm/web-tree-sitter.wasm +0 -0
  60. package/dist/web/client/app.js +3351 -3351
  61. package/dist/web/client/icon.svg +22 -22
  62. package/dist/web/client/index.html +360 -360
  63. package/dist/web/client/manifest.json +15 -15
  64. package/dist/web/client/style.css +991 -991
  65. package/dist/web/client/sw.js +86 -86
  66. package/package.json +1 -1
@@ -1,360 +1,360 @@
1
- <!DOCTYPE html>
2
- <html lang="en" data-theme="dark">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
- <title>ai-cli Web UI</title>
7
- <!-- PWA -->
8
- <link rel="manifest" href="manifest.json">
9
- <meta name="theme-color" content="#6419e6">
10
- <meta name="apple-mobile-web-app-capable" content="yes">
11
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
12
- <link rel="apple-touch-icon" href="icon-192.png">
13
- <link rel="icon" type="image/svg+xml" href="icon.svg">
14
- <link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
15
- <!-- Tailwind CSS + DaisyUI (vendored locally — v0.4.153). These were on
16
- cdn.tailwindcss.com / cdn.jsdelivr.net, which GFW blocks in CN; when the
17
- CDN failed the whole UI rendered unstyled AND the `.hidden` login overlay
18
- became visible (no users → "can't log in"). Serving locally makes the UI
19
- work offline / behind GFW with zero behavior change. -->
20
- <link href="vendor/daisyui-full.min.css" rel="stylesheet">
21
- <script src="vendor/tailwind.js"></script>
22
- <!-- Markdown + Code highlighting -->
23
- <script src="vendor/marked.min.js"></script>
24
- <link id="hljs-theme" rel="stylesheet" href="vendor/github-dark.min.css">
25
- <script src="vendor/highlight.min.js"></script>
26
- <link rel="stylesheet" href="style.css">
27
- </head>
28
- <body class="bg-base-300 h-screen overflow-hidden">
29
-
30
- <!-- ── Login / Register Screen (shown when auth required) ──── -->
31
- <div id="auth-screen" class="auth-screen hidden">
32
- <div class="auth-card">
33
- <div class="text-center mb-6">
34
- <div class="text-4xl mb-2">🤖</div>
35
- <h1 class="text-2xl font-bold text-primary">ai-cli</h1>
36
- <p id="auth-subtitle" class="text-sm text-base-content/60 mt-1">Sign in to continue</p>
37
- </div>
38
- <div id="auth-error" class="alert alert-error text-sm mb-4 hidden"></div>
39
- <form id="auth-form" onsubmit="handleAuth(event)">
40
- <div class="form-control mb-3">
41
- <label class="label"><span class="label-text">Username</span></label>
42
- <input id="auth-username" type="text" class="input input-bordered w-full" placeholder="username" autocomplete="username" required minlength="2" maxlength="32">
43
- </div>
44
- <div class="form-control mb-4">
45
- <label class="label"><span class="label-text">Password</span></label>
46
- <input id="auth-password" type="password" class="input input-bordered w-full" placeholder="password" autocomplete="current-password" required minlength="4">
47
- </div>
48
- <button id="auth-submit" type="submit" class="btn btn-primary w-full">Sign In</button>
49
- </form>
50
- <div class="text-center mt-4">
51
- <button id="auth-toggle" class="btn btn-ghost btn-sm" onclick="toggleAuthMode()">
52
- Don't have an account? <span class="text-primary">Register</span>
53
- </button>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- <div id="app" class="flex flex-col h-screen">
59
-
60
- <!-- ── Navbar ─────────────────────────────────────── -->
61
- <div class="navbar bg-base-200 border-b border-base-content/10 px-4 min-h-[3.5rem] flex-shrink-0">
62
- <div class="navbar-start gap-2">
63
- <button id="btn-sidebar-toggle" class="btn btn-sm btn-ghost sidebar-toggle-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
64
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
65
- </button>
66
- <span class="text-lg font-bold text-primary brand-text">🤖 ai-cli</span>
67
- <select id="provider-select" class="select select-sm select-bordered navbar-select" title="Provider"></select>
68
- <select id="model-select" class="select select-sm select-bordered navbar-select" title="Model"></select>
69
- </div>
70
- <div class="navbar-end gap-1">
71
- <button id="btn-think" class="btn btn-sm btn-ghost" title="Toggle thinking mode">💭 Think</button>
72
- <button id="btn-plan" class="btn btn-sm btn-ghost" title="Toggle plan mode">📋 Plan</button>
73
- <div class="divider divider-horizontal mx-0"></div>
74
- <button id="btn-clear" class="btn btn-sm btn-ghost" title="Clear conversation">🗑️</button>
75
- <button id="btn-compact" class="btn btn-sm btn-ghost" title="Compact conversation">📦</button>
76
- <div class="dropdown dropdown-end">
77
- <div tabindex="0" role="button" class="btn btn-sm btn-ghost">🎨</div>
78
- <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
79
- <li><a onclick="setTheme('dark')">🌙 Dark</a></li>
80
- <li><a onclick="setTheme('night')">🌃 Night</a></li>
81
- <li><a onclick="setTheme('dim')">🌑 Dim</a></li>
82
- <li><a onclick="setTheme('synthwave')">🎵 Synthwave</a></li>
83
- <li><a onclick="setTheme('cyberpunk')">🤖 Cyberpunk</a></li>
84
- <li><a onclick="setTheme('light')">☀️ Light</a></li>
85
- <li><a onclick="setTheme('cupcake')">🧁 Cupcake</a></li>
86
- <li><a onclick="setTheme('nord')">❄️ Nord</a></li>
87
- </ul>
88
- </div>
89
- <!-- Enable Auth button (shown when no users registered) -->
90
- <button id="btn-enable-auth" class="btn btn-sm btn-ghost hidden" title="Enable multi-user auth" onclick="showEnableAuthDialog()">👤 Enable Auth</button>
91
- <!-- User menu (shown when authenticated) -->
92
- <div id="user-menu" class="dropdown dropdown-end hidden">
93
- <div tabindex="0" role="button" class="btn btn-sm btn-ghost gap-1">
94
- <span id="user-display">👤</span>
95
- </div>
96
- <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
97
- <li><a id="user-label" class="text-sm font-semibold pointer-events-none"></a></li>
98
- <li><a onclick="handleLogout()">🚪 Logout</a></li>
99
- </ul>
100
- </div>
101
- </div>
102
- </div>
103
-
104
- <!-- ── Main content: sidebar + chat ──────────────── -->
105
- <div class="flex flex-1 overflow-hidden">
106
-
107
- <!-- Sidebar -->
108
- <aside id="sidebar" class="sidebar bg-base-200 border-r border-base-content/10 flex flex-col flex-shrink-0 overflow-hidden">
109
- <!-- Sidebar tabs -->
110
- <div class="flex border-b border-base-content/10 flex-shrink-0">
111
- <button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
112
- <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
113
- <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
114
- <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
115
- <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
116
- <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="hub" title="Multi-agent brainstorm room (P4)">🏛 Hub</button>
117
- </div>
118
- <!-- Sessions tab -->
119
- <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
120
- <div class="p-2 border-b border-base-content/10 flex items-center justify-between gap-1">
121
- <input id="session-search" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search...">
122
- <button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" onclick="toggleBatchSelect()">☑</button>
123
- <button id="btn-new-session" class="btn btn-xs btn-primary btn-outline flex-shrink-0 whitespace-nowrap" title="New session">+ New</button>
124
- </div>
125
- <!-- Batch action bar (hidden by default) -->
126
- <div id="batch-bar" class="hidden px-2 py-1 border-b border-base-content/10 flex items-center gap-1 bg-base-300 text-xs">
127
- <span class="batch-count opacity-60">0 selected</span>
128
- <span class="flex-1"></span>
129
- <button class="btn btn-xs btn-ghost" onclick="selectAllSessions()">All</button>
130
- <button class="btn btn-xs btn-error btn-outline" onclick="batchDeleteSelected()">🗑 Delete</button>
131
- <button class="btn btn-xs btn-ghost" onclick="toggleBatchSelect()">Cancel</button>
132
- </div>
133
- <div id="session-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
134
- <div class="text-xs opacity-40 text-center py-4">No sessions yet</div>
135
- </div>
136
- </div>
137
- <!-- Branches tab (B2) -->
138
- <div id="tab-branches" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
139
- <div class="p-2 border-b border-base-content/10 flex items-center gap-1">
140
- <span class="text-xs opacity-60 flex-1 truncate" id="branches-header">No session</span>
141
- <button id="btn-branch-new" class="btn btn-xs btn-primary btn-outline flex-shrink-0 whitespace-nowrap" title="Fork at current tip">+ Fork</button>
142
- </div>
143
- <div id="branch-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
144
- <div class="text-xs opacity-40 text-center py-4">Load a session to see branches</div>
145
- </div>
146
- </div>
147
-
148
- <!-- Tools tab -->
149
- <div id="tab-tools" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
150
- <div class="p-2 border-b border-base-content/10">
151
- <input id="tools-search" type="text" class="input input-xs input-bordered w-full" placeholder="Search tools...">
152
- </div>
153
- <div id="tools-list" class="flex-1 overflow-y-auto p-2 text-sm">
154
- <div class="text-xs opacity-40 text-center py-4">Type /tools to load</div>
155
- </div>
156
- </div>
157
- <!-- Files tab -->
158
- <div id="tab-files" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
159
- <div class="p-2 border-b border-base-content/10 flex items-center gap-1">
160
- <span class="text-xs opacity-40 flex-1 truncate" id="file-tree-cwd" title="">cwd</span>
161
- <button id="btn-file-tree-refresh" class="btn btn-xs btn-ghost opacity-50 hover:opacity-100 flex-shrink-0" title="Refresh file tree">↺</button>
162
- </div>
163
- <div id="file-tree" class="flex-1 overflow-y-auto py-1 text-sm">
164
- <div class="text-xs opacity-40 text-center py-4">Click tab to load</div>
165
- </div>
166
- </div>
167
-
168
- <!-- Memory tab (B4, v0.4.90+) -->
169
- <div id="tab-memory" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
170
- <div class="p-2 border-b border-base-content/10 flex flex-col gap-1">
171
- <div class="flex items-center gap-1">
172
- <input id="memory-search-input" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search past chats (semantic)…">
173
- <button id="btn-memory-search" class="btn btn-xs btn-primary btn-outline flex-shrink-0" title="Search">🔍</button>
174
- </div>
175
- <div class="flex items-center gap-1 text-xs">
176
- <label class="flex items-center gap-1 opacity-70 cursor-pointer">
177
- <input id="memory-exclude-current" type="checkbox" class="checkbox checkbox-xs">
178
- <span>exclude current</span>
179
- </label>
180
- <span class="flex-1"></span>
181
- <button id="btn-memory-refresh" class="btn btn-xs btn-ghost" title="Incremental refresh">↻</button>
182
- <button id="btn-memory-rebuild" class="btn btn-xs btn-ghost" title="Full rebuild (slow first run)">⟳ Rebuild</button>
183
- </div>
184
- <div id="memory-status" class="text-xs opacity-50 truncate">Loading index status…</div>
185
- </div>
186
- <div id="memory-hits" class="flex-1 overflow-y-auto p-2 flex flex-col gap-2 text-sm">
187
- <div class="text-xs opacity-40 text-center py-4">Type a query above to recall past chats.</div>
188
- </div>
189
- </div>
190
-
191
- <!-- Hub tab (P4) — multi-agent brainstorm room -->
192
- <div id="tab-hub" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
193
- <div class="p-2 flex flex-col gap-2 overflow-y-auto">
194
- <p class="text-xs opacity-60 leading-snug">Several AI agents discuss a topic in the chat area, then a structured summary is saved to history (open it and hit 🎬 to replay).</p>
195
- <textarea id="hub-topic" class="textarea textarea-bordered textarea-sm w-full" rows="2" placeholder="Discussion topic…"></textarea>
196
- <label class="text-xs opacity-70">Preset
197
- <select id="hub-preset" class="select select-bordered select-xs w-full mt-1">
198
- <option value="brainstorm">brainstorm — 创意者·分析师·执行者</option>
199
- <option value="tech-review">tech-review — 架构师·开发者·安全</option>
200
- <option value="code-review">code-review — 质量·性能·测试</option>
201
- <option value="debate">debate — 正方·反方·主持人</option>
202
- </select>
203
- </label>
204
- <label class="text-xs opacity-70">Max rounds
205
- <input id="hub-rounds" type="number" min="1" max="20" value="6" class="input input-bordered input-xs w-full mt-1">
206
- </label>
207
- <label class="flex items-center gap-2 text-xs cursor-pointer">
208
- <input id="hub-mix" type="checkbox" class="checkbox checkbox-xs"> Mix providers (Claude vs GPT vs DeepSeek…)
209
- </label>
210
- <label class="flex items-center gap-2 text-xs cursor-pointer">
211
- <input id="hub-vote" type="checkbox" class="checkbox checkbox-xs" checked> Convergence voting (2/3 ends early)
212
- </label>
213
- <label class="flex items-center gap-2 text-xs cursor-pointer">
214
- <input id="hub-steer" type="checkbox" class="checkbox checkbox-xs"> Human steering (pause each round to guide/stop)
215
- </label>
216
- <label class="flex items-center gap-2 text-xs cursor-pointer">
217
- <input id="hub-lanes" type="checkbox" class="checkbox checkbox-xs"> Lanes view (one column per agent)
218
- </label>
219
- <button id="hub-start-btn" class="btn btn-primary btn-sm w-full" onclick="startHub()">🏛 Start discussion</button>
220
- <button id="hub-abort-btn" class="btn btn-error btn-outline btn-xs w-full hidden" onclick="abortHub()">⏹ Stop</button>
221
- <div id="hub-status" class="text-xs opacity-60"></div>
222
- </div>
223
- </div>
224
- </aside>
225
- <!-- Sidebar resize handle -->
226
- <div id="sidebar-resize" class="sidebar-resize-handle" title="Drag to resize sidebar"></div>
227
- <!-- Sidebar backdrop (mobile overlay) -->
228
- <div id="sidebar-backdrop" class="sidebar-backdrop hidden" onclick="closeSidebar()"></div>
229
-
230
- <!-- Chat Area -->
231
- <div class="flex-1 flex flex-col overflow-hidden">
232
- <!-- Session Tab Bar -->
233
- <div id="session-tabs" class="session-tab-bar bg-base-200 border-b border-base-content/10 flex-shrink-0">
234
- <div id="session-tabs-list" class="session-tabs-scroll"></div>
235
- <button id="btn-add-tab" class="session-tab-add" title="New tab">+</button>
236
- </div>
237
- <main id="chat-area" class="flex-1 overflow-y-auto px-4 py-4 relative">
238
- <!-- Round progress bar (sticky top, hidden by default) -->
239
- <div id="round-progress" class="round-progress-bar hidden">
240
- <div class="round-progress-inner">
241
- <span id="round-progress-label" class="round-progress-label">Round 1/25</span>
242
- <progress id="round-progress-bar" class="progress progress-primary progress-sm flex-1" value="0" max="100"></progress>
243
- </div>
244
- </div>
245
- <div id="messages" class="max-w-4xl mx-auto flex flex-col gap-3">
246
- <!-- Welcome message -->
247
- <div class="chat chat-start">
248
- <div class="chat-bubble chat-bubble-primary">
249
- Welcome to <strong>ai-cli Web UI</strong>! Select a provider & model above, then start chatting.
250
- <br><span class="text-xs opacity-70">Type <code>/command</code> for REPL commands. Shift+Enter for newline. Type <code>@</code> to reference files.</span>
251
- </div>
252
- </div>
253
- </div>
254
- </main>
255
- </div><!-- end chat column (tab bar + chat area) -->
256
-
257
- </div>
258
-
259
- <!-- ── Input Area ─────────────────────────────────── -->
260
- <footer class="bg-base-200 border-t border-base-content/10 px-4 py-3 flex-shrink-0 input-footer">
261
- <div class="max-w-4xl mx-auto flex gap-2 items-end">
262
- <button id="btn-templates" class="btn btn-ghost btn-square btn-sm self-center" title="Prompt templates">
263
- <span class="text-lg">📝</span>
264
- </button>
265
- <textarea id="user-input"
266
- class="textarea textarea-bordered flex-1 min-h-[2.75rem] max-h-[200px] resize-none leading-relaxed"
267
- placeholder="Type a message... (Shift+Enter for newline)"
268
- rows="1"></textarea>
269
- <button id="btn-send" class="btn btn-primary btn-square" title="Send">
270
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
271
- <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
272
- </svg>
273
- </button>
274
- <button id="btn-stop" class="btn btn-error btn-square hidden" title="Stop">
275
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
276
- <rect x="6" y="6" width="12" height="12" rx="1"/>
277
- </svg>
278
- </button>
279
- </div>
280
- <!-- Status bar -->
281
- <div class="max-w-4xl mx-auto flex gap-4 mt-2 text-xs opacity-50">
282
- <span id="status-session"></span>
283
- <span id="status-tokens"></span>
284
- <span id="connection-status">⚪ Connecting...</span>
285
- </div>
286
- </footer>
287
-
288
- </div>
289
-
290
- <!-- ── Prompt Templates Modal ───────────────────────── -->
291
- <dialog id="templates-modal" class="modal">
292
- <div class="modal-box max-w-2xl bg-base-200">
293
- <div class="flex items-center justify-between mb-3">
294
- <h3 class="font-bold text-lg">📝 Prompt Templates</h3>
295
- <div class="flex gap-1">
296
- <button class="btn btn-xs btn-ghost" onclick="importTemplates()" title="Import">📥 Import</button>
297
- <button class="btn btn-xs btn-ghost" onclick="exportTemplates()" title="Export">📤 Export</button>
298
- <button class="btn btn-xs btn-ghost" onclick="showAddTemplate()" title="New template">+ New</button>
299
- </div>
300
- </div>
301
-
302
- <!-- Add/Edit form (hidden by default) -->
303
- <div id="template-form" class="hidden mb-3 p-3 bg-base-300 rounded-lg">
304
- <input id="tpl-name" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Template name">
305
- <textarea id="tpl-content" class="textarea textarea-bordered w-full text-sm mb-2" rows="4" placeholder="Prompt content..."></textarea>
306
- <input id="tpl-tags" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Tags (comma-separated, optional)">
307
- <div class="flex gap-2 justify-end">
308
- <button class="btn btn-sm btn-ghost" onclick="cancelTemplateForm()">Cancel</button>
309
- <button class="btn btn-sm btn-primary" onclick="saveTemplateForm()">Save</button>
310
- </div>
311
- <input type="hidden" id="tpl-edit-id">
312
- </div>
313
-
314
- <!-- Search -->
315
- <input id="template-search" type="text" class="input input-sm input-bordered w-full mb-3" placeholder="Search templates...">
316
-
317
- <!-- Template list -->
318
- <div id="template-list" class="flex flex-col gap-1 max-h-[50vh] overflow-y-auto"></div>
319
-
320
- <div class="modal-action mt-3">
321
- <form method="dialog"><button class="btn btn-sm">Close</button></form>
322
- </div>
323
- </div>
324
- <form method="dialog" class="modal-backdrop"><button>close</button></form>
325
- </dialog>
326
-
327
- <!-- ── Session Replay Modal (B1) ───────────────────────── -->
328
- <dialog id="replay-modal" class="modal">
329
- <div class="modal-box max-w-5xl bg-base-200 w-11/12">
330
- <div class="flex items-center justify-between mb-3">
331
- <h3 class="font-bold text-lg">🎬 Session Replay</h3>
332
- <form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
333
- </div>
334
- <div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
335
- <div id="replay-usage" class="text-xs mb-3"></div>
336
- <div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
337
- </div>
338
- <form method="dialog" class="modal-backdrop"><button>close</button></form>
339
- </dialog>
340
-
341
- <script src="app.js"></script>
342
- <script>
343
- // Self-updating service worker (v0.4.159). A new SW postMessages
344
- // 'sw-updated' (and triggers controllerchange) when it takes over; we
345
- // reload once so the user never gets stuck on a stale index.html/app.js
346
- // after upgrading aicli. index.html/app.js are also network-only in sw.js.
347
- if ('serviceWorker' in navigator) {
348
- let swReloaded = false;
349
- const reloadOnce = () => { if (!swReloaded) { swReloaded = true; location.reload(); } };
350
- navigator.serviceWorker.addEventListener('message', (e) => {
351
- if (e.data && e.data.type === 'sw-updated') reloadOnce();
352
- });
353
- navigator.serviceWorker.addEventListener('controllerchange', reloadOnce);
354
- navigator.serviceWorker.register('sw.js').then((reg) => {
355
- reg.update();
356
- }).catch(() => {});
357
- }
358
- </script>
359
- </body>
360
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
+ <title>ai-cli Web UI</title>
7
+ <!-- PWA -->
8
+ <link rel="manifest" href="manifest.json">
9
+ <meta name="theme-color" content="#6419e6">
10
+ <meta name="apple-mobile-web-app-capable" content="yes">
11
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
12
+ <link rel="apple-touch-icon" href="icon-192.png">
13
+ <link rel="icon" type="image/svg+xml" href="icon.svg">
14
+ <link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
15
+ <!-- Tailwind CSS + DaisyUI (vendored locally — v0.4.153). These were on
16
+ cdn.tailwindcss.com / cdn.jsdelivr.net, which GFW blocks in CN; when the
17
+ CDN failed the whole UI rendered unstyled AND the `.hidden` login overlay
18
+ became visible (no users → "can't log in"). Serving locally makes the UI
19
+ work offline / behind GFW with zero behavior change. -->
20
+ <link href="vendor/daisyui-full.min.css" rel="stylesheet">
21
+ <script src="vendor/tailwind.js"></script>
22
+ <!-- Markdown + Code highlighting -->
23
+ <script src="vendor/marked.min.js"></script>
24
+ <link id="hljs-theme" rel="stylesheet" href="vendor/github-dark.min.css">
25
+ <script src="vendor/highlight.min.js"></script>
26
+ <link rel="stylesheet" href="style.css">
27
+ </head>
28
+ <body class="bg-base-300 h-screen overflow-hidden">
29
+
30
+ <!-- ── Login / Register Screen (shown when auth required) ──── -->
31
+ <div id="auth-screen" class="auth-screen hidden">
32
+ <div class="auth-card">
33
+ <div class="text-center mb-6">
34
+ <div class="text-4xl mb-2">🤖</div>
35
+ <h1 class="text-2xl font-bold text-primary">ai-cli</h1>
36
+ <p id="auth-subtitle" class="text-sm text-base-content/60 mt-1">Sign in to continue</p>
37
+ </div>
38
+ <div id="auth-error" class="alert alert-error text-sm mb-4 hidden"></div>
39
+ <form id="auth-form" onsubmit="handleAuth(event)">
40
+ <div class="form-control mb-3">
41
+ <label class="label"><span class="label-text">Username</span></label>
42
+ <input id="auth-username" type="text" class="input input-bordered w-full" placeholder="username" autocomplete="username" required minlength="2" maxlength="32">
43
+ </div>
44
+ <div class="form-control mb-4">
45
+ <label class="label"><span class="label-text">Password</span></label>
46
+ <input id="auth-password" type="password" class="input input-bordered w-full" placeholder="password" autocomplete="current-password" required minlength="4">
47
+ </div>
48
+ <button id="auth-submit" type="submit" class="btn btn-primary w-full">Sign In</button>
49
+ </form>
50
+ <div class="text-center mt-4">
51
+ <button id="auth-toggle" class="btn btn-ghost btn-sm" onclick="toggleAuthMode()">
52
+ Don't have an account? <span class="text-primary">Register</span>
53
+ </button>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div id="app" class="flex flex-col h-screen">
59
+
60
+ <!-- ── Navbar ─────────────────────────────────────── -->
61
+ <div class="navbar bg-base-200 border-b border-base-content/10 px-4 min-h-[3.5rem] flex-shrink-0">
62
+ <div class="navbar-start gap-2">
63
+ <button id="btn-sidebar-toggle" class="btn btn-sm btn-ghost sidebar-toggle-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
64
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
65
+ </button>
66
+ <span class="text-lg font-bold text-primary brand-text">🤖 ai-cli</span>
67
+ <select id="provider-select" class="select select-sm select-bordered navbar-select" title="Provider"></select>
68
+ <select id="model-select" class="select select-sm select-bordered navbar-select" title="Model"></select>
69
+ </div>
70
+ <div class="navbar-end gap-1">
71
+ <button id="btn-think" class="btn btn-sm btn-ghost" title="Toggle thinking mode">💭 Think</button>
72
+ <button id="btn-plan" class="btn btn-sm btn-ghost" title="Toggle plan mode">📋 Plan</button>
73
+ <div class="divider divider-horizontal mx-0"></div>
74
+ <button id="btn-clear" class="btn btn-sm btn-ghost" title="Clear conversation">🗑️</button>
75
+ <button id="btn-compact" class="btn btn-sm btn-ghost" title="Compact conversation">📦</button>
76
+ <div class="dropdown dropdown-end">
77
+ <div tabindex="0" role="button" class="btn btn-sm btn-ghost">🎨</div>
78
+ <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
79
+ <li><a onclick="setTheme('dark')">🌙 Dark</a></li>
80
+ <li><a onclick="setTheme('night')">🌃 Night</a></li>
81
+ <li><a onclick="setTheme('dim')">🌑 Dim</a></li>
82
+ <li><a onclick="setTheme('synthwave')">🎵 Synthwave</a></li>
83
+ <li><a onclick="setTheme('cyberpunk')">🤖 Cyberpunk</a></li>
84
+ <li><a onclick="setTheme('light')">☀️ Light</a></li>
85
+ <li><a onclick="setTheme('cupcake')">🧁 Cupcake</a></li>
86
+ <li><a onclick="setTheme('nord')">❄️ Nord</a></li>
87
+ </ul>
88
+ </div>
89
+ <!-- Enable Auth button (shown when no users registered) -->
90
+ <button id="btn-enable-auth" class="btn btn-sm btn-ghost hidden" title="Enable multi-user auth" onclick="showEnableAuthDialog()">👤 Enable Auth</button>
91
+ <!-- User menu (shown when authenticated) -->
92
+ <div id="user-menu" class="dropdown dropdown-end hidden">
93
+ <div tabindex="0" role="button" class="btn btn-sm btn-ghost gap-1">
94
+ <span id="user-display">👤</span>
95
+ </div>
96
+ <ul tabindex="0" class="dropdown-content menu bg-base-200 rounded-box z-10 w-40 p-2 shadow-lg border border-base-content/10">
97
+ <li><a id="user-label" class="text-sm font-semibold pointer-events-none"></a></li>
98
+ <li><a onclick="handleLogout()">🚪 Logout</a></li>
99
+ </ul>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- ── Main content: sidebar + chat ──────────────── -->
105
+ <div class="flex flex-1 overflow-hidden">
106
+
107
+ <!-- Sidebar -->
108
+ <aside id="sidebar" class="sidebar bg-base-200 border-r border-base-content/10 flex flex-col flex-shrink-0 overflow-hidden">
109
+ <!-- Sidebar tabs -->
110
+ <div class="flex border-b border-base-content/10 flex-shrink-0">
111
+ <button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
112
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
113
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
114
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
115
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
116
+ <button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="hub" title="Multi-agent brainstorm room (P4)">🏛 Hub</button>
117
+ </div>
118
+ <!-- Sessions tab -->
119
+ <div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
120
+ <div class="p-2 border-b border-base-content/10 flex items-center justify-between gap-1">
121
+ <input id="session-search" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search...">
122
+ <button id="btn-batch-select" class="btn btn-xs btn-ghost flex-shrink-0" title="Select multiple" onclick="toggleBatchSelect()">☑</button>
123
+ <button id="btn-new-session" class="btn btn-xs btn-primary btn-outline flex-shrink-0 whitespace-nowrap" title="New session">+ New</button>
124
+ </div>
125
+ <!-- Batch action bar (hidden by default) -->
126
+ <div id="batch-bar" class="hidden px-2 py-1 border-b border-base-content/10 flex items-center gap-1 bg-base-300 text-xs">
127
+ <span class="batch-count opacity-60">0 selected</span>
128
+ <span class="flex-1"></span>
129
+ <button class="btn btn-xs btn-ghost" onclick="selectAllSessions()">All</button>
130
+ <button class="btn btn-xs btn-error btn-outline" onclick="batchDeleteSelected()">🗑 Delete</button>
131
+ <button class="btn btn-xs btn-ghost" onclick="toggleBatchSelect()">Cancel</button>
132
+ </div>
133
+ <div id="session-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
134
+ <div class="text-xs opacity-40 text-center py-4">No sessions yet</div>
135
+ </div>
136
+ </div>
137
+ <!-- Branches tab (B2) -->
138
+ <div id="tab-branches" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
139
+ <div class="p-2 border-b border-base-content/10 flex items-center gap-1">
140
+ <span class="text-xs opacity-60 flex-1 truncate" id="branches-header">No session</span>
141
+ <button id="btn-branch-new" class="btn btn-xs btn-primary btn-outline flex-shrink-0 whitespace-nowrap" title="Fork at current tip">+ Fork</button>
142
+ </div>
143
+ <div id="branch-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
144
+ <div class="text-xs opacity-40 text-center py-4">Load a session to see branches</div>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Tools tab -->
149
+ <div id="tab-tools" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
150
+ <div class="p-2 border-b border-base-content/10">
151
+ <input id="tools-search" type="text" class="input input-xs input-bordered w-full" placeholder="Search tools...">
152
+ </div>
153
+ <div id="tools-list" class="flex-1 overflow-y-auto p-2 text-sm">
154
+ <div class="text-xs opacity-40 text-center py-4">Type /tools to load</div>
155
+ </div>
156
+ </div>
157
+ <!-- Files tab -->
158
+ <div id="tab-files" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
159
+ <div class="p-2 border-b border-base-content/10 flex items-center gap-1">
160
+ <span class="text-xs opacity-40 flex-1 truncate" id="file-tree-cwd" title="">cwd</span>
161
+ <button id="btn-file-tree-refresh" class="btn btn-xs btn-ghost opacity-50 hover:opacity-100 flex-shrink-0" title="Refresh file tree">↺</button>
162
+ </div>
163
+ <div id="file-tree" class="flex-1 overflow-y-auto py-1 text-sm">
164
+ <div class="text-xs opacity-40 text-center py-4">Click tab to load</div>
165
+ </div>
166
+ </div>
167
+
168
+ <!-- Memory tab (B4, v0.4.90+) -->
169
+ <div id="tab-memory" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
170
+ <div class="p-2 border-b border-base-content/10 flex flex-col gap-1">
171
+ <div class="flex items-center gap-1">
172
+ <input id="memory-search-input" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search past chats (semantic)…">
173
+ <button id="btn-memory-search" class="btn btn-xs btn-primary btn-outline flex-shrink-0" title="Search">🔍</button>
174
+ </div>
175
+ <div class="flex items-center gap-1 text-xs">
176
+ <label class="flex items-center gap-1 opacity-70 cursor-pointer">
177
+ <input id="memory-exclude-current" type="checkbox" class="checkbox checkbox-xs">
178
+ <span>exclude current</span>
179
+ </label>
180
+ <span class="flex-1"></span>
181
+ <button id="btn-memory-refresh" class="btn btn-xs btn-ghost" title="Incremental refresh">↻</button>
182
+ <button id="btn-memory-rebuild" class="btn btn-xs btn-ghost" title="Full rebuild (slow first run)">⟳ Rebuild</button>
183
+ </div>
184
+ <div id="memory-status" class="text-xs opacity-50 truncate">Loading index status…</div>
185
+ </div>
186
+ <div id="memory-hits" class="flex-1 overflow-y-auto p-2 flex flex-col gap-2 text-sm">
187
+ <div class="text-xs opacity-40 text-center py-4">Type a query above to recall past chats.</div>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Hub tab (P4) — multi-agent brainstorm room -->
192
+ <div id="tab-hub" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
193
+ <div class="p-2 flex flex-col gap-2 overflow-y-auto">
194
+ <p class="text-xs opacity-60 leading-snug">Several AI agents discuss a topic in the chat area, then a structured summary is saved to history (open it and hit 🎬 to replay).</p>
195
+ <textarea id="hub-topic" class="textarea textarea-bordered textarea-sm w-full" rows="2" placeholder="Discussion topic…"></textarea>
196
+ <label class="text-xs opacity-70">Preset
197
+ <select id="hub-preset" class="select select-bordered select-xs w-full mt-1">
198
+ <option value="brainstorm">brainstorm — 创意者·分析师·执行者</option>
199
+ <option value="tech-review">tech-review — 架构师·开发者·安全</option>
200
+ <option value="code-review">code-review — 质量·性能·测试</option>
201
+ <option value="debate">debate — 正方·反方·主持人</option>
202
+ </select>
203
+ </label>
204
+ <label class="text-xs opacity-70">Max rounds
205
+ <input id="hub-rounds" type="number" min="1" max="20" value="6" class="input input-bordered input-xs w-full mt-1">
206
+ </label>
207
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
208
+ <input id="hub-mix" type="checkbox" class="checkbox checkbox-xs"> Mix providers (Claude vs GPT vs DeepSeek…)
209
+ </label>
210
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
211
+ <input id="hub-vote" type="checkbox" class="checkbox checkbox-xs" checked> Convergence voting (2/3 ends early)
212
+ </label>
213
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
214
+ <input id="hub-steer" type="checkbox" class="checkbox checkbox-xs"> Human steering (pause each round to guide/stop)
215
+ </label>
216
+ <label class="flex items-center gap-2 text-xs cursor-pointer">
217
+ <input id="hub-lanes" type="checkbox" class="checkbox checkbox-xs"> Lanes view (one column per agent)
218
+ </label>
219
+ <button id="hub-start-btn" class="btn btn-primary btn-sm w-full" onclick="startHub()">🏛 Start discussion</button>
220
+ <button id="hub-abort-btn" class="btn btn-error btn-outline btn-xs w-full hidden" onclick="abortHub()">⏹ Stop</button>
221
+ <div id="hub-status" class="text-xs opacity-60"></div>
222
+ </div>
223
+ </div>
224
+ </aside>
225
+ <!-- Sidebar resize handle -->
226
+ <div id="sidebar-resize" class="sidebar-resize-handle" title="Drag to resize sidebar"></div>
227
+ <!-- Sidebar backdrop (mobile overlay) -->
228
+ <div id="sidebar-backdrop" class="sidebar-backdrop hidden" onclick="closeSidebar()"></div>
229
+
230
+ <!-- Chat Area -->
231
+ <div class="flex-1 flex flex-col overflow-hidden">
232
+ <!-- Session Tab Bar -->
233
+ <div id="session-tabs" class="session-tab-bar bg-base-200 border-b border-base-content/10 flex-shrink-0">
234
+ <div id="session-tabs-list" class="session-tabs-scroll"></div>
235
+ <button id="btn-add-tab" class="session-tab-add" title="New tab">+</button>
236
+ </div>
237
+ <main id="chat-area" class="flex-1 overflow-y-auto px-4 py-4 relative">
238
+ <!-- Round progress bar (sticky top, hidden by default) -->
239
+ <div id="round-progress" class="round-progress-bar hidden">
240
+ <div class="round-progress-inner">
241
+ <span id="round-progress-label" class="round-progress-label">Round 1/25</span>
242
+ <progress id="round-progress-bar" class="progress progress-primary progress-sm flex-1" value="0" max="100"></progress>
243
+ </div>
244
+ </div>
245
+ <div id="messages" class="max-w-4xl mx-auto flex flex-col gap-3">
246
+ <!-- Welcome message -->
247
+ <div class="chat chat-start">
248
+ <div class="chat-bubble chat-bubble-primary">
249
+ Welcome to <strong>ai-cli Web UI</strong>! Select a provider & model above, then start chatting.
250
+ <br><span class="text-xs opacity-70">Type <code>/command</code> for REPL commands. Shift+Enter for newline. Type <code>@</code> to reference files.</span>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ </main>
255
+ </div><!-- end chat column (tab bar + chat area) -->
256
+
257
+ </div>
258
+
259
+ <!-- ── Input Area ─────────────────────────────────── -->
260
+ <footer class="bg-base-200 border-t border-base-content/10 px-4 py-3 flex-shrink-0 input-footer">
261
+ <div class="max-w-4xl mx-auto flex gap-2 items-end">
262
+ <button id="btn-templates" class="btn btn-ghost btn-square btn-sm self-center" title="Prompt templates">
263
+ <span class="text-lg">📝</span>
264
+ </button>
265
+ <textarea id="user-input"
266
+ class="textarea textarea-bordered flex-1 min-h-[2.75rem] max-h-[200px] resize-none leading-relaxed"
267
+ placeholder="Type a message... (Shift+Enter for newline)"
268
+ rows="1"></textarea>
269
+ <button id="btn-send" class="btn btn-primary btn-square" title="Send">
270
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
271
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
272
+ </svg>
273
+ </button>
274
+ <button id="btn-stop" class="btn btn-error btn-square hidden" title="Stop">
275
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
276
+ <rect x="6" y="6" width="12" height="12" rx="1"/>
277
+ </svg>
278
+ </button>
279
+ </div>
280
+ <!-- Status bar -->
281
+ <div class="max-w-4xl mx-auto flex gap-4 mt-2 text-xs opacity-50">
282
+ <span id="status-session"></span>
283
+ <span id="status-tokens"></span>
284
+ <span id="connection-status">⚪ Connecting...</span>
285
+ </div>
286
+ </footer>
287
+
288
+ </div>
289
+
290
+ <!-- ── Prompt Templates Modal ───────────────────────── -->
291
+ <dialog id="templates-modal" class="modal">
292
+ <div class="modal-box max-w-2xl bg-base-200">
293
+ <div class="flex items-center justify-between mb-3">
294
+ <h3 class="font-bold text-lg">📝 Prompt Templates</h3>
295
+ <div class="flex gap-1">
296
+ <button class="btn btn-xs btn-ghost" onclick="importTemplates()" title="Import">📥 Import</button>
297
+ <button class="btn btn-xs btn-ghost" onclick="exportTemplates()" title="Export">📤 Export</button>
298
+ <button class="btn btn-xs btn-ghost" onclick="showAddTemplate()" title="New template">+ New</button>
299
+ </div>
300
+ </div>
301
+
302
+ <!-- Add/Edit form (hidden by default) -->
303
+ <div id="template-form" class="hidden mb-3 p-3 bg-base-300 rounded-lg">
304
+ <input id="tpl-name" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Template name">
305
+ <textarea id="tpl-content" class="textarea textarea-bordered w-full text-sm mb-2" rows="4" placeholder="Prompt content..."></textarea>
306
+ <input id="tpl-tags" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Tags (comma-separated, optional)">
307
+ <div class="flex gap-2 justify-end">
308
+ <button class="btn btn-sm btn-ghost" onclick="cancelTemplateForm()">Cancel</button>
309
+ <button class="btn btn-sm btn-primary" onclick="saveTemplateForm()">Save</button>
310
+ </div>
311
+ <input type="hidden" id="tpl-edit-id">
312
+ </div>
313
+
314
+ <!-- Search -->
315
+ <input id="template-search" type="text" class="input input-sm input-bordered w-full mb-3" placeholder="Search templates...">
316
+
317
+ <!-- Template list -->
318
+ <div id="template-list" class="flex flex-col gap-1 max-h-[50vh] overflow-y-auto"></div>
319
+
320
+ <div class="modal-action mt-3">
321
+ <form method="dialog"><button class="btn btn-sm">Close</button></form>
322
+ </div>
323
+ </div>
324
+ <form method="dialog" class="modal-backdrop"><button>close</button></form>
325
+ </dialog>
326
+
327
+ <!-- ── Session Replay Modal (B1) ───────────────────────── -->
328
+ <dialog id="replay-modal" class="modal">
329
+ <div class="modal-box max-w-5xl bg-base-200 w-11/12">
330
+ <div class="flex items-center justify-between mb-3">
331
+ <h3 class="font-bold text-lg">🎬 Session Replay</h3>
332
+ <form method="dialog"><button class="btn btn-sm btn-ghost">✕</button></form>
333
+ </div>
334
+ <div id="replay-meta" class="text-xs opacity-70 mb-2"></div>
335
+ <div id="replay-usage" class="text-xs mb-3"></div>
336
+ <div id="replay-timeline" class="flex flex-col gap-2 max-h-[70vh] overflow-y-auto pr-2"></div>
337
+ </div>
338
+ <form method="dialog" class="modal-backdrop"><button>close</button></form>
339
+ </dialog>
340
+
341
+ <script src="app.js"></script>
342
+ <script>
343
+ // Self-updating service worker (v0.4.159). A new SW postMessages
344
+ // 'sw-updated' (and triggers controllerchange) when it takes over; we
345
+ // reload once so the user never gets stuck on a stale index.html/app.js
346
+ // after upgrading aicli. index.html/app.js are also network-only in sw.js.
347
+ if ('serviceWorker' in navigator) {
348
+ let swReloaded = false;
349
+ const reloadOnce = () => { if (!swReloaded) { swReloaded = true; location.reload(); } };
350
+ navigator.serviceWorker.addEventListener('message', (e) => {
351
+ if (e.data && e.data.type === 'sw-updated') reloadOnce();
352
+ });
353
+ navigator.serviceWorker.addEventListener('controllerchange', reloadOnce);
354
+ navigator.serviceWorker.register('sw.js').then((reg) => {
355
+ reg.update();
356
+ }).catch(() => {});
357
+ }
358
+ </script>
359
+ </body>
360
+ </html>