clawaid 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +27 -10
- package/dist/server.js.map +1 -1
- package/package.json +14 -2
- package/web/index.html +640 -1221
package/web/index.html
CHANGED
|
@@ -6,1331 +6,750 @@
|
|
|
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;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.option-title {
|
|
280
|
-
font-size: 14px;
|
|
281
|
-
font-weight: 600;
|
|
282
|
-
color: #202124;
|
|
283
|
-
}
|
|
284
|
-
|
|
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;
|
|
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;
|
|
343
73
|
}
|
|
74
|
+
.step-output-label { font-size: 11px; color: var(--muted); margin-bottom: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
344
75
|
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
80
|
}
|
|
81
|
+
.confirm-bar.risk-high { background: var(--red-bg); border-top-color: var(--red-border); }
|
|
348
82
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
}
|
|
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; }
|
|
456
152
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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;
|
|
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;
|
|
470
158
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
color: #5f6368;
|
|
477
|
-
padding: 4px 0;
|
|
478
|
-
display: flex;
|
|
479
|
-
gap: 6px;
|
|
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;
|
|
480
164
|
}
|
|
481
|
-
.alt-list li::before { content: '→'; color: #9aa0a6; flex-shrink: 0; }
|
|
482
165
|
|
|
483
|
-
/*
|
|
484
|
-
.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
@keyframes fadeIn {
|
|
488
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
489
|
-
to { opacity: 1; transform: translateY(0); }
|
|
490
|
-
}
|
|
491
|
-
.fade-in { animation: fadeIn 0.25s ease; }
|
|
492
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
644
|
-
<span
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
251
|
+
function sendFeedback(value, btn) {
|
|
252
|
+
const container = btn.parentElement;
|
|
253
|
+
container.innerHTML = value === 'yes'
|
|
254
|
+
? '<span style="color:#34a853;font-size:13px;">Thanks! Glad it helped 🎉</span>'
|
|
255
|
+
: '<span style="color:#ea4335;font-size:13px;">Sorry to hear that. Reach out below — we\'ll help! 👇</span>';
|
|
256
|
+
fetch('/api/feedback', {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
headers: { 'Content-Type': 'application/json' },
|
|
259
|
+
body: JSON.stringify({ feedback: value, sessionId })
|
|
260
|
+
}).catch(() => {});
|
|
261
|
+
}
|
|
651
262
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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>
|
|
263
|
+
function scrollBottom() {
|
|
264
|
+
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
265
|
+
}
|
|
663
266
|
|
|
664
|
-
|
|
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>
|
|
267
|
+
// ─── Step rendering ───────────────────────────────────────────────────────
|
|
671
268
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
269
|
+
function iconForStep(step) {
|
|
270
|
+
if (step.type === 'read') return '🔍';
|
|
271
|
+
if (step.type === 'fix') return '🔧';
|
|
272
|
+
if (step.type === 'done') return step.fixed ? '✅' : '😔';
|
|
273
|
+
return '⚙️';
|
|
274
|
+
}
|
|
676
275
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
</div>
|
|
276
|
+
function badgeForStep(step) {
|
|
277
|
+
let html = '';
|
|
278
|
+
if (step.type === 'read') html += '<span class="step-badge badge-read">read</span>';
|
|
279
|
+
if (step.type === 'fix') html += '<span class="step-badge badge-fix">fix</span>';
|
|
280
|
+
if (step.type === 'done') html += '<span class="step-badge badge-done">done</span>';
|
|
281
|
+
if (step.risk === 'medium') html += ' <span class="step-badge badge-risk-medium">medium risk</span>';
|
|
282
|
+
if (step.risk === 'high') html += ' <span class="step-badge badge-risk-high">⚠ high risk</span>';
|
|
283
|
+
return html;
|
|
284
|
+
}
|
|
687
285
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
286
|
+
function addStepCard(step, index) {
|
|
287
|
+
const card = document.createElement('div');
|
|
288
|
+
card.className = 'step';
|
|
289
|
+
card.id = 'step-' + index;
|
|
290
|
+
const reasoningContent = step.thinking || step.reason || '';
|
|
291
|
+
const reasoningHtml = reasoningContent ? `
|
|
292
|
+
<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>
|
|
293
|
+
<div class="step-reasoning-text">${esc(reasoningContent)}</div>
|
|
294
|
+
` : '';
|
|
295
|
+
card.innerHTML = `
|
|
296
|
+
<div class="step-top" onclick="this.parentElement.classList.toggle('open')">
|
|
297
|
+
<div class="step-icon">${iconForStep(step)}</div>
|
|
298
|
+
<div class="step-title">${esc(step.description || step.summary || 'Working...')}</div>
|
|
299
|
+
${badgeForStep(step)}
|
|
300
|
+
<div class="step-status" id="step-status-${index}"><div class="spinner"></div></div>
|
|
694
301
|
</div>
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
302
|
+
<div class="step-detail" id="step-detail-${index}">
|
|
303
|
+
${reasoningHtml}
|
|
304
|
+
${step.command ? `<div class="step-cmd">$ ${esc(step.command)}</div>` : ''}
|
|
305
|
+
<div id="step-output-area-${index}"></div>
|
|
306
|
+
</div>
|
|
307
|
+
`;
|
|
308
|
+
feed.appendChild(card);
|
|
309
|
+
scrollBottom();
|
|
310
|
+
return card;
|
|
311
|
+
}
|
|
704
312
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
<
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
313
|
+
function finishStepCard(index, output, skipped) {
|
|
314
|
+
const statusEl = document.getElementById('step-status-' + index);
|
|
315
|
+
if (statusEl) statusEl.innerHTML = skipped ? '⏭️' : '✓';
|
|
316
|
+
const outputArea = document.getElementById('step-output-area-' + index);
|
|
317
|
+
if (outputArea && output && !skipped) {
|
|
318
|
+
const trimmed = output.length > 800 ? output.slice(0, 800) + '\n...' : output;
|
|
319
|
+
outputArea.innerHTML = `
|
|
320
|
+
<div class="step-output-label">Output</div>
|
|
321
|
+
<div class="step-output">${esc(trimmed)}</div>
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
717
325
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
326
|
+
function addConfirmBar(step, index) {
|
|
327
|
+
const card = document.getElementById('step-' + index);
|
|
328
|
+
if (!card) return;
|
|
329
|
+
const risk = step.risk || 'medium';
|
|
330
|
+
const bar = document.createElement('div');
|
|
331
|
+
bar.className = 'confirm-bar' + (risk === 'high' ? ' risk-high' : '');
|
|
332
|
+
bar.id = 'confirm-bar-' + index;
|
|
333
|
+
bar.innerHTML = `
|
|
334
|
+
<span style="font-size:13px; color:var(--text); flex:1;">This will modify your system. Allow?</span>
|
|
335
|
+
<button class="btn btn-green" id="btn-yes-${index}">✓ Allow</button>
|
|
336
|
+
<button class="btn btn-ghost" id="btn-no-${index}">Skip</button>
|
|
337
|
+
`;
|
|
338
|
+
card.appendChild(bar);
|
|
339
|
+
card.classList.add('open'); // auto-expand so user sees what's about to run
|
|
340
|
+
|
|
341
|
+
document.getElementById('btn-yes-' + index).addEventListener('click', () => {
|
|
342
|
+
bar.remove();
|
|
343
|
+
fetch('/api/confirm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, confirmed: true }) });
|
|
344
|
+
});
|
|
345
|
+
document.getElementById('btn-no-' + index).addEventListener('click', () => {
|
|
346
|
+
bar.remove();
|
|
347
|
+
fetch('/api/confirm', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, confirmed: false }) });
|
|
348
|
+
});
|
|
722
349
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
</button>
|
|
726
|
-
</div>
|
|
350
|
+
scrollBottom();
|
|
351
|
+
}
|
|
727
352
|
|
|
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>
|
|
353
|
+
// ─── Scan block (initial observation phase) ───────────────────────────────
|
|
354
|
+
|
|
355
|
+
function addScanBlock() {
|
|
356
|
+
const div = document.createElement('div');
|
|
357
|
+
div.className = 'step';
|
|
358
|
+
div.id = 'scan-block';
|
|
359
|
+
div.innerHTML = `
|
|
360
|
+
<div class="step-top" onclick="this.parentElement.classList.toggle('open')">
|
|
361
|
+
<div class="step-icon">📡</div>
|
|
362
|
+
<div class="step-title" id="scan-title">Scanning your system...</div>
|
|
363
|
+
<div class="step-status" id="scan-status"><div class="spinner"></div></div>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="step-detail" id="scan-detail"></div>
|
|
366
|
+
`;
|
|
367
|
+
feed.appendChild(div);
|
|
368
|
+
}
|
|
750
369
|
|
|
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>
|
|
370
|
+
function finishScanBlock() {
|
|
371
|
+
const el = document.getElementById('scan-status');
|
|
372
|
+
if (el) el.innerHTML = '✓';
|
|
373
|
+
const title = document.getElementById('scan-title');
|
|
374
|
+
if (title) title.textContent = 'System data collected';
|
|
375
|
+
}
|
|
764
376
|
|
|
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>
|
|
377
|
+
// ─── Result cards ─────────────────────────────────────────────────────────
|
|
787
378
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
379
|
+
function renderWarnings(warnings) {
|
|
380
|
+
if (!warnings || warnings.length === 0) return '';
|
|
381
|
+
let html = '<div class="warnings-list"><div class="warnings-title">⚠️ Other things to know</div>';
|
|
382
|
+
for (const w of warnings) {
|
|
383
|
+
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>`;
|
|
384
|
+
}
|
|
385
|
+
html += '</div>';
|
|
386
|
+
return html;
|
|
387
|
+
}
|
|
791
388
|
|
|
792
|
-
|
|
389
|
+
function showResult(data) {
|
|
390
|
+
const div = document.createElement('div');
|
|
391
|
+
div.className = 'result';
|
|
392
|
+
const warningsHtml = renderWarnings(data.warnings);
|
|
393
|
+
|
|
394
|
+
const feedbackHtml = `
|
|
395
|
+
<div style="margin-top:18px;padding:14px;background:#f8f9fa;border-radius:10px;border:1px solid #e0e0e0;">
|
|
396
|
+
<div style="font-size:13px;color:#555;margin-bottom:8px;">Was this helpful?</div>
|
|
397
|
+
<div style="display:flex;gap:8px;margin-bottom:12px;">
|
|
398
|
+
<button class="btn btn-ghost" onclick="sendFeedback('yes',this)" style="flex:1;">👍 Yes</button>
|
|
399
|
+
<button class="btn btn-ghost" onclick="sendFeedback('no',this)" style="flex:1;">👎 No</button>
|
|
400
|
+
</div>
|
|
401
|
+
<div style="font-size:12px;color:#888;line-height:1.5;">
|
|
402
|
+
💬 Questions or feedback?<br>
|
|
403
|
+
WeChat: <b>oliver56666</b> · <a href="https://github.com/jjj5666/clawaid/issues" target="_blank" style="color:#1a73e8;">GitHub Issues</a>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
`;
|
|
793
407
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
408
|
+
if (data.healthy) {
|
|
409
|
+
div.innerHTML = `
|
|
410
|
+
<div class="result-icon">💚</div>
|
|
411
|
+
<div class="result-title">All clear!</div>
|
|
412
|
+
<div class="result-desc">${esc(data.summary || 'Your OpenClaw is running normally.')}</div>
|
|
413
|
+
${warningsHtml}
|
|
414
|
+
${feedbackHtml}
|
|
415
|
+
<button class="btn btn-ghost btn-big" onclick="location.reload()" style="margin-top:14px;">🔄 Scan Again</button>
|
|
416
|
+
`;
|
|
417
|
+
} else if (data.fixed) {
|
|
418
|
+
div.innerHTML = `
|
|
419
|
+
<div class="result-icon">✅</div>
|
|
420
|
+
<div class="result-title">Fixed!</div>
|
|
421
|
+
<div class="result-desc">${esc(data.summary || 'OpenClaw has been repaired.')}</div>
|
|
422
|
+
${warningsHtml}
|
|
423
|
+
${feedbackHtml}
|
|
424
|
+
<button class="btn btn-ghost btn-big" onclick="location.reload()" style="margin-top:14px;">🔄 Scan Again</button>
|
|
425
|
+
`;
|
|
426
|
+
} else {
|
|
427
|
+
div.innerHTML = `
|
|
428
|
+
<div class="result-icon">😔</div>
|
|
429
|
+
<div class="result-title">Couldn't fix automatically</div>
|
|
430
|
+
<div class="result-desc">${esc(data.summary || 'After multiple attempts, the issue persists.')}</div>
|
|
431
|
+
${warningsHtml}
|
|
432
|
+
${feedbackHtml}
|
|
433
|
+
<button class="btn btn-ghost btn-big" onclick="location.reload()" style="margin-top:14px;">🔄 Try Again</button>
|
|
434
|
+
`;
|
|
435
|
+
}
|
|
797
436
|
|
|
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;
|
|
437
|
+
feed.appendChild(div);
|
|
438
|
+
scrollBottom();
|
|
826
439
|
}
|
|
827
440
|
|
|
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
|
-
}
|
|
441
|
+
// ─── SSE connection ───────────────────────────────────────────────────────
|
|
837
442
|
|
|
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
|
-
}
|
|
443
|
+
let currentStepIndex = -1;
|
|
444
|
+
|
|
445
|
+
let scanBlockAdded = false;
|
|
845
446
|
|
|
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
447
|
function startDiagnosis() {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
448
|
+
startScreen.classList.add('hidden');
|
|
449
|
+
feed.classList.remove('hidden');
|
|
450
|
+
feed.innerHTML = '';
|
|
451
|
+
currentStepIndex = -1;
|
|
452
|
+
scanBlockAdded = false;
|
|
883
453
|
|
|
884
454
|
eventSource = new EventSource('/api/diagnose');
|
|
885
455
|
|
|
886
456
|
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
|
-
}
|
|
457
|
+
let msg;
|
|
458
|
+
try { msg = JSON.parse(e.data); } catch { return; }
|
|
459
|
+
sessionId = msg.sessionId || sessionId;
|
|
460
|
+
onEvent(msg.type, msg.data || {});
|
|
962
461
|
};
|
|
963
462
|
|
|
964
463
|
eventSource.onerror = function() {
|
|
965
|
-
|
|
966
|
-
|
|
464
|
+
// Only show error if we haven't completed
|
|
465
|
+
if (!['fixed','not_fixed','healthy'].includes(document.body.dataset.state || '')) {
|
|
466
|
+
const err = document.createElement('div');
|
|
467
|
+
err.className = 'result';
|
|
468
|
+
err.innerHTML = `
|
|
469
|
+
<div class="result-icon">⚠️</div>
|
|
470
|
+
<div class="result-title">Connection lost</div>
|
|
471
|
+
<div class="result-desc">The server may have stopped.</div>
|
|
472
|
+
<button class="btn btn-ghost btn-big" onclick="location.reload()">🔄 Retry</button>
|
|
473
|
+
`;
|
|
474
|
+
feed.appendChild(err);
|
|
967
475
|
}
|
|
968
476
|
};
|
|
969
477
|
}
|
|
970
478
|
|
|
971
|
-
function
|
|
972
|
-
switch(
|
|
973
|
-
case '
|
|
974
|
-
|
|
975
|
-
$('scan-status-text').textContent = 'Checking gateway...';
|
|
479
|
+
function onEvent(type, data) {
|
|
480
|
+
switch (type) {
|
|
481
|
+
case 'session_start':
|
|
482
|
+
sessionId = data.sessionId;
|
|
976
483
|
break;
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
484
|
+
|
|
485
|
+
case 'state_change':
|
|
486
|
+
document.body.dataset.state = data.state;
|
|
487
|
+
if (data.state === 'observing' && !scanBlockAdded) {
|
|
488
|
+
addScanBlock();
|
|
489
|
+
scanBlockAdded = true;
|
|
490
|
+
}
|
|
491
|
+
if (data.state === 'running') finishScanBlock();
|
|
983
492
|
break;
|
|
984
|
-
|
|
985
|
-
|
|
493
|
+
|
|
494
|
+
case 'progress': {
|
|
495
|
+
// Append to scan block detail while scanning
|
|
496
|
+
const detail = document.getElementById('scan-detail');
|
|
497
|
+
if (detail) {
|
|
498
|
+
const line = document.createElement('div');
|
|
499
|
+
line.style.cssText = 'font-size:12px; color:var(--text2); padding:2px 0;';
|
|
500
|
+
line.textContent = data.message || '';
|
|
501
|
+
detail.appendChild(line);
|
|
502
|
+
}
|
|
986
503
|
break;
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
case 'step_start': {
|
|
507
|
+
currentStepIndex = data.index;
|
|
508
|
+
const step = data.step;
|
|
509
|
+
if (step.type === 'done') {
|
|
510
|
+
// Show result, don't add a step card
|
|
511
|
+
} else {
|
|
512
|
+
addStepCard(step, data.index);
|
|
513
|
+
}
|
|
990
514
|
break;
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
case 'step_done': {
|
|
518
|
+
finishStepCard(data.index, data.output, data.skipped);
|
|
994
519
|
break;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case 'confirm_needed': {
|
|
523
|
+
addConfirmBar(data.step, currentStepIndex);
|
|
999
524
|
break;
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
case '
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
case 'complete': {
|
|
528
|
+
if (eventSource) { eventSource.close(); eventSource = null; }
|
|
529
|
+
finishScanBlock();
|
|
530
|
+
showResult(data);
|
|
1006
531
|
break;
|
|
1007
|
-
|
|
1008
|
-
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
case 'paywall': {
|
|
535
|
+
if (eventSource) { eventSource.close(); eventSource = null; }
|
|
536
|
+
showPaywall(data);
|
|
1009
537
|
break;
|
|
1010
|
-
|
|
1011
|
-
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
case 'request_input': {
|
|
541
|
+
if (data.field === 'apiKey') showApiKeyInput(data);
|
|
542
|
+
if (data.field === 'userDescription') showDescriptionInput(data);
|
|
1012
543
|
break;
|
|
1013
|
-
|
|
1014
|
-
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
case 'error': {
|
|
547
|
+
const err = document.createElement('div');
|
|
548
|
+
err.className = 'result';
|
|
549
|
+
err.innerHTML = `
|
|
550
|
+
<div class="result-icon">❌</div>
|
|
551
|
+
<div class="result-title">Error</div>
|
|
552
|
+
<div class="result-desc">${esc(data.message || 'Unknown error')}</div>
|
|
553
|
+
<button class="btn btn-ghost btn-big" onclick="location.reload()">🔄 Retry</button>
|
|
554
|
+
`;
|
|
555
|
+
feed.appendChild(err);
|
|
556
|
+
scrollBottom();
|
|
1015
557
|
break;
|
|
558
|
+
}
|
|
1016
559
|
}
|
|
1017
560
|
}
|
|
1018
561
|
|
|
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
|
-
}
|
|
562
|
+
// ─── Paywall / API key ────────────────────────────────────────────────────
|
|
1071
563
|
|
|
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
|
-
|
|
564
|
+
function showPaywall(data) {
|
|
565
|
+
const price = data.price || data;
|
|
566
|
+
const priceStr = (price && price.price) || '$1.99';
|
|
567
|
+
const isCN = price && price.isChinese;
|
|
568
|
+
const credits = (price && price.credits) || 5;
|
|
569
|
+
|
|
570
|
+
const div = document.createElement('div');
|
|
571
|
+
div.className = 'start-card';
|
|
572
|
+
div.innerHTML = `
|
|
573
|
+
<div style="text-align:center; margin-bottom:12px;">
|
|
574
|
+
<div style="font-size:32px; font-weight:700; color:var(--blue);">${esc(priceStr)}</div>
|
|
575
|
+
<div style="font-size:13px; color:var(--muted);">${isCN ? '包含 '+credits+' 次修复' : 'Includes '+credits+' fix credits'}</div>
|
|
576
|
+
</div>
|
|
577
|
+
<a href="https://clawaid.app/#buy" target="_blank" class="btn btn-primary btn-big" style="text-decoration:none; margin-bottom:12px;">💳 Buy Credits</a>
|
|
578
|
+
<hr style="border:none; border-top:1px solid var(--border); margin:12px 0;">
|
|
579
|
+
<p style="font-size:13px; color:var(--text2); margin-bottom:8px;">Already have a token?</p>
|
|
580
|
+
<input class="input-field" id="pw-token" type="text" placeholder="Paste your token..." />
|
|
581
|
+
<button class="btn btn-ghost btn-big" id="pw-activate">🔑 Activate</button>
|
|
582
|
+
<div id="pw-msg" style="font-size:12px; text-align:center; margin-top:6px; min-height:16px;"></div>
|
|
583
|
+
`;
|
|
584
|
+
feed.appendChild(div);
|
|
585
|
+
|
|
586
|
+
document.getElementById('pw-activate').addEventListener('click', async () => {
|
|
587
|
+
const token = document.getElementById('pw-token').value.trim();
|
|
588
|
+
const msg = document.getElementById('pw-msg');
|
|
589
|
+
if (!token) { msg.style.color = 'var(--red)'; msg.textContent = 'Enter your token.'; return; }
|
|
590
|
+
msg.style.color = 'var(--muted)'; msg.textContent = 'Checking...';
|
|
591
|
+
try {
|
|
592
|
+
const r = await (await fetch('/api/redeem', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) })).json();
|
|
593
|
+
if (r.valid) { msg.style.color = 'var(--green)'; msg.textContent = '✅ Activated! Restarting...'; setTimeout(() => location.reload(), 1500); }
|
|
594
|
+
else { msg.style.color = 'var(--red)'; msg.textContent = '❌ Invalid token.'; }
|
|
595
|
+
} catch { msg.style.color = 'var(--red)'; msg.textContent = '❌ Network error.'; }
|
|
596
|
+
});
|
|
1096
597
|
}
|
|
1097
598
|
|
|
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>
|
|
599
|
+
// ─── User description dialog ──────────────────────────────────────────
|
|
600
|
+
|
|
601
|
+
function showDescriptionInput(data) {
|
|
602
|
+
let screenshotData = null;
|
|
603
|
+
|
|
604
|
+
const div = document.createElement('div');
|
|
605
|
+
div.className = 'start-card';
|
|
606
|
+
div.id = 'desc-input-card';
|
|
607
|
+
div.innerHTML = `
|
|
608
|
+
<p style="font-size:15px; font-weight:500; color:var(--text); margin-bottom:4px;">What's going on?</p>
|
|
609
|
+
<p style="font-size:13px; color:var(--text2); margin-bottom:12px;">Describe the problem, or just scan — ClawAid will figure it out.</p>
|
|
610
|
+
<div class="desc-input-wrap">
|
|
611
|
+
<div class="desc-textarea-wrap">
|
|
612
|
+
<textarea class="desc-textarea" id="desc-text" placeholder="e.g. OpenClaw stopped responding after I updated... (optional)"></textarea>
|
|
613
|
+
<button class="desc-camera-btn" id="desc-camera-btn" title="Add screenshot">📷</button>
|
|
614
|
+
<input type="file" id="screenshot-input" accept="image/*" style="display:none;" />
|
|
1116
615
|
</div>
|
|
1117
|
-
<div class="
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
616
|
+
<div id="screenshot-preview-row" style="display:none;" class="screenshot-preview-row">
|
|
617
|
+
<img id="screenshot-thumb" src="" />
|
|
618
|
+
<span id="screenshot-name" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">Screenshot attached</span>
|
|
619
|
+
<button class="spr-remove" id="screenshot-remove" title="Remove">✕</button>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
<div class="btn-row" style="margin-top:10px;">
|
|
623
|
+
<button class="btn btn-primary" id="btn-desc-submit" disabled>📝 Send & diagnose</button>
|
|
624
|
+
<button class="btn btn-green" id="btn-desc-skip">🔍 Just scan & fix</button>
|
|
625
|
+
</div>
|
|
626
|
+
`;
|
|
627
|
+
feed.appendChild(div);
|
|
628
|
+
scrollBottom();
|
|
629
|
+
|
|
630
|
+
const textarea = document.getElementById('desc-text');
|
|
631
|
+
const cameraBtn = document.getElementById('desc-camera-btn');
|
|
632
|
+
const fileInput = document.getElementById('screenshot-input');
|
|
633
|
+
const previewRow = document.getElementById('screenshot-preview-row');
|
|
634
|
+
const thumb = document.getElementById('screenshot-thumb');
|
|
635
|
+
const removeBtn = document.getElementById('screenshot-remove');
|
|
636
|
+
const submitBtn = document.getElementById('btn-desc-submit');
|
|
637
|
+
|
|
638
|
+
// Update submit button enabled state based on textarea content
|
|
639
|
+
function updateSubmitState() {
|
|
640
|
+
submitBtn.disabled = textarea.value.trim().length === 0;
|
|
641
|
+
}
|
|
642
|
+
textarea.addEventListener('input', updateSubmitState);
|
|
643
|
+
updateSubmitState();
|
|
644
|
+
|
|
645
|
+
function handleFile(file) {
|
|
646
|
+
if (!file || !file.type.startsWith('image/')) return;
|
|
647
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
648
|
+
alert('Image too large (max 5MB)');
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const reader = new FileReader();
|
|
652
|
+
reader.onload = function(e) {
|
|
653
|
+
screenshotData = e.target.result;
|
|
654
|
+
thumb.src = screenshotData;
|
|
655
|
+
previewRow.style.display = 'flex';
|
|
656
|
+
cameraBtn.classList.add('has-image');
|
|
657
|
+
};
|
|
658
|
+
reader.readAsDataURL(file);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
cameraBtn.addEventListener('click', function(e) {
|
|
662
|
+
e.preventDefault();
|
|
663
|
+
fileInput.click();
|
|
1124
664
|
});
|
|
1125
665
|
|
|
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
|
-
});
|
|
666
|
+
fileInput.addEventListener('change', function(e) {
|
|
667
|
+
if (e.target.files[0]) handleFile(e.target.files[0]);
|
|
1149
668
|
});
|
|
1150
|
-
}
|
|
1151
669
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
670
|
+
removeBtn.addEventListener('click', function() {
|
|
671
|
+
screenshotData = null;
|
|
672
|
+
previewRow.style.display = 'none';
|
|
673
|
+
thumb.src = '';
|
|
674
|
+
cameraBtn.classList.remove('has-image');
|
|
675
|
+
fileInput.value = '';
|
|
676
|
+
});
|
|
1157
677
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
678
|
+
// Drag & drop on the whole textarea area
|
|
679
|
+
const textareaWrap = div.querySelector('.desc-textarea-wrap');
|
|
680
|
+
textareaWrap.addEventListener('dragover', function(e) { e.preventDefault(); textarea.style.borderColor = 'var(--blue)'; });
|
|
681
|
+
textareaWrap.addEventListener('dragleave', function() { textarea.style.borderColor = ''; });
|
|
682
|
+
textareaWrap.addEventListener('drop', function(e) {
|
|
683
|
+
e.preventDefault(); textarea.style.borderColor = '';
|
|
684
|
+
if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
|
|
685
|
+
});
|
|
1163
686
|
|
|
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!';
|
|
687
|
+
// Paste support
|
|
688
|
+
document.addEventListener('paste', function onPaste(e) {
|
|
689
|
+
const items = e.clipboardData && e.clipboardData.items;
|
|
690
|
+
if (items) {
|
|
691
|
+
for (let i = 0; i < items.length; i++) {
|
|
692
|
+
if (items[i].type.startsWith('image/')) {
|
|
693
|
+
handleFile(items[i].getAsFile());
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
1202
697
|
}
|
|
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
|
-
}
|
|
1224
|
-
|
|
1225
|
-
$('paywall-price').textContent = priceStr;
|
|
1226
|
-
$('paywall-credits').textContent = isChinese
|
|
1227
|
-
? `包含 ${credits} 次修复`
|
|
1228
|
-
: `Includes ${credits} fix credits`;
|
|
698
|
+
}, { once: false });
|
|
1229
699
|
|
|
1230
|
-
|
|
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', {
|
|
700
|
+
function submit(description) {
|
|
701
|
+
div.remove();
|
|
702
|
+
fetch('/api/input', {
|
|
1249
703
|
method: 'POST',
|
|
1250
704
|
headers: { 'Content-Type': 'application/json' },
|
|
1251
|
-
body: JSON.stringify({
|
|
705
|
+
body: JSON.stringify({
|
|
706
|
+
sessionId,
|
|
707
|
+
field: 'userDescription',
|
|
708
|
+
value: description || '',
|
|
709
|
+
screenshot: screenshotData || undefined,
|
|
710
|
+
})
|
|
1252
711
|
});
|
|
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
712
|
}
|
|
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
713
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
});
|
|
714
|
+
submitBtn.addEventListener('click', function() {
|
|
715
|
+
submit(textarea.value.trim());
|
|
716
|
+
});
|
|
717
|
+
document.getElementById('btn-desc-skip').addEventListener('click', function() {
|
|
718
|
+
submit('');
|
|
719
|
+
});
|
|
720
|
+
textarea.addEventListener('keydown', function(e) {
|
|
721
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
722
|
+
if (textarea.value.trim()) submit(textarea.value.trim());
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
textarea.focus();
|
|
726
|
+
}
|
|
1317
727
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
728
|
+
function showApiKeyInput(data) {
|
|
729
|
+
const div = document.createElement('div');
|
|
730
|
+
div.className = 'start-card';
|
|
731
|
+
div.innerHTML = `
|
|
732
|
+
<p style="font-size:14px; color:var(--text2); margin-bottom:12px;">${esc(data.instructions || 'Enter your OpenRouter API key.')}</p>
|
|
733
|
+
<input class="input-field" id="ak-input" type="password" placeholder="sk-or-..." autocomplete="off" />
|
|
734
|
+
<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>
|
|
735
|
+
<button class="btn btn-primary btn-big" id="ak-submit">Continue</button>
|
|
736
|
+
`;
|
|
737
|
+
feed.appendChild(div);
|
|
738
|
+
|
|
739
|
+
const submit = async () => {
|
|
740
|
+
const key = document.getElementById('ak-input').value.trim();
|
|
741
|
+
if (!key) return;
|
|
742
|
+
document.getElementById('ak-submit').disabled = true;
|
|
743
|
+
await fetch('/api/input', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, field: 'apiKey', value: key }) });
|
|
744
|
+
};
|
|
745
|
+
document.getElementById('ak-submit').addEventListener('click', submit);
|
|
746
|
+
document.getElementById('ak-input').addEventListener('keydown', e => { if (e.key === 'Enter') submit(); });
|
|
747
|
+
document.getElementById('ak-input').focus();
|
|
748
|
+
}
|
|
1321
749
|
|
|
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
|
-
});
|
|
750
|
+
// ─── Start ────────────────────────────────────────────────────────────────
|
|
1329
751
|
|
|
1330
|
-
|
|
1331
|
-
$('btn-start-scan').addEventListener('click', function() {
|
|
1332
|
-
startDiagnosis();
|
|
1333
|
-
});
|
|
752
|
+
document.getElementById('btn-start').addEventListener('click', startDiagnosis);
|
|
1334
753
|
|
|
1335
754
|
})();
|
|
1336
755
|
</script>
|