clawaid 1.1.2 → 1.1.3

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.
package/web/index.html CHANGED
@@ -6,1331 +6,722 @@
6
6
  <title>ClawAid</title>
7
7
  <style>
8
8
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
-
10
- body {
11
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, sans-serif;
12
- background: #fafafa;
13
- color: #202124;
14
- min-height: 100vh;
15
- display: flex;
16
- flex-direction: column;
17
- align-items: center;
18
- }
19
-
20
- .container {
21
- width: 100%;
22
- max-width: 600px;
23
- padding: 48px 24px 80px;
24
- }
9
+ :root {
10
+ --bg: #f5f5f5; --surface: #fff; --border: #e5e5e5;
11
+ --text: #111; --text2: #555; --muted: #999;
12
+ --blue: #2563eb; --blue-bg: #eff6ff; --blue-border: #bfdbfe;
13
+ --green: #16a34a; --green-bg: #f0fdf4; --green-border: #bbf7d0;
14
+ --red: #dc2626; --red-bg: #fef2f2; --red-border: #fecaca;
15
+ --yellow: #ca8a04; --yellow-bg: #fefce8; --yellow-border: #fde68a;
16
+ --r: 12px; --rs: 8px;
17
+ }
18
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; padding: 24px 16px 80px; }
19
+ .wrap { max-width: 540px; margin: 0 auto; }
25
20
 
26
21
  /* Header */
