ai-browser 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +10 -1
  2. package/dist/agent/agent-loop.d.ts +18 -0
  3. package/dist/agent/agent-loop.d.ts.map +1 -1
  4. package/dist/agent/agent-loop.js +233 -3
  5. package/dist/agent/agent-loop.js.map +1 -1
  6. package/dist/agent/content-budget.d.ts.map +1 -1
  7. package/dist/agent/content-budget.js +4 -0
  8. package/dist/agent/content-budget.js.map +1 -1
  9. package/dist/agent/conversation-manager.d.ts.map +1 -1
  10. package/dist/agent/conversation-manager.js +20 -6
  11. package/dist/agent/conversation-manager.js.map +1 -1
  12. package/dist/agent/index.js +7 -2
  13. package/dist/agent/index.js.map +1 -1
  14. package/dist/agent/prompt.d.ts +1 -1
  15. package/dist/agent/prompt.d.ts.map +1 -1
  16. package/dist/agent/prompt.js +8 -0
  17. package/dist/agent/prompt.js.map +1 -1
  18. package/dist/agent/tool-usage-tracker.d.ts.map +1 -1
  19. package/dist/agent/tool-usage-tracker.js +7 -3
  20. package/dist/agent/tool-usage-tracker.js.map +1 -1
  21. package/dist/agent/types.d.ts +6 -0
  22. package/dist/agent/types.d.ts.map +1 -1
  23. package/dist/api/mcp-sse.d.ts +2 -1
  24. package/dist/api/mcp-sse.d.ts.map +1 -1
  25. package/dist/api/mcp-sse.js +2 -1
  26. package/dist/api/mcp-sse.js.map +1 -1
  27. package/dist/api/routes.d.ts +2 -1
  28. package/dist/api/routes.d.ts.map +1 -1
  29. package/dist/api/routes.js +289 -14
  30. package/dist/api/routes.js.map +1 -1
  31. package/dist/browser/BrowserManager.d.ts.map +1 -1
  32. package/dist/browser/BrowserManager.js +5 -2
  33. package/dist/browser/BrowserManager.js.map +1 -1
  34. package/dist/cli/mcp-stdio.js +3 -0
  35. package/dist/cli/mcp-stdio.js.map +1 -1
  36. package/dist/cli/server.js +15 -3
  37. package/dist/cli/server.js.map +1 -1
  38. package/dist/mcp/ai-markdown.d.ts.map +1 -1
  39. package/dist/mcp/ai-markdown.js +106 -38
  40. package/dist/mcp/ai-markdown.js.map +1 -1
  41. package/dist/mcp/browser-mcp-server.d.ts +2 -0
  42. package/dist/mcp/browser-mcp-server.d.ts.map +1 -1
  43. package/dist/mcp/browser-mcp-server.js +72 -13
  44. package/dist/mcp/browser-mcp-server.js.map +1 -1
  45. package/dist/mcp/task-tools.d.ts.map +1 -1
  46. package/dist/mcp/task-tools.js +1 -0
  47. package/dist/mcp/task-tools.js.map +1 -1
  48. package/dist/memory/KnowledgeCardStore.d.ts +35 -0
  49. package/dist/memory/KnowledgeCardStore.d.ts.map +1 -0
  50. package/dist/memory/KnowledgeCardStore.js +304 -0
  51. package/dist/memory/KnowledgeCardStore.js.map +1 -0
  52. package/dist/memory/MemoryCapturer.d.ts +14 -0
  53. package/dist/memory/MemoryCapturer.d.ts.map +1 -0
  54. package/dist/memory/MemoryCapturer.js +183 -0
  55. package/dist/memory/MemoryCapturer.js.map +1 -0
  56. package/dist/memory/MemoryInjector.d.ts +23 -0
  57. package/dist/memory/MemoryInjector.d.ts.map +1 -0
  58. package/dist/memory/MemoryInjector.js +180 -0
  59. package/dist/memory/MemoryInjector.js.map +1 -0
  60. package/dist/memory/RecordingConverter.d.ts +16 -0
  61. package/dist/memory/RecordingConverter.d.ts.map +1 -0
  62. package/dist/memory/RecordingConverter.js +108 -0
  63. package/dist/memory/RecordingConverter.js.map +1 -0
  64. package/dist/memory/SessionRecorder.d.ts +39 -0
  65. package/dist/memory/SessionRecorder.d.ts.map +1 -0
  66. package/dist/memory/SessionRecorder.js +198 -0
  67. package/dist/memory/SessionRecorder.js.map +1 -0
  68. package/dist/memory/index.d.ts +8 -0
  69. package/dist/memory/index.d.ts.map +1 -0
  70. package/dist/memory/index.js +6 -0
  71. package/dist/memory/index.js.map +1 -0
  72. package/dist/memory/types.d.ts +39 -0
  73. package/dist/memory/types.d.ts.map +1 -0
  74. package/dist/memory/types.js +2 -0
  75. package/dist/memory/types.js.map +1 -0
  76. package/dist/semantic/PageAnalyzer.d.ts.map +1 -1
  77. package/dist/semantic/PageAnalyzer.js +2 -1
  78. package/dist/semantic/PageAnalyzer.js.map +1 -1
  79. package/dist/task/templates/login-keep-session.d.ts.map +1 -1
  80. package/dist/task/templates/login-keep-session.js +2 -4
  81. package/dist/task/templates/login-keep-session.js.map +1 -1
  82. package/dist/task/tool-actions.d.ts.map +1 -1
  83. package/dist/task/tool-actions.js +6 -11
  84. package/dist/task/tool-actions.js.map +1 -1
  85. package/dist/utils/safe-page.d.ts +9 -0
  86. package/dist/utils/safe-page.d.ts.map +1 -0
  87. package/dist/utils/safe-page.js +14 -0
  88. package/dist/utils/safe-page.js.map +1 -0
  89. package/package.json +3 -1
  90. package/public/index.html +1651 -133
  91. package/public/task-result.html +107 -12
  92. package/public/tasks.html +83 -10
package/public/index.html CHANGED
@@ -4,50 +4,71 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>AI Browser</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
7
10
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
8
11
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
12
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
10
13
  <style>
11
14
  /* ===== CSS Design System ===== */
12
15
  :root {
13
- --bg-canvas: #0d1117;
14
- --bg-surface: #161b22;
15
- --bg-overlay: #21262d;
16
- --border: #30363d;
17
- --text-primary: #c9d1d9;
18
- --text-secondary: #8b949e;
19
- --text-muted: #484f58;
20
- --accent: #58a6ff;
21
- --green: #238636;
22
- --green-hover: #2ea043;
23
- --red: #da3633;
24
- --blue: #1f6feb;
25
- --purple: #8957e5;
26
- --orange: #f78166;
27
- --radius-sm: 4px;
28
- --radius-md: 6px;
29
- --radius-lg: 8px;
30
- --radius-xl: 12px;
31
- --shadow: 0 1px 3px rgba(0,0,0,0.3);
32
- --transition: 150ms ease;
16
+ --bg-canvas: #0a0e14;
17
+ --bg-surface: #12171f;
18
+ --bg-overlay: #1a2030;
19
+ --bg-elevated: #1e2536;
20
+ --border: #252d3a;
21
+ --border-subtle: #1c2333;
22
+ --border-accent: rgba(99,160,255,0.2);
23
+ --text-primary: #d4dae4;
24
+ --text-secondary: #8892a2;
25
+ --text-muted: #4a5568;
26
+ --accent: #63a0ff;
27
+ --accent-dim: rgba(99,160,255,0.12);
28
+ --accent-glow: rgba(99,160,255,0.25);
29
+ --green: #22c55e;
30
+ --green-dim: rgba(34,197,94,0.12);
31
+ --green-hover: #16a34a;
32
+ --red: #ef4444;
33
+ --red-dim: rgba(239,68,68,0.12);
34
+ --blue: #3b82f6;
35
+ --blue-dim: rgba(59,130,246,0.12);
36
+ --purple: #a78bfa;
37
+ --purple-dim: rgba(167,139,250,0.12);
38
+ --orange: #fb923c;
39
+ --orange-dim: rgba(251,146,60,0.12);
40
+ --yellow: #facc15;
41
+ --radius-sm: 6px;
42
+ --radius-md: 8px;
43
+ --radius-lg: 12px;
44
+ --radius-xl: 16px;
45
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.15);
46
+ --shadow: 0 2px 8px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.2);
47
+ --shadow-lg: 0 4px 16px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.2);
48
+ --shadow-glow: 0 0 20px rgba(99,160,255,0.08);
49
+ --transition: 180ms cubic-bezier(0.4, 0, 0.2, 1);
50
+ --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
33
51
  }
34
52
 
35
53
  * { box-sizing: border-box; margin: 0; padding: 0; }
36
54
 
37
55
  body {
38
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
56
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
39
57
  background: var(--bg-canvas);
58
+ background-image: radial-gradient(ellipse at 50% 0%, rgba(99,160,255,0.03) 0%, transparent 60%);
40
59
  color: var(--text-primary);
41
60
  height: 100vh;
42
61
  display: flex;
43
62
  flex-direction: column;
44
63
  overflow: hidden;
64
+ -webkit-font-smoothing: antialiased;
65
+ -moz-osx-font-smoothing: grayscale;
45
66
  }
46
67
 
47
68
  /* ===== Scrollbar ===== */
48
- ::-webkit-scrollbar { width: 6px; height: 6px; }
69
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
49
70
  ::-webkit-scrollbar-track { background: transparent; }
50
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
71
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
51
72
  ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
52
73
 
53
74
  /* ===== Header / Nav ===== */
@@ -57,9 +78,12 @@ body {
57
78
  padding: 0 20px;
58
79
  display: flex;
59
80
  align-items: center;
60
- height: 48px;
81
+ height: 52px;
61
82
  flex-shrink: 0;
62
83
  gap: 16px;
84
+ box-shadow: 0 1px 8px rgba(0,0,0,0.2);
85
+ position: relative;
86
+ z-index: 10;
63
87
  }
