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/dist/diagnose.d.ts +1 -0
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +64 -9
- package/dist/diagnose.js.map +1 -1
- package/dist/loop.d.ts +39 -31
- package/dist/loop.d.ts.map +1 -1
- package/dist/loop.js +265 -307
- package/dist/loop.js.map +1 -1
- package/dist/observe.d.ts.map +1 -1
- package/dist/observe.js +58 -38
- package/dist/observe.js.map +1 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +62 -27
- package/dist/rules.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -10
- package/dist/server.js.map +1 -1
- package/package.json +14 -2
- package/web/index.html +612 -1221
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
height:
|
|
255
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
365
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
/*
|
|
473
|
-
.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
/*
|
|
494
|
-
.
|
|
495
|
-
|
|
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
|
-
.
|
|
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="
|
|
208
|
+
<div class="wrap">
|
|
533
209
|
|
|
534
|
-
|
|
535
|
-
|
|
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
|
|
213
|
+
<p>Automatic diagnostics & repair for OpenClaw</p>
|
|
539
214
|
</div>
|
|
540
215
|
|
|
541
|
-
<!--
|
|
542
|
-
<div id="
|
|
543
|
-
<div
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
<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
|
-
<!--
|
|
559
|
-
<div id="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
<ul class="alt-list" id="alt-list"></ul>
|
|
637
|
-
</div>
|
|
638
|
-
</div>
|
|
247
|
+
function esc(s) {
|
|
248
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
249
|
+
}
|
|
639
250
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
<
|
|
675
|
-
<
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
<
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
</button>
|
|
726
|
-
</div>
|
|
338
|
+
scrollBottom();
|
|
339
|
+
}
|
|
727
340
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
377
|
+
function showResult(data) {
|
|
378
|
+
const div = document.createElement('div');
|
|
379
|
+
div.className = 'result';
|
|
380
|
+
const warningsHtml = renderWarnings(data.warnings);
|
|
793
381
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
799
|
-
|
|
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
|
-
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
|
888
|
-
try {
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
966
|
-
|
|
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
|
|
972
|
-
switch(
|
|
973
|
-
case '
|
|
974
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
985
|
-
|
|
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
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
-
|
|
992
|
-
|
|
993
|
-
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case 'step_done': {
|
|
490
|
+
finishStepCard(data.index, data.output, data.skipped);
|
|
994
491
|
break;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
case 'confirm_needed': {
|
|
495
|
+
addConfirmBar(data.step, currentStepIndex);
|
|
999
496
|
break;
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
case '
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
case 'complete': {
|
|
500
|
+
if (eventSource) { eventSource.close(); eventSource = null; }
|
|
501
|
+
finishScanBlock();
|
|
502
|
+
showResult(data);
|
|
1006
503
|
break;
|
|
1007
|
-
|
|
1008
|
-
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case 'paywall': {
|
|
507
|
+
if (eventSource) { eventSource.close(); eventSource = null; }
|
|
508
|
+
showPaywall(data);
|
|
1009
509
|
break;
|
|
1010
|
-
|
|
1011
|
-
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
case 'request_input': {
|
|
513
|
+
if (data.field === 'apiKey') showApiKeyInput(data);
|
|
514
|
+
if (data.field === 'userDescription') showDescriptionInput(data);
|
|
1012
515
|
break;
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
<
|
|
1112
|
-
|
|
1113
|
-
|
|
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="
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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 & diagnose</button>
|
|
596
|
+
<button class="btn btn-green" id="btn-desc-skip">🔍 Just scan & 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
|
-
|
|
1127
|
-
|
|
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
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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({
|
|
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, '&')
|
|
1283
|
-
.replace(/</g, '<')
|
|
1284
|
-
.replace(/>/g, '>')
|
|
1285
|
-
.replace(/"/g, '"');
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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
|
-
|
|
1319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>
|