27
- .header {
28
- text-align: center;
29
- margin-bottom: 40px;
30
- }
31
-
32
- .logo {
33
- font-size: 40px;
34
- margin-bottom: 8px;
35
- }
36
-
37
- .header h1 {
38
- font-size: 28px;
39
- font-weight: 400;
40
- color: #202124;
41
- margin-bottom: 6px;
42
- }
43
-
44
- .header p {
45
- font-size: 14px;
46
- color: #5f6368;
47
- }
48
-
49
- /* Status card */
50
- .card {
51
- background: #fff;
52
- border: 1px solid #e8eaed;
53
- border-radius: 12px;
54
- padding: 32px;
55
- margin-bottom: 16px;
56
- }
57
-
58
- /* Progress log */
59
- .log-area {
60
- background: #f8f9fa;
61
- border: 1px solid #e0e0e0;
62
- border-radius: 8px;
63
- padding: 16px;
64
- font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
65
- font-size: 12px;
66
- color: #3c4043;
67
- line-height: 1.6;
68
- max-height: 280px;
69
- overflow-y: auto;
70
- white-space: pre-wrap;
71
- word-break: break-word;
72
- }
73
-
74
- .log-area .log-line { display: block; }
75
- .log-area .log-line.success { color: #137333; }
76
- .log-area .log-line.error { color: #c5221f; }
77
- .log-area .log-line.warn { color: #e37400; }
78
- .log-area .log-line.meta { color: #1a73e8; font-weight: 600; }
79
-
80
- /* Show details toggle */
81
- .details-toggle {
82
- display: flex;
83
- align-items: center;
84
- gap: 6px;
85
- font-size: 12px;
86
- color: #1a73e8;
87
- cursor: pointer;
88
- margin-top: 12px;
89
- user-select: none;
90
- background: none;
91
- border: none;
92
- padding: 0;
93
- }
94
- .details-toggle:hover { text-decoration: underline; }
95
- .details-toggle .toggle-arrow { transition: transform 0.2s; display: inline-block; }
96
- .details-toggle.open .toggle-arrow { transform: rotate(90deg); }
97
-
98
- .details-panel {
99
- margin-top: 8px;
100
- display: none;
101
- }
102
- .details-panel.open { display: block; }
103
-
104
- /* Spinner */
105
- .spinner {
106
- display: inline-block;
107
- width: 18px;
108
- height: 18px;
109
- border: 2px solid #e0e0e0;
110
- border-top-color: #1a73e8;
111
- border-radius: 50%;
112
- animation: spin 0.8s linear infinite;
113
- flex-shrink: 0;
114
- }
115
-
116
- @keyframes spin {
117
- to { transform: rotate(360deg); }
118
- }
119
-
120
- /* Status row */
121
- .status-row {
122
- display: flex;
123
- align-items: center;
124
- gap: 10px;
125
- font-size: 15px;
126
- color: #202124;
127
- margin-bottom: 4px;
128
- }
129
-
130
- .status-sub {
131
- font-size: 12px;
132
- color: #9aa0a6;
133
- margin-left: 28px;
134
- }
135
-
136
- /* Phase label */
137
- .phase-label {
138
- font-size: 10px;
139
- font-weight: 600;
140
- text-transform: uppercase;
141
- letter-spacing: 1px;
142
- color: #9aa0a6;
143
- margin-bottom: 16px;
144
- }
145
-
146
- /* Diagnosis card */
147
- .diagnosis-section {
148
- margin-bottom: 20px;
149
- }
150
-
151
- .section-label {
152
- font-size: 11px;
153
- font-weight: 600;
154
- color: #9aa0a6;
155
- text-transform: uppercase;
156
- letter-spacing: 0.5px;
157
- margin-bottom: 8px;
158
- }
159
-
160
- .diagnosis-text {
161
- font-size: 15px;
162
- color: #202124;
163
- line-height: 1.6;
164
- }
165
-
166
- .root-cause {
167
- background: #fef7e0;
168
- border: 1px solid #f9ab00;
169
- border-radius: 8px;
170
- padding: 10px 14px;
171
- font-size: 13px;
172
- color: #7a4f00;
173
- margin-top: 10px;
174
- }
175
-
176
- .confidence-bar {
177
- height: 4px;
178
- background: #e8f0fe;
179
- border-radius: 2px;
180
- margin-top: 12px;
22
+ .hdr { text-align: center; margin-bottom: 28px; }
23
+ .hdr .logo { font-size: 40px; line-height: 1; margin-bottom: 6px; }
24
+ .hdr h1 { font-size: 22px; font-weight: 600; }
25
+ .hdr p { font-size: 13px; color: var(--text2); margin-top: 2px; }
26
+
27
+ /* Steps feed */
28
+ #feed { display: flex; flex-direction: column; gap: 8px; }
29
+
30
+ /* Step card */
31
+ .step {
32
+ background: var(--surface);
33
+ border: 1px solid var(--border);
34
+ border-radius: var(--r);
181
35
  overflow: hidden;
182
- }
183
-
184
- .confidence-fill {
185
- height: 100%;
186
- background: #1a73e8;
187
- border-radius: 2px;
188
- transition: width 0.5s ease;
189
- }
190
-
191
- .confidence-label {
192
- font-size: 11px;
193
- color: #9aa0a6;
194
- margin-top: 4px;
195
- }
196
-
197
- /* Collapsible reasoning */
198
- .collapsible-toggle {
199
- display: flex;
200
- align-items: center;
201
- gap: 6px;
202
- font-size: 12px;
203
- color: #5f6368;
204
- cursor: pointer;
205
- margin-top: 12px;
206
- background: none;
207
- border: none;
208
- padding: 0;
209
- user-select: none;
210
- }
211
- .collapsible-toggle:hover { color: #202124; }
212
- .collapsible-toggle .arrow { transition: transform 0.2s; display: inline-block; }
213
- .collapsible-toggle.open .arrow { transform: rotate(90deg); }
214
- .collapsible-panel { display: none; margin-top: 8px; }
215
- .collapsible-panel.open { display: block; }
216
-
217
- /* Divider */
218
- .divider {
219
- border: none;
220
- border-top: 1px solid #f1f3f4;
221
- margin: 16px 0;
222
- }
223
-
224
- /* Option cards */
225
- .options-list {
226
- display: flex;
227
- flex-direction: column;
228
- gap: 12px;
229
- margin-top: 8px;
230
- }
231
-
232
- .option-card {
233
- border: 1.5px solid #e0e0e0;
234
- border-radius: 10px;
235
- padding: 16px;
236
- transition: border-color 0.15s;
237
- position: relative;
238
- }
239
-
240
- .option-card.recommended {
241
- border-color: #1a73e8;
242
- background: #f8fbff;
243
- }
244
-
245
- .option-card-header {
246
- display: flex;
247
- align-items: flex-start;
248
- gap: 10px;
36
+ animation: fadeUp 0.2s ease;
37
+ }
38
+ @keyframes fadeUp { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
39
+
40
+ .step-top {
41
+ display: flex; align-items: center; gap: 10px;
42
+ padding: 14px 16px; cursor: pointer; user-select: none;
43
+ }
44
+ .step-icon { font-size: 18px; width: 24px; text-align: center; flex-shrink: 0; }
45
+ .step-title { font-size: 14px; font-weight: 500; flex: 1; color: var(--text); }
46
+ .step-badge {
47
+ font-size: 10px; font-weight: 600; padding: 2px 7px;
48
+ border-radius: 4px; text-transform: uppercase;
49
+ }
50
+ .badge-read { background: var(--blue-bg); color: var(--blue); }
51
+ .badge-fix { background: var(--yellow-bg); color: var(--yellow); }
52
+ .badge-done { background: var(--green-bg); color: var(--green); }
53
+ .badge-risk-medium { background: var(--yellow-bg); color: var(--yellow); }
54
+ .badge-risk-high { background: var(--red-bg); color: var(--red); }
55
+
56
+ .step-status { font-size: 18px; flex-shrink: 0; }
57
+
58
+ /* Expandable detail */
59
+ .step-detail { display: none; padding: 0 16px 14px; }
60
+ .step.open .step-detail { display: block; }
61
+ .step-reason { font-size: 13px; color: var(--text2); line-height: 1.5; margin-bottom: 8px; }
62
+ .step-cmd {
63
+ font-family: 'SF Mono', monospace; font-size: 12px;
64
+ background: #f8f8f8; border: 1px solid var(--border); border-radius: var(--rs);
65
+ padding: 8px 10px; color: var(--text2); word-break: break-all;
249
66
  margin-bottom: 8px;
250
67
  }
251
-
252
- .option-letter {
253
- width: 26px;
254
- height: 26px;
255
- border-radius: 50%;
256
- background: #e8eaed;
257
- color: #3c4043;
258
- font-size: 13px;
259
- font-weight: 700;
260
- display: flex;
261
- align-items: center;
262
- justify-content: center;
263
- flex-shrink: 0;
264
- }
265
-
266
- .option-card.recommended .option-letter {
267
- background: #1a73e8;
268
- color: #fff;
269
- }
270
-
271
- .option-title-row {
272
- flex: 1;
273
- display: flex;
274
- align-items: center;
275
- gap: 8px;
276
- flex-wrap: wrap;
68
+ .step-output {
69
+ font-family: 'SF Mono', monospace; font-size: 11px;
70
+ background: #fafafa; border: 1px solid var(--border); border-radius: var(--rs);
71
+ padding: 8px 10px; color: var(--text2); max-height: 160px;
72
+ overflow-y: auto; white-space: pre-wrap; word-break: break-all; line-height: 1.4;
277
73
  }
74
+ .step-output-label { font-size: 11px; color: var(--muted); margin-bottom: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
278
75
 
279
- .option-title {
280
- font-size: 14px;
281
- font-weight: 600;
282
- color: #202124;
76
+ /* Confirm bar */
77
+ .confirm-bar {
78
+ display: flex; gap: 8px; padding: 12px 16px;
79
+ background: var(--yellow-bg); border-top: 1px solid var(--yellow-border);
283
80
  }
81
+ .confirm-bar.risk-high { background: var(--red-bg); border-top-color: var(--red-border); }
284
82
 
285
- .recommended-badge {
286
- font-size: 10px;
287
- background: #e8f0fe;
288
- color: #1a73e8;
289
- border: 1px solid #c5d9f7;
290
- border-radius: 4px;
291
- padding: 2px 6px;
292
- font-weight: 600;
293
- }
294
-
295
- .risk-badge {
296
- font-size: 10px;
297
- padding: 2px 6px;
298
- border-radius: 4px;
299
- font-weight: 600;
300
- }
301
- .risk-low { background: #e6f4ea; color: #137333; }
302
- .risk-medium { background: #fef7e0; color: #7a4f00; }
303
- .risk-high { background: #fce8e6; color: #c5221f; }
304
-
305
- .option-description {
306
- font-size: 13px;
307
- color: #5f6368;
308
- line-height: 1.5;
309
- margin-left: 36px;
310
- }
311
-
312
- .option-fix-btn {
313
- display: inline-flex;
314
- align-items: center;
315
- gap: 6px;
316
- margin-top: 12px;
317
- margin-left: 36px;
318
- padding: 8px 18px;
319
- font-size: 13px;
320
- font-weight: 500;
321
- background: #f1f3f4;
322
- color: #3c4043;
323
- border: none;
324
- border-radius: 6px;
325
- cursor: pointer;
326
- transition: background 0.15s;
327
- }
328
-
329
- .option-card.recommended .option-fix-btn {
330
- background: #1a73e8;
331
- color: #fff;
332
- }
333
-
334
- .option-fix-btn:hover { opacity: 0.85; }
335
- .option-fix-btn:disabled { opacity: 0.5; cursor: not-allowed; }
336
-
337
- /* Auto-fix progress */
338
- .auto-fix-row {
339
- display: flex;
340
- align-items: center;
341
- gap: 12px;
342
- padding: 12px 0;
343
- }
344
-
345
- .auto-fix-icon {
346
- font-size: 20px;
347
- }
348
-
349
- .auto-fix-text {
350
- font-size: 15px;
351
- font-weight: 500;
352
- color: #202124;
353
- }
354
-
355
- .auto-fix-sub {
356
- font-size: 12px;
357
- color: #9aa0a6;
358
- margin-top: 2px;
359
- }
83
+ /* Spinner */
84
+ .spinner { width: 16px; height: 16px; border: 2px solid #e0e0e0; border-top-color: var(--blue); border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0; }
85
+ @keyframes spin { to { transform: rotate(360deg); } }
360
86
 
361
87
  /* Buttons */
362
88
  .btn {
363
- display: inline-flex;
364
- align-items: center;
365
- gap: 8px;
366
- padding: 12px 24px;
367
- font-size: 15px;
368
- font-weight: 500;
369
- border: none;
370
- border-radius: 8px;
371
- cursor: pointer;
372
- transition: background 0.15s, transform 0.1s;
373
- width: 100%;
374
- justify-content: center;
89
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
90
+ padding: 8px 16px; font-size: 13px; font-weight: 500;
91
+ border: none; border-radius: var(--rs); cursor: pointer;
375
92
  }
376
-
377
- .btn:active { transform: scale(0.98); }
93
+ .btn:hover:not(:disabled) { opacity: 0.85; }
378
94
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
95
+ .btn-primary { background: var(--blue); color: #fff; }
96
+ .btn-green { background: var(--green); color: #fff; }
97
+ .btn-ghost { background: transparent; color: var(--text2); border: 1px solid var(--border); }
98
+ .btn-big { width: 100%; padding: 12px 20px; font-size: 15px; border-radius: var(--r); }
99
+ /* .btn-coffee removed */
100
+
101
+ /* Result card */
102
+ .result {
103
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
104
+ padding: 24px 20px; text-align: center; animation: fadeUp 0.3s ease;
105
+ }
106
+ .result-icon { font-size: 40px; margin-bottom: 10px; }
107
+ .result-title { font-size: 18px; font-weight: 600; margin-bottom: 6px; }
108
+ .result-desc { font-size: 14px; color: var(--text2); line-height: 1.6; margin-bottom: 16px; }
109
+
110
+ /* Start card */
111
+ .start-card {
112
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
113
+ padding: 24px 20px;
114
+ }
115
+ .start-card p { font-size: 14px; color: var(--text2); line-height: 1.6; margin-bottom: 12px; }
116
+ .start-card ul { padding-left: 18px; font-size: 13px; color: var(--text2); line-height: 1.8; margin-bottom: 16px; }
117
+
118
+ /* Input */
119
+ .input-field { width: 100%; padding: 10px 12px; font-size: 14px; border: 1px solid var(--border); border-radius: var(--rs); font-family: monospace; margin-bottom: 8px; }
120
+ .input-field:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 2px rgba(37,99,235,.12); }
121
+
122
+ /* desc-textarea now defined in the integrated input card section below */
123
+
124
+ /* .screenshot-area removed — replaced by integrated camera button approach */
125
+
126
+ .btn-row { display: flex; gap: 8px; }
127
+ .btn-row .btn { flex: 1; }
128
+
129
+ /* Warnings list */
130
+ .warnings-list { margin-top: 14px; text-align: left; }
131
+ .warning-item {
132
+ display: flex; align-items: flex-start; gap: 8px;
133
+ padding: 10px 12px; margin-bottom: 6px;
134
+ background: var(--yellow-bg); border: 1px solid var(--yellow-border);
135
+ border-radius: var(--rs); font-size: 13px; color: var(--text); line-height: 1.5;
136
+ transition: opacity 0.3s, max-height 0.3s;
137
+ }
138
+ .warning-item .wi-icon { flex-shrink: 0; font-size: 15px; margin-top: 1px; }
139
+ .warning-item .wi-text { flex: 1; }
140
+ .warning-item .wi-dismiss {
141
+ flex-shrink: 0; background: none; border: none; cursor: pointer;
142
+ font-size: 14px; color: var(--muted); padding: 0 0 0 6px; line-height: 1;
143
+ align-self: flex-start; margin-top: 1px;
144
+ }
145
+ .warning-item .wi-dismiss:hover { color: var(--text); }
146
+ .warning-item.dismissing { opacity: 0; pointer-events: none; }
147
+ .warnings-title { font-size: 13px; font-weight: 600; color: var(--text2); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
379
148
 
380
- .btn-primary {
381
- background: #1a73e8;
382
- color: #fff;
383
- }
384
- .btn-primary:hover:not(:disabled) { background: #1557b0; }
385
-
386
- .btn-secondary {
387
- background: #f1f3f4;
388
- color: #3c4043;
389
- }
390
- .btn-secondary:hover:not(:disabled) { background: #e8eaed; }
391
-
392
- .btn-coffee {
393
- background: #FFDD00;
394
- color: #000;
395
- font-size: 14px;
396
- }
397
- .btn-coffee:hover:not(:disabled) { background: #f0cf00; }
398
-
399
- /* Input field */
400
- .input-group { margin-bottom: 16px; }
401
-
402
- .input-label {
403
- display: block;
404
- font-size: 13px;
405
- font-weight: 500;
406
- color: #3c4043;
407
- margin-bottom: 6px;
408
- }
409
-
410
- .input-field {
411
- width: 100%;
412
- padding: 10px 14px;
413
- font-size: 14px;
414
- border: 1px solid #dadce0;
415
- border-radius: 8px;
416
- outline: none;
417
- transition: border-color 0.15s;
418
- font-family: monospace;
419
- color: #202124;
420
- }
421
- .input-field:focus { border-color: #1a73e8; box-shadow: 0 0 0 2px rgba(26,115,232,0.2); }
422
-
423
- .trust-badge {
424
- display: flex;
425
- align-items: center;
426
- gap: 6px;
427
- font-size: 12px;
428
- color: #137333;
429
- margin-top: 8px;
430
- background: #e6f4ea;
431
- padding: 6px 10px;
432
- border-radius: 6px;
433
- }
434
-
435
- /* Result screens */
436
- .result-icon {
437
- font-size: 48px;
438
- text-align: center;
439
- margin-bottom: 16px;
440
- }
441
-
442
- .result-title {
443
- font-size: 22px;
444
- font-weight: 500;
445
- text-align: center;
446
- margin-bottom: 8px;
447
- }
448
-
449
- .result-desc {
450
- font-size: 14px;
451
- color: #5f6368;
452
- text-align: center;
453
- line-height: 1.6;
454
- margin-bottom: 24px;
455
- }
456
-
457
- .report-area {
458
- background: #f8f9fa;
459
- border: 1px solid #e0e0e0;
460
- border-radius: 8px;
461
- padding: 16px;
462
- font-family: monospace;
463
- font-size: 11px;
464
- color: #3c4043;
465
- max-height: 200px;
466
- overflow-y: auto;
467
- white-space: pre-wrap;
468
- word-break: break-all;
469
- margin-bottom: 12px;
470
- }
149
+ /* Footer */
150
+ .footer { text-align: center; font-size: 12px; color: var(--muted); margin-top: 28px; }
151
+ .footer a { color: var(--blue); text-decoration: none; }
471
152
 
472
- /* Alternatives */
473
- .alt-list { list-style: none; margin-top: 6px; }
474
- .alt-list li {
475
- font-size: 13px;
476
- color: #5f6368;
477
- padding: 4px 0;
478
- display: flex;
479
- gap: 6px;
153
+ /* AI reasoning toggle */
154
+ .step-reasoning-toggle {
155
+ display: inline-block; font-size: 12px; color: var(--muted);
156
+ cursor: pointer; margin-bottom: 8px; user-select: none;
157
+ text-decoration: underline dotted;
480
158
  }
481
- .alt-list li::before { content: '→'; color: #9aa0a6; flex-shrink: 0; }
482
-
483
- /* Sections */
484
- .section { display: none; }
485
- .section.visible { display: block; }
486
-
487
- @keyframes fadeIn {
488
- from { opacity: 0; transform: translateY(8px); }
489
- to { opacity: 1; transform: translateY(0); }
159
+ .step-reasoning-toggle:hover { color: var(--text2); }
160
+ .step-reasoning-text {
161
+ display: none; font-size: 12px; color: var(--muted); line-height: 1.5;
162
+ background: #fafafa; border: 1px solid var(--border); border-radius: var(--rs);
163
+ padding: 8px 10px; margin-bottom: 8px; white-space: pre-wrap; word-break: break-word;
490
164
  }
491
- .fade-in { animation: fadeIn 0.25s ease; }
492
165
 
493
- /* Footer */
494
- .footer {
495
- text-align: center;
496
- font-size: 12px;
497
- color: #9aa0a6;
498
- margin-top: 32px;
499
- }
500
- .footer a { color: #1a73e8; text-decoration: none; }
501
- .footer a:hover { text-decoration: underline; }
502
-
503
- /* Copy button */
504
- .copy-btn {
505
- background: none;
506
- border: 1px solid #dadce0;
507
- border-radius: 6px;
508
- padding: 6px 12px;
509
- font-size: 12px;
510
- cursor: pointer;
511
- color: #5f6368;
512
- transition: background 0.15s;
166
+ /* Integrated input card */
167
+ .desc-input-wrap {
168
+ position: relative;
513
169
  }
514
- .copy-btn:hover { background: #f1f3f4; }
515
-
516
- /* Round badge */
517
- .round-badge {
518
- display: inline-flex;
519
- align-items: center;
520
- gap: 6px;
521
- background: #e8f0fe;
522
- color: #1a73e8;
523
- font-size: 12px;
524
- font-weight: 600;
525
- padding: 4px 10px;
526
- border-radius: 20px;
527
- margin-bottom: 12px;
170
+ .desc-textarea-wrap {
171
+ position: relative;
528
172
  }
173
+ .desc-textarea {
174
+ width: 100%; padding: 10px 12px; padding-bottom: 36px; font-size: 14px;
175
+ border: 1px solid var(--border); border-radius: var(--rs);
176
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
177
+ resize: vertical; min-height: 80px; line-height: 1.5;
178
+ }
179
+ .desc-textarea:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 2px rgba(37,99,235,.12); }
180
+ .desc-textarea::placeholder { color: var(--muted); }
181
+ .desc-camera-btn {
182
+ position: absolute; bottom: 8px; right: 8px;
183
+ background: none; border: none; cursor: pointer; font-size: 18px;
184
+ line-height: 1; padding: 4px; color: var(--muted); border-radius: 6px;
185
+ transition: color 0.15s, background 0.15s;
186
+ }
187
+ .desc-camera-btn:hover { color: var(--text); background: var(--bg); }
188
+ .desc-camera-btn.has-image { color: var(--green); }
189
+
190
+ .screenshot-preview-row {
191
+ display: flex; align-items: center; gap: 8px;
192
+ margin-top: 6px; margin-bottom: 8px;
193
+ padding: 6px 10px; background: var(--green-bg); border: 1px solid var(--green-border);
194
+ border-radius: var(--rs); font-size: 12px; color: var(--green);
195
+ }
196
+ .screenshot-preview-row img { height: 40px; border-radius: 4px; }
197
+ .screenshot-preview-row .spr-remove {
198
+ margin-left: auto; background: none; border: none; cursor: pointer;
199
+ font-size: 14px; color: var(--muted);
200
+ }
201
+ .screenshot-preview-row .spr-remove:hover { color: var(--red); }
202
+
203
+ /* Hide sections */
204
+ .hidden { display: none; }
529
205
  </style>
530
206
  </head>
531
207
  <body>
532
- <div class="container">
208
+ <div class="wrap">
533
209
 
534
- <!-- Header -->
535
- <div class="header">
536
- <div class="logo" style="font-size:48px; line-height:1;">🦞🩹</div>
210
+ <div class="hdr">
211
+ <div class="logo">🦞🩹</div>
537
212
  <h1>ClawAid</h1>
538
- <p>Automatic diagnostics and repair for OpenClaw</p>
213
+ <p>Automatic diagnostics & repair for OpenClaw</p>
539
214
  </div>
540
215
 
541
- <!-- ===== SECTION: Privacy ===== -->
542
- <div id="section-privacy" class="section visible card fade-in">
543
- <div style="text-align:center; font-size:32px; margin-bottom:12px;">🔒</div>
544
- <div style="font-size:16px; font-weight:500; text-align:center; margin-bottom:12px;">Before we start</div>
545
- <div style="font-size:13px; color:#5f6368; line-height:1.6; margin-bottom:16px;">
546
- ClawAid will collect your OpenClaw config, gateway status, and recent logs to diagnose issues.
547
- <ul style="margin-top:8px; padding-left:20px;">
548
- <li>API keys are partially redacted before sending</li>
549
- <li>Data is sent to <strong>our diagnostic service</strong> for analysis only</li>
550
- <li>Nothing is stored or shared beyond this session</li>
216
+ <!-- Start screen -->
217
+ <div id="start-screen">
218
+ <div class="start-card">
219
+ <p>ClawAid reads your OpenClaw config, gateway status, and logs to find and fix problems.</p>
220
+ <ul>
221
+ <li>API keys are redacted before leaving your machine</li>
222
+ <li>Data is sent to our diagnostic service — never stored</li>
223
+ <li>All commands run locally on your machine</li>
551
224
  </ul>
225
+ <button class="btn btn-primary btn-big" id="btn-start">🩺 Start Scan</button>
552
226
  </div>
553
- <button class="btn btn-primary" id="btn-start-scan">
554
- 🩺 Start Scan
555
- </button>
556
227
  </div>
557
228
 
558
- <!-- ===== SECTION: Scanning ===== -->
559
- <div id="section-scanning" class="section card fade-in">
560
- <div class="phase-label">Phase 1 — Scanning</div>
561
- <div class="status-row">
562
- <span class="spinner"></span>
563
- <span id="scan-status-text">Starting scan...</span>
564
- </div>
565
- <div class="status-sub" id="scan-sub-text"></div>
229
+ <!-- Steps feed -->
230
+ <div id="feed" class="hidden"></div>
566
231
 
567
- <button class="details-toggle" id="btn-toggle-scan-log" onclick="toggleDetails('scan-log')">
568
- <span class="toggle-arrow">▶</span> Show details
569
- </button>
570
- <div class="details-panel" id="details-scan-log">
571
- <div class="log-area" id="log-area">Connecting to diagnostic engine...</div>
572
- </div>
573
- </div>
232
+ <div class="footer">ClawAid v2.0.0 · <a href="https://clawaid.app" target="_blank">clawaid.app</a></div>
574
233
 
575
- <!-- ===== SECTION: API Key Input ===== -->
576
- <div id="section-apikey" class="section card fade-in">
577
- <div class="phase-label">Action Required</div>
578
- <div class="diagnosis-section">
579
- <div class="section-label">API Key Required</div>
580
- <p class="diagnosis-text" id="apikey-instructions">
581
- ClawAid needs an OpenRouter API key to run diagnostics.
582
- </p>
583
- </div>
584
- <hr class="divider">
585
- <div class="input-group">
586
- <label class="input-label" for="apikey-input">OpenRouter API Key</label>
587
- <input
588
- class="input-field"
589
- id="apikey-input"
590
- type="password"
591
- placeholder="sk-or-..."
592
- autocomplete="off"
593
- spellcheck="false"
594
- />
595
- <div class="trust-badge">
596
- 🔒 Local only. Not stored. Only sent to OpenRouter for AI diagnosis.
597
- </div>
598
- <div style="margin-top: 6px;">
599
- <a href="https://openrouter.ai/keys" target="_blank" style="font-size: 12px; color: #1a73e8;">
600
- Get an OpenRouter API key →
601
- </a>
602
- </div>
603
- </div>
604
- <button class="btn btn-primary" id="btn-submit-key">
605
- Start Diagnosis
606
- </button>
607
- </div>
234
+ </div>
608
235
 
609
- <!-- ===== SECTION: Diagnosis ===== -->
610
- <div id="section-diagnosis" class="section card fade-in">
611
- <div class="phase-label">Phase 2 — Diagnosis</div>
612
- <div id="round-badge-diag" class="round-badge" style="display:none;">
613
- <span>🔄</span><span id="round-badge-text">Round 2</span>
614
- </div>
236
+ <script>
237
+ (function() {
238
+ 'use strict';
615
239
 
616
- <div class="diagnosis-section">
617
- <div class="section-label">What's wrong</div>
618
- <p class="diagnosis-text" id="diag-text">Analyzing...</p>
619
- <div class="root-cause" id="root-cause-box"></div>
620
- <div class="confidence-bar">
621
- <div class="confidence-fill" id="confidence-fill" style="width:0%"></div>
622
- </div>
623
- <div class="confidence-label" id="confidence-label">Confidence: —</div>
624
- </div>
240
+ let sessionId = null;
241
+ let eventSource = null;
242
+ const feed = document.getElementById('feed');
243
+ const startScreen = document.getElementById('start-screen');
625
244
 
626
- <button class="collapsible-toggle" id="toggle-reasoning" onclick="toggleReasoning()">
627
- <span class="arrow">▶</span> Diagnostic details
628
- </button>
629
- <div class="collapsible-panel" id="reasoning-panel">
630
- <div class="log-area" id="reasoning-log" style="max-height:180px;"></div>
631
- </div>
245
+ // ─── Helpers ──────────────────────────────────────────────────────────────
632
246
 
633
- <div id="alternatives-section" style="display:none; margin-top:12px;">
634
- <hr class="divider">
635
- <div class="section-label">Other possibilities</div>
636
- <ul class="alt-list" id="alt-list"></ul>
637
- </div>
638
- </div>
247
+ function esc(s) {
248
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
249
+ }
639
250
 
640
- <!-- ===== SECTION: Options (user choice) ===== -->
641
- <div id="section-options" class="section card fade-in">
642
- <div class="phase-label">Phase 3 — Choose a Fix</div>
643
- <div id="round-badge-opts" class="round-badge" style="display:none;">
644
- <span>🔄</span><span id="round-opts-text">Round 2</span>
645
- </div>
646
- <p style="font-size:14px; color:#5f6368; margin-bottom:16px;">
647
- Select a repair option below. The recommended fix is highlighted.
648
- </p>
649
- <div class="options-list" id="options-list"></div>
650
- </div>
251
+ function scrollBottom() {
252
+ window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
253
+ }
651
254
 
652
- <!-- ===== SECTION: Auto-executing ===== -->
653
- <div id="section-auto-fix" class="section card fade-in">
654
- <div class="phase-label">Phase 3 — Auto-fixing</div>
655
- <div class="auto-fix-row">
656
- <div class="auto-fix-icon">🔧</div>
657
- <div>
658
- <div class="auto-fix-text">Auto-fixing...</div>
659
- <div class="auto-fix-sub" id="auto-fix-sub">Applying recommended fix</div>
660
- </div>
661
- <span class="spinner" style="margin-left:auto;"></span>
662
- </div>
255
+ // ─── Step rendering ───────────────────────────────────────────────────────
663
256
 
664
- <button class="details-toggle" id="btn-toggle-fix-log" onclick="toggleDetails('fix-log')">
665
- <span class="toggle-arrow">▶</span> Show details
666
- </button>
667
- <div class="details-panel" id="details-fix-log">
668
- <div class="log-area" id="exec-log"></div>
669
- </div>
670
- </div>
257
+ function iconForStep(step) {
258
+ if (step.type === 'read') return '🔍';
259
+ if (step.type === 'fix') return '🔧';
260
+ if (step.type === 'done') return step.fixed ? '✅' : '😔';
261
+ return '⚙️';
262
+ }
671
263
 
672
- <!-- ===== SECTION: Fixed ===== -->
673
- <div id="section-fixed" class="section card fade-in">
674
- <div class="result-icon">✅</div>
675
- <div class="result-title">OpenClaw is fixed!</div>
264
+ function badgeForStep(step) {
265
+ let html = '';
266
+ if (step.type === 'read') html += '<span class="step-badge badge-read">read</span>';
267
+ if (step.type === 'fix') html += '<span class="step-badge badge-fix">fix</span>';
268
+ if (step.type === 'done') html += '<span class="step-badge badge-done">done</span>';
269
+ if (step.risk === 'medium') html += ' <span class="step-badge badge-risk-medium">medium risk</span>';
270
+ if (step.risk === 'high') html += ' <span class="step-badge badge-risk-high">⚠ high risk</span>';
271
+ return html;
272
+ }
676
273
 
677
- <div id="fixed-summary" style="text-align:left; margin: 20px 0;">
678
- <div style="margin-bottom:16px;">
679
- <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:1px; color:#9aa0a6; margin-bottom:6px;">What was wrong</div>
680
- <div id="fixed-what-wrong" style="font-size:14px; color:#3c4043; line-height:1.6;"></div>
681
- </div>
682
- <div style="border-top:1px solid #e8eaed; padding-top:16px;">
683
- <div style="font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:1px; color:#9aa0a6; margin-bottom:6px;">What we fixed</div>
684
- <div id="fixed-what-fixed" style="font-size:14px; color:#3c4043; line-height:1.6;"></div>
274
+ function addStepCard(step, index) {
275
+ const card = document.createElement('div');
276
+ card.className = 'step';
277
+ card.id = 'step-' + index;
278
+ const reasoningContent = step.thinking || step.reason || '';
279
+ const reasoningHtml = reasoningContent ? `
280
+ <span class="step-reasoning-toggle" onclick="(function(el){var t=el.nextElementSibling;t.style.display=t.style.display==='block'?'none':'block';el.textContent=t.style.display==='block'?'▲ Hide AI reasoning':'💭 Show AI reasoning';})(this)">💭 Show AI reasoning</span>
281
+ <div class="step-reasoning-text">${esc(reasoningContent)}</div>
282
+ ` : '';
283
+ card.innerHTML = `
284
+ <div class="step-top" onclick="this.parentElement.classList.toggle('open')">
285
+ <div class="step-icon">${iconForStep(step)}</div>
286
+ <div class="step-title">${esc(step.description || step.summary || 'Working...')}</div>
287
+ ${badgeForStep(step)}
288
+ <div class="step-status" id="step-status-${index}"><div class="spinner"></div></div>
685
289
  </div>
686
- </div>
687
-
688
- <div style="margin-bottom:16px;">
689
- <button class="details-toggle" id="btn-fixed-details" onclick="toggleFixedDetails()">
690
- <span class="toggle-arrow">▶</span> Show technical details
691
- </button>
692
- <div class="details-panel" id="fixed-details-panel" style="margin-top:8px;">
693
- <p id="fixed-explanation" style="font-size:13px; color:#5f6368;"></p>
290
+ <div class="step-detail" id="step-detail-${index}">
291
+ ${reasoningHtml}
292
+ ${step.command ? `<div class="step-cmd">$ ${esc(step.command)}</div>` : ''}
293
+ <div id="step-output-area-${index}"></div>
694
294
  </div>
695
- </div>
696
-
697
- <button class="btn btn-coffee" onclick="window.open('https://buymeacoffee.com/clawaid','_blank')">
698
- Buy me a coffee
699
- </button>
700
- <div style="margin-top: 8px; text-align: center; font-size: 12px; color: #9aa0a6;">
701
- ClawAid is free. If it saved your day, a coffee helps keep it alive!
702
- </div>
703
- </div>
295
+ `;
296
+ feed.appendChild(card);
297
+ scrollBottom();
298
+ return card;
299
+ }
704
300
 
705
- <!-- ===== SECTION: Healthy ===== -->
706
- <div id="section-healthy" class="section card fade-in">
707
- <div class="result-icon">💚</div>
708
- <div class="result-title">OpenClaw is healthy!</div>
709
- <p class="result-desc" id="healthy-explanation">Your system is running normally. No issues detected.</p>
710
-
711
- <div id="healthy-reasoning" style="display:none; text-align:left; margin-bottom:12px;">
712
- <button class="details-toggle" onclick="this.nextElementSibling.classList.toggle('open'); this.querySelector('.toggle-arrow').textContent = this.nextElementSibling.classList.contains('open') ? '▼' : '▶'">
713
- <span class="toggle-arrow">▶</span> How we diagnosed this
714
- </button>
715
- <div class="details-panel" id="healthy-reasoning-list" style="margin-top:8px;"></div>
716
- </div>
301
+ function finishStepCard(index, output, skipped) {
302
+ const statusEl = document.getElementById('step-status-' + index);
303
+ if (statusEl) statusEl.innerHTML = skipped ? '⏭️' : '✓';
304
+ const outputArea = document.getElementById('step-output-area-' + index);
305
+ if (outputArea && output && !skipped) {
306
+ const trimmed = output.length > 800 ? output.slice(0, 800) + '\n...' : output;
307
+ outputArea.innerHTML = `
308
+ <div class="step-output-label">Output</div>
309
+ <div class="step-output">${esc(trimmed)}</div>
310
+ `;
311
+ }
312
+ }
717
313
 
718
- <div id="healthy-warnings" style="display:none; text-align:left; margin-bottom:16px;">
719
- <div style="font-size:13px; font-weight:600; color:#e37400; margin-bottom:8px;">⚠️ Non-critical notes</div>
720
- <ul id="healthy-warnings-list" style="list-style:none; padding:0; margin:0;"></ul>
721
- </div>
314
+ function addConfirmBar(step, index) {
315
+ const card = document.getElementById('step-' + index);
316
+ if (!card) return;
317
+ const risk = step.risk || 'medium';
318
+ const bar = document.createElement('div');
319
+ bar.className = 'confirm-bar' + (risk === 'high' ? ' risk-high' : '');
320
+ bar.id = 'confirm-bar-' + index;
321
+ bar.innerHTML = `
322
+ <span style="font-size:13px; color:var(--text); flex:1;">This will modify your system. Allow?</span>
323
+ <button class="btn btn-green" id="btn-yes-${index}">✓ Allow</button>
324
+ <button class="btn btn-ghost" id="btn-no-${index}">Skip</button>
325
+ `;
326
+ card.appendChild(bar);
327
+ card.classList.add('open'); // auto-expand so user sees what's about to run
328
+
329
+ document.getElementById('btn-yes-' + index).addEventListener('click', () => {
330
+ bar.remove();
331
+ fetch('/api/confirm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, confirmed: true }) });
332
+ });
333
+ document.getElementById('btn-no-' + index).addEventListener('click', () => {
334
+ bar.remove();
335
+ fetch('/api/confirm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, confirmed: false }) });
336
+ });
722
337
 
723
- <button class="btn btn-secondary" onclick="location.reload()" style="margin-bottom: 8px;">
724
- 🔄 Scan Again
725
- </button>
726
- </div>
338
+ scrollBottom();
339
+ }
727
340
 
728
- <!-- ===== SECTION: Not Fixed ===== -->
729
- <div id="section-notfixed" class="section card fade-in">
730
- <div class="result-icon">😔</div>
731
- <div class="result-title">Couldn't fix automatically</div>
732
- <p class="result-desc" id="notfixed-explanation">
733
- After multiple attempts, the issue couldn't be resolved automatically.
734
- Here's the diagnostic report to share with the community.
735
- </p>
736
-
737
- <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
738
- <span style="font-size: 13px; font-weight: 500; color: #3c4043;">Diagnostic Report</span>
739
- <button class="copy-btn" id="btn-copy-report">Copy</button>
740
- </div>
741
- <div class="report-area" id="report-area"></div>
742
-
743
- <button class="btn btn-secondary" onclick="window.open('https://discord.gg/openclaw','_blank')" style="margin-bottom: 8px;">
744
- 💬 Get Help on Discord
745
- </button>
746
- <button class="btn btn-secondary" onclick="location.reload()">
747
- 🔄 Try Again
748
- </button>
749
- </div>
341
+ // ─── Scan block (initial observation phase) ───────────────────────────────
342
+
343
+ function addScanBlock() {
344
+ const div = document.createElement('div');
345
+ div.className = 'step';
346
+ div.id = 'scan-block';
347
+ div.innerHTML = `
348
+ <div class="step-top" onclick="this.parentElement.classList.toggle('open')">
349
+ <div class="step-icon">📡</div>
350
+ <div class="step-title" id="scan-title">Scanning your system...</div>
351
+ <div class="step-status" id="scan-status"><div class="spinner"></div></div>
352
+ </div>
353
+ <div class="step-detail" id="scan-detail"></div>
354
+ `;
355
+ feed.appendChild(div);
356
+ }
750
357
 
751
- <!-- ===== SECTION: Paywall ===== -->
752
- <div id="section-paywall" class="section card fade-in">
753
- <div style="text-align:center; font-size:40px; margin-bottom:12px;">🔐</div>
754
- <div style="font-size:18px; font-weight:500; text-align:center; margin-bottom:8px;">Use credits to continue</div>
755
- <p style="font-size:13px; color:#5f6368; text-align:center; line-height:1.6; margin-bottom:20px;">
756
- You've used your free diagnostic scans. Purchase credits to keep diagnosing.
757
- </p>
758
-
759
- <!-- Price display -->
760
- <div id="paywall-price-box" style="background:#e8f0fe; border-radius:10px; padding:16px; text-align:center; margin-bottom:20px;">
761
- <div id="paywall-price" style="font-size:28px; font-weight:700; color:#1a73e8;">$1.99</div>
762
- <div id="paywall-credits" style="font-size:13px; color:#5f6368; margin-top:4px;">Credits for more scans</div>
763
- </div>
358
+ function finishScanBlock() {
359
+ const el = document.getElementById('scan-status');
360
+ if (el) el.innerHTML = '✓';
361
+ const title = document.getElementById('scan-title');
362
+ if (title) title.textContent = 'System data collected';
363
+ }
764
364
 
765
- <!-- Buy button -->
766
- <a id="paywall-buy-btn" href="https://clawaid.app/#buy" target="_blank"
767
- style="display:flex; align-items:center; justify-content:center; gap:8px;
768
- padding:12px 24px; font-size:15px; font-weight:500; text-decoration:none;
769
- background:#1a73e8; color:#fff; border-radius:8px; margin-bottom:20px;">
770
- 💳 Buy Credits
771
- </a>
772
-
773
- <hr class="divider">
774
-
775
- <!-- Redeem token -->
776
- <div style="font-size:13px; font-weight:500; color:#3c4043; margin-bottom:8px;">Already have a token?</div>
777
- <div class="input-group" style="margin-bottom:8px;">
778
- <input class="input-field" id="paywall-token-input" type="text"
779
- placeholder="Paste your activation token..."
780
- autocomplete="off" spellcheck="false" />
781
- </div>
782
- <button class="btn btn-secondary" id="paywall-activate-btn" style="margin-bottom:8px;">
783
- 🔑 Activate Token
784
- </button>
785
- <div id="paywall-activate-msg" style="font-size:12px; color:#5f6368; min-height:18px; text-align:center;"></div>
786
- </div>
365
+ // ─── Result cards ─────────────────────────────────────────────────────────
787
366
 
788
- <div class="footer">
789
- <p>ClawAid v1.1.0 · <a href="https://clawaid.app" target="_blank">clawaid.app</a></p>
790
- </div>
367
+ function renderWarnings(warnings) {
368
+ if (!warnings || warnings.length === 0) return '';
369
+ let html = '<div class="warnings-list"><div class="warnings-title">⚠️ Other things to know</div>';
370
+ for (const w of warnings) {
371
+ html += `<div class="warning-item"><span class="wi-icon">💡</span><span class="wi-text">${esc(w)}</span><button class="wi-dismiss" title="Dismiss" onclick="(function(btn){var item=btn.closest('.warning-item');item.classList.add('dismissing');setTimeout(function(){item.remove();},300);})(this)">×</button></div>`;
372
+ }
373
+ html += '</div>';
374
+ return html;
375
+ }
791
376
 
792
- </div>
377
+ function showResult(data) {
378
+ const div = document.createElement('div');
379
+ div.className = 'result';
380
+ const warningsHtml = renderWarnings(data.warnings);
793
381
 
794
- <script>
795
- (function() {
796
- 'use strict';
382
+ if (data.healthy) {
383
+ div.innerHTML = `
384
+ <div class="result-icon">💚</div>
385
+ <div class="result-title">All clear!</div>
386
+ <div class="result-desc">${esc(data.summary || 'Your OpenClaw is running normally.')}</div>
387
+ ${warningsHtml}
388
+ <button class="btn btn-ghost btn-big" onclick="location.reload()" style="margin-top:14px;">🔄 Scan Again</button>
389
+ `;
390
+ } else if (data.fixed) {
391
+ div.innerHTML = `
392
+ <div class="result-icon">✅</div>
393
+ <div class="result-title">Fixed!</div>
394
+ <div class="result-desc">${esc(data.summary || 'OpenClaw has been repaired.')}</div>
395
+ ${warningsHtml}
396
+ <button class="btn btn-ghost btn-big" onclick="location.reload()" style="margin-top:14px;">🔄 Scan Again</button>
397
+ `;
398
+ } else {
399
+ div.innerHTML = `
400
+ <div class="result-icon">😔</div>
401
+ <div class="result-title">Couldn't fix automatically</div>
402
+ <div class="result-desc">${esc(data.summary || 'After multiple attempts, the issue persists.')}</div>
403
+ ${warningsHtml}
404
+ <button class="btn btn-ghost btn-big" onclick="window.open('https://discord.gg/openclaw','_blank')" style="margin-bottom:8px; margin-top:14px;">💬 Get Help on Discord</button>
405
+ <button class="btn btn-ghost btn-big" onclick="location.reload()">🔄 Try Again</button>
406
+ `;
407
+ }
797
408
 
798
- // State
799
- let sessionId = null;
800
- let currentState = 'idle';
801
- let eventSource = null;
802
- let diagnosisData = null; // last diagnosis received
803
- let chosenOption = null; // option selected for fix
804
-
805
- // Elements
806
- const $ = id => document.getElementById(id);
807
-
808
- const sections = {
809
- privacy: $('section-privacy'),
810
- scanning: $('section-scanning'),
811
- apikey: $('section-apikey'),
812
- diagnosis: $('section-diagnosis'),
813
- options: $('section-options'),
814
- autofix: $('section-auto-fix'),
815
- fixed: $('section-fixed'),
816
- healthy: $('section-healthy'),
817
- notfixed: $('section-notfixed'),
818
- paywall: $('section-paywall'),
819
- };
820
-
821
- function showOnly(name) {
822
- Object.entries(sections).forEach(([key, el]) => {
823
- el.classList.toggle('visible', key === name);
824
- });
825
- currentState = name;
409
+ feed.appendChild(div);
410
+ scrollBottom();
826
411
  }
827
412
 
828
- function appendLog(areaId, msg, cls) {
829
- const area = $(areaId);
830
- if (!area) return;
831
- const line = document.createElement('span');
832
- line.className = 'log-line' + (cls ? ' ' + cls : '');
833
- line.textContent = msg + '\n';
834
- area.appendChild(line);
835
- area.scrollTop = area.scrollHeight;
836
- }
413
+ // ─── SSE connection ───────────────────────────────────────────────────────
837
414
 
838
- function classForMessage(msg) {
839
- if (msg.startsWith('✓') || msg.startsWith('✅') || msg.includes('Success') || msg.includes('fixed')) return 'success';
840
- if (msg.startsWith('❌') || msg.startsWith('✗') || msg.includes('Error') || msg.includes('failed')) return 'error';
841
- if (msg.startsWith('⚠️') || msg.startsWith('Warning')) return 'warn';
842
- if (msg.startsWith('🤖') || msg.startsWith('💭') || msg.startsWith('🔄') || msg.startsWith('📡')) return 'meta';
843
- return '';
844
- }
415
+ let currentStepIndex = -1;
416
+
417
+ let scanBlockAdded = false;
845
418
 
846
- // Details toggle (scan + fix log panels)
847
- window.toggleDetails = function(panelSuffix) {
848
- const btnId = 'btn-toggle-' + panelSuffix;
849
- const panelId = 'details-' + panelSuffix;
850
- const btn = $(btnId);
851
- const panel = $(panelId);
852
- if (!btn || !panel) return;
853
- const isOpen = panel.classList.toggle('open');
854
- btn.classList.toggle('open', isOpen);
855
- btn.querySelector('.toggle-arrow').textContent = isOpen ? '▼' : '▶';
856
- btn.childNodes[1].textContent = isOpen ? ' Hide details' : ' Show details';
857
- };
858
-
859
- // Fixed details toggle
860
- window.toggleFixedDetails = function() {
861
- const btn = $('btn-fixed-details');
862
- const panel = $('fixed-details-panel');
863
- const isOpen = panel.classList.toggle('open');
864
- btn.querySelector('.toggle-arrow').textContent = isOpen ? '▼' : '▶';
865
- btn.childNodes[1].textContent = isOpen ? ' Hide technical details' : ' Show technical details';
866
- };
867
-
868
- // Reasoning toggle
869
- window.toggleReasoning = function() {
870
- const btn = $('toggle-reasoning');
871
- const panel = $('reasoning-panel');
872
- const isOpen = panel.classList.toggle('open');
873
- btn.classList.toggle('open', isOpen);
874
- btn.querySelector('.arrow').textContent = isOpen ? '▼' : '▶';
875
- btn.childNodes[1].textContent = isOpen ? ' Hide diagnostic details' : ' Diagnostic details';
876
- };
877
-
878
- // Start SSE connection
879
419
  function startDiagnosis() {
880
- showOnly('scanning');
881
- $('log-area').textContent = '';
882
- $('exec-log').textContent = '';
420
+ startScreen.classList.add('hidden');
421
+ feed.classList.remove('hidden');
422
+ feed.innerHTML = '';
423
+ currentStepIndex = -1;
424
+ scanBlockAdded = false;
883
425
 
884
426
  eventSource = new EventSource('/api/diagnose');
885
427
 
886
428
  eventSource.onmessage = function(e) {
887
- let parsed;
888
- try { parsed = JSON.parse(e.data); } catch { return; }
889
-
890
- const { type, data } = parsed;
891
- sessionId = parsed.sessionId || sessionId;
892
-
893
- switch(type) {
894
- case 'session_start':
895
- sessionId = data.sessionId;
896
- $('scan-status-text').textContent = 'Checking gateway...';
897
- break;
898
-
899
- case 'progress': {
900
- const msg = data.message || '';
901
- const cls = classForMessage(msg);
902
- appendLog('log-area', msg, cls);
903
- appendLog('exec-log', msg, cls);
904
- appendLog('reasoning-log', msg, cls);
905
-
906
- // Update clean status text
907
- const clean = msg.replace(/^[🔍🤔🔧✅⚠️❌💭🔄→📡🔒 ]+/, '').trim().slice(0, 80);
908
- if (clean) {
909
- $('scan-status-text').textContent = clean;
910
- $('auto-fix-sub').textContent = clean;
911
- }
912
- break;
913
- }
914
-
915
- case 'state_change':
916
- handleStateChange(data.state);
917
- break;
918
-
919
- case 'diagnosis':
920
- showDiagnosis(data);
921
- break;
922
-
923
- case 'action_result':
924
- break;
925
-
926
- case 'verify_result':
927
- appendLog('exec-log', '─────────────────────────────────────', '');
928
- if (data.fixed) {
929
- appendLog('exec-log', '✅ Verified — issue resolved!', 'success');
930
- appendLog('exec-log', ' ' + data.explanation, 'success');
931
- } else {
932
- appendLog('exec-log', '⚠️ Not yet fixed: ' + data.explanation, 'warn');
933
- appendLog('exec-log', ' Re-analyzing...', '');
934
- }
935
- break;
936
-
937
- case 'request_input':
938
- if (data.field === 'apiKey') showApiKeyInput(data);
939
- break;
940
-
941
- case 'paywall':
942
- showPaywall(data);
943
- break;
944
-
945
- case 'complete':
946
- handleComplete(data);
947
- break;
948
-
949
- case 'error':
950
- appendLog('log-area', '─────────────────────────────────────', '');
951
- appendLog('log-area', '❌ Error: ' + data.message, 'error');
952
- appendLog('log-area', ' Check your API key and internet connection, then reload to try again.', 'warn');
953
- $('scan-status-text').textContent = 'Error — click "Show details"';
954
- showOnly('scanning');
955
- // Auto-open details on error
956
- const detailPanel = $('details-scan-log');
957
- if (detailPanel && !detailPanel.classList.contains('open')) {
958
- window.toggleDetails('scan-log');
959
- }
960
- break;
961
- }
429
+ let msg;
430
+ try { msg = JSON.parse(e.data); } catch { return; }
431
+ sessionId = msg.sessionId || sessionId;
432
+ onEvent(msg.type, msg.data || {});
962
433
  };
963
434
 
964
435
  eventSource.onerror = function() {
965
- if (currentState !== 'fixed' && currentState !== 'notfixed' && currentState !== 'healthy') {
966
- appendLog('log-area', '⚠️ Connection lost. The server may have stopped.', 'warn');
436
+ // Only show error if we haven't completed
437
+ if (!['fixed','not_fixed','healthy'].includes(document.body.dataset.state || '')) {
438
+ const err = document.createElement('div');
439
+ err.className = 'result';
440
+ err.innerHTML = `
441
+ <div class="result-icon">⚠️</div>
442
+ <div class="result-title">Connection lost</div>
443
+ <div class="result-desc">The server may have stopped.</div>
444
+ <button class="btn btn-ghost btn-big" onclick="location.reload()">🔄 Retry</button>
445
+ `;
446
+ feed.appendChild(err);
967
447
  }
968
448
  };
969
449
  }
970
450
 
971
- function handleStateChange(state) {
972
- switch(state) {
973
- case 'observing':
974
- showOnly('scanning');
975
- $('scan-status-text').textContent = 'Checking gateway...';
451
+ function onEvent(type, data) {
452
+ switch (type) {
453
+ case 'session_start':
454
+ sessionId = data.sessionId;
976
455
  break;
977
- case 'diagnosing':
978
- showOnly('scanning');
979
- $('scan-status-text').textContent = 'Analyzing your system...';
980
- $('scan-sub-text').textContent = 'This may take a few seconds';
981
- appendLog('log-area', '─────────────────────────────────────', '');
982
- appendLog('log-area', '🤖 Sending data to AI for diagnosis...', 'meta');
456
+
457
+ case 'state_change':
458
+ document.body.dataset.state = data.state;
459
+ if (data.state === 'observing' && !scanBlockAdded) {
460
+ addScanBlock();
461
+ scanBlockAdded = true;
462
+ }
463
+ if (data.state === 'running') finishScanBlock();
983
464
  break;
984
- case 'showing_options':
985
- // Options will show via 'diagnosis' event + render options
465
+
466
+ case 'progress': {
467
+ // Append to scan block detail while scanning
468
+ const detail = document.getElementById('scan-detail');
469
+ if (detail) {
470
+ const line = document.createElement('div');
471
+ line.style.cssText = 'font-size:12px; color:var(--text2); padding:2px 0;';
472
+ line.textContent = data.message || '';
473
+ detail.appendChild(line);
474
+ }
986
475
  break;
987
- case 'auto_executing':
988
- $('exec-log').textContent = '';
989
- showOnly('autofix');
476
+ }
477
+
478
+ case 'step_start': {
479
+ currentStepIndex = data.index;
480
+ const step = data.step;
481
+ if (step.type === 'done') {
482
+ // Show result, don't add a step card
483
+ } else {
484
+ addStepCard(step, data.index);
485
+ }
990
486
  break;
991
- case 'executing':
992
- $('exec-log').textContent = '';
993
- showOnly('autofix');
487
+ }
488
+
489
+ case 'step_done': {
490
+ finishStepCard(data.index, data.output, data.skipped);
994
491
  break;
995
- case 'verifying':
996
- $('auto-fix-sub').textContent = 'Verifying repair...';
997
- appendLog('exec-log', '─────────────────────────────────────', '');
998
- appendLog('exec-log', '🔍 Checking if the fix worked...', 'meta');
492
+ }
493
+
494
+ case 'confirm_needed': {
495
+ addConfirmBar(data.step, currentStepIndex);
999
496
  break;
1000
- case 'needs_api_key':
1001
- break; // handled by request_input event
1002
- case 'paywall':
1003
- break; // handled by 'paywall' event
1004
- case 'error':
1005
- $('scan-status-text').textContent = 'Error occurred';
497
+ }
498
+
499
+ case 'complete': {
500
+ if (eventSource) { eventSource.close(); eventSource = null; }
501
+ finishScanBlock();
502
+ showResult(data);
1006
503
  break;
1007
- case 'fixed':
1008
- showOnly('fixed');
504
+ }
505
+
506
+ case 'paywall': {
507
+ if (eventSource) { eventSource.close(); eventSource = null; }
508
+ showPaywall(data);
1009
509
  break;
1010
- case 'healthy':
1011
- showOnly('healthy');
510
+ }
511
+
512
+ case 'request_input': {
513
+ if (data.field === 'apiKey') showApiKeyInput(data);
514
+ if (data.field === 'userDescription') showDescriptionInput(data);
1012
515
  break;
1013
- case 'not_fixed':
1014
- showOnly('notfixed');
516
+ }
517
+
518
+ case 'error': {
519
+ const err = document.createElement('div');
520
+ err.className = 'result';
521
+ err.innerHTML = `
522
+ <div class="result-icon">❌</div>
523
+ <div class="result-title">Error</div>
524
+ <div class="result-desc">${esc(data.message || 'Unknown error')}</div>
525
+ <button class="btn btn-ghost btn-big" onclick="location.reload()">🔄 Retry</button>
526
+ `;
527
+ feed.appendChild(err);
528
+ scrollBottom();
1015
529
  break;
530
+ }
1016
531
  }
1017
532
  }
1018
533
 
1019
- function showDiagnosis(data) {
1020
- diagnosisData = data;
1021
- const confidence = Math.round((data.confidence || 0.5) * 100);
1022
- const options = data.options || [];
1023
- const round = data.round || 1;
1024
-
1025
- // Render reasoning as clean list
1026
- const reasoningLog = $('reasoning-log');
1027
- reasoningLog.innerHTML = '';
1028
- const reasoning = data.reasoning || [];
1029
- if (reasoning.length > 0) {
1030
- reasoning.forEach(function(r, i) {
1031
- const line = document.createElement('div');
1032
- line.style.cssText = 'padding:6px 0; font-size:13px; color:#3c4043; border-bottom:1px solid #f1f3f4;';
1033
- line.textContent = (i+1) + '. ' + r;
1034
- reasoningLog.appendChild(line);
1035
- });
1036
- } else {
1037
- reasoningLog.textContent = 'No reasoning details available.';
1038
- }
1039
-
1040
- // Update diagnosis card
1041
- $('diag-text').textContent = data.diagnosis || '';
1042
- $('root-cause-box').textContent = '🎯 ' + (data.rootCause || 'Unknown root cause');
1043
- $('confidence-fill').style.width = confidence + '%';
1044
- $('confidence-label').textContent = 'Confidence: ' + confidence + '%';
1045
-
1046
- // Round badge
1047
- if (round > 1) {
1048
- $('round-badge-diag').style.display = 'inline-flex';
1049
- $('round-badge-text').textContent = 'Round ' + round + ' — new approach';
1050
- $('round-badge-opts').style.display = 'inline-flex';
1051
- $('round-opts-text').textContent = 'Round ' + round + ' — new approach';
1052
- } else {
1053
- $('round-badge-diag').style.display = 'none';
1054
- $('round-badge-opts').style.display = 'none';
1055
- }
1056
-
1057
- // Alternatives
1058
- const alts = data.alternativeHypotheses || [];
1059
- if (alts.length > 0) {
1060
- $('alternatives-section').style.display = 'block';
1061
- const altList = $('alt-list');
1062
- altList.innerHTML = '';
1063
- alts.forEach(alt => {
1064
- const li = document.createElement('li');
1065
- li.textContent = alt;
1066
- altList.appendChild(li);
1067
- });
1068
- } else {
1069
- $('alternatives-section').style.display = 'none';
1070
- }
534
+ // ─── Paywall / API key ────────────────────────────────────────────────────
1071
535
 
1072
- // Show diagnosis card
1073
- showOnly('diagnosis');
1074
-
1075
- // Auto-check if we should show options too immediately
1076
- const recommended = options.filter(o => o.recommended);
1077
- const canAutoExecute = recommended.length > 0 && recommended.every(o => o.autoExecute);
1078
-
1079
- if (canAutoExecute) {
1080
- // Store recommended option for result page
1081
- chosenOption = recommended[0] || null;
1082
- // Auto-execute: show diagnosis briefly, then trigger fix
1083
- setTimeout(function() {
1084
- fetch('/api/fix', {
1085
- method: 'POST',
1086
- headers: { 'Content-Type': 'application/json' },
1087
- body: JSON.stringify({ sessionId: sessionId }),
1088
- });
1089
- }, 1200);
1090
- } else if (options.length > 0) {
1091
- // Render option cards and switch to options section
1092
- renderOptions(options);
1093
- // Show diagnosis briefly then switch to options after 1s
1094
- setTimeout(() => showOnly('options'), 3000);
1095
- }
536
+ function showPaywall(data) {
537
+ const price = data.price || data;
538
+ const priceStr = (price && price.price) || '$1.99';
539
+ const isCN = price && price.isChinese;
540
+ const credits = (price && price.credits) || 5;
541
+
542
+ const div = document.createElement('div');
543
+ div.className = 'start-card';
544
+ div.innerHTML = `
545
+ <div style="text-align:center; margin-bottom:12px;">
546
+ <div style="font-size:32px; font-weight:700; color:var(--blue);">${esc(priceStr)}</div>
547
+ <div style="font-size:13px; color:var(--muted);">${isCN ? '包含 '+credits+' 次修复' : 'Includes '+credits+' fix credits'}</div>
548
+ </div>
549
+ <a href="https://clawaid.app/#buy" target="_blank" class="btn btn-primary btn-big" style="text-decoration:none; margin-bottom:12px;">💳 Buy Credits</a>
550
+ <hr style="border:none; border-top:1px solid var(--border); margin:12px 0;">
551
+ <p style="font-size:13px; color:var(--text2); margin-bottom:8px;">Already have a token?</p>
552
+ <input class="input-field" id="pw-token" type="text" placeholder="Paste your token..." />
553
+ <button class="btn btn-ghost btn-big" id="pw-activate">🔑 Activate</button>
554
+ <div id="pw-msg" style="font-size:12px; text-align:center; margin-top:6px; min-height:16px;"></div>
555
+ `;
556
+ feed.appendChild(div);
557
+
558
+ document.getElementById('pw-activate').addEventListener('click', async () => {
559
+ const token = document.getElementById('pw-token').value.trim();
560
+ const msg = document.getElementById('pw-msg');
561
+ if (!token) { msg.style.color = 'var(--red)'; msg.textContent = 'Enter your token.'; return; }
562
+ msg.style.color = 'var(--muted)'; msg.textContent = 'Checking...';
563
+ try {
564
+ const r = await (await fetch('/api/redeem', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) })).json();
565
+ if (r.valid) { msg.style.color = 'var(--green)'; msg.textContent = '✅ Activated! Restarting...'; setTimeout(() => location.reload(), 1500); }
566
+ else { msg.style.color = 'var(--red)'; msg.textContent = '❌ Invalid token.'; }
567
+ } catch { msg.style.color = 'var(--red)'; msg.textContent = '❌ Network error.'; }
568
+ });
1096
569
  }
1097
570
 
1098
- function renderOptions(options) {
1099
- const list = $('options-list');
1100
- list.innerHTML = '';
1101
-
1102
- options.forEach(opt => {
1103
- const card = document.createElement('div');
1104
- card.className = 'option-card' + (opt.recommended ? ' recommended' : '');
1105
-
1106
- const riskClass = 'risk-' + (opt.risk || 'low');
1107
-
1108
- card.innerHTML = `
1109
- <div class="option-card-header">
1110
- <div class="option-letter">${escHtml(opt.id)}</div>
1111
- <div class="option-title-row">
1112
- <span class="option-title">${escHtml(opt.title)}</span>
1113
- ${opt.recommended ? '<span class="recommended-badge">★ Recommended</span>' : ''}
1114
- <span class="risk-badge ${riskClass}">${escHtml(opt.risk)}</span>
1115
- </div>
571
+ // ─── User description dialog ──────────────────────────────────────────
572
+
573
+ function showDescriptionInput(data) {
574
+ let screenshotData = null;
575
+
576
+ const div = document.createElement('div');
577
+ div.className = 'start-card';
578
+ div.id = 'desc-input-card';
579
+ div.innerHTML = `
580
+ <p style="font-size:15px; font-weight:500; color:var(--text); margin-bottom:4px;">What's going on?</p>
581
+ <p style="font-size:13px; color:var(--text2); margin-bottom:12px;">Describe the problem, or just scan — ClawAid will figure it out.</p>
582
+ <div class="desc-input-wrap">
583
+ <div class="desc-textarea-wrap">
584
+ <textarea class="desc-textarea" id="desc-text" placeholder="e.g. OpenClaw stopped responding after I updated... (optional)"></textarea>
585
+ <button class="desc-camera-btn" id="desc-camera-btn" title="Add screenshot">📷</button>
586
+ <input type="file" id="screenshot-input" accept="image/*" style="display:none;" />
1116
587
  </div>
1117
- <div class="option-description">${escHtml(opt.description)}</div>
1118
- <button class="option-fix-btn" data-option-id="${escHtml(opt.id)}">
1119
- 🔧 Fix with this option
1120
- </button>
1121
- `;
1122
-
1123
- list.appendChild(card);
588
+ <div id="screenshot-preview-row" style="display:none;" class="screenshot-preview-row">
589
+ <img id="screenshot-thumb" src="" />
590
+ <span id="screenshot-name" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">Screenshot attached</span>
591
+ <button class="spr-remove" id="screenshot-remove" title="Remove">✕</button>
592
+ </div>
593
+ </div>
594
+ <div class="btn-row" style="margin-top:10px;">
595
+ <button class="btn btn-primary" id="btn-desc-submit" disabled>📝 Send &amp; diagnose</button>
596
+ <button class="btn btn-green" id="btn-desc-skip">🔍 Just scan &amp; fix</button>
597
+ </div>
598
+ `;
599
+ feed.appendChild(div);
600
+ scrollBottom();
601
+
602
+ const textarea = document.getElementById('desc-text');
603
+ const cameraBtn = document.getElementById('desc-camera-btn');
604
+ const fileInput = document.getElementById('screenshot-input');
605
+ const previewRow = document.getElementById('screenshot-preview-row');
606
+ const thumb = document.getElementById('screenshot-thumb');
607
+ const removeBtn = document.getElementById('screenshot-remove');
608
+ const submitBtn = document.getElementById('btn-desc-submit');
609
+
610
+ // Update submit button enabled state based on textarea content
611
+ function updateSubmitState() {
612
+ submitBtn.disabled = textarea.value.trim().length === 0;
613
+ }
614
+ textarea.addEventListener('input', updateSubmitState);
615
+ updateSubmitState();
616
+
617
+ function handleFile(file) {
618
+ if (!file || !file.type.startsWith('image/')) return;
619
+ if (file.size > 5 * 1024 * 1024) {
620
+ alert('Image too large (max 5MB)');
621
+ return;
622
+ }
623
+ const reader = new FileReader();
624
+ reader.onload = function(e) {
625
+ screenshotData = e.target.result;
626
+ thumb.src = screenshotData;
627
+ previewRow.style.display = 'flex';
628
+ cameraBtn.classList.add('has-image');
629
+ };
630
+ reader.readAsDataURL(file);
631
+ }
632
+
633
+ cameraBtn.addEventListener('click', function(e) {
634
+ e.preventDefault();
635
+ fileInput.click();
1124
636
  });
1125
637
 
1126
- // Attach click handlers
1127
- list.querySelectorAll('.option-fix-btn').forEach(btn => {
1128
- btn.addEventListener('click', async function() {
1129
- const optionId = this.dataset.optionId;
1130
- // Store chosen option for result page
1131
- chosenOption = (diagnosisData && diagnosisData.options) ? diagnosisData.options.find(o => o.id === optionId) || diagnosisData.options.find(o => o.recommended) : null;
1132
- // Disable all buttons
1133
- list.querySelectorAll('.option-fix-btn').forEach(b => { b.disabled = true; });
1134
-
1135
- $('exec-log').textContent = '';
1136
- appendLog('exec-log', '🔧 Starting repair: Option ' + optionId + '...', 'meta');
1137
- showOnly('autofix');
1138
-
1139
- try {
1140
- await fetch('/api/fix', {
1141
- method: 'POST',
1142
- headers: { 'Content-Type': 'application/json' },
1143
- body: JSON.stringify({ sessionId, optionId }),
1144
- });
1145
- } catch(e) {
1146
- appendLog('exec-log', '❌ Failed to start fix: ' + e.message, 'error');
1147
- }
1148
- });
638
+ fileInput.addEventListener('change', function(e) {
639
+ if (e.target.files[0]) handleFile(e.target.files[0]);
1149
640
  });
1150
- }
1151
641
 
1152
- function showApiKeyInput(data) {
1153
- if (data.instructions) $('apikey-instructions').textContent = data.instructions;
1154
- showOnly('apikey');
1155
- $('apikey-input').focus();
1156
- }
642
+ removeBtn.addEventListener('click', function() {
643
+ screenshotData = null;
644
+ previewRow.style.display = 'none';
645
+ thumb.src = '';
646
+ cameraBtn.classList.remove('has-image');
647
+ fileInput.value = '';
648
+ });
1157
649
 
1158
- function handleComplete(data) {
1159
- if (eventSource) {
1160
- eventSource.close();
1161
- eventSource = null;
1162
- }
650
+ // Drag & drop on the whole textarea area
651
+ const textareaWrap = div.querySelector('.desc-textarea-wrap');
652
+ textareaWrap.addEventListener('dragover', function(e) { e.preventDefault(); textarea.style.borderColor = 'var(--blue)'; });
653
+ textareaWrap.addEventListener('dragleave', function() { textarea.style.borderColor = ''; });
654
+ textareaWrap.addEventListener('drop', function(e) {
655
+ e.preventDefault(); textarea.style.borderColor = '';
656
+ if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
657
+ });
1163
658
 
1164
- if (data.healthy) {
1165
- $('healthy-explanation').textContent = data.explanation || 'Your system is running normally. No issues detected.';
1166
- // Show reasoning if any
1167
- const reasoning = data.reasoning || [];
1168
- if (reasoning.length > 0) {
1169
- $('healthy-reasoning').style.display = 'block';
1170
- const list = $('healthy-reasoning-list');
1171
- list.innerHTML = '';
1172
- reasoning.forEach(function(r, i) {
1173
- const div = document.createElement('div');
1174
- div.style.cssText = 'padding:4px 0; font-size:13px; color:#5f6368;';
1175
- div.textContent = (i+1) + '. ' + r;
1176
- list.appendChild(div);
1177
- });
1178
- }
1179
- // Show warnings if any
1180
- const warnings = data.warnings || [];
1181
- if (warnings.length > 0) {
1182
- $('healthy-warnings').style.display = 'block';
1183
- const list = $('healthy-warnings-list');
1184
- list.innerHTML = '';
1185
- warnings.forEach(function(w) {
1186
- const li = document.createElement('li');
1187
- li.style.cssText = 'font-size:13px; color:#5f6368; padding:6px 10px; background:#fef7e0; border-radius:6px; margin-bottom:6px; line-height:1.4;';
1188
- li.textContent = w;
1189
- list.appendChild(li);
1190
- });
1191
- }
1192
- showOnly('healthy');
1193
- } else if (data.fixed) {
1194
- // Populate result summary
1195
- const wrongEl = $('fixed-what-wrong');
1196
- const fixedEl = $('fixed-what-fixed');
1197
- if (wrongEl) wrongEl.textContent = (diagnosisData && diagnosisData.diagnosis) || data.explanation || 'An issue was detected and resolved.';
1198
- if (fixedEl && chosenOption) {
1199
- fixedEl.innerHTML = '<strong>' + chosenOption.title + '</strong><br><span style="color:#5f6368">' + (chosenOption.description || '') + '</span>';
1200
- } else if (fixedEl) {
1201
- fixedEl.textContent = data.explanation || 'OpenClaw has been repaired!';
659
+ // Paste support
660
+ document.addEventListener('paste', function onPaste(e) {
661
+ const items = e.clipboardData && e.clipboardData.items;
662
+ if (items) {
663
+ for (let i = 0; i < items.length; i++) {
664
+ if (items[i].type.startsWith('image/')) {
665
+ handleFile(items[i].getAsFile());
666
+ break;
667
+ }
668
+ }
1202
669
  }
1203
- $('fixed-explanation').textContent = data.explanation || 'OpenClaw has been repaired!';
1204
- showOnly('fixed');
1205
- } else {
1206
- $('notfixed-explanation').textContent = data.explanation || 'Could not automatically fix the issue.';
1207
- if (data.diagnosticReport) $('report-area').textContent = data.diagnosticReport;
1208
- showOnly('notfixed');
1209
- }
1210
- }
1211
-
1212
- function showPaywall(data) {
1213
- // data.price may be an object { price, currency, isChinese, credits } or a string
1214
- let priceStr, isChinese, credits;
1215
- if (data.price && typeof data.price === 'object') {
1216
- priceStr = data.price.price || (data.price.currency === 'CNY' ? '¥9.9' : '$1.99');
1217
- isChinese = data.price.isChinese;
1218
- credits = data.price.credits || data.credits || 5;
1219
- } else {
1220
- priceStr = data.price || (data.isChinese ? '¥9.9' : '$1.99');
1221
- isChinese = Boolean(data.isChinese);
1222
- credits = data.credits || 5;
1223
- }
670
+ }, { once: false });
1224
671
 
1225
- $('paywall-price').textContent = priceStr;
1226
- $('paywall-credits').textContent = isChinese
1227
- ? `包含 ${credits} 次修复`
1228
- : `Includes ${credits} fix credits`;
1229
-
1230
- showOnly('paywall');
1231
- }
1232
-
1233
- // Paywall activate button
1234
- $('paywall-activate-btn').addEventListener('click', async function() {
1235
- const token = $('paywall-token-input').value.trim();
1236
- if (!token) {
1237
- $('paywall-activate-msg').style.color = '#c5221f';
1238
- $('paywall-activate-msg').textContent = 'Please enter your activation token.';
1239
- return;
1240
- }
1241
-
1242
- this.disabled = true;
1243
- this.textContent = 'Activating...';
1244
- $('paywall-activate-msg').style.color = '#5f6368';
1245
- $('paywall-activate-msg').textContent = 'Verifying token...';
1246
-
1247
- try {
1248
- const resp = await fetch('/api/redeem', {
672
+ function submit(description) {
673
+ div.remove();
674
+ fetch('/api/input', {
1249
675
  method: 'POST',
1250
676
  headers: { 'Content-Type': 'application/json' },
1251
- body: JSON.stringify({ token }),
677
+ body: JSON.stringify({
678
+ sessionId,
679
+ field: 'userDescription',
680
+ value: description || '',
681
+ screenshot: screenshotData || undefined,
682
+ })
1252
683
  });
1253
- const result = await resp.json();
1254
-
1255
- if (result.valid) {
1256
- $('paywall-activate-msg').style.color = '#137333';
1257
- $('paywall-activate-msg').textContent = `✅ Activated! Credits: ${result.credits || 0}. Restarting scan...`;
1258
- // Re-start the diagnosis after a brief delay
1259
- setTimeout(() => {
1260
- startDiagnosis();
1261
- }, 1500);
1262
- } else {
1263
- $('paywall-activate-msg').style.color = '#c5221f';
1264
- $('paywall-activate-msg').textContent = '❌ Invalid or expired token. Please try again.';
1265
- this.disabled = false;
1266
- this.textContent = '🔑 Activate Token';
1267
- }
1268
- } catch (e) {
1269
- $('paywall-activate-msg').style.color = '#c5221f';
1270
- $('paywall-activate-msg').textContent = '❌ Network error. Please check your connection.';
1271
- this.disabled = false;
1272
- this.textContent = '🔑 Activate Token';
1273
- }
1274
- });
1275
-
1276
- $('paywall-token-input').addEventListener('keydown', function(e) {
1277
- if (e.key === 'Enter') $('paywall-activate-btn').click();
1278
- });
1279
-
1280
- function escHtml(str) {
1281
- return String(str)
1282
- .replace(/&/g, '&amp;')
1283
- .replace(/</g, '&lt;')
1284
- .replace(/>/g, '&gt;')
1285
- .replace(/"/g, '&quot;');
1286
- }
1287
-
1288
- // API key submit
1289
- $('btn-submit-key').addEventListener('click', async function() {
1290
- const key = $('apikey-input').value.trim();
1291
- if (!key) {
1292
- $('apikey-input').focus();
1293
- $('apikey-input').style.borderColor = '#c5221f';
1294
- return;
1295
684
  }
1296
685
 
1297
- $('apikey-input').style.borderColor = '';
1298
- this.disabled = true;
1299
- this.textContent = 'Starting...';
1300
-
1301
- showOnly('scanning');
1302
- $('log-area').textContent = '';
1303
- $('scan-status-text').textContent = 'Initializing with API key...';
1304
-
1305
- try {
1306
- await fetch('/api/input', {
1307
- method: 'POST',
1308
- headers: { 'Content-Type': 'application/json' },
1309
- body: JSON.stringify({ sessionId, field: 'apiKey', value: key }),
1310
- });
1311
- } catch(e) {
1312
- showOnly('apikey');
1313
- this.disabled = false;
1314
- this.textContent = 'Start Diagnosis';
1315
- }
1316
- });
686
+ submitBtn.addEventListener('click', function() {
687
+ submit(textarea.value.trim());
688
+ });
689
+ document.getElementById('btn-desc-skip').addEventListener('click', function() {
690
+ submit('');
691
+ });
692
+ textarea.addEventListener('keydown', function(e) {
693
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
694
+ if (textarea.value.trim()) submit(textarea.value.trim());
695
+ }
696
+ });
697
+ textarea.focus();
698
+ }
1317
699
 
1318
- $('apikey-input').addEventListener('keydown', function(e) {
1319
- if (e.key === 'Enter') $('btn-submit-key').click();
1320
- });
700
+ function showApiKeyInput(data) {
701
+ const div = document.createElement('div');
702
+ div.className = 'start-card';
703
+ div.innerHTML = `
704
+ <p style="font-size:14px; color:var(--text2); margin-bottom:12px;">${esc(data.instructions || 'Enter your OpenRouter API key.')}</p>
705
+ <input class="input-field" id="ak-input" type="password" placeholder="sk-or-..." autocomplete="off" />
706
+ <div style="font-size:12px; color:var(--muted); margin-bottom:10px;">🔒 Local only. <a href="https://openrouter.ai/keys" target="_blank" style="color:var(--blue);">Get a key →</a></div>
707
+ <button class="btn btn-primary btn-big" id="ak-submit">Continue</button>
708
+ `;
709
+ feed.appendChild(div);
710
+
711
+ const submit = async () => {
712
+ const key = document.getElementById('ak-input').value.trim();
713
+ if (!key) return;
714
+ document.getElementById('ak-submit').disabled = true;
715
+ await fetch('/api/input', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, field: 'apiKey', value: key }) });
716
+ };
717
+ document.getElementById('ak-submit').addEventListener('click', submit);
718
+ document.getElementById('ak-input').addEventListener('keydown', e => { if (e.key === 'Enter') submit(); });
719
+ document.getElementById('ak-input').focus();
720
+ }
1321
721
 
1322
- $('btn-copy-report').addEventListener('click', function() {
1323
- const text = $('report-area').textContent;
1324
- navigator.clipboard.writeText(text).then(() => {
1325
- this.textContent = 'Copied!';
1326
- setTimeout(() => { this.textContent = 'Copy'; }, 2000);
1327
- });
1328
- });
722
+ // ─── Start ────────────────────────────────────────────────────────────────
1329
723
 
1330
- // Privacy notice — start scan on button click
1331
- $('btn-start-scan').addEventListener('click', function() {
1332
- startDiagnosis();
1333
- });
724
+ document.getElementById('btn-start').addEventListener('click', startDiagnosis);
1334
725
 
1335
726
  })();
1336
727
  </script>