64
88
  .app-logo {
65
89
  color: var(--accent);
@@ -67,39 +91,40 @@ body {
67
91
  font-weight: 700;
68
92
  white-space: nowrap;
69
93
  letter-spacing: -0.3px;
94
+ background: linear-gradient(135deg, var(--accent), var(--purple));
95
+ -webkit-background-clip: text;
96
+ -webkit-text-fill-color: transparent;
97
+ background-clip: text;
70
98
  }
71
99
  .nav-tabs {
72
100
  display: flex;
73
101
  gap: 2px;
74
102
  margin-left: 8px;
103
+ background: var(--bg-canvas);
104
+ border-radius: var(--radius-md);
105
+ padding: 3px;
75
106
  }
76
107
  .nav-tab {
77
108
  padding: 6px 16px;
78
109
  font-size: 13px;
79
110
  font-weight: 500;
80
111
  border: none;
81
- border-radius: var(--radius-md);
112
+ border-radius: var(--radius-sm);
82
113
  background: transparent;
83
114
  color: var(--text-secondary);
84
115
  cursor: pointer;
85
116
  transition: all var(--transition);
86
117
  position: relative;
87
118
  }
119
+ .nav-tab .tab-icon { font-size: 14px; margin-right: 2px; }
88
120
  .nav-tab:hover { color: var(--text-primary); background: var(--bg-overlay); }
89
121
  .nav-tab.active {
90
122
  color: var(--accent);
91
- background: rgba(88,166,255,0.1);
123
+ background: var(--bg-overlay);
124
+ box-shadow: var(--shadow-sm);
92
125
  }
93
126
  .nav-tab.active::after {
94
- content: '';
95
- position: absolute;
96
- bottom: -7px;
97
- left: 50%;
98
- transform: translateX(-50%);
99
- width: 60%;
100
- height: 2px;
101
- background: var(--accent);
102
- border-radius: 1px;
127
+ display: none;
103
128
  }
104
129
  .header-right {
105
130
  display: flex;
@@ -109,7 +134,7 @@ body {
109
134
 
110
135
  /* ===== Shared Button Styles ===== */
111
136
  .btn {
112
- padding: 6px 14px;
137
+ padding: 7px 14px;
113
138
  font-size: 13px;
114
139
  border: 1px solid var(--border);
115
140
  border-radius: var(--radius-md);
@@ -119,34 +144,39 @@ body {
119
144
  background: var(--bg-overlay);
120
145
  color: var(--text-primary);
121
146
  white-space: nowrap;
147
+ box-shadow: var(--shadow-sm);
122
148
  }
123
- .btn:hover { border-color: var(--accent); color: var(--accent); }
149
+ .btn:hover { border-color: var(--accent); color: var(--accent); background: var(--bg-elevated); }
124
150
  .btn-primary {
125
- background: var(--green);
151
+ background: linear-gradient(135deg, var(--green), var(--green-hover));
126
152
  color: #fff;
127
- border-color: var(--green);
153
+ border-color: transparent;
154
+ box-shadow: 0 2px 8px rgba(34,197,94,0.2);
128
155
  }
129
- .btn-primary:hover { background: var(--green-hover); border-color: var(--green-hover); color: #fff; }
156
+ .btn-primary:hover { background: linear-gradient(135deg, var(--green-hover), #15803d); border-color: transparent; color: #fff; box-shadow: 0 2px 12px rgba(34,197,94,0.3); }
130
157
  .btn-primary:disabled {
131
158
  background: var(--bg-overlay);
132
159
  color: var(--text-muted);
133
160
  border-color: var(--border);
134
161
  cursor: not-allowed;
162
+ box-shadow: none;
135
163
  }
136
164
  .btn-danger { border-color: var(--red); color: var(--red); }
137
- .btn-danger:hover { background: var(--red); color: #fff; }
165
+ .btn-danger:hover { background: var(--red); color: #fff; box-shadow: 0 2px 8px rgba(239,68,68,0.25); }
138
166
  .btn-send {
139
- padding: 10px 20px;
140
- background: var(--green);
167
+ padding: 10px 24px;
168
+ background: linear-gradient(135deg, var(--accent), #4f8ef7);
141
169
  color: #fff;
142
170
  border: none;
143
171
  border-radius: var(--radius-lg);
144
172
  font-size: 14px;
145
173
  font-weight: 600;
146
174
  cursor: pointer;
175
+ box-shadow: 0 2px 10px rgba(99,160,255,0.25);
176
+ transition: all var(--transition);
147
177
  }
148
- .btn-send:hover { background: var(--green-hover); }
149
- .btn-send:disabled { background: var(--bg-overlay); color: var(--text-muted); cursor: not-allowed; }
178
+ .btn-send:hover { box-shadow: 0 4px 16px rgba(99,160,255,0.35); transform: translateY(-1px); }
179
+ .btn-send:disabled { background: var(--bg-overlay); color: var(--text-muted); cursor: not-allowed; box-shadow: none; transform: none; }
150
180
 
151
181
  /* ===== Main Layout ===== */
152
182
  .app-main {
@@ -165,15 +195,17 @@ body {
165
195
  animation: viewFadeIn 200ms ease;
166
196
  }
167
197
  @keyframes viewFadeIn {
168
- from { opacity: 0; }
169
- to { opacity: 1; }
198
+ from { opacity: 0; transform: translateY(6px); }
199
+ to { opacity: 1; transform: translateY(0); }
170
200
  }
171
201
 
172
202
  /* ===== Shared Modal ===== */
173
203
  .modal-overlay {
174
204
  position: fixed;
175
205
  inset: 0;
176
- background: rgba(0,0,0,0.7);
206
+ background: rgba(0,0,0,0.75);
207
+ backdrop-filter: blur(8px);
208
+ -webkit-backdrop-filter: blur(8px);
177
209
  display: flex;
178
210
  align-items: center;
179
211
  justify-content: center;
@@ -183,41 +215,43 @@ body {
183
215
  .modal {
184
216
  background: var(--bg-surface);
185
217
  border: 1px solid var(--border);
186
- border-radius: 10px;
187
- padding: 24px;
218
+ border-radius: var(--radius-xl);
219
+ padding: 28px;
188
220
  width: 440px;
189
221
  max-width: 90vw;
222
+ box-shadow: var(--shadow-lg), var(--shadow-glow);
190
223
  }
191
- .modal h2 { font-size: 16px; color: var(--text-primary); margin-bottom: 16px; }
224
+ .modal h2 { font-size: 16px; color: var(--text-primary); margin-bottom: 16px; font-weight: 600; }
192
225
  .modal label {
193
226
  display: block;
194
227
  font-size: 13px;
195
228
  color: var(--text-secondary);
196
229
  margin-bottom: 4px;
197
- margin-top: 12px;
230
+ margin-top: 14px;
198
231
  }
199
232
  .modal label:first-of-type { margin-top: 0; }
200
233
  .modal input[type="text"],
201
234
  .modal input[type="password"],
202
235
  .modal input[type="number"] {
203
236
  width: 100%;
204
- padding: 8px 12px;
237
+ padding: 9px 12px;
205
238
  font-size: 14px;
206
239
  border: 1px solid var(--border);
207
240
  border-radius: var(--radius-md);
208
241
  background: var(--bg-canvas);
209
242
  color: var(--text-primary);
210
243
  outline: none;
244
+ transition: all var(--transition);
211
245
  }
212
- .modal input:focus { border-color: var(--accent); }
246
+ .modal input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
213
247
  .modal-btns {
214
248
  display: flex;
215
249
  gap: 8px;
216
250
  justify-content: flex-end;
217
251
  margin-top: 20px;
218
252
  }
219
- .btn-save { background: var(--green); color: #fff; border-color: var(--green); }
220
- .btn-save:hover { background: var(--green-hover); color: #fff; }
253
+ .btn-save { background: linear-gradient(135deg, var(--green), var(--green-hover)); color: #fff; border-color: transparent; box-shadow: 0 2px 8px rgba(34,197,94,0.2); }
254
+ .btn-save:hover { background: linear-gradient(135deg, var(--green-hover), #15803d); color: #fff; }
221
255
 
222
256
  /* ===== Shared Markdown ===== */
223
257
  .markdown-body {
@@ -241,20 +275,21 @@ body {
241
275
  }
242
276
  #view-semantic .sem-toolbar input[type="text"] {
243
277
  flex: 1;
244
- padding: 8px 14px;
278
+ padding: 9px 14px;
245
279
  font-size: 14px;
246
280
  border: 1px solid var(--border);
247
281
  border-radius: var(--radius-md);
248
282
  background: var(--bg-canvas);
249
283
  color: var(--text-primary);
250
284
  outline: none;
285
+ transition: all var(--transition);
251
286
  }
252
- #view-semantic .sem-toolbar input:focus { border-color: var(--accent); }
287
+ #view-semantic .sem-toolbar input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
253
288
 
254
289
  .sem-stats-bar {
255
290
  background: var(--bg-overlay);
256
291
  border-bottom: 1px solid var(--border);
257
- padding: 6px 20px;
292
+ padding: 7px 20px;
258
293
  display: flex;
259
294
  gap: 16px;
260
295
  font-size: 12px;
@@ -264,7 +299,7 @@ body {
264
299
  .sem-stats-bar .stat-item {
265
300
  display: flex;
266
301
  align-items: center;
267
- gap: 4px;
302
+ gap: 5px;
268
303
  }
269
304
  .sem-stats-bar .stat-val {
270
305
  color: var(--accent);
@@ -301,15 +336,16 @@ body {
301
336
  flex-shrink: 0;
302
337
  }
303
338
  .sem-content-tab {
304
- padding: 4px 12px;
339
+ padding: 5px 12px;
305
340
  font-size: 12px;
306
341
  border: 1px solid var(--border);
307
342
  border-radius: var(--radius-sm);
308
343
  background: transparent;
309
344
  color: var(--text-secondary);
310
345
  cursor: pointer;
346
+ transition: all var(--transition);
311
347
  }
312
- .sem-content-tab.active { background: var(--bg-overlay); color: var(--accent); border-color: var(--accent); }
348
+ .sem-content-tab.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
313
349
 
314
350
  .sem-content-area {
315
351
  flex: 1;
@@ -358,14 +394,14 @@ body {
358
394
  display: flex;
359
395
  align-items: center;
360
396
  justify-content: space-between;
361
- padding: 8px 12px;
397
+ padding: 10px 14px;
362
398
  border: 1px solid var(--border);
363
399
  border-radius: var(--radius-md);
364
400
  margin-bottom: 6px;
365
401
  background: var(--bg-canvas);
366
- transition: all 0.15s;
402
+ transition: all var(--transition);
367
403
  }
368
- .element-item:hover { border-color: var(--accent); background: var(--bg-surface); }
404
+ .element-item:hover { border-color: var(--accent); background: var(--bg-surface); box-shadow: 0 0 0 1px var(--accent-dim); }
369
405
  .element-info { flex: 1; min-width: 0; }
370
406
  .element-type {
371
407
  display: inline-block;
@@ -374,11 +410,12 @@ body {
374
410
  font-size: 11px;
375
411
  font-weight: 600;
376
412
  margin-right: 8px;
413
+ letter-spacing: 0.3px;
377
414
  }
378
- .element-type.button { background: var(--green); color: white; }
379
- .element-type.link { background: var(--blue); color: white; }
380
- .element-type.textbox { background: var(--purple); color: white; }
381
- .element-type.checkbox { background: var(--orange); color: white; }
415
+ .element-type.button { background: var(--green-dim); color: var(--green); }
416
+ .element-type.link { background: var(--blue-dim); color: var(--accent); }
417
+ .element-type.textbox { background: var(--purple-dim); color: var(--purple); }
418
+ .element-type.checkbox { background: var(--orange-dim); color: var(--orange); }
382
419
  .element-label { font-weight: 500; color: var(--text-primary); font-size: 13px; }
383
420
  .element-id {
384
421
  font-size: 11px;
@@ -416,15 +453,16 @@ body {
416
453
  }
417
454
  .filter-bar.hidden { display: none; }
418
455
  .filter-btn {
419
- padding: 3px 10px;
456
+ padding: 4px 10px;
420
457
  font-size: 11px;
421
458
  background: var(--bg-overlay);
422
459
  color: var(--text-secondary);
423
460
  border: 1px solid var(--border);
424
461
  border-radius: var(--radius-sm);
425
462
  cursor: pointer;
463
+ transition: all var(--transition);
426
464
  }
427
- .filter-btn.active { background: var(--blue); color: white; border-color: var(--blue); }
465
+ .filter-btn.active { background: var(--blue-dim); color: var(--accent); border-color: var(--accent); }
428
466
 
429
467
  /* Log drawer */
430
468
  .sem-log-drawer {
@@ -474,15 +512,16 @@ body {
474
512
  display: none;
475
513
  }
476
514
  .sem-status.visible { display: block; }
477
- .sem-status.loading { background: #1f2937; color: #60a5fa; }
478
- .sem-status.success { background: #064e3b; color: #34d399; }
479
- .sem-status.error { background: #7f1d1d; color: #fca5a5; }
515
+ .sem-status.loading { background: var(--accent-dim); color: var(--accent); }
516
+ .sem-status.success { background: var(--green-dim); color: var(--green); }
517
+ .sem-status.error { background: var(--red-dim); color: var(--red); }
480
518
 
481
519
  .placeholder-text {
482
520
  color: var(--text-muted);
483
521
  font-size: 14px;
484
522
  text-align: center;
485
- padding: 40px 20px;
523
+ padding: 48px 20px;
524
+ line-height: 1.6;
486
525
  }
487
526
  </style>
488
527
  <style>
@@ -502,22 +541,23 @@ body {
502
541
  }
503
542
  .chat-input-bar {
504
543
  border-top: 1px solid var(--border);
505
- padding: 12px 16px;
544
+ padding: 14px 16px;
506
545
  display: flex;
507
- gap: 8px;
546
+ gap: 10px;
508
547
  background: var(--bg-surface);
509
548
  }
510
549
  .chat-input-bar input {
511
550
  flex: 1;
512
- padding: 10px 14px;
551
+ padding: 10px 16px;
513
552
  font-size: 14px;
514
553
  border: 1px solid var(--border);
515
554
  border-radius: var(--radius-lg);
516
555
  background: var(--bg-canvas);
517
556
  color: var(--text-primary);
518
557
  outline: none;
558
+ transition: all var(--transition);
519
559
  }
520
- .chat-input-bar input:focus { border-color: var(--accent); }
560
+ .chat-input-bar input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
521
561
 
522
562
  /* Preview panel */
523
563
  .preview-panel {
@@ -536,15 +576,16 @@ body {
536
576
  }
537
577
  .preview-tabs { display: flex; gap: 4px; }
538
578
  .preview-tab {
539
- padding: 4px 14px;
579
+ padding: 5px 14px;
540
580
  font-size: 12px;
541
581
  border: 1px solid var(--border);
542
582
  border-radius: var(--radius-sm);
543
583
  background: transparent;
544
584
  color: var(--text-secondary);
545
585
  cursor: pointer;
586
+ transition: all var(--transition);
546
587
  }
547
- .preview-tab.active { background: var(--bg-overlay); color: var(--accent); border-color: var(--accent); }
588
+ .preview-tab.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
548
589
  .preview-url {
549
590
  font-size: 12px;
550
591
  color: var(--accent);
@@ -635,14 +676,15 @@ body {
635
676
  .msg-user { text-align: right; }
636
677
  .msg-user .bubble {
637
678
  display: inline-block;
638
- background: var(--blue);
679
+ background: linear-gradient(135deg, var(--blue), #2563eb);
639
680
  color: #fff;
640
- padding: 8px 14px;
641
- border-radius: 12px 12px 2px 12px;
681
+ padding: 10px 16px;
682
+ border-radius: 16px 16px 4px 16px;
642
683
  max-width: 85%;
643
684
  text-align: left;
644
685
  font-size: 14px;
645
686
  animation: fadeIn 200ms ease;
687
+ box-shadow: 0 2px 8px rgba(59,130,246,0.2);
646
688
  }
647
689
  .msg-system {
648
690
  text-align: center;
@@ -654,19 +696,21 @@ body {
654
696
  background: var(--bg-surface);
655
697
  border: 1px solid var(--border);
656
698
  border-radius: var(--radius-lg);
657
- padding: 8px 12px;
699
+ padding: 10px 14px;
658
700
  font-size: 13px;
659
701
  color: var(--text-secondary);
660
702
  font-style: italic;
661
703
  animation: fadeIn 200ms ease;
704
+ border-left: 3px solid var(--accent);
662
705
  }
663
706
  .msg-thinking .markdown-body { font-style: normal; font-size: 13px; }
664
707
  .msg .md-inline { font-style: normal; }
665
708
  .msg-tool-call {
666
709
  background: var(--bg-canvas);
667
- border: 1px solid var(--blue);
710
+ border: 1px solid var(--border);
711
+ border-left: 3px solid var(--accent);
668
712
  border-radius: var(--radius-lg);
669
- padding: 8px 12px;
713
+ padding: 10px 14px;
670
714
  font-size: 13px;
671
715
  animation: fadeIn 200ms ease;
672
716
  }
@@ -716,8 +760,8 @@ body {
716
760
  font-size: 13px;
717
761
  animation: fadeIn 200ms ease;
718
762
  }
719
- .msg-tool-result.success { border: 1px solid var(--green); }
720
- .msg-tool-result.fail { border: 1px solid var(--red); }
763
+ .msg-tool-result.success { border: 1px solid var(--border); border-left: 3px solid var(--green); }
764
+ .msg-tool-result.fail { border: 1px solid var(--border); border-left: 3px solid var(--red); }
721
765
  .msg-tool-result .result-content {
722
766
  max-height: 120px;
723
767
  overflow: hidden;
@@ -756,34 +800,58 @@ body {
756
800
  }
757
801
 
758
802
  .msg-error {
759
- background: #1c0c0c;
760
- border: 1px solid var(--red);
803
+ background: var(--red-dim);
804
+ border: 1px solid var(--border);
805
+ border-left: 3px solid var(--red);
761
806
  border-radius: var(--radius-lg);
762
- padding: 8px 12px;
807
+ padding: 10px 14px;
763
808
  font-size: 13px;
764
809
  color: #fca5a5;
765
810
  animation: fadeIn 200ms ease;
766
811
  }
767
812
  .msg-done {
768
- border-radius: 10px;
769
- padding: 12px 16px;
813
+ border-radius: var(--radius-lg);
814
+ padding: 14px 18px;
770
815
  font-size: 14px;
771
816
  animation: fadeIn 200ms ease;
772
817
  }
773
- .msg-done.success { background: #0a2e1a; border: 2px solid var(--green); }
774
- .msg-done.fail { background: #2d0a0a; border: 2px solid var(--red); }
818
+ .msg-done.success { background: var(--green-dim); border: 1px solid var(--green); border-left: 4px solid var(--green); }
819
+ .msg-done.fail { background: var(--red-dim); border: 1px solid var(--red); border-left: 4px solid var(--red); }
775
820
  .msg-done .title { font-weight: 700; margin-bottom: 4px; }
776
821
  .msg-done .markdown-body { font-size: 14px; }
777
822
 
823
+ .msg-memory-recall {
824
+ background: linear-gradient(135deg, rgba(139, 92, 246, 0.08), rgba(59, 130, 246, 0.08));
825
+ border: 1px solid rgba(139, 92, 246, 0.3);
826
+ border-left: 3px solid #8b5cf6;
827
+ border-radius: var(--radius-lg);
828
+ padding: 10px 14px;
829
+ font-size: 13px;
830
+ color: #c4b5fd;
831
+ animation: fadeIn 200ms ease;
832
+ }
833
+ .memory-recall-hint {
834
+ display: flex;
835
+ align-items: center;
836
+ gap: 6px;
837
+ }
838
+ .memory-recall-hint .memory-icon {
839
+ font-size: 16px;
840
+ }
841
+ .memory-recall-hint strong {
842
+ color: #a78bfa;
843
+ }
844
+
778
845
  .step-tag {
779
846
  display: inline-block;
780
- background: var(--bg-overlay);
781
- color: var(--text-secondary);
847
+ background: var(--accent-dim);
848
+ color: var(--accent);
782
849
  font-size: 11px;
783
- padding: 1px 6px;
784
- border-radius: 3px;
850
+ padding: 2px 8px;
851
+ border-radius: var(--radius-sm);
785
852
  margin-right: 6px;
786
853
  font-style: normal;
854
+ font-weight: 600;
787
855
  }
788
856
 
789
857
  /* Progress bar */
@@ -798,10 +866,11 @@ body {
798
866
  .progress-bar-wrap.active { display: block; }
799
867
  .progress-bar-fill {
800
868
  height: 100%;
801
- background: var(--accent);
869
+ background: linear-gradient(90deg, var(--accent), var(--purple));
802
870
  border-radius: 2px;
803
871
  transition: width 0.4s ease;
804
872
  width: 0%;
873
+ box-shadow: 0 0 8px rgba(99,160,255,0.4);
805
874
  }
806
875
  .progress-info {
807
876
  display: none;
@@ -844,7 +913,9 @@ body {
844
913
  .type-input-modal {
845
914
  position: fixed;
846
915
  inset: 0;
847
- background: rgba(0,0,0,0.8);
916
+ background: rgba(0,0,0,0.75);
917
+ backdrop-filter: blur(8px);
918
+ -webkit-backdrop-filter: blur(8px);
848
919
  display: flex;
849
920
  align-items: center;
850
921
  justify-content: center;
@@ -854,9 +925,10 @@ body {
854
925
  .type-input-modal .modal-content {
855
926
  background: var(--bg-surface);
856
927
  border: 1px solid var(--border);
857
- border-radius: var(--radius-lg);
858
- padding: 24px;
928
+ border-radius: var(--radius-xl);
929
+ padding: 28px;
859
930
  width: 400px;
931
+ box-shadow: var(--shadow-lg), var(--shadow-glow);
860
932
  }
861
933
  .type-input-modal .modal-title {
862
934
  font-size: 16px;
@@ -873,8 +945,9 @@ body {
873
945
  color: var(--text-primary);
874
946
  outline: none;
875
947
  margin-bottom: 16px;
948
+ transition: all var(--transition);
876
949
  }
877
- .type-input-modal .modal-input:focus { border-color: var(--accent); }
950
+ .type-input-modal .modal-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
878
951
  .type-input-modal .modal-buttons {
879
952
  display: flex;
880
953
  gap: 10px;
@@ -904,11 +977,12 @@ body {
904
977
  background: var(--bg-surface);
905
978
  border: 1px solid var(--border);
906
979
  border-radius: var(--radius-lg);
907
- padding: 14px;
980
+ padding: 16px;
908
981
  cursor: pointer;
909
982
  transition: all var(--transition);
983
+ box-shadow: var(--shadow-sm);
910
984
  }
911
- .task-card:hover { border-color: var(--accent); }
985
+ .task-card:hover { border-color: var(--accent); box-shadow: var(--shadow), 0 0 0 1px var(--accent-dim); transform: translateY(-1px); }
912
986
  .task-card .task-header {
913
987
  display: flex;
914
988
  justify-content: space-between;
@@ -922,14 +996,15 @@ body {
922
996
  }
923
997
  .task-status {
924
998
  display: inline-block;
925
- padding: 2px 8px;
999
+ padding: 3px 10px;
926
1000
  border-radius: var(--radius-sm);
927
1001
  font-size: 11px;
928
1002
  font-weight: 600;
1003
+ letter-spacing: 0.3px;
929
1004
  }
930
- .task-status.running { background: rgba(88,166,255,0.15); color: var(--accent); }
931
- .task-status.done { background: rgba(35,134,54,0.15); color: #3fb950; }
932
- .task-status.failed { background: rgba(218,54,51,0.15); color: #f85149; }
1005
+ .task-status.running { background: var(--accent-dim); color: var(--accent); }
1006
+ .task-status.done { background: var(--green-dim); color: var(--green); }
1007
+ .task-status.failed { background: var(--red-dim); color: var(--red); }
933
1008
  .task-status.pending { background: var(--bg-overlay); color: var(--text-muted); }
934
1009
  .task-card .task-goal {
935
1010
  font-size: 14px;
@@ -994,27 +1069,620 @@ body {
994
1069
  .task-create-form input,
995
1070
  .task-create-form textarea {
996
1071
  width: 100%;
997
- padding: 8px 12px;
1072
+ padding: 9px 12px;
998
1073
  font-size: 14px;
999
1074
  border: 1px solid var(--border);
1000
1075
  border-radius: var(--radius-md);
1001
1076
  background: var(--bg-canvas);
1002
1077
  color: var(--text-primary);
1003
1078
  outline: none;
1079
+ transition: all var(--transition);
1004
1080
  }
1005
1081
  .task-create-form input:focus,
1006
- .task-create-form textarea:focus { border-color: var(--accent); }
1082
+ .task-create-form textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
1007
1083
  .task-create-form textarea { min-height: 80px; resize: vertical; }
1008
1084
  .task-create-form .form-row {
1009
1085
  display: grid;
1010
1086
  grid-template-columns: 1fr 1fr;
1011
1087
  gap: 12px;
1012
1088
  }
1013
- .task-create-form .form-actions {
1014
- margin-top: 16px;
1089
+ .task-create-form .form-actions {
1090
+ margin-top: 16px;
1091
+ display: flex;
1092
+ gap: 8px;
1093
+ }
1094
+
1095
+ /* ===== Memory View Styles ===== */
1096
+ .memory-toolbar {
1097
+ background: var(--bg-surface);
1098
+ border-bottom: 1px solid var(--border);
1099
+ padding: 10px 20px;
1100
+ display: flex;
1101
+ gap: 8px;
1102
+ align-items: center;
1103
+ flex-shrink: 0;
1104
+ }
1105
+ .memory-list {
1106
+ flex: 1;
1107
+ overflow-y: auto;
1108
+ padding: 16px;
1109
+ display: grid;
1110
+ grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
1111
+ gap: 12px;
1112
+ align-content: start;
1113
+ }
1114
+ .memory-card {
1115
+ background: var(--bg-surface);
1116
+ border: 1px solid var(--border);
1117
+ border-radius: var(--radius-lg);
1118
+ padding: 16px;
1119
+ cursor: pointer;
1120
+ transition: all var(--transition);
1121
+ box-shadow: var(--shadow-sm);
1122
+ }
1123
+ .memory-card:hover { border-color: var(--accent); box-shadow: var(--shadow), 0 0 0 1px var(--accent-dim); transform: translateY(-1px); }
1124
+ .memory-card .memory-domain {
1125
+ font-size: 15px;
1126
+ font-weight: 600;
1127
+ color: var(--text-primary);
1128
+ margin-bottom: 6px;
1129
+ display: flex;
1130
+ align-items: center;
1131
+ gap: 8px;
1132
+ }
1133
+ .memory-card .memory-domain .site-type {
1134
+ font-size: 10px;
1135
+ font-weight: 600;
1136
+ padding: 2px 6px;
1137
+ border-radius: var(--radius-sm);
1138
+ background: var(--purple-dim);
1139
+ color: var(--purple);
1140
+ text-transform: uppercase;
1141
+ letter-spacing: 0.5px;
1142
+ }
1143
+ .memory-card .memory-domain .login-badge {
1144
+ font-size: 10px;
1145
+ font-weight: 600;
1146
+ padding: 2px 6px;
1147
+ border-radius: var(--radius-sm);
1148
+ background: var(--orange-dim);
1149
+ color: var(--orange);
1150
+ }
1151
+ .memory-card .memory-meta {
1152
+ font-size: 12px;
1153
+ color: var(--text-muted);
1154
+ margin-bottom: 8px;
1155
+ }
1156
+ .memory-card .memory-patterns {
1157
+ display: flex;
1158
+ flex-wrap: wrap;
1159
+ gap: 4px;
1160
+ }
1161
+ .memory-card .pattern-tag {
1162
+ font-size: 11px;
1163
+ padding: 2px 8px;
1164
+ border-radius: var(--radius-sm);
1165
+ background: var(--bg-overlay);
1166
+ color: var(--text-secondary);
1167
+ border: 1px solid var(--border-subtle);
1168
+ max-width: 200px;
1169
+ overflow: hidden;
1170
+ text-overflow: ellipsis;
1171
+ white-space: nowrap;
1172
+ }
1173
+ .memory-detail {
1174
+ flex: 1;
1175
+ overflow-y: auto;
1176
+ padding: 16px;
1177
+ }
1178
+ .memory-detail-header {
1179
+ display: flex;
1180
+ align-items: center;
1181
+ gap: 12px;
1182
+ margin-bottom: 16px;
1183
+ }
1184
+ .memory-detail .pattern-list {
1185
+ display: flex;
1186
+ flex-direction: column;
1187
+ gap: 8px;
1188
+ }
1189
+ .memory-detail .pattern-item {
1190
+ background: var(--bg-canvas);
1191
+ border: 1px solid var(--border);
1192
+ border-radius: var(--radius-md);
1193
+ padding: 12px 14px;
1194
+ transition: all var(--transition);
1195
+ }
1196
+ .memory-detail .pattern-item:hover { border-color: var(--border); background: var(--bg-surface); }
1197
+ .memory-detail .pattern-type {
1198
+ display: inline-block;
1199
+ font-size: 10px;
1200
+ font-weight: 600;
1201
+ padding: 2px 6px;
1202
+ border-radius: var(--radius-sm);
1203
+ margin-right: 6px;
1204
+ text-transform: uppercase;
1205
+ letter-spacing: 0.5px;
1206
+ }
1207
+ .memory-detail .pattern-type.selector { background: var(--green-dim); color: var(--green); }
1208
+ .memory-detail .pattern-type.navigation_path { background: var(--blue-dim); color: var(--accent); }
1209
+ .memory-detail .pattern-type.login_required { background: var(--orange-dim); color: var(--orange); }
1210
+ .memory-detail .pattern-type.spa_hint { background: var(--purple-dim); color: var(--purple); }
1211
+ .memory-detail .pattern-type.page_structure { background: var(--accent-dim); color: var(--accent); }
1212
+ .memory-detail .pattern-desc { font-size: 13px; color: var(--text-primary); margin-top: 4px; }
1213
+ .memory-detail .pattern-value {
1214
+ font-size: 12px;
1215
+ font-family: 'Monaco', 'Menlo', monospace;
1216
+ color: var(--text-secondary);
1217
+ margin-top: 4px;
1218
+ padding: 4px 8px;
1219
+ background: var(--bg-overlay);
1220
+ border-radius: var(--radius-sm);
1221
+ overflow: hidden;
1222
+ text-overflow: ellipsis;
1223
+ white-space: nowrap;
1224
+ }
1225
+ .memory-detail .pattern-meta {
1226
+ font-size: 11px;
1227
+ color: var(--text-muted);
1228
+ margin-top: 4px;
1229
+ display: flex;
1230
+ gap: 12px;
1231
+ }
1232
+ .memory-empty {
1233
+ display: flex;
1234
+ flex-direction: column;
1235
+ align-items: center;
1236
+ justify-content: center;
1237
+ padding: 60px 20px;
1238
+ color: var(--text-muted);
1239
+ text-align: center;
1240
+ }
1241
+ .memory-empty .empty-icon {
1242
+ font-size: 48px;
1243
+ margin-bottom: 16px;
1244
+ opacity: 0.3;
1245
+ }
1246
+
1247
+ /* ===== Recording View ===== */
1248
+ .rec-state { display: none; }
1249
+ .rec-state.active { display: flex; flex-direction: column; height: 100%; }
1250
+
1251
+ .rec-state.rec-setup {
1252
+ align-items: center;
1253
+ justify-content: center;
1254
+ padding: 60px 20px;
1255
+ gap: 20px;
1256
+ }
1257
+ .rec-setup .rec-url-row {
1258
+ display: flex;
1259
+ gap: 8px;
1260
+ width: 100%;
1261
+ max-width: 600px;
1262
+ }
1263
+ .rec-setup .rec-url-row input {
1264
+ flex: 1;
1265
+ }
1266
+ .rec-setup .rec-hint {
1267
+ font-size: 13px;
1268
+ color: var(--text-muted);
1269
+ text-align: center;
1270
+ max-width: 480px;
1271
+ line-height: 1.6;
1272
+ }
1273
+
1274
+ .rec-split {
1275
+ display: flex;
1276
+ flex: 1;
1277
+ overflow: hidden;
1278
+ }
1279
+ .rec-left-panel {
1280
+ width: 50%;
1281
+ border-right: 1px solid var(--border);
1282
+ display: flex;
1283
+ flex-direction: column;
1284
+ overflow: hidden;
1285
+ }
1286
+ .rec-right-panel {
1287
+ width: 50%;
1288
+ display: flex;
1289
+ flex-direction: column;
1290
+ overflow: hidden;
1291
+ }
1292
+
1293
+ /* Recording indicator bar */
1294
+ .rec-indicator-bar {
1295
+ display: flex;
1296
+ align-items: center;
1297
+ gap: 10px;
1298
+ padding: 10px 16px;
1299
+ background: var(--bg-overlay);
1300
+ border-bottom: 1px solid var(--border);
1301
+ flex-shrink: 0;
1302
+ }
1303
+ .rec-dot {
1304
+ width: 10px;
1305
+ height: 10px;
1306
+ border-radius: 50%;
1307
+ background: var(--red);
1308
+ animation: recPulse 1.2s ease-in-out infinite;
1309
+ }
1310
+ @keyframes recPulse {
1311
+ 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(239,68,68,0.5); }
1312
+ 50% { opacity: 0.6; box-shadow: 0 0 0 6px rgba(239,68,68,0); }
1313
+ }
1314
+ .rec-indicator-bar .rec-timer {
1315
+ font-size: 14px;
1316
+ font-weight: 600;
1317
+ color: var(--text-primary);
1318
+ font-variant-numeric: tabular-nums;
1319
+ }
1320
+ .rec-indicator-bar .rec-event-count {
1321
+ font-size: 12px;
1322
+ color: var(--text-secondary);
1323
+ margin-left: auto;
1324
+ }
1325
+ .rec-indicator-bar .btn-danger {
1326
+ flex-shrink: 0;
1327
+ }
1328
+
1329
+ /* Event stream */
1330
+ .rec-event-stream {
1331
+ flex: 1;
1332
+ overflow-y: auto;
1333
+ padding: 8px 0;
1334
+ }
1335
+ .rec-event-item {
1336
+ display: flex;
1337
+ align-items: center;
1338
+ gap: 8px;
1339
+ padding: 6px 16px;
1340
+ font-size: 12px;
1341
+ color: var(--text-secondary);
1342
+ animation: recFadeIn 0.3s ease;
1343
+ }
1344
+ @keyframes recFadeIn {
1345
+ from { opacity: 0; transform: translateY(-4px); }
1346
+ to { opacity: 1; transform: translateY(0); }
1347
+ }
1348
+ .rec-event-item .ev-icon {
1349
+ font-size: 14px;
1350
+ width: 20px;
1351
+ text-align: center;
1352
+ flex-shrink: 0;
1353
+ }
1354
+ .rec-event-item .ev-badge {
1355
+ font-size: 10px;
1356
+ padding: 1px 6px;
1357
+ border-radius: 3px;
1358
+ font-weight: 600;
1359
+ text-transform: uppercase;
1360
+ flex-shrink: 0;
1361
+ }
1362
+ .ev-badge.navigate { background: var(--blue-dim); color: var(--blue); }
1363
+ .ev-badge.click { background: var(--green-dim); color: var(--green); }
1364
+ .ev-badge.type { background: var(--purple-dim); color: var(--purple); }
1365
+ .ev-badge.select { background: var(--orange-dim); color: var(--orange); }
1366
+ .ev-badge.scroll { background: var(--accent-dim); color: var(--accent); }
1367
+ .rec-event-item .ev-desc {
1368
+ flex: 1;
1369
+ overflow: hidden;
1370
+ text-overflow: ellipsis;
1371
+ white-space: nowrap;
1372
+ }
1373
+ .rec-event-item .ev-time {
1374
+ font-size: 10px;
1375
+ color: var(--text-muted);
1376
+ flex-shrink: 0;
1377
+ }
1378
+
1379
+ /* Screenshot preview */
1380
+ .rec-preview-header, .rec-review-header {
1381
+ padding: 10px 16px;
1382
+ font-size: 12px;
1383
+ font-weight: 600;
1384
+ color: var(--text-secondary);
1385
+ border-bottom: 1px solid var(--border);
1386
+ flex-shrink: 0;
1387
+ }
1388
+ .rec-preview-body {
1389
+ flex: 1;
1390
+ overflow: auto;
1391
+ display: flex;
1392
+ align-items: flex-start;
1393
+ justify-content: center;
1394
+ padding: 12px;
1395
+ background: var(--bg-canvas);
1396
+ }
1397
+ .rec-preview-body img {
1398
+ max-width: 100%;
1399
+ border-radius: var(--radius-sm);
1400
+ border: 1px solid var(--border);
1401
+ }
1402
+ .rec-preview-body .skeleton {
1403
+ width: 100%;
1404
+ aspect-ratio: 16/10;
1405
+ background: linear-gradient(90deg, var(--bg-overlay) 25%, var(--bg-elevated) 50%, var(--bg-overlay) 75%);
1406
+ background-size: 200% 100%;
1407
+ animation: shimmer 1.5s infinite;
1408
+ border-radius: var(--radius-sm);
1409
+ }
1410
+ @keyframes shimmer {
1411
+ 0% { background-position: 200% 0; }
1412
+ 100% { background-position: -200% 0; }
1413
+ }
1414
+
1415
+ /* Review state */
1416
+ .rec-summary {
1417
+ padding: 16px;
1418
+ border-bottom: 1px solid var(--border);
1419
+ display: flex;
1420
+ flex-wrap: wrap;
1421
+ gap: 16px;
1422
+ flex-shrink: 0;
1423
+ }
1424
+ .rec-summary .sum-item {
1425
+ font-size: 12px;
1426
+ color: var(--text-secondary);
1427
+ }
1428
+ .rec-summary .sum-item strong {
1429
+ color: var(--text-primary);
1430
+ font-weight: 600;
1431
+ }
1432
+ .rec-timeline {
1433
+ flex: 1;
1434
+ overflow-y: auto;
1435
+ padding: 8px 0;
1436
+ }
1437
+ .rec-patterns-body {
1438
+ flex: 1;
1439
+ overflow-y: auto;
1440
+ padding: 12px 16px;
1441
+ }
1442
+ .rec-pattern-item {
1443
+ padding: 10px 12px;
1444
+ background: var(--bg-overlay);
1445
+ border-radius: var(--radius-sm);
1446
+ margin-bottom: 8px;
1447
+ font-size: 12px;
1448
+ }
1449
+ .rec-pattern-item .pat-type {
1450
+ font-size: 10px;
1451
+ padding: 1px 6px;
1452
+ border-radius: 3px;
1453
+ font-weight: 600;
1454
+ background: var(--accent-dim);
1455
+ color: var(--accent);
1456
+ margin-right: 6px;
1457
+ }
1458
+ .rec-pattern-item .pat-desc {
1459
+ color: var(--text-primary);
1460
+ margin-top: 4px;
1461
+ }
1462
+ .rec-pattern-item .pat-value {
1463
+ color: var(--text-muted);
1464
+ font-family: monospace;
1465
+ font-size: 11px;
1466
+ margin-top: 4px;
1467
+ word-break: break-all;
1468
+ }
1469
+ .rec-intent-section {
1470
+ padding: 12px 16px;
1471
+ border-bottom: 1px solid var(--border);
1472
+ }
1473
+ .rec-intent-header {
1474
+ display: flex;
1475
+ align-items: center;
1476
+ justify-content: space-between;
1477
+ font-size: 12px;
1478
+ font-weight: 600;
1479
+ color: var(--text-secondary);
1480
+ text-transform: uppercase;
1481
+ letter-spacing: 0.5px;
1482
+ margin-bottom: 8px;
1483
+ }
1484
+ .rec-intent-hint {
1485
+ font-weight: 400;
1486
+ text-transform: none;
1487
+ letter-spacing: 0;
1488
+ color: var(--text-muted);
1489
+ font-size: 11px;
1490
+ }
1491
+ .rec-intent-textarea {
1492
+ width: 100%;
1493
+ min-height: 80px;
1494
+ max-height: 200px;
1495
+ padding: 10px 12px;
1496
+ border: 1px solid var(--border);
1497
+ border-radius: var(--radius-sm);
1498
+ background: var(--bg-canvas);
1499
+ color: var(--text-primary);
1500
+ font-family: inherit;
1501
+ font-size: 13px;
1502
+ line-height: 1.5;
1503
+ resize: vertical;
1504
+ box-sizing: border-box;
1505
+ }
1506
+ .rec-intent-textarea:focus {
1507
+ outline: none;
1508
+ border-color: var(--accent);
1509
+ }
1510
+ .rec-intent-textarea.loading {
1511
+ opacity: 0.5;
1512
+ background: repeating-linear-gradient(
1513
+ -45deg,
1514
+ var(--bg-canvas),
1515
+ var(--bg-canvas) 10px,
1516
+ var(--bg-overlay) 10px,
1517
+ var(--bg-overlay) 20px
1518
+ );
1519
+ background-size: 28px 28px;
1520
+ animation: recShimmer 1s linear infinite;
1521
+ }
1522
+ @keyframes recShimmer {
1523
+ to { background-position: 28px 0; }
1524
+ }
1525
+ .rec-actions {
1526
+ padding: 12px 16px;
1527
+ border-top: 1px solid var(--border);
1528
+ display: flex;
1529
+ gap: 8px;
1530
+ justify-content: flex-end;
1531
+ flex-shrink: 0;
1532
+ }
1533
+ .rec-success-msg {
1534
+ padding: 12px 16px;
1535
+ background: var(--green-dim);
1536
+ color: var(--green);
1537
+ font-size: 13px;
1538
+ border-radius: var(--radius-sm);
1539
+ margin: 12px 16px;
1540
+ display: flex;
1541
+ align-items: center;
1542
+ gap: 8px;
1543
+ }
1544
+ .rec-empty-events {
1545
+ display: flex;
1546
+ flex-direction: column;
1547
+ align-items: center;
1548
+ justify-content: center;
1549
+ flex: 1;
1550
+ color: var(--text-muted);
1551
+ font-size: 13px;
1552
+ gap: 8px;
1553
+ }
1554
+
1555
+ /* ===== Welcome Banner ===== */
1556
+ .welcome-banner {
1557
+ background: linear-gradient(135deg, var(--bg-surface), var(--bg-elevated));
1558
+ border: 1px solid var(--border);
1559
+ border-radius: var(--radius-xl);
1560
+ padding: 28px 32px;
1561
+ margin: 20px;
1562
+ position: relative;
1563
+ box-shadow: var(--shadow-lg), var(--shadow-glow);
1564
+ animation: viewFadeIn 400ms ease;
1565
+ }
1566
+ .welcome-banner .welcome-close {
1567
+ position: absolute;
1568
+ top: 12px;
1569
+ right: 16px;
1570
+ background: none;
1571
+ border: none;
1572
+ color: var(--text-muted);
1573
+ font-size: 18px;
1574
+ cursor: pointer;
1575
+ padding: 4px 8px;
1576
+ border-radius: var(--radius-sm);
1577
+ transition: all var(--transition);
1578
+ }
1579
+ .welcome-banner .welcome-close:hover { color: var(--text-primary); background: var(--bg-overlay); }
1580
+ .welcome-title {
1581
+ font-size: 20px;
1582
+ font-weight: 700;
1583
+ color: var(--text-primary);
1584
+ margin-bottom: 6px;
1585
+ display: flex;
1586
+ align-items: center;
1587
+ gap: 10px;
1588
+ }
1589
+ .welcome-title .logo-icon {
1590
+ font-size: 24px;
1591
+ background: linear-gradient(135deg, var(--accent), var(--purple));
1592
+ -webkit-background-clip: text;
1593
+ -webkit-text-fill-color: transparent;
1594
+ background-clip: text;
1595
+ }
1596
+ .welcome-subtitle {
1597
+ font-size: 13px;
1598
+ color: var(--text-secondary);
1599
+ margin-bottom: 20px;
1600
+ line-height: 1.5;
1601
+ }
1602
+ .welcome-grid {
1603
+ display: grid;
1604
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
1605
+ gap: 12px;
1606
+ }
1607
+ .welcome-card {
1608
+ background: var(--bg-canvas);
1609
+ border: 1px solid var(--border);
1610
+ border-radius: var(--radius-lg);
1611
+ padding: 16px;
1612
+ cursor: pointer;
1613
+ transition: all var(--transition);
1614
+ text-align: left;
1615
+ font-family: inherit;
1616
+ color: inherit;
1617
+ }
1618
+ .welcome-card:hover { border-color: var(--accent); background: var(--bg-overlay); transform: translateY(-2px); box-shadow: var(--shadow); }
1619
+ .welcome-card .wc-icon { font-size: 22px; margin-bottom: 8px; }
1620
+ .welcome-card .wc-name { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
1621
+ .welcome-card .wc-desc { font-size: 11px; color: var(--text-secondary); line-height: 1.5; }
1622
+ .welcome-tip {
1623
+ margin-top: 16px;
1624
+ padding: 10px 14px;
1625
+ background: var(--accent-dim);
1626
+ border-radius: var(--radius-md);
1627
+ font-size: 12px;
1628
+ color: var(--accent);
1629
+ display: flex;
1630
+ align-items: center;
1631
+ gap: 8px;
1632
+ }
1633
+
1634
+ /* ===== Onboarding Empty States ===== */
1635
+ .empty-state {
1636
+ display: flex;
1637
+ flex-direction: column;
1638
+ align-items: center;
1639
+ justify-content: center;
1640
+ padding: 48px 24px;
1641
+ text-align: center;
1642
+ flex: 1;
1643
+ }
1644
+ .empty-state .es-icon { font-size: 48px; opacity: 0.25; margin-bottom: 12px; }
1645
+ .empty-state .es-title { font-size: 16px; font-weight: 600; color: var(--text-primary); margin-bottom: 6px; }
1646
+ .empty-state .es-desc { font-size: 13px; color: var(--text-secondary); max-width: 420px; line-height: 1.6; margin-bottom: 16px; }
1647
+ .empty-state .es-steps {
1648
+ text-align: left;
1649
+ font-size: 12px;
1650
+ color: var(--text-secondary);
1651
+ line-height: 2;
1652
+ margin-bottom: 16px;
1653
+ }
1654
+ .empty-state .es-steps .step-num {
1655
+ display: inline-flex;
1656
+ align-items: center;
1657
+ justify-content: center;
1658
+ width: 20px;
1659
+ height: 20px;
1660
+ border-radius: 50%;
1661
+ background: var(--accent-dim);
1662
+ color: var(--accent);
1663
+ font-size: 11px;
1664
+ font-weight: 600;
1665
+ margin-right: 8px;
1666
+ }
1667
+ .empty-state .es-examples {
1015
1668
  display: flex;
1016
- gap: 8px;
1669
+ flex-wrap: wrap;
1670
+ gap: 6px;
1671
+ justify-content: center;
1672
+ margin-top: 4px;
1017
1673
  }
1674
+ .empty-state .es-example {
1675
+ font-size: 11px;
1676
+ padding: 4px 10px;
1677
+ background: var(--bg-overlay);
1678
+ border: 1px solid var(--border);
1679
+ border-radius: var(--radius-sm);
1680
+ color: var(--text-secondary);
1681
+ cursor: pointer;
1682
+ transition: all var(--transition);
1683
+ font-family: inherit;
1684
+ }
1685
+ .empty-state .es-example:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
1018
1686
 
1019
1687
  /* ===== Responsive ===== */
1020
1688
  @media (max-width: 900px) {
@@ -1025,6 +1693,12 @@ body {
1025
1693
  .chat-panel, .preview-panel { width: 100%; border-right: none; }
1026
1694
  .preview-panel { border-top: 1px solid var(--border); }
1027
1695
  .tasks-list { grid-template-columns: 1fr; }
1696
+ .memory-list { grid-template-columns: 1fr !important; }
1697
+ .rec-split { flex-direction: column; }
1698
+ .rec-left-panel, .rec-right-panel { width: 100%; border-right: none; }
1699
+ .rec-right-panel { border-top: 1px solid var(--border); }
1700
+ .welcome-banner { margin: 12px; padding: 20px; }
1701
+ .welcome-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
1028
1702
  }
1029
1703
  @media (max-width: 768px) {
1030
1704
  .app-logo { font-size: 13px; }
@@ -1036,11 +1710,17 @@ body {
1036
1710
  }
1037
1711
  @media (max-width: 480px) {
1038
1712
  .preview-panel { display: none; }
1713
+ .rec-right-panel { display: none; }
1714
+ .rec-left-panel { width: 100%; border-right: none; }
1039
1715
  .chat-panel { width: 100%; border-right: none; }
1040
1716
  .chat-input-bar input { font-size: 16px; padding: 12px 14px; }
1041
1717
  .msg-tool-result .result-content { max-height: 80px; }
1042
1718
  .header-right { gap: 4px; }
1043
1719
  .btn { padding: 5px 8px; font-size: 12px; }
1720
+ .tab-icon { display: none; }
1721
+ .welcome-banner { margin: 8px; padding: 16px; }
1722
+ .welcome-grid { grid-template-columns: 1fr 1fr; }
1723
+ .welcome-tip { flex-direction: column; text-align: center; }
1044
1724
  }
1045
1725
  </style>
1046
1726
  </head>
@@ -1049,25 +1729,62 @@ body {
1049
1729
  <header class="app-header">
1050
1730
  <span class="app-logo">AI Browser</span>
1051
1731
  <div class="nav-tabs">
1052
- <button class="nav-tab active" onclick="switchView('semantic')">Semantic</button>
1053
- <button class="nav-tab" onclick="switchView('agent')">Agent</button>
1054
- <button class="nav-tab" onclick="switchView('tasks')">Tasks</button>
1732
+ <button class="nav-tab active" data-view="semantic" onclick="switchView('semantic')" title="Analyze page structure and elements"><span class="tab-icon">&#128270;</span> Semantic</button>
1733
+ <button class="nav-tab" data-view="agent" onclick="switchView('agent')" title="AI agent that browses the web for you"><span class="tab-icon">&#129302;</span> Agent</button>
1734
+ <button class="nav-tab" data-view="tasks" onclick="switchView('tasks')" title="Run deterministic task templates"><span class="tab-icon">&#128203;</span> Tasks</button>
1735
+ <button class="nav-tab" data-view="memory" onclick="switchView('memory')" title="Site knowledge learned from browsing"><span class="tab-icon">&#129504;</span> Memory</button>
1736
+ <button class="nav-tab" data-view="record" onclick="switchView('record')" title="Record browsing sessions to build memory"><span class="tab-icon">&#9210;</span> Record</button>
1055
1737
  </div>
1056
1738
  <div class="header-right" id="headerRight">
1057
1739
  <button class="btn" id="btnCloseSession" style="display:none" onclick="Sem.closeSession()">Close Session</button>
1058
1740
  <button class="btn btn-danger" id="btnStop" style="display:none" onclick="Agent.stop()">Stop</button>
1059
1741
  <button class="btn" id="btnClear" style="display:none" onclick="Agent.clearChat()">Clear</button>
1060
- <button class="btn" id="btnSettings" onclick="openSettings()">Settings</button>
1742
+ <button class="btn" id="btnSettings" onclick="openSettings()" title="Configure LLM provider (API key, model, base URL)">&#9881; Settings</button>
1061
1743
  </div>
1062
1744
  </header>
1063
1745
 
1746
+ <!-- ===== Welcome Banner ===== -->
1747
+ <div class="welcome-banner" id="welcomeBanner" style="display:none;">
1748
+ <button class="welcome-close" onclick="dismissWelcome()" title="Dismiss" aria-label="Dismiss welcome banner">&times;</button>
1749
+ <div class="welcome-title"><span class="logo-icon">&#127760;</span> Welcome to AI Browser</div>
1750
+ <div class="welcome-subtitle">An AI-powered browser automation platform. Analyze pages, run autonomous agents, create repeatable tasks, and build site memory — all from one interface.</div>
1751
+ <div class="welcome-grid">
1752
+ <button class="welcome-card" onclick="dismissWelcome();switchView('semantic')">
1753
+ <div class="wc-icon">&#128270;</div>
1754
+ <div class="wc-name">Semantic</div>
1755
+ <div class="wc-desc">Open any URL and inspect its structure — interactive elements, markdown content, and screenshots.</div>
1756
+ </button>
1757
+ <button class="welcome-card" onclick="dismissWelcome();switchView('agent')">
1758
+ <div class="wc-icon">&#129302;</div>
1759
+ <div class="wc-name">Agent</div>
1760
+ <div class="wc-desc">Give a natural-language task. The AI agent opens a browser and completes it autonomously.</div>
1761
+ </button>
1762
+ <button class="welcome-card" onclick="dismissWelcome();switchView('tasks')">
1763
+ <div class="wc-icon">&#128203;</div>
1764
+ <div class="wc-name">Tasks</div>
1765
+ <div class="wc-desc">Create and run deterministic task templates — repeatable browser automations with artifacts.</div>
1766
+ </button>
1767
+ <button class="welcome-card" onclick="dismissWelcome();switchView('memory')">
1768
+ <div class="wc-icon">&#129504;</div>
1769
+ <div class="wc-name">Memory</div>
1770
+ <div class="wc-desc">View site knowledge cards — selectors, navigation paths, and patterns the system has learned.</div>
1771
+ </button>
1772
+ <button class="welcome-card" onclick="dismissWelcome();switchView('record')">
1773
+ <div class="wc-icon">&#9210;</div>
1774
+ <div class="wc-name">Record</div>
1775
+ <div class="wc-desc">Record your own browsing session. Your actions are captured and saved as reusable site memory.</div>
1776
+ </button>
1777
+ </div>
1778
+ <div class="welcome-tip">&#128161; First time? Start with <strong style="margin:0 4px;">Semantic</strong> to explore a page, or jump to <strong style="margin:0 4px;">Agent</strong> to let AI do the work. Configure your LLM in <strong style="margin:0 4px;">Settings</strong> (top right).</div>
1779
+ </div>
1780
+
1064
1781
  <!-- ===== Main ===== -->
1065
1782
  <main class="app-main">
1066
1783
  <!-- Semantic View -->
1067
1784
  <div id="view-semantic" class="view active">
1068
1785
  <!-- Toolbar -->
1069
1786
  <div class="sem-toolbar">
1070
- <input type="text" id="semUrlInput" placeholder="Enter URL, e.g. https://www.baidu.com" value="https://www.baidu.com">
1787
+ <input type="text" id="semUrlInput" placeholder="Enter URL to analyze, e.g. https://www.baidu.com" value="https://www.baidu.com">
1071
1788
  <button class="btn btn-primary" id="semAnalyzeBtn" onclick="Sem.analyze()">Analyze</button>
1072
1789
  <button class="btn" id="semRefreshBtn" style="display:none" onclick="Sem.refreshElements()">Refresh</button>
1073
1790
  <button class="btn" id="semSendToAgent" style="display:none" onclick="sendToAgent()">Send to Agent</button>
@@ -1096,7 +1813,17 @@ body {
1096
1813
  </div>
1097
1814
  <div class="filter-bar hidden" id="semFilterBar"></div>
1098
1815
  <div class="sem-content-area" id="semLeftContent">
1099
- <div class="placeholder-text">Enter a URL and click Analyze to start</div>
1816
+ <div class="empty-state">
1817
+ <div class="es-icon">&#128270;</div>
1818
+ <div class="es-title">Semantic Page Analysis</div>
1819
+ <div class="es-desc">Enter a URL above and click Analyze. The page will be parsed into structured markdown and interactive elements — buttons, links, inputs — each with a semantic ID you can use to automate interactions.</div>
1820
+ <div class="es-steps">
1821
+ <div><span class="step-num">1</span>Enter a URL in the toolbar above</div>
1822
+ <div><span class="step-num">2</span>Click <strong>Analyze</strong> to open the page</div>
1823
+ <div><span class="step-num">3</span>Browse elements, click or type into them</div>
1824
+ <div><span class="step-num">4</span>Click <strong>Send to Agent</strong> to hand off the session</div>
1825
+ </div>
1826
+ </div>
1100
1827
  </div>
1101
1828
  </div>
1102
1829
 
@@ -1107,7 +1834,7 @@ body {
1107
1834
  <button class="sem-content-tab" onclick="Sem.switchRightTab('raw', this)">Raw Markdown</button>
1108
1835
  </div>
1109
1836
  <div class="sem-right-area" id="semRightContent">
1110
- <div class="placeholder-text">Waiting for analysis...</div>
1837
+ <div class="placeholder-text" style="padding:32px 20px;font-size:13px;">Screenshot and raw markdown will appear here after analysis.</div>
1111
1838
  </div>
1112
1839
  </div>
1113
1840
  </div>
@@ -1127,7 +1854,22 @@ body {
1127
1854
  <!-- Left: Chat -->
1128
1855
  <div class="chat-panel">
1129
1856
  <div class="chat-messages" id="chatMessages">
1130
- <div class="placeholder-text">Enter a task to start</div>
1857
+ <div class="empty-state" id="agentEmptyState">
1858
+ <div class="es-icon">&#129302;</div>
1859
+ <div class="es-title">AI Browsing Agent</div>
1860
+ <div class="es-desc">Describe a task in natural language. The AI agent will open a browser, navigate pages, click buttons, fill forms, and extract information — all autonomously.</div>
1861
+ <div class="es-steps">
1862
+ <div><span class="step-num">1</span>Type a task in the input below</div>
1863
+ <div><span class="step-num">2</span>Click <strong>Send</strong> — the agent starts working</div>
1864
+ <div><span class="step-num">3</span>Watch progress in real-time on the right panel</div>
1865
+ </div>
1866
+ <div style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">Try an example:</div>
1867
+ <div class="es-examples">
1868
+ <button class="es-example" onclick="document.getElementById('taskInput').value=this.textContent;document.getElementById('taskInput').focus()">Open Baidu and search for weather</button>
1869
+ <button class="es-example" onclick="document.getElementById('taskInput').value=this.textContent;document.getElementById('taskInput').focus()">Go to GitHub trending and list top 5 repos</button>
1870
+ <button class="es-example" onclick="document.getElementById('taskInput').value=this.textContent;document.getElementById('taskInput').focus()">Search Amazon for wireless headphones under $50</button>
1871
+ </div>
1872
+ </div>
1131
1873
  </div>
1132
1874
  <div class="typing-indicator" id="typingIndicator">
1133
1875
  <div class="typing-dot"></div>
@@ -1143,7 +1885,7 @@ body {
1143
1885
  <span id="progressSteps"></span>
1144
1886
  </div>
1145
1887
  <div class="chat-input-bar">
1146
- <input type="text" id="taskInput" placeholder="Enter a task, e.g.: Open Baidu and search for weather">
1888
+ <input type="text" id="taskInput" placeholder="Describe a task, e.g.: Open Baidu and search for weather, then summarize the results">
1147
1889
  <button class="btn-send" id="sendBtn" onclick="Agent.start()">Send</button>
1148
1890
  </div>
1149
1891
  </div>
@@ -1159,7 +1901,7 @@ body {
1159
1901
  <span class="preview-url" id="previewUrl"></span>
1160
1902
  </div>
1161
1903
  <div class="preview-body" id="previewBody">
1162
- <div class="placeholder-text">Waiting for Agent output...</div>
1904
+ <div class="placeholder-text" style="padding:32px 20px;font-size:13px;">Page markdown and screenshots will appear here as the agent browses.</div>
1163
1905
  </div>
1164
1906
  </div>
1165
1907
  </div>
@@ -1170,13 +1912,103 @@ body {
1170
1912
  <div class="tasks-toolbar">
1171
1913
  <button class="btn btn-primary" id="btnNewTask" onclick="Tasks.showCreate()">+ New Task</button>
1172
1914
  <button class="btn" onclick="Tasks.refreshList()">Refresh</button>
1915
+ <span style="font-size:12px;color:var(--text-muted);margin-left:auto;">Run deterministic task templates — repeatable browser automations</span>
1173
1916
  </div>
1174
1917
  <div id="tasksList" class="tasks-list">
1175
- <div class="placeholder-text">Loading tasks...</div>
1918
+ <div class="empty-state" id="tasksEmptyState">
1919
+ <div class="es-icon">&#128203;</div>
1920
+ <div class="es-title">Task Templates</div>
1921
+ <div class="es-desc">Tasks are deterministic, repeatable browser automations. Create a task template with a URL and goal, then run it anytime. Unlike the Agent (which uses AI reasoning), tasks follow predefined steps.</div>
1922
+ <div class="es-steps">
1923
+ <div><span class="step-num">1</span>Click <strong>+ New Task</strong> to create a template</div>
1924
+ <div><span class="step-num">2</span>Set a URL, goal, and optional parameters</div>
1925
+ <div><span class="step-num">3</span>Run the task and view results with artifacts</div>
1926
+ </div>
1927
+ </div>
1176
1928
  </div>
1177
1929
  <div id="taskDetail" class="task-detail" style="display:none"></div>
1178
1930
  <div id="taskCreate" class="task-detail" style="display:none"></div>
1179
1931
  </div>
1932
+
1933
+ <!-- Memory View -->
1934
+ <div id="view-memory" class="view">
1935
+ <div class="memory-toolbar">
1936
+ <button class="btn" onclick="Memory.refreshList()">Refresh</button>
1937
+ <span style="font-size:12px;color:var(--text-muted);margin-left:auto;">Site knowledge cards — selectors, navigation paths, and patterns learned from browsing</span>
1938
+ </div>
1939
+ <div id="memoryList" class="memory-list">
1940
+ <div class="placeholder-text">Loading memory...</div>
1941
+ </div>
1942
+ <div id="memoryDetail" class="memory-detail" style="display:none"></div>
1943
+ </div>
1944
+
1945
+ <!-- Record View -->
1946
+ <div id="view-record" class="view">
1947
+ <!-- Setup State -->
1948
+ <div id="rec-setup" class="rec-state active rec-setup">
1949
+ <div style="font-size:36px;opacity:0.3;margin-bottom:8px">&#9679;</div>
1950
+ <div style="font-size:16px;font-weight:600;color:var(--text-primary)">Record a Browsing Session</div>
1951
+ <div class="rec-hint">Enter a URL to start recording. A browser window will open — interact with the page normally. Your actions will be captured and converted to site memory.</div>
1952
+ <div class="rec-url-row">
1953
+ <input type="text" id="recUrlInput" placeholder="https://example.com" value="">
1954
+ <button class="btn btn-primary" id="recStartBtn" onclick="Rec.start()">Start Recording</button>
1955
+ </div>
1956
+ </div>
1957
+
1958
+ <!-- Recording State -->
1959
+ <div id="rec-active" class="rec-state">
1960
+ <div class="rec-split">
1961
+ <div class="rec-left-panel">
1962
+ <div class="rec-indicator-bar">
1963
+ <span class="rec-dot"></span>
1964
+ <span class="rec-timer" id="recTimer">00:00</span>
1965
+ <span class="rec-event-count" id="recEventCount">0 events</span>
1966
+ <button class="btn btn-danger" onclick="Rec.stop()">Stop Recording</button>
1967
+ </div>
1968
+ <div class="rec-event-stream" id="recEventStream">
1969
+ <div class="rec-empty-events">
1970
+ <span style="font-size:24px;opacity:0.3">&#128065;</span>
1971
+ <span>Waiting for interactions...</span>
1972
+ </div>
1973
+ </div>
1974
+ </div>
1975
+ <div class="rec-right-panel">
1976
+ <div class="rec-preview-header">Live Preview</div>
1977
+ <div class="rec-preview-body" id="recPreviewBody">
1978
+ <div class="skeleton"></div>
1979
+ </div>
1980
+ </div>
1981
+ </div>
1982
+ </div>
1983
+
1984
+ <!-- Review State -->
1985
+ <div id="rec-review" class="rec-state">
1986
+ <div class="rec-split">
1987
+ <div class="rec-left-panel">
1988
+ <div class="rec-summary" id="recSummary"></div>
1989
+ <div class="rec-intent-section" id="recIntentSection">
1990
+ <div class="rec-intent-header">
1991
+ <span>Task Intent</span>
1992
+ <span class="rec-intent-hint" id="recIntentHint">AI summarizing...</span>
1993
+ </div>
1994
+ <textarea id="recIntentText" class="rec-intent-textarea" rows="6" maxlength="1000" aria-label="Task Intent" placeholder="LLM is analyzing your recording..."></textarea>
1995
+ </div>
1996
+ <div class="rec-timeline" id="recTimeline"></div>
1997
+ </div>
1998
+ <div class="rec-right-panel">
1999
+ <div class="rec-review-header">Extracted Patterns</div>
2000
+ <div class="rec-patterns-body" id="recPatternsBody">
2001
+ <div class="placeholder-text">No patterns extracted.</div>
2002
+ </div>
2003
+ <div id="recSuccessArea"></div>
2004
+ <div class="rec-actions" id="recActions">
2005
+ <button class="btn" onclick="Rec.discard()">Discard</button>
2006
+ <button class="btn btn-primary" id="recSaveBtn" onclick="Rec.saveToMemory()">Save to Memory</button>
2007
+ </div>
2008
+ </div>
2009
+ </div>
2010
+ </div>
2011
+ </div>
1180
2012
  </main>
1181
2013
  <div class="modal-overlay hidden" id="settingsModal">
1182
2014
  <div class="modal">
@@ -1189,6 +2021,8 @@ body {
1189
2021
  <input type="password" id="cfgApiKey" placeholder="sk-...">
1190
2022
  <label>Max Iterations</label>
1191
2023
  <input type="number" id="cfgMaxIterations" placeholder="Default (server-controlled)" min="1" max="1000">
2024
+ <label>LLM Timeout (seconds)</label>
2025
+ <input type="number" id="cfgTimeout" placeholder="300" min="10" max="600">
1192
2026
  <label style="display:flex;align-items:center;gap:8px;margin-top:14px;cursor:pointer;">
1193
2027
  <input type="checkbox" id="cfgHeadless" checked style="width:auto;cursor:pointer;">
1194
2028
  Headless Mode
@@ -1247,6 +2081,7 @@ function openSettings() {
1247
2081
  document.getElementById('cfgModel').value = s.model || '';
1248
2082
  document.getElementById('cfgApiKey').value = s.apiKey || '';
1249
2083
  document.getElementById('cfgMaxIterations').value = s.maxIterations || '';
2084
+ document.getElementById('cfgTimeout').value = s.timeout || '';
1250
2085
  document.getElementById('cfgHeadless').checked = s.headless !== false;
1251
2086
  document.getElementById('settingsModal').classList.remove('hidden');
1252
2087
  }
@@ -1261,6 +2096,7 @@ function saveSettings() {
1261
2096
  model: document.getElementById('cfgModel').value.trim(),
1262
2097
  apiKey: document.getElementById('cfgApiKey').value.trim(),
1263
2098
  maxIterations: parseInt(document.getElementById('cfgMaxIterations').value) || 0,
2099
+ timeout: parseInt(document.getElementById('cfgTimeout').value) || 0,
1264
2100
  headless: document.getElementById('cfgHeadless').checked,
1265
2101
  };
1266
2102
  localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
@@ -1306,6 +2142,18 @@ function testConnection() {
1306
2142
 
1307
2143
  // ========== Navigation ==========
1308
2144
  function switchView(view) {
2145
+ // Warn if leaving record view while recording or reviewing
2146
+ if (App.currentView === 'record' && view !== 'record' && Rec.state === 'recording') {
2147
+ if (!confirm('Recording is in progress. Leave and stop recording?')) return;
2148
+ Rec.stopPolling();
2149
+ Rec._stopBackendRecording();
2150
+ Rec.cleanup();
2151
+ Rec.reset();
2152
+ }
2153
+ if (App.currentView === 'record' && view !== 'record' && Rec.state === 'review') {
2154
+ Rec.cleanup();
2155
+ Rec.reset();
2156
+ }
1309
2157
  App.currentView = view;
1310
2158
  location.hash = view;
1311
2159
  // Clean up Tasks polling when leaving
@@ -1316,11 +2164,20 @@ function switchView(view) {
1316
2164
  document.getElementById('view-semantic').classList.toggle('active', view === 'semantic');
1317
2165
  document.getElementById('view-agent').classList.toggle('active', view === 'agent');
1318
2166
  document.getElementById('view-tasks').classList.toggle('active', view === 'tasks');
2167
+ document.getElementById('view-memory').classList.toggle('active', view === 'memory');
2168
+ document.getElementById('view-record').classList.toggle('active', view === 'record');
1319
2169
  // Update nav tabs
1320
2170
  document.querySelectorAll('.nav-tab').forEach(function(tab) {
1321
- tab.classList.toggle('active', tab.textContent.toLowerCase() === view);
2171
+ tab.classList.toggle('active', tab.getAttribute('data-view') === view);
1322
2172
  });
1323
2173
  if (view === 'tasks') Tasks.refreshList();
2174
+ if (view === 'memory') Memory.refreshList();
2175
+ if (view === 'record') {
2176
+ var saved = localStorage.getItem('rec_last_url');
2177
+ if (saved && !document.getElementById('recUrlInput').value) {
2178
+ document.getElementById('recUrlInput').value = saved;
2179
+ }
2180
+ }
1324
2181
  updateHeaderButtons();
1325
2182
  }
1326
2183
 
@@ -1340,7 +2197,7 @@ function updateHeaderButtons() {
1340
2197
  // Hash routing
1341
2198
  window.addEventListener('hashchange', function() {
1342
2199
  var hash = location.hash.replace('#', '');
1343
- var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : 'semantic';
2200
+ var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : (hash === 'memory') ? 'memory' : (hash === 'record') ? 'record' : 'semantic';
1344
2201
  if (view !== App.currentView) switchView(view);
1345
2202
  });
1346
2203
 
@@ -1856,8 +2713,8 @@ var Agent = {
1856
2713
  document.getElementById('previewUrl').textContent = '';
1857
2714
  self.renderPreview();
1858
2715
 
1859
- // Remove placeholder
1860
- var ph = document.getElementById('chatMessages').querySelector('.placeholder-text');
2716
+ // Remove placeholder / empty state
2717
+ var ph = document.getElementById('chatMessages').querySelector('.placeholder-text') || document.getElementById('agentEmptyState');
1861
2718
  if (ph) ph.remove();
1862
2719
 
1863
2720
  document.getElementById('sendBtn').disabled = true;
@@ -1873,6 +2730,7 @@ var Agent = {
1873
2730
  if (settings.baseURL) body.baseURL = settings.baseURL;
1874
2731
  if (settings.model) body.model = settings.model;
1875
2732
  if (settings.maxIterations) body.maxIterations = settings.maxIterations;
2733
+ if (settings.timeout && settings.timeout > 0) body.timeout = settings.timeout;
1876
2734
  if (settings.headless === false) body.headless = false;
1877
2735
  if (self.conversationHistory.length > 0) body.messages = self.conversationHistory;
1878
2736
 
@@ -1997,6 +2855,9 @@ Agent.handleEvent = function(ev) {
1997
2855
  document.getElementById('progressInfo').classList.add('active');
1998
2856
  }
1999
2857
  break;
2858
+ case 'memory_recall':
2859
+ self.addMsg('memory_recall', ev.domain, ev.iteration, ev.patternCount);
2860
+ break;
2000
2861
  case 'done':
2001
2862
  self.addMsg('done', ev.success ? (ev.result || 'Done') : (ev.error || 'Failed'), null, ev.success);
2002
2863
  var summary = 'Total steps: ' + ev.iterations;
@@ -2075,6 +2936,14 @@ Agent.addMsg = function(type, content, iteration, extra, extra2) {
2075
2936
  el.className = 'msg msg-error';
2076
2937
  el.innerHTML = (iteration ? this.stepTag(iteration) : '') + 'Error: ' + esc(content);
2077
2938
  break;
2939
+ case 'memory_recall':
2940
+ el.className = 'msg msg-memory-recall';
2941
+ el.innerHTML = this.stepTag(iteration) +
2942
+ '<div class="memory-recall-hint">' +
2943
+ '<span class="memory-icon">🧠</span> ' +
2944
+ '<span>Recalled site memory for <strong>' + esc(content) + '</strong> (' + (extra || 0) + ' patterns)</span>' +
2945
+ '</div>';
2946
+ break;
2078
2947
  case 'done':
2079
2948
  el.className = 'msg msg-done ' + (extra ? 'success' : 'fail');
2080
2949
  var title = extra ? 'Task Completed' : 'Task Failed';
@@ -2367,7 +3236,7 @@ var Tasks = {
2367
3236
  fetch('/v1/tasks').then(function(r) { return r.json(); }).then(function(data) {
2368
3237
  var tasks = data.tasks || [];
2369
3238
  if (tasks.length === 0) {
2370
- list.innerHTML = '<div class="placeholder-text">No tasks yet. Click "+ New Task" to create one.</div>';
3239
+ list.innerHTML = '<div class="empty-state"><div class="es-icon">&#128203;</div><div class="es-title">No Tasks Yet</div><div class="es-desc">Click <strong>+ New Task</strong> above to create a deterministic task template. Tasks are repeatable browser automations that run the same steps every time.</div><div class="es-steps"><div><span class="step-num">1</span>Click <strong>+ New Task</strong> to create a template</div><div><span class="step-num">2</span>Set a URL, goal, and optional parameters</div><div><span class="step-num">3</span>Run the task and view results with artifacts</div></div></div>';
2371
3240
  return;
2372
3241
  }
2373
3242
  list.innerHTML = '';
@@ -2537,13 +3406,662 @@ document.getElementById('typeValue').addEventListener('keypress', function(e) {
2537
3406
  });
2538
3407
  </script>
2539
3408
 
3409
+ <script>
3410
+ // ===== Memory View =====
3411
+ var Memory = {
3412
+ refreshList: function() {
3413
+ var list = document.getElementById('memoryList');
3414
+ list.innerHTML = '<div class="placeholder-text">Loading...</div>';
3415
+ document.getElementById('memoryDetail').style.display = 'none';
3416
+ list.style.display = '';
3417
+
3418
+ fetch('/v1/memory').then(function(r) { return r.json(); }).then(function(data) {
3419
+ var entries = data.entries || [];
3420
+ if (entries.length === 0) {
3421
+ list.innerHTML = '<div class="empty-state"><div class="es-icon">&#129504;</div><div class="es-title">No Site Memories Yet</div><div class="es-desc">Memories are automatically learned when the agent completes tasks, or you can manually record browsing sessions in the <strong>Record</strong> tab. Each memory card stores selectors, navigation paths, and interaction patterns for a domain.</div><div class="es-examples"><span class="es-example" onclick="switchView(\'record\')">Go to Record tab</span><span class="es-example" onclick="switchView(\'agent\')">Run an Agent task</span></div></div>';
3422
+ return;
3423
+ }
3424
+ list.innerHTML = '';
3425
+ entries.forEach(function(entry) {
3426
+ var card = document.createElement('div');
3427
+ card.className = 'memory-card';
3428
+ card.onclick = function() { Memory.showDetail(entry.domain); };
3429
+
3430
+ var badges = '';
3431
+ if (entry.siteType && entry.siteType !== 'unknown') {
3432
+ badges += '<span class="site-type">' + esc(entry.siteType) + '</span>';
3433
+ }
3434
+ if (entry.requiresLogin) {
3435
+ badges += '<span class="login-badge">Login</span>';
3436
+ }
3437
+
3438
+ var patterns = (entry.topPatterns || []).slice(0, 3);
3439
+ var patternHtml = patterns.map(function(p) {
3440
+ return '<span class="pattern-tag">' + esc(p) + '</span>';
3441
+ }).join('');
3442
+
3443
+ var lastUsed = entry.lastUsedAt ? new Date(entry.lastUsedAt).toLocaleDateString() : '-';
3444
+
3445
+ card.innerHTML =
3446
+ '<div class="memory-domain">' + esc(entry.domain) + badges + '</div>' +
3447
+ '<div class="memory-meta">' + entry.patternCount + ' patterns · Last used: ' + lastUsed + '</div>' +
3448
+ '<div class="memory-patterns">' + patternHtml + '</div>';
3449
+ list.appendChild(card);
3450
+ });
3451
+ }).catch(function() {
3452
+ list.innerHTML = '<div class="placeholder-text">Failed to load memories.</div>';
3453
+ });
3454
+ },
3455
+
3456
+ showDetail: function(domain) {
3457
+ document.getElementById('memoryList').style.display = 'none';
3458
+ var panel = document.getElementById('memoryDetail');
3459
+ panel.style.display = 'block';
3460
+ panel.innerHTML = '<div class="placeholder-text">Loading...</div>';
3461
+
3462
+ fetch('/v1/memory/' + encodeURIComponent(domain)).then(function(r) { return r.json(); }).then(function(card) {
3463
+ var siteTag = card.siteType && card.siteType !== 'unknown' ? ' [' + card.siteType.toUpperCase() + ']' : '';
3464
+ var html = '<div class="memory-detail-header">' +
3465
+ '<button class="btn" onclick="Memory.backToList()">&larr; Back</button>' +
3466
+ '<span style="font-size:16px;font-weight:600">' + esc(card.domain) + esc(siteTag) + '</span>' +
3467
+ '<span style="font-size:12px;color:var(--text-muted)">v' + card.version + '</span>' +
3468
+ '<button class="btn btn-danger" style="margin-left:auto" onclick="Memory.deleteDomain(\'' + esc(card.domain).replace(/'/g, '&#39;') + '\')">Delete</button>' +
3469
+ '</div>';
3470
+
3471
+ html += '<div style="font-size:13px;color:var(--text-secondary);margin-bottom:16px">' +
3472
+ 'Created: ' + new Date(card.createdAt).toLocaleString() +
3473
+ ' · Updated: ' + new Date(card.updatedAt).toLocaleString() +
3474
+ (card.requiresLogin ? ' · <span style="color:var(--orange)">Requires Login</span>' : '') +
3475
+ '</div>';
3476
+
3477
+ html += '<div class="pattern-list">';
3478
+ (card.patterns || []).forEach(function(p) {
3479
+ var now = Date.now();
3480
+ var daysSince = (now - p.lastUsedAt) / 86400000;
3481
+ var effConf = (p.confidence * Math.pow(0.95, daysSince)).toFixed(2);
3482
+
3483
+ html += '<div class="pattern-item">' +
3484
+ '<span class="pattern-type ' + esc(p.type) + '">' + esc(p.type) + '</span>' +
3485
+ '<span style="font-size:11px;color:var(--text-muted)">' + esc(p.source || '') + '</span>' +
3486
+ '<div class="pattern-desc">' + esc(p.description) + '</div>' +
3487
+ '<div class="pattern-value">' + esc(p.value) + '</div>' +
3488
+ '<div class="pattern-meta">' +
3489
+ '<span>Confidence: ' + effConf + '</span>' +
3490
+ '<span>Used: ' + p.useCount + 'x</span>' +
3491
+ '<span>Last: ' + new Date(p.lastUsedAt).toLocaleDateString() + '</span>' +
3492
+ '</div>' +
3493
+ '</div>';
3494
+ });
3495
+ html += '</div>';
3496
+
3497
+ panel.innerHTML = html;
3498
+ }).catch(function() {
3499
+ panel.innerHTML = '<div class="placeholder-text">Failed to load card.</div>' +
3500
+ '<button class="btn" onclick="Memory.backToList()" style="margin-top:12px">&larr; Back</button>';
3501
+ });
3502
+ },
3503
+
3504
+ backToList: function() {
3505
+ document.getElementById('memoryDetail').style.display = 'none';
3506
+ document.getElementById('memoryList').style.display = '';
3507
+ Memory.refreshList();
3508
+ },
3509
+
3510
+ deleteDomain: function(domain) {
3511
+ if (!confirm('Delete all memories for ' + domain + '?')) return;
3512
+ fetch('/v1/memory/' + encodeURIComponent(domain), { method: 'DELETE' })
3513
+ .then(function() { Memory.backToList(); })
3514
+ .catch(function(err) { alert('Delete failed: ' + err.message); });
3515
+ }
3516
+ };
3517
+ </script>
3518
+
3519
+ <script>
3520
+ // ========== Recording ==========
3521
+ function recJsonOrThrow(r) {
3522
+ if (!r.ok) return r.text().then(function(text) {
3523
+ try {
3524
+ var body = JSON.parse(text);
3525
+ var err = body.error;
3526
+ var msg = (err && err.message) ? err.message : (body.message || ('HTTP ' + r.status));
3527
+ throw new Error(msg);
3528
+ } catch(e) {
3529
+ if (e instanceof SyntaxError) throw new Error('HTTP ' + r.status + ': ' + (text.slice(0, 200) || 'Unknown error'));
3530
+ throw e;
3531
+ }
3532
+ });
3533
+ return r.json();
3534
+ }
3535
+
3536
+ /** Extract structural patterns from recording events (shared by renderReview and saveToMemory) */
3537
+ function extractPatternsFromEvents(events) {
3538
+ var patterns = [];
3539
+ var seen = {};
3540
+ var now = Date.now();
3541
+ var navEvents = events.filter(function(e) { return e.type === 'navigate' && e.url; });
3542
+ if (navEvents.length >= 2) {
3543
+ var paths = navEvents.map(function(e) {
3544
+ try { return new URL(e.url).pathname; } catch(x) { return e.url; }
3545
+ }).join(' → ');
3546
+ if (!seen[paths]) {
3547
+ seen[paths] = true;
3548
+ patterns.push({ type: 'navigation_path', description: 'Navigation path', value: paths, confidence: 0.8, source: 'human_recording', createdAt: now, lastUsedAt: now, useCount: 0 });
3549
+ }
3550
+ }
3551
+ events.forEach(function(e) {
3552
+ if (e.type === 'click' && e.target && !seen['click:' + e.target]) {
3553
+ seen['click:' + e.target] = true;
3554
+ patterns.push({ type: 'selector', description: e.targetLabel || e.target, value: e.target, confidence: 0.8, source: 'human_recording', createdAt: now, lastUsedAt: now, useCount: 0 });
3555
+ }
3556
+ if ((e.type === 'type' || e.type === 'select') && e.target && !seen['input:' + e.target]) {
3557
+ seen['input:' + e.target] = true;
3558
+ patterns.push({ type: 'page_structure', description: e.targetLabel || e.target, value: e.target, confidence: 0.8, source: 'human_recording', createdAt: now, lastUsedAt: now, useCount: 0 });
3559
+ }
3560
+ });
3561
+ return patterns;
3562
+ }
3563
+
3564
+ var Rec = {
3565
+ state: 'setup',
3566
+ sessionId: null,
3567
+ startTime: null,
3568
+ timers: {},
3569
+ lastEventCount: 0,
3570
+ events: [],
3571
+ _pollFailCount: 0,
3572
+ _pollAbort: null, // AbortController for in-flight poll requests
3573
+ _stopping: false, // guard against double-click on stop
3574
+ _reviewData: null, // stored stop response for saveToMemory
3575
+ _summarizeAbort: null, // AbortController for in-flight LLM summarization
3576
+ _saving: false, // guard against double-click on save
3577
+
3578
+ setState: function(s) {
3579
+ this.state = s;
3580
+ document.getElementById('rec-setup').classList.toggle('active', s === 'setup');
3581
+ document.getElementById('rec-active').classList.toggle('active', s === 'recording');
3582
+ document.getElementById('rec-review').classList.toggle('active', s === 'review');
3583
+ },
3584
+
3585
+ start: function() {
3586
+ var url = document.getElementById('recUrlInput').value.trim();
3587
+ if (!url) { alert('Please enter a URL.'); return; }
3588
+ if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
3589
+ localStorage.setItem('rec_last_url', url);
3590
+
3591
+ var btn = document.getElementById('recStartBtn');
3592
+ btn.disabled = true;
3593
+ btn.textContent = 'Starting...';
3594
+ var self = this;
3595
+
3596
+ // 1. Create headful session
3597
+ fetch('/v1/sessions', {
3598
+ method: 'POST',
3599
+ headers: { 'Content-Type': 'application/json' },
3600
+ body: JSON.stringify({ options: { headless: false } })
3601
+ })
3602
+ .then(recJsonOrThrow)
3603
+ .then(function(data) {
3604
+ self.sessionId = data.sessionId;
3605
+ // 2. Navigate
3606
+ return fetch('/v1/sessions/' + self.sessionId + '/navigate', {
3607
+ method: 'POST',
3608
+ headers: { 'Content-Type': 'application/json' },
3609
+ body: JSON.stringify({ url: url })
3610
+ });
3611
+ })
3612
+ .then(recJsonOrThrow)
3613
+ .then(function() {
3614
+ // 3. Start recording
3615
+ return fetch('/v1/sessions/' + self.sessionId + '/recording/start', {
3616
+ method: 'POST',
3617
+ headers: { 'Content-Type': 'application/json' },
3618
+ body: '{}'
3619
+ });
3620
+ })
3621
+ .then(recJsonOrThrow)
3622
+ .then(function() {
3623
+ self.startTime = Date.now();
3624
+ self.lastEventCount = 0;
3625
+ self.events = [];
3626
+ self._pollFailCount = 0;
3627
+ document.getElementById('recEventStream').innerHTML =
3628
+ '<div class="rec-empty-events"><span style="font-size:24px;opacity:0.3">&#128065;</span><span>Waiting for interactions...</span></div>';
3629
+ document.getElementById('recTimer').textContent = '00:00';
3630
+ document.getElementById('recEventCount').textContent = '0 events';
3631
+ document.getElementById('recPreviewBody').innerHTML = '<div class="skeleton"></div>';
3632
+ self.setState('recording');
3633
+ self.startPolling();
3634
+ })
3635
+ .catch(function(err) {
3636
+ alert('Failed to start recording: ' + err.message);
3637
+ if (self.sessionId) self.cleanup();
3638
+ btn.disabled = false;
3639
+ btn.textContent = 'Start Recording';
3640
+ });
3641
+ },
3642
+
3643
+ stop: function() {
3644
+ if (!this.sessionId || this._stopping) return;
3645
+ this._stopping = true;
3646
+ var self = this;
3647
+ // Abort all in-flight poll requests first so the connection pool is free
3648
+ self.stopPolling();
3649
+
3650
+ fetch('/v1/sessions/' + self.sessionId + '/recording/stop', {
3651
+ method: 'POST',
3652
+ headers: { 'Content-Type': 'application/json' },
3653
+ body: JSON.stringify({ convertToMemory: false })
3654
+ })
3655
+ .then(recJsonOrThrow)
3656
+ .then(function(data) {
3657
+ self._stopping = false;
3658
+ self._reviewData = data;
3659
+ self.renderReview(data);
3660
+ self.setState('review');
3661
+ // Fire LLM summarization in background
3662
+ self.summarizeIntent(data);
3663
+ })
3664
+ .catch(function(err) {
3665
+ self._stopping = false;
3666
+ alert('Failed to stop recording: ' + err.message);
3667
+ if (self.sessionId) {
3668
+ self.setState('recording');
3669
+ self.startPolling();
3670
+ }
3671
+ });
3672
+ },
3673
+
3674
+ discard: function() {
3675
+ this.stopPolling();
3676
+ if (this.state === 'recording') {
3677
+ this._stopBackendRecording();
3678
+ }
3679
+ this.cleanup();
3680
+ this.reset();
3681
+ },
3682
+
3683
+ goToMemory: function() {
3684
+ this.cleanup();
3685
+ this.reset();
3686
+ switchView('memory');
3687
+ },
3688
+
3689
+ summarizeIntent: function(data) {
3690
+ var recording = data.recording || {};
3691
+ var events = recording.events || [];
3692
+ var domain = data.domain || recording.domain || '';
3693
+ var textarea = document.getElementById('recIntentText');
3694
+ var hint = document.getElementById('recIntentHint');
3695
+ var saveBtn = document.getElementById('recSaveBtn');
3696
+
3697
+ if (events.length === 0) {
3698
+ textarea.classList.remove('loading');
3699
+ textarea.value = '';
3700
+ textarea.placeholder = 'No events to analyze.';
3701
+ hint.textContent = '';
3702
+ return;
3703
+ }
3704
+
3705
+ // Disable save while summarizing
3706
+ if (saveBtn) saveBtn.disabled = true;
3707
+ textarea.classList.add('loading');
3708
+ textarea.disabled = true;
3709
+ hint.textContent = 'AI summarizing...';
3710
+
3711
+ // Abort any previous summarize request
3712
+ if (this._summarizeAbort) this._summarizeAbort.abort();
3713
+ this._summarizeAbort = new AbortController();
3714
+ var signal = this._summarizeAbort.signal;
3715
+
3716
+ // Pass LLM config from user settings (same as agent endpoints)
3717
+ var settings = loadSettings();
3718
+ var payload = { events: events, domain: domain };
3719
+ if (settings.apiKey) payload.apiKey = settings.apiKey;
3720
+ if (settings.baseURL) payload.baseURL = settings.baseURL;
3721
+ if (settings.model) payload.model = settings.model;
3722
+ payload.timeout = (settings.timeout && settings.timeout > 0) ? settings.timeout : 300;
3723
+
3724
+ fetch('/v1/recordings/summarize', {
3725
+ method: 'POST',
3726
+ headers: { 'Content-Type': 'application/json' },
3727
+ body: JSON.stringify(payload),
3728
+ signal: signal
3729
+ })
3730
+ // Backend returns 200 with { intent, error? } even on LLM failure — intentional
3731
+ .then(function(r) {
3732
+ if (!r.ok) return { intent: '', error: 'Server error (' + r.status + ')' };
3733
+ return r.json();
3734
+ })
3735
+ .then(function(result) {
3736
+ textarea.classList.remove('loading');
3737
+ textarea.disabled = false;
3738
+ if (saveBtn) saveBtn.disabled = false;
3739
+ if (result.intent) {
3740
+ textarea.value = result.intent;
3741
+ hint.textContent = 'You can edit this before saving.';
3742
+ } else {
3743
+ textarea.value = '';
3744
+ textarea.placeholder = result.error || 'Could not generate summary. You can write one manually.';
3745
+ hint.textContent = result.error ? 'LLM unavailable' : '';
3746
+ if (saveBtn) saveBtn.disabled = false;
3747
+ }
3748
+ })
3749
+ .catch(function(err) {
3750
+ if (err.name === 'AbortError') return; // expected on discard/reset
3751
+ textarea.classList.remove('loading');
3752
+ textarea.disabled = false;
3753
+ if (saveBtn) saveBtn.disabled = false;
3754
+ textarea.placeholder = 'Summarization failed. You can write one manually.';
3755
+ hint.textContent = '';
3756
+ });
3757
+ },
3758
+
3759
+ saveToMemory: function() {
3760
+ var data = this._reviewData;
3761
+ if (!data) { alert('No recording data.'); return; }
3762
+ if (this._saving) return;
3763
+ this._saving = true;
3764
+
3765
+ var recording = data.recording || {};
3766
+ var domain = data.domain || recording.domain || '';
3767
+ if (!domain) { alert('Could not determine domain.'); return; }
3768
+
3769
+ var btn = document.getElementById('recSaveBtn');
3770
+ btn.disabled = true;
3771
+ btn.textContent = 'Saving...';
3772
+ var self = this;
3773
+
3774
+ // Build patterns from RecordingConverter (backend already computed them in stop)
3775
+ // We re-use the existing PUT /v1/memory/:domain endpoint
3776
+ // First, get the patterns that RecordingConverter would produce
3777
+ // We'll send the events to the backend to convert + add intent
3778
+ var intentText = document.getElementById('recIntentText').value.trim();
3779
+ var patterns = [];
3780
+
3781
+ // Add task_intent pattern if user provided/edited intent
3782
+ if (intentText) {
3783
+ patterns.push({
3784
+ type: 'task_intent',
3785
+ description: 'Recording task intent',
3786
+ value: intentText,
3787
+ confidence: 0.9,
3788
+ source: 'human_recording'
3789
+ });
3790
+ }
3791
+
3792
+ // Extract structural patterns from events (shared function)
3793
+ var events = recording.events || [];
3794
+ var structural = extractPatternsFromEvents(events);
3795
+ for (var i = 0; i < structural.length; i++) patterns.push(structural[i]);
3796
+
3797
+ if (patterns.length === 0) {
3798
+ alert('No patterns or intent to save.');
3799
+ btn.disabled = false;
3800
+ btn.textContent = 'Save to Memory';
3801
+ self._saving = false;
3802
+ return;
3803
+ }
3804
+
3805
+ fetch('/v1/memory/' + encodeURIComponent(domain), {
3806
+ method: 'PUT',
3807
+ headers: { 'Content-Type': 'application/json' },
3808
+ body: JSON.stringify({ patterns: patterns })
3809
+ })
3810
+ .then(recJsonOrThrow)
3811
+ .then(function(result) {
3812
+ document.getElementById('recSuccessArea').innerHTML =
3813
+ '<div class="rec-success-msg">&#10003; Saved to memory for <strong>' + esc(domain) + '</strong> (' + result.patternCount + ' patterns)</div>';
3814
+ btn.textContent = 'Saved!';
3815
+ // Replace buttons with "View Memory" link
3816
+ document.getElementById('recActions').innerHTML =
3817
+ '<button class="btn" onclick="Rec.discard()">New Recording</button>' +
3818
+ '<button class="btn btn-primary" onclick="Rec.goToMemory()">View Memory</button>';
3819
+ })
3820
+ .catch(function(err) {
3821
+ alert('Failed to save: ' + err.message);
3822
+ btn.disabled = false;
3823
+ btn.textContent = 'Save to Memory';
3824
+ self._saving = false;
3825
+ });
3826
+ },
3827
+
3828
+ reset: function() {
3829
+ this.state = 'setup';
3830
+ this.sessionId = null;
3831
+ this.startTime = null;
3832
+ this.lastEventCount = 0;
3833
+ this.events = [];
3834
+ this._pollFailCount = 0;
3835
+ this._pollAbort = null;
3836
+ this._stopping = false;
3837
+ this._reviewData = null;
3838
+ if (this._summarizeAbort) { this._summarizeAbort.abort(); this._summarizeAbort = null; }
3839
+ this._saving = false;
3840
+ this.setState('setup');
3841
+ document.getElementById('recSuccessArea').innerHTML = '';
3842
+ var btn = document.getElementById('recStartBtn');
3843
+ btn.disabled = false;
3844
+ btn.textContent = 'Start Recording';
3845
+ },
3846
+
3847
+ cleanup: function() {
3848
+ if (!this.sessionId) return;
3849
+ fetch('/v1/sessions/' + this.sessionId, { method: 'DELETE' }).catch(function() {});
3850
+ this.sessionId = null;
3851
+ },
3852
+
3853
+ /** Fire-and-forget stop recording on backend to clean up recorders Map */
3854
+ _stopBackendRecording: function() {
3855
+ if (!this.sessionId) return;
3856
+ fetch('/v1/sessions/' + this.sessionId + '/recording/stop', {
3857
+ method: 'POST',
3858
+ headers: { 'Content-Type': 'application/json' },
3859
+ body: JSON.stringify({ convertToMemory: false })
3860
+ }).catch(function() {});
3861
+ },
3862
+
3863
+ startPolling: function() {
3864
+ var self = this;
3865
+ this._pollAbort = new AbortController();
3866
+ this.timers.timerInterval = setInterval(function() { self.updateTimer(); }, 1000);
3867
+ this.timers.statusPoll = setInterval(function() { self.pollStatus(); }, 1000);
3868
+ this.timers.screenshotPoll = setInterval(function() { self.pollScreenshot(); }, 3000);
3869
+ // Immediate first screenshot (tracked for cleanup)
3870
+ this.timers.initialScreenshot = setTimeout(function() { self.pollScreenshot(); }, 500);
3871
+ },
3872
+
3873
+ stopPolling: function() {
3874
+ if (this.timers.timerInterval) clearInterval(this.timers.timerInterval);
3875
+ if (this.timers.statusPoll) clearInterval(this.timers.statusPoll);
3876
+ if (this.timers.screenshotPoll) clearInterval(this.timers.screenshotPoll);
3877
+ if (this.timers.initialScreenshot) clearTimeout(this.timers.initialScreenshot);
3878
+ this.timers = {};
3879
+ // Abort any in-flight poll fetches immediately
3880
+ if (this._pollAbort) {
3881
+ this._pollAbort.abort();
3882
+ this._pollAbort = null;
3883
+ }
3884
+ },
3885
+
3886
+ updateTimer: function() {
3887
+ if (!this.startTime) return;
3888
+ var elapsed = Math.floor((Date.now() - this.startTime) / 1000);
3889
+ var m = String(Math.floor(elapsed / 60)).padStart(2, '0');
3890
+ var s = String(elapsed % 60).padStart(2, '0');
3891
+ document.getElementById('recTimer').textContent = m + ':' + s;
3892
+ },
3893
+
3894
+ pollStatus: function() {
3895
+ if (!this.sessionId || !this._pollAbort) return;
3896
+ var self = this;
3897
+ var signal = this._pollAbort.signal;
3898
+ fetch('/v1/sessions/' + this.sessionId + '/recording', { signal: signal })
3899
+ .then(recJsonOrThrow)
3900
+ .then(function(data) {
3901
+ self._pollFailCount = 0;
3902
+ var count = data.eventCount || 0;
3903
+ document.getElementById('recEventCount').textContent = count + ' event' + (count !== 1 ? 's' : '');
3904
+ if (count > self.lastEventCount) {
3905
+ var stream = document.getElementById('recEventStream');
3906
+ if (self.lastEventCount === 0) stream.innerHTML = '';
3907
+ for (var i = self.lastEventCount; i < count; i++) {
3908
+ var item = document.createElement('div');
3909
+ item.className = 'rec-event-item';
3910
+ item.innerHTML = '<span class="ev-icon">&#9679;</span>' +
3911
+ '<span class="ev-badge click">event</span>' +
3912
+ '<span class="ev-desc">Interaction #' + (i + 1) + '</span>' +
3913
+ '<span class="ev-time">' + self.formatTime(Date.now()) + '</span>';
3914
+ stream.appendChild(item);
3915
+ stream.scrollTop = stream.scrollHeight;
3916
+ }
3917
+ self.lastEventCount = count;
3918
+ }
3919
+ })
3920
+ .catch(function(err) {
3921
+ if (err.name === 'AbortError') return; // expected on stop
3922
+ self._pollFailCount++;
3923
+ if (self._pollFailCount >= 5) {
3924
+ self.stopPolling();
3925
+ alert('Lost connection to recording session.');
3926
+ }
3927
+ });
3928
+ },
3929
+
3930
+ pollScreenshot: function() {
3931
+ if (!this.sessionId || !this._pollAbort) return;
3932
+ var container = document.getElementById('recPreviewBody');
3933
+ var signal = this._pollAbort.signal;
3934
+ fetch('/v1/sessions/' + this.sessionId + '/screenshot', { signal: signal })
3935
+ .then(function(r) { return r.ok ? r.json() : null; })
3936
+ .then(function(data) {
3937
+ if (data && data.image) {
3938
+ var imgEl = document.createElement('img');
3939
+ imgEl.src = data.image;
3940
+ imgEl.alt = 'Live preview';
3941
+ container.innerHTML = '';
3942
+ container.appendChild(imgEl);
3943
+ }
3944
+ })
3945
+ .catch(function() {}); // AbortError or network error — silent
3946
+ },
3947
+
3948
+ renderReview: function(data) {
3949
+ var recording = data.recording || {};
3950
+ var events = recording.events || [];
3951
+ var domain = data.domain || recording.domain || '-';
3952
+ var duration = recording.endedAt && recording.startedAt
3953
+ ? Math.round((recording.endedAt - recording.startedAt) / 1000)
3954
+ : 0;
3955
+ var dMin = String(Math.floor(duration / 60)).padStart(2, '0');
3956
+ var dSec = String(duration % 60).padStart(2, '0');
3957
+
3958
+ // Summary
3959
+ document.getElementById('recSummary').innerHTML =
3960
+ '<div class="sum-item"><strong>' + esc(domain) + '</strong><br>Domain</div>' +
3961
+ '<div class="sum-item"><strong>' + events.length + '</strong><br>Events</div>' +
3962
+ '<div class="sum-item"><strong>' + dMin + ':' + dSec + '</strong><br>Duration</div>';
3963
+
3964
+ // Intent textarea — reset to loading state (summarizeIntent will fill it)
3965
+ var textarea = document.getElementById('recIntentText');
3966
+ textarea.value = '';
3967
+ textarea.classList.add('loading');
3968
+ textarea.disabled = true;
3969
+ document.getElementById('recIntentHint').textContent = 'AI summarizing...';
3970
+
3971
+ // Timeline
3972
+ var timeline = document.getElementById('recTimeline');
3973
+ if (events.length === 0) {
3974
+ timeline.innerHTML = '<div class="rec-empty-events"><span>No events recorded.</span></div>';
3975
+ } else {
3976
+ var html = '';
3977
+ var self = this;
3978
+ events.forEach(function(evt) {
3979
+ html += self.renderEventItem(evt);
3980
+ });
3981
+ timeline.innerHTML = html;
3982
+ }
3983
+
3984
+ // Patterns preview (show what will be extracted, not yet saved)
3985
+ var patternsBody = document.getElementById('recPatternsBody');
3986
+ var previewPatterns = extractPatternsFromEvents(events);
3987
+
3988
+ if (previewPatterns.length > 0) {
3989
+ var phtml = '';
3990
+ previewPatterns.forEach(function(p) { phtml += Rec.renderPattern(p); });
3991
+ patternsBody.innerHTML = phtml;
3992
+ } else {
3993
+ patternsBody.innerHTML = '<div class="placeholder-text">No structural patterns detected.</div>';
3994
+ }
3995
+
3996
+ // Reset action buttons and success area
3997
+ document.getElementById('recSuccessArea').innerHTML = '';
3998
+ document.getElementById('recActions').innerHTML =
3999
+ '<button class="btn" onclick="Rec.discard()">Discard</button>' +
4000
+ '<button class="btn btn-primary" id="recSaveBtn" onclick="Rec.saveToMemory()">Save to Memory</button>';
4001
+ },
4002
+
4003
+ renderEventItem: function(evt) {
4004
+ var icons = { navigate: '&#8599;', click: '&#9673;', type: '&#9000;', select: '&#9776;', scroll: '&#8597;' };
4005
+ var icon = icons[evt.type] || '&#9679;';
4006
+ var desc = '';
4007
+ if (evt.type === 'navigate') {
4008
+ desc = evt.url || '';
4009
+ } else if (evt.type === 'click') {
4010
+ desc = evt.targetLabel || evt.target || 'element';
4011
+ } else if (evt.type === 'type') {
4012
+ desc = (evt.targetLabel || 'input') + (evt.value ? ': ' + evt.value.slice(0, 30) : '');
4013
+ } else if (evt.type === 'select') {
4014
+ desc = (evt.targetLabel || 'select') + (evt.value ? ' → ' + evt.value : '');
4015
+ } else if (evt.type === 'scroll') {
4016
+ desc = 'scrollY: ' + (evt.value || '0');
4017
+ }
4018
+ return '<div class="rec-event-item">' +
4019
+ '<span class="ev-icon">' + icon + '</span>' +
4020
+ '<span class="ev-badge ' + esc(evt.type) + '">' + esc(evt.type) + '</span>' +
4021
+ '<span class="ev-desc" title="' + esc(desc) + '">' + esc(desc) + '</span>' +
4022
+ '<span class="ev-time">' + this.formatTime(evt.timestamp) + '</span>' +
4023
+ '</div>';
4024
+ },
4025
+
4026
+ renderPattern: function(p) {
4027
+ return '<div class="rec-pattern-item">' +
4028
+ '<span class="pat-type">' + esc(p.type) + '</span>' +
4029
+ '<div class="pat-desc">' + esc(p.description) + '</div>' +
4030
+ '<div class="pat-value">' + esc(p.value) + '</div>' +
4031
+ '</div>';
4032
+ },
4033
+
4034
+ formatTime: function(ts) {
4035
+ if (!ts) return '';
4036
+ var d = new Date(ts);
4037
+ return String(d.getHours()).padStart(2, '0') + ':' +
4038
+ String(d.getMinutes()).padStart(2, '0') + ':' +
4039
+ String(d.getSeconds()).padStart(2, '0');
4040
+ }
4041
+ };
4042
+ </script>
4043
+
4044
+ <script>
4045
+ // ========== Welcome Banner ==========
4046
+ function dismissWelcome() {
4047
+ document.getElementById('welcomeBanner').style.display = 'none';
4048
+ localStorage.setItem('ai_browser_welcomed', '1');
4049
+ }
4050
+ function showWelcomeIfNeeded() {
4051
+ if (!localStorage.getItem('ai_browser_welcomed')) {
4052
+ document.getElementById('welcomeBanner').style.display = '';
4053
+ }
4054
+ }
4055
+ </script>
4056
+
2540
4057
  <script>
2541
4058
  // ========== Init ==========
2542
4059
  (function() {
2543
4060
  var hash = location.hash.replace('#', '');
2544
- var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : 'semantic';
4061
+ var view = (hash === 'agent') ? 'agent' : (hash === 'tasks') ? 'tasks' : (hash === 'memory') ? 'memory' : (hash === 'record') ? 'record' : 'semantic';
2545
4062
  switchView(view);
2546
4063
  ChatStore.load();
4064
+ showWelcomeIfNeeded();
2547
4065
  })();
2548
4066
  </script>
2549
4067
  </body>