opencroc 1.6.7 โ 1.6.9
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/web/assets/botreview/char_0.png +0 -0
- package/dist/web/assets/botreview/char_1.png +0 -0
- package/dist/web/assets/botreview/char_2.png +0 -0
- package/dist/web/assets/botreview/coffee-machine.gif +0 -0
- package/dist/web/assets/botreview/server.gif +0 -0
- package/dist/web/assets/botreview/walls.png +0 -0
- package/dist/web/assets/star/desk-v3.webp +0 -0
- package/dist/web/assets/star/office_bg_small.webp +0 -0
- package/dist/web/assets/star/star-idle-v5.png +0 -0
- package/dist/web/assets/star/star-working-spritesheet-grid.webp +0 -0
- package/dist/web/index.html +1097 -185
- package/package.json +1 -1
package/dist/web/index.html
CHANGED
|
@@ -1,150 +1,756 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
2
|
+
<html lang="zh-CN" data-theme="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>OpenCroc Studio
|
|
6
|
+
<title>OpenCroc Studio</title>
|
|
7
7
|
<style>
|
|
8
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
9
|
+
Design System โ CSS Variables (borrowed from OpenClaw)
|
|
10
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
8
11
|
:root {
|
|
9
|
-
--bg
|
|
10
|
-
--bg-panel:
|
|
11
|
-
--bg-card:
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
12
|
+
--bg: #0a0f1a;
|
|
13
|
+
--bg-panel: #111827;
|
|
14
|
+
--bg-card: #1a2332;
|
|
15
|
+
--bg-hover: #243044;
|
|
16
|
+
--bg-soft: rgba(30, 41, 59, 0.5);
|
|
17
|
+
--accent: #34d399;
|
|
18
|
+
--accent-dim: #059669;
|
|
19
|
+
--accent-bg: rgba(52, 211, 153, 0.12);
|
|
20
|
+
--red: #f87171;
|
|
21
|
+
--red-bg: rgba(248, 113, 113, 0.12);
|
|
22
|
+
--orange: #fbbf24;
|
|
23
|
+
--orange-bg: rgba(251, 191, 36, 0.12);
|
|
24
|
+
--blue: #60a5fa;
|
|
25
|
+
--blue-bg: rgba(96, 165, 250, 0.12);
|
|
26
|
+
--purple: #a78bfa;
|
|
27
|
+
--purple-bg: rgba(167, 139, 250, 0.12);
|
|
28
|
+
--text: #f1f5f9;
|
|
29
|
+
--text-dim: #94a3b8;
|
|
30
|
+
--text-subtle:#64748b;
|
|
31
|
+
--border: rgba(148, 163, 184, 0.15);
|
|
32
|
+
--border-accent: rgba(52, 211, 153, 0.3);
|
|
33
|
+
--shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
34
|
+
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
35
|
+
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
36
|
+
--radius-lg: 14px;
|
|
37
|
+
--radius-md: 10px;
|
|
38
|
+
--radius-sm: 6px;
|
|
39
|
+
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
21
40
|
}
|
|
41
|
+
[data-theme="light"] {
|
|
42
|
+
--bg: #f1f5f9;
|
|
43
|
+
--bg-panel: #ffffff;
|
|
44
|
+
--bg-card: #f8fafc;
|
|
45
|
+
--bg-hover: #e2e8f0;
|
|
46
|
+
--bg-soft: rgba(241, 245, 249, 0.8);
|
|
47
|
+
--accent: #059669;
|
|
48
|
+
--accent-dim: #047857;
|
|
49
|
+
--accent-bg: rgba(5, 150, 105, 0.1);
|
|
50
|
+
--red: #dc2626;
|
|
51
|
+
--red-bg: rgba(220, 38, 38, 0.08);
|
|
52
|
+
--orange: #d97706;
|
|
53
|
+
--orange-bg: rgba(217, 119, 6, 0.08);
|
|
54
|
+
--blue: #2563eb;
|
|
55
|
+
--blue-bg: rgba(37, 99, 235, 0.08);
|
|
56
|
+
--purple: #7c3aed;
|
|
57
|
+
--purple-bg: rgba(124, 58, 237, 0.08);
|
|
58
|
+
--text: #0f172a;
|
|
59
|
+
--text-dim: #475569;
|
|
60
|
+
--text-subtle:#94a3b8;
|
|
61
|
+
--border: rgba(100, 116, 139, 0.2);
|
|
62
|
+
--border-accent: rgba(5, 150, 105, 0.35);
|
|
63
|
+
--shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.08);
|
|
64
|
+
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.06);
|
|
65
|
+
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
69
|
+
Base Reset & Typography (system font stack)
|
|
70
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
22
71
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
23
|
-
body {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
body {
|
|
73
|
+
background: var(--bg);
|
|
74
|
+
color: var(--text);
|
|
75
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", "PingFang SC", sans-serif;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
height: 100vh;
|
|
78
|
+
line-height: 1.5;
|
|
79
|
+
-webkit-font-smoothing: antialiased;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Scrollbar styling */
|
|
83
|
+
::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
84
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
85
|
+
::-webkit-scrollbar-thumb { background: var(--text-subtle); border-radius: 4px; }
|
|
86
|
+
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
|
|
87
|
+
|
|
88
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
89
|
+
Layout Grid โ Responsive
|
|
90
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
91
|
+
.app {
|
|
92
|
+
display: grid;
|
|
93
|
+
grid-template-rows: 56px 1fr 220px;
|
|
94
|
+
grid-template-columns: 240px 1fr 320px;
|
|
95
|
+
grid-template-areas:
|
|
96
|
+
"header header header"
|
|
97
|
+
"sidebar main panel"
|
|
98
|
+
"office office office";
|
|
99
|
+
height: 100vh;
|
|
100
|
+
gap: 6px;
|
|
101
|
+
padding: 6px;
|
|
102
|
+
}
|
|
103
|
+
@media (max-width: 1200px) {
|
|
104
|
+
.app {
|
|
105
|
+
grid-template-columns: 200px 1fr 280px;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
@media (max-width: 960px) {
|
|
109
|
+
.app {
|
|
110
|
+
grid-template-columns: 1fr;
|
|
111
|
+
grid-template-rows: 56px 1fr 200px;
|
|
112
|
+
grid-template-areas:
|
|
113
|
+
"header"
|
|
114
|
+
"main"
|
|
115
|
+
"office";
|
|
116
|
+
}
|
|
117
|
+
.sidebar, .log-panel { display: none; }
|
|
118
|
+
.sidebar.mobile-open, .log-panel.mobile-open {
|
|
119
|
+
display: flex;
|
|
120
|
+
position: fixed;
|
|
121
|
+
top: 62px; bottom: 0;
|
|
122
|
+
width: 280px;
|
|
123
|
+
z-index: 150;
|
|
124
|
+
}
|
|
125
|
+
.sidebar.mobile-open { left: 0; }
|
|
126
|
+
.log-panel.mobile-open { right: 0; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
130
|
+
Header
|
|
131
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
132
|
+
.header {
|
|
133
|
+
grid-area: header;
|
|
134
|
+
background: var(--bg-panel);
|
|
135
|
+
border: 1px solid var(--border);
|
|
136
|
+
border-radius: var(--radius-lg);
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
padding: 0 14px;
|
|
140
|
+
gap: 10px;
|
|
141
|
+
box-shadow: var(--shadow-sm);
|
|
142
|
+
}
|
|
143
|
+
.header .logo {
|
|
144
|
+
width: 36px; height: 36px;
|
|
145
|
+
border-radius: var(--radius-md);
|
|
146
|
+
display: grid; place-items: center;
|
|
147
|
+
background: var(--accent-bg);
|
|
148
|
+
border: 1px solid var(--border-accent);
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
}
|
|
151
|
+
.header .logo svg { width: 22px; height: 22px; }
|
|
152
|
+
.header .title-wrap { display:flex; flex-direction:column; gap:1px; min-width:0; }
|
|
153
|
+
.header h1 { font-size:14px; font-weight:700; color:var(--accent); letter-spacing:.3px; white-space:nowrap; }
|
|
154
|
+
.header .subtitle { font-size:10px; color:var(--text-subtle); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
155
|
+
.header .actions { display:flex; gap:5px; flex-shrink:0; }
|
|
156
|
+
.header .spacer { flex:1; }
|
|
157
|
+
|
|
158
|
+
/* View Switch */
|
|
159
|
+
.view-switch {
|
|
160
|
+
display: flex;
|
|
161
|
+
background: var(--bg-card);
|
|
162
|
+
border: 1px solid var(--border);
|
|
163
|
+
border-radius: var(--radius-md);
|
|
164
|
+
padding: 2px;
|
|
165
|
+
margin-right: 2px;
|
|
166
|
+
}
|
|
167
|
+
.view-switch button {
|
|
168
|
+
background: transparent;
|
|
169
|
+
color: var(--text-dim);
|
|
170
|
+
border: none;
|
|
171
|
+
font-family: inherit;
|
|
172
|
+
font-size: 11px;
|
|
173
|
+
padding: 5px 12px;
|
|
174
|
+
border-radius: var(--radius-sm);
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
transition: all var(--transition);
|
|
177
|
+
display: flex; align-items: center; gap: 4px;
|
|
178
|
+
}
|
|
179
|
+
.view-switch button:hover { color: var(--text); background: var(--bg-hover); }
|
|
180
|
+
.view-switch button.active {
|
|
181
|
+
background: var(--accent-bg);
|
|
182
|
+
color: var(--accent);
|
|
183
|
+
box-shadow: 0 0 0 1px var(--border-accent);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Buttons */
|
|
187
|
+
.btn {
|
|
188
|
+
background: var(--accent-dim);
|
|
189
|
+
color: #fff;
|
|
190
|
+
border: 1px solid var(--border-accent);
|
|
191
|
+
padding: 5px 12px;
|
|
192
|
+
font-family: inherit;
|
|
193
|
+
font-size: 11px;
|
|
194
|
+
font-weight: 500;
|
|
195
|
+
border-radius: var(--radius-sm);
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
transition: all var(--transition);
|
|
198
|
+
display: flex; align-items: center; gap: 4px;
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
}
|
|
201
|
+
.btn:hover { background: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
202
|
+
.btn:active { transform: translateY(0); }
|
|
203
|
+
.btn:disabled { opacity: .35; cursor: not-allowed; transform: none; }
|
|
204
|
+
.btn.danger { background: #991b1b; border-color: rgba(248,113,113,.3); }
|
|
205
|
+
.btn.danger:hover { background: #b91c1c; }
|
|
206
|
+
.btn svg { width: 14px; height: 14px; }
|
|
207
|
+
.mode-select {
|
|
208
|
+
background: var(--bg-card);
|
|
209
|
+
color: var(--text);
|
|
210
|
+
border: 1px solid var(--border);
|
|
211
|
+
border-radius: var(--radius-sm);
|
|
212
|
+
font-family: inherit;
|
|
213
|
+
font-size: 11px;
|
|
214
|
+
padding: 5px 8px;
|
|
215
|
+
min-width: 80px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Stats */
|
|
220
|
+
.header .stats { display:flex; gap:6px; font-size:10px; color:var(--text-dim); flex-shrink:0; }
|
|
221
|
+
.header .stats > div {
|
|
222
|
+
background: var(--bg-card);
|
|
223
|
+
border: 1px solid var(--border);
|
|
224
|
+
border-radius: var(--radius-sm);
|
|
225
|
+
padding: 4px 8px;
|
|
226
|
+
min-width: 60px;
|
|
227
|
+
text-align: center;
|
|
228
|
+
}
|
|
229
|
+
.header .stats .label { font-size:9px; color:var(--text-subtle); text-transform:uppercase; letter-spacing:.5px; }
|
|
230
|
+
.header .stats .value { display:block; color:var(--accent); font-weight:700; font-size:13px; margin-top:1px; }
|
|
231
|
+
.conn-dot {
|
|
232
|
+
width: 10px; height: 10px;
|
|
233
|
+
border-radius: 50%;
|
|
234
|
+
background: var(--red);
|
|
235
|
+
box-shadow: 0 0 0 3px var(--red-bg);
|
|
236
|
+
transition: all .3s;
|
|
237
|
+
flex-shrink: 0;
|
|
238
|
+
}
|
|
239
|
+
.conn-dot.on { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); animation: conn-pulse 2s infinite; }
|
|
240
|
+
@keyframes conn-pulse { 0%,100%{ box-shadow: 0 0 0 3px var(--accent-bg) } 50%{ box-shadow: 0 0 0 6px var(--accent-bg) } }
|
|
241
|
+
|
|
242
|
+
/* Theme toggle */
|
|
243
|
+
.theme-toggle {
|
|
244
|
+
background: none; border: 1px solid var(--border); color: var(--text-dim);
|
|
245
|
+
width: 32px; height: 32px; border-radius: var(--radius-sm);
|
|
246
|
+
display: grid; place-items: center; cursor: pointer;
|
|
247
|
+
transition: all var(--transition);
|
|
248
|
+
}
|
|
249
|
+
.theme-toggle:hover { background: var(--bg-hover); color: var(--text); }
|
|
250
|
+
.theme-toggle svg { width: 16px; height: 16px; }
|
|
251
|
+
|
|
252
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
253
|
+
Sidebar
|
|
254
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
255
|
+
.sidebar {
|
|
256
|
+
grid-area: sidebar;
|
|
257
|
+
background: var(--bg-panel);
|
|
258
|
+
border: 1px solid var(--border);
|
|
259
|
+
border-radius: var(--radius-lg);
|
|
260
|
+
overflow-y: auto;
|
|
261
|
+
padding: 10px;
|
|
262
|
+
box-shadow: var(--shadow-sm);
|
|
263
|
+
}
|
|
264
|
+
.sidebar h3 {
|
|
265
|
+
font-size: 10px; text-transform: uppercase; color: var(--text-subtle);
|
|
266
|
+
padding: 10px 4px 6px; letter-spacing: 1.2px; font-weight: 600;
|
|
267
|
+
display: flex; align-items: center; gap: 6px;
|
|
268
|
+
}
|
|
269
|
+
.sidebar h3 svg { width: 14px; height: 14px; opacity: 0.6; }
|
|
270
|
+
.mod-item {
|
|
271
|
+
padding: 7px 10px;
|
|
272
|
+
border-radius: var(--radius-sm);
|
|
273
|
+
font-size: 12px;
|
|
274
|
+
cursor: pointer;
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: 8px;
|
|
278
|
+
transition: all var(--transition);
|
|
279
|
+
margin-bottom: 2px;
|
|
280
|
+
border: 1px solid transparent;
|
|
281
|
+
color: var(--text-dim);
|
|
282
|
+
}
|
|
283
|
+
.mod-item:hover {
|
|
284
|
+
background: var(--bg-hover);
|
|
285
|
+
border-color: var(--border);
|
|
286
|
+
color: var(--text);
|
|
287
|
+
transform: translateX(2px);
|
|
288
|
+
}
|
|
289
|
+
.mod-count {
|
|
290
|
+
margin-left: auto; font-size: 9px; color: var(--text-subtle);
|
|
291
|
+
background: var(--bg-card); padding: 1px 6px; border-radius: 8px;
|
|
292
|
+
}
|
|
293
|
+
.dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; transition: background .3s; }
|
|
294
|
+
.dot.idle { background: var(--text-subtle); }
|
|
295
|
+
.dot.testing, .dot.working { background: var(--orange); animation: dot-pulse 1s infinite; }
|
|
296
|
+
.dot.thinking { background: var(--blue); animation: dot-pulse 1.4s infinite; }
|
|
297
|
+
.dot.passed, .dot.done { background: var(--accent); }
|
|
298
|
+
.dot.failed, .dot.error { background: var(--red); animation: dot-shake .3s infinite; }
|
|
299
|
+
@keyframes dot-pulse { 0%,100%{ opacity:1; transform:scale(1) } 50%{ opacity:.5; transform:scale(.85) } }
|
|
300
|
+
@keyframes dot-shake { 0%,100%{ transform:translateX(0) } 25%{ transform:translateX(-1px) } 75%{ transform:translateX(1px) } }
|
|
301
|
+
|
|
302
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
303
|
+
Main Content
|
|
304
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
305
|
+
.main {
|
|
306
|
+
grid-area: main;
|
|
307
|
+
position: relative;
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
background: var(--bg-panel);
|
|
310
|
+
border: 1px solid var(--border);
|
|
311
|
+
border-radius: var(--radius-lg);
|
|
312
|
+
box-shadow: var(--shadow-md);
|
|
313
|
+
}
|
|
56
314
|
#graph-canvas { width:100%; height:100%; display:block; cursor:grab; }
|
|
57
|
-
.
|
|
315
|
+
.view { position:absolute; inset:0; }
|
|
316
|
+
.view.hidden { display:none; }
|
|
317
|
+
|
|
318
|
+
/* Pixel Office Stage */
|
|
319
|
+
.pixel-stage {
|
|
320
|
+
position: relative; width:100%; height:100%; overflow:hidden;
|
|
321
|
+
background:
|
|
322
|
+
linear-gradient(rgba(8,12,20,.15), rgba(8,12,20,.4)),
|
|
323
|
+
url('./assets/star/office_bg_small.webp') center/cover no-repeat;
|
|
324
|
+
}
|
|
325
|
+
.pixel-stage::before {
|
|
326
|
+
content:''; position:absolute; inset:0;
|
|
327
|
+
background-image:
|
|
328
|
+
linear-gradient(rgba(148,163,184,.08) 1px, transparent 1px),
|
|
329
|
+
linear-gradient(90deg, rgba(148,163,184,.08) 1px, transparent 1px);
|
|
330
|
+
background-size: 32px 32px;
|
|
331
|
+
pointer-events:none;
|
|
332
|
+
}
|
|
333
|
+
.pixel-stage .asset {
|
|
334
|
+
position:absolute; image-rendering:pixelated; z-index:2;
|
|
335
|
+
filter: drop-shadow(0 8px 16px rgba(0,0,0,.4));
|
|
336
|
+
transition: opacity .3s;
|
|
337
|
+
}
|
|
338
|
+
.pixel-stage .desk-asset { width:180px; bottom:90px; left:40px; }
|
|
339
|
+
.pixel-stage .server-asset { width:110px; top:24px; right:34px; }
|
|
340
|
+
.pixel-stage .coffee-asset { width:72px; top:100px; right:180px; }
|
|
341
|
+
.pixel-stage .walls-asset { width:220px; top:10px; left:46%; transform:translateX(-50%); opacity:.4; }
|
|
342
|
+
.pixel-agent-layer { position:absolute; inset:0; z-index:3; }
|
|
343
|
+
.pixel-agent {
|
|
344
|
+
position:absolute; width:44px;
|
|
345
|
+
transition: left .6s cubic-bezier(.4,0,.2,1), top .6s cubic-bezier(.4,0,.2,1);
|
|
346
|
+
image-rendering:pixelated;
|
|
347
|
+
filter: drop-shadow(0 6px 10px rgba(0,0,0,.4));
|
|
348
|
+
}
|
|
349
|
+
/* Sprite-canvas replaces static img for animated agents */
|
|
350
|
+
.pixel-sprite-canvas {
|
|
351
|
+
position:absolute; width:48px; height:48px;
|
|
352
|
+
image-rendering:pixelated;
|
|
353
|
+
transition: left .6s cubic-bezier(.4,0,.2,1), top .6s cubic-bezier(.4,0,.2,1);
|
|
354
|
+
filter: drop-shadow(0 6px 10px rgba(0,0,0,.4));
|
|
355
|
+
}
|
|
356
|
+
.pixel-agent.working,.pixel-agent.testing { animation: agent-bob .5s ease-in-out infinite alternate; }
|
|
357
|
+
.pixel-agent.thinking { animation: agent-think 1.2s ease-in-out infinite; }
|
|
358
|
+
.pixel-agent.error,.pixel-agent.failed { animation: agent-shake .25s ease-in-out infinite; }
|
|
359
|
+
.pixel-agent.done,.pixel-agent.passed { animation: agent-pulse .9s ease-in-out infinite; }
|
|
360
|
+
.pixel-sprite-canvas.working,.pixel-sprite-canvas.testing { animation: agent-bob .5s ease-in-out infinite alternate; }
|
|
361
|
+
.pixel-sprite-canvas.thinking { animation: agent-think 1.2s ease-in-out infinite; }
|
|
362
|
+
.pixel-sprite-canvas.error,.pixel-sprite-canvas.failed { animation: agent-shake .25s ease-in-out infinite; }
|
|
363
|
+
.pixel-sprite-canvas.done,.pixel-sprite-canvas.passed { animation: agent-pulse .9s ease-in-out infinite; }
|
|
364
|
+
@keyframes agent-bob { from{transform:translateY(0)} to{transform:translateY(-4px)} }
|
|
365
|
+
@keyframes agent-think { 0%,100%{transform:rotate(0) scale(1)} 50%{transform:rotate(-3deg) scale(1.02)} }
|
|
366
|
+
@keyframes agent-shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-3px)} 75%{transform:translateX(3px)} }
|
|
367
|
+
@keyframes agent-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.08)} }
|
|
368
|
+
|
|
369
|
+
/* Pixel Agent Label โ enhanced with status bubble */
|
|
370
|
+
.pixel-label {
|
|
371
|
+
position:absolute; transform:translate(-50%,-50%);
|
|
372
|
+
background: var(--bg-panel);
|
|
373
|
+
border: 1px solid var(--border-accent);
|
|
374
|
+
border-radius: var(--radius-sm);
|
|
375
|
+
padding: 3px 8px;
|
|
376
|
+
font-size: 10px;
|
|
377
|
+
color: var(--text);
|
|
378
|
+
white-space:nowrap;
|
|
379
|
+
z-index:4;
|
|
380
|
+
box-shadow: var(--shadow-sm);
|
|
381
|
+
backdrop-filter: blur(4px);
|
|
382
|
+
}
|
|
383
|
+
.pixel-label .status-tag {
|
|
384
|
+
display:inline-block; font-size:8px; padding:1px 4px; border-radius:4px; margin-left:4px;
|
|
385
|
+
font-weight:600; text-transform:uppercase; letter-spacing:.3px;
|
|
386
|
+
}
|
|
387
|
+
.pixel-label .status-tag.working { background:var(--orange-bg); color:var(--orange); }
|
|
388
|
+
.pixel-label .status-tag.thinking { background:var(--blue-bg); color:var(--blue); }
|
|
389
|
+
.pixel-label .status-tag.done { background:var(--accent-bg); color:var(--accent); }
|
|
390
|
+
.pixel-label .status-tag.error { background:var(--red-bg); color:var(--red); }
|
|
391
|
+
|
|
392
|
+
/* Pixel KPIs */
|
|
393
|
+
.pixel-kpis { position:absolute; left:14px; top:14px; display:flex; gap:6px; z-index:4; }
|
|
394
|
+
.pixel-kpi {
|
|
395
|
+
min-width:80px;
|
|
396
|
+
background: var(--bg-panel);
|
|
397
|
+
border: 1px solid var(--border);
|
|
398
|
+
border-radius: var(--radius-md);
|
|
399
|
+
padding: 8px 10px;
|
|
400
|
+
box-shadow: var(--shadow-md);
|
|
401
|
+
backdrop-filter: blur(8px);
|
|
402
|
+
}
|
|
403
|
+
.pixel-kpi .t { font-size:9px; color:var(--text-subtle); text-transform:uppercase; letter-spacing:.5px; }
|
|
404
|
+
.pixel-kpi .v { font-size:16px; color:var(--accent); font-weight:700; margin-top:2px; }
|
|
405
|
+
.pixel-kpi.errors .v { color:var(--red); }
|
|
406
|
+
.pixel-kpi.done .v { color:var(--accent); }
|
|
407
|
+
|
|
408
|
+
/* Bubble messages (Star-Office style) */
|
|
409
|
+
.pixel-bubble {
|
|
410
|
+
position:absolute; z-index:5; pointer-events:none;
|
|
411
|
+
background: var(--bg-panel);
|
|
412
|
+
border: 1px solid var(--border-accent);
|
|
413
|
+
border-radius: var(--radius-sm) var(--radius-sm) var(--radius-sm) 2px;
|
|
414
|
+
padding: 4px 10px;
|
|
415
|
+
font-size: 10px;
|
|
416
|
+
color: var(--text);
|
|
417
|
+
box-shadow: var(--shadow-sm);
|
|
418
|
+
animation: bubble-in .3s ease-out, bubble-out .3s ease-in 2.7s forwards;
|
|
419
|
+
white-space: nowrap;
|
|
420
|
+
}
|
|
421
|
+
@keyframes bubble-in { from{opacity:0;transform:translateY(6px) scale(.9)} to{opacity:1;transform:translateY(0) scale(1)} }
|
|
422
|
+
@keyframes bubble-out { from{opacity:1} to{opacity:0;transform:translateY(-4px)} }
|
|
423
|
+
|
|
424
|
+
/* Tooltip */
|
|
425
|
+
.tooltip {
|
|
426
|
+
position:absolute;
|
|
427
|
+
background: var(--bg-panel);
|
|
428
|
+
border: 1px solid var(--border-accent);
|
|
429
|
+
border-radius: var(--radius-md);
|
|
430
|
+
padding: 10px 14px;
|
|
431
|
+
font-size: 12px;
|
|
432
|
+
pointer-events:none;
|
|
433
|
+
z-index:100;
|
|
434
|
+
display:none;
|
|
435
|
+
max-width:300px;
|
|
436
|
+
box-shadow: var(--shadow-lg);
|
|
437
|
+
line-height: 1.6;
|
|
438
|
+
}
|
|
58
439
|
.tooltip.visible { display:block; }
|
|
440
|
+
.tooltip b { color: var(--accent); }
|
|
59
441
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.panel
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
442
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
443
|
+
Right Panel (Log/Tests/Results/Reports)
|
|
444
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
445
|
+
.log-panel {
|
|
446
|
+
grid-area: panel;
|
|
447
|
+
background: var(--bg-panel);
|
|
448
|
+
border: 1px solid var(--border);
|
|
449
|
+
border-radius: var(--radius-lg);
|
|
450
|
+
display: flex; flex-direction: column;
|
|
451
|
+
overflow: hidden;
|
|
452
|
+
box-shadow: var(--shadow-sm);
|
|
453
|
+
}
|
|
454
|
+
.panel-tabs {
|
|
455
|
+
display: flex;
|
|
456
|
+
border-bottom: 1px solid var(--border);
|
|
457
|
+
background: var(--bg-card);
|
|
458
|
+
flex-shrink: 0;
|
|
459
|
+
}
|
|
460
|
+
.panel-tabs .tab {
|
|
461
|
+
background: none; border: none;
|
|
462
|
+
color: var(--text-dim);
|
|
463
|
+
font-family: inherit;
|
|
464
|
+
font-size: 11px;
|
|
465
|
+
padding: 10px 12px;
|
|
466
|
+
cursor: pointer;
|
|
467
|
+
border-bottom: 2px solid transparent;
|
|
468
|
+
transition: all var(--transition);
|
|
469
|
+
display: flex; align-items: center; gap: 4px;
|
|
470
|
+
}
|
|
471
|
+
.panel-tabs .tab:hover { color: var(--text); background: var(--bg-hover); }
|
|
472
|
+
.panel-tabs .tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
473
|
+
.panel-tabs .tab svg { width: 14px; height: 14px; }
|
|
474
|
+
.tab-badge {
|
|
475
|
+
display:inline-block; background:var(--accent); color:var(--bg);
|
|
476
|
+
border-radius:8px; padding:0 5px; font-size:9px; font-weight:700;
|
|
477
|
+
}
|
|
478
|
+
.tab-badge.alert { background:var(--red); color:#fff; }
|
|
479
|
+
|
|
480
|
+
.file-list { flex:1; overflow-y:auto; padding:6px 8px; font-size:11px; }
|
|
481
|
+
.file-item {
|
|
482
|
+
padding: 8px 10px;
|
|
483
|
+
border-bottom: 1px solid var(--border);
|
|
484
|
+
cursor: pointer;
|
|
485
|
+
border-radius: var(--radius-sm);
|
|
486
|
+
transition: all var(--transition);
|
|
487
|
+
margin-bottom:2px;
|
|
488
|
+
}
|
|
489
|
+
.file-item:hover { background: var(--bg-hover); }
|
|
490
|
+
.file-item .fname { color: var(--accent); font-weight: 600; font-size:12px; }
|
|
491
|
+
.file-item .fmeta { color: var(--text-dim); font-size: 10px; margin-top: 3px; }
|
|
492
|
+
.file-preview {
|
|
493
|
+
position: fixed; top: 50%; left: 50%;
|
|
494
|
+
transform: translate(-50%, -50%);
|
|
495
|
+
width: min(800px, 90vw);
|
|
496
|
+
max-height: 80vh;
|
|
497
|
+
background: var(--bg-panel);
|
|
498
|
+
border: 1px solid var(--border-accent);
|
|
499
|
+
border-radius: var(--radius-lg);
|
|
500
|
+
z-index: 200;
|
|
501
|
+
display: none; flex-direction: column;
|
|
502
|
+
box-shadow: var(--shadow-lg);
|
|
503
|
+
}
|
|
504
|
+
.file-preview.visible { display: flex; }
|
|
505
|
+
.file-preview .fp-header {
|
|
506
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
507
|
+
padding: 12px 16px;
|
|
508
|
+
border-bottom: 1px solid var(--border);
|
|
509
|
+
}
|
|
510
|
+
.file-preview .fp-header h4 { font-size: 13px; color: var(--accent); font-weight: 600; }
|
|
511
|
+
.file-preview .fp-close {
|
|
512
|
+
background: none; border: none; color: var(--text-dim);
|
|
513
|
+
font-size: 20px; cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm);
|
|
514
|
+
transition: all var(--transition);
|
|
515
|
+
}
|
|
516
|
+
.file-preview .fp-close:hover { background: var(--bg-hover); color: var(--text); }
|
|
517
|
+
.file-preview pre {
|
|
518
|
+
flex:1; overflow:auto; padding:16px; margin:0;
|
|
519
|
+
font-size: 12px; line-height: 1.6;
|
|
520
|
+
font-family: "Cascadia Code", "Fira Code", "JetBrains Mono", Consolas, monospace;
|
|
521
|
+
color: var(--text); background: var(--bg);
|
|
522
|
+
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
|
523
|
+
}
|
|
524
|
+
.log-list { flex:1; overflow-y:auto; padding:6px 10px; font-size:11px; line-height:1.7; }
|
|
525
|
+
.log-list .log-entry {
|
|
526
|
+
padding: 4px 0;
|
|
527
|
+
border-bottom: 1px solid var(--border);
|
|
528
|
+
word-break: break-word;
|
|
529
|
+
font-family: "Cascadia Code", "Fira Code", Consolas, monospace;
|
|
530
|
+
font-size: 10px;
|
|
531
|
+
}
|
|
532
|
+
.log-list .log-entry .timestamp { color: var(--text-subtle); margin-right: 6px; }
|
|
533
|
+
.log-list .log-entry.warn { color: var(--orange); }
|
|
534
|
+
.log-list .log-entry.error { color: var(--red); }
|
|
535
|
+
.log-list .log-entry.info { color: var(--text-dim); }
|
|
536
|
+
|
|
537
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
538
|
+
Bottom: Croc Office (Agent Desks)
|
|
539
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
540
|
+
.office {
|
|
541
|
+
grid-area: office;
|
|
542
|
+
background: var(--bg-panel);
|
|
543
|
+
border: 1px solid var(--border);
|
|
544
|
+
border-radius: var(--radius-lg);
|
|
545
|
+
display: flex;
|
|
546
|
+
overflow-x: auto;
|
|
547
|
+
padding: 10px;
|
|
548
|
+
gap: 8px;
|
|
549
|
+
box-shadow: var(--shadow-sm);
|
|
550
|
+
}
|
|
551
|
+
.desk {
|
|
552
|
+
flex: 0 0 180px;
|
|
553
|
+
background: var(--bg-card);
|
|
554
|
+
border: 1px solid var(--border);
|
|
555
|
+
border-radius: var(--radius-md);
|
|
556
|
+
padding: 12px 10px;
|
|
557
|
+
display: flex;
|
|
558
|
+
flex-direction: column;
|
|
559
|
+
align-items: center;
|
|
560
|
+
gap: 4px;
|
|
561
|
+
position: relative;
|
|
562
|
+
overflow: hidden;
|
|
563
|
+
transition: all var(--transition);
|
|
564
|
+
box-shadow: var(--shadow-sm);
|
|
565
|
+
}
|
|
566
|
+
.desk:hover { border-color: var(--border-accent); transform: translateY(-2px); box-shadow: var(--shadow-md); }
|
|
567
|
+
.desk .badge { position:absolute; top:8px; right:8px; width:8px; height:8px; border-radius:50%; }
|
|
568
|
+
|
|
569
|
+
/* SVG Croc sprites replace emoji */
|
|
570
|
+
.desk .croc-sprite {
|
|
571
|
+
width: 48px; height: 48px;
|
|
572
|
+
position: relative; z-index: 1;
|
|
573
|
+
display: grid; place-items: center;
|
|
574
|
+
}
|
|
575
|
+
.desk .croc-sprite svg { width:44px; height:44px; }
|
|
576
|
+
.desk.idle .croc-sprite { animation: croc-idle 3s ease-in-out infinite; }
|
|
577
|
+
.desk.working .croc-sprite, .desk.testing .croc-sprite { animation: croc-work .5s ease-in-out infinite alternate; }
|
|
578
|
+
.desk.thinking .croc-sprite { animation: croc-think 1.2s ease-in-out infinite; }
|
|
579
|
+
.desk.done .croc-sprite, .desk.passed .croc-sprite { animation: croc-done .8s ease-out 1; }
|
|
580
|
+
.desk.error .croc-sprite, .desk.failed .croc-sprite { animation: croc-error .3s ease-in-out 3; }
|
|
90
581
|
@keyframes croc-idle { 0%,90%,100%{transform:translateY(0)} 95%{transform:translateY(-3px)} }
|
|
91
|
-
@keyframes croc-work { from{transform:translateY(0) rotate(-
|
|
92
|
-
@keyframes croc-think { 0%,100%{transform:scale(1) rotate(0)} 25%{transform:scale(1.
|
|
93
|
-
@keyframes croc-done { 0%{transform:scale(1)} 50%{transform:scale(1.
|
|
94
|
-
@keyframes croc-error { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-
|
|
95
|
-
.desk .croc-name { font-size:
|
|
96
|
-
.desk .croc-role {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.desk .
|
|
582
|
+
@keyframes croc-work { from{transform:translateY(0) rotate(-2deg)} to{transform:translateY(-5px) rotate(2deg)} }
|
|
583
|
+
@keyframes croc-think { 0%,100%{transform:scale(1) rotate(0)} 25%{transform:scale(1.04) rotate(-2deg)} 75%{transform:scale(1.04) rotate(2deg)} }
|
|
584
|
+
@keyframes croc-done { 0%{transform:scale(1)} 50%{transform:scale(1.15) translateY(-6px)} 100%{transform:scale(1)} }
|
|
585
|
+
@keyframes croc-error { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-3px)} 75%{transform:translateX(3px)} }
|
|
586
|
+
.desk .croc-name { font-size: 12px; font-weight: 600; color: var(--accent); }
|
|
587
|
+
.desk .croc-role {
|
|
588
|
+
font-size: 9px; color: var(--text-subtle); text-transform: uppercase;
|
|
589
|
+
letter-spacing: .5px; font-weight: 500;
|
|
590
|
+
}
|
|
591
|
+
.desk .croc-task {
|
|
592
|
+
font-size: 10px; color: var(--orange); text-align: center;
|
|
593
|
+
max-width: 160px; overflow: hidden; text-overflow: ellipsis;
|
|
594
|
+
white-space: nowrap; min-height: 14px;
|
|
595
|
+
}
|
|
596
|
+
.desk .progress-bar {
|
|
597
|
+
width: 90%; height: 3px; background: var(--bg-hover);
|
|
598
|
+
border-radius: 2px; margin-top: 3px; overflow: hidden;
|
|
599
|
+
}
|
|
600
|
+
.desk .progress-bar .fill {
|
|
601
|
+
height: 100%; background: linear-gradient(90deg, var(--accent-dim), var(--accent));
|
|
602
|
+
transition: width .4s cubic-bezier(.4,0,.2,1); border-radius: 2px;
|
|
603
|
+
}
|
|
604
|
+
.desk .desk-icon {
|
|
605
|
+
position: absolute; bottom: 6px; right: 8px; opacity: .25;
|
|
606
|
+
}
|
|
607
|
+
.desk .desk-icon svg { width: 16px; height: 16px; }
|
|
608
|
+
|
|
609
|
+
/* Loading skeleton */
|
|
610
|
+
.skeleton { background: linear-gradient(90deg, var(--bg-card) 25%, var(--bg-hover) 50%, var(--bg-card) 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius-sm); }
|
|
611
|
+
@keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
|
|
612
|
+
|
|
613
|
+
/* Typewriter cursor */
|
|
614
|
+
.typewriter-cursor {
|
|
615
|
+
display:inline-block; width:1px; height:1em; background:var(--accent);
|
|
616
|
+
margin-left:2px; vertical-align:text-bottom;
|
|
617
|
+
animation: cursor-blink .6s steps(1) infinite;
|
|
618
|
+
}
|
|
619
|
+
@keyframes cursor-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
620
|
+
|
|
621
|
+
/* Keyboard shortcuts legend */
|
|
622
|
+
.shortcut-legend {
|
|
623
|
+
position:fixed; bottom:12px; right:12px; z-index:90;
|
|
624
|
+
background:var(--bg-panel); border:1px solid var(--border);
|
|
625
|
+
border-radius:var(--radius-md); padding:10px 14px;
|
|
626
|
+
font-size:10px; color:var(--text-dim);
|
|
627
|
+
box-shadow:var(--shadow-md); opacity:0; pointer-events:none;
|
|
628
|
+
transition:opacity .2s;
|
|
629
|
+
display:grid; grid-template-columns:auto auto; gap:3px 12px;
|
|
630
|
+
}
|
|
631
|
+
.shortcut-legend.visible { opacity:1; pointer-events:auto; }
|
|
632
|
+
.shortcut-legend kbd {
|
|
633
|
+
display:inline-block; min-width:18px; text-align:center;
|
|
634
|
+
background:var(--bg-card); border:1px solid var(--border);
|
|
635
|
+
border-radius:3px; padding:1px 5px; font-family:inherit;
|
|
636
|
+
font-size:9px; font-weight:600; color:var(--text);
|
|
637
|
+
}
|
|
101
638
|
</style>
|
|
102
639
|
</head>
|
|
103
640
|
<body>
|
|
104
641
|
<div class="app">
|
|
105
642
|
<header class="header">
|
|
106
|
-
<div class="logo"
|
|
107
|
-
|
|
643
|
+
<div class="logo">
|
|
644
|
+
<svg viewBox="0 0 16 16" fill="none"><rect x="4" y="2" width="8" height="3" rx="1" fill="var(--accent)"/><rect x="3" y="5" width="10" height="6" rx="2" fill="var(--accent)" opacity=".8"/><rect x="5" y="11" width="2" height="2" fill="var(--accent)" opacity=".6"/><rect x="9" y="11" width="2" height="2" fill="var(--accent)" opacity=".6"/><rect x="5" y="6" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="9" y="6" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="6" y="9" width="4" height="1" fill="var(--bg)" opacity=".5"/></svg>
|
|
645
|
+
</div>
|
|
646
|
+
<div class="title-wrap">
|
|
647
|
+
<h1>OpenCroc Studio</h1>
|
|
648
|
+
<div class="subtitle">Pixel Ops Dashboard ยท Real-time Multi-Agent Runtime</div>
|
|
649
|
+
</div>
|
|
108
650
|
<div class="actions">
|
|
109
|
-
<
|
|
110
|
-
|
|
651
|
+
<div class="view-switch">
|
|
652
|
+
<button id="view-dashboard" class="active">
|
|
653
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="3" rx="1"/><rect x="9" y="6" width="6" height="6" rx="1" opacity=".6"/><rect x="1" y="9" width="6" height="3" rx="1" opacity=".6"/></svg>
|
|
654
|
+
Dashboard
|
|
655
|
+
</button>
|
|
656
|
+
<button id="view-office">
|
|
657
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="2" y="8" width="12" height="6" rx="1"/><rect x="5" y="3" width="6" height="5" rx="1" opacity=".6"/><rect x="4" y="14" width="2" height="1"/><rect x="10" y="14" width="2" height="1"/></svg>
|
|
658
|
+
Pixel Office
|
|
659
|
+
</button>
|
|
660
|
+
</div>
|
|
661
|
+
<button class="btn" id="btn-scan" title="Scan project">
|
|
662
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="4"/><line x1="10" y1="10" x2="14" y2="14"/></svg>
|
|
663
|
+
Scan
|
|
664
|
+
</button>
|
|
665
|
+
<button class="btn" id="btn-pipeline" title="Run full pipeline">
|
|
666
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><polygon points="4,2 14,8 4,14"/></svg>
|
|
667
|
+
Pipeline
|
|
668
|
+
</button>
|
|
111
669
|
<select id="run-mode" class="mode-select" title="Test run mode">
|
|
112
670
|
<option value="auto">Auto</option>
|
|
113
671
|
<option value="reuse">Reuse</option>
|
|
114
672
|
<option value="managed">Managed</option>
|
|
115
673
|
</select>
|
|
116
|
-
<button class="btn" id="btn-run-tests" title="Run generated tests" disabled
|
|
117
|
-
|
|
118
|
-
|
|
674
|
+
<button class="btn" id="btn-run-tests" title="Run generated tests" disabled>
|
|
675
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 8l3 3 7-7"/></svg>
|
|
676
|
+
Tests
|
|
677
|
+
</button>
|
|
678
|
+
<button class="btn" id="btn-reports" title="Generate reports" disabled>
|
|
679
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1" width="12" height="14" rx="2"/><line x1="5" y1="5" x2="11" y2="5"/><line x1="5" y1="8" x2="11" y2="8"/><line x1="5" y1="11" x2="9" y2="11"/></svg>
|
|
680
|
+
Reports
|
|
681
|
+
</button>
|
|
682
|
+
<button class="btn danger" id="btn-reset" title="Reset agents">
|
|
683
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="3" y="3" width="10" height="10" rx="2"/></svg>
|
|
684
|
+
Reset
|
|
685
|
+
</button>
|
|
119
686
|
</div>
|
|
687
|
+
<div class="spacer"></div>
|
|
120
688
|
<div class="stats">
|
|
121
|
-
<div>Modules
|
|
122
|
-
<div>Models
|
|
123
|
-
<div>APIs
|
|
124
|
-
<div>Tests
|
|
125
|
-
<div id="s-results-wrap" style="display:none">Results
|
|
689
|
+
<div><div class="label">Modules</div><span class="value" id="s-mod">-</span></div>
|
|
690
|
+
<div><div class="label">Models</div><span class="value" id="s-mdl">-</span></div>
|
|
691
|
+
<div><div class="label">APIs</div><span class="value" id="s-api">-</span></div>
|
|
692
|
+
<div><div class="label">Tests</div><span class="value" id="s-files">-</span></div>
|
|
693
|
+
<div id="s-results-wrap" style="display:none"><div class="label">Results</div><span class="value" id="s-results">-</span></div>
|
|
126
694
|
</div>
|
|
695
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme">
|
|
696
|
+
<svg id="theme-icon-dark" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 1 0 0 14A5 5 0 0 1 8 1z"/></svg>
|
|
697
|
+
<svg id="theme-icon-light" viewBox="0 0 16 16" fill="currentColor" style="display:none"><circle cx="8" cy="8" r="3"/><g stroke="currentColor" stroke-width="1.2"><line x1="8" y1="1" x2="8" y2="3"/><line x1="8" y1="13" x2="8" y2="15"/><line x1="1" y1="8" x2="3" y2="8"/><line x1="13" y1="8" x2="15" y2="8"/><line x1="3.05" y1="3.05" x2="4.46" y2="4.46"/><line x1="11.54" y1="11.54" x2="12.95" y2="12.95"/><line x1="3.05" y1="12.95" x2="4.46" y2="11.54"/><line x1="11.54" y1="4.46" x2="12.95" y2="3.05"/></g></svg>
|
|
698
|
+
</button>
|
|
127
699
|
<div class="conn-dot" id="conn-dot" title="WebSocket"></div>
|
|
128
700
|
</header>
|
|
129
701
|
|
|
130
702
|
<aside class="sidebar">
|
|
131
|
-
<h3
|
|
703
|
+
<h3>
|
|
704
|
+
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1 2h14v2H1zm0 4h10v2H1zm0 4h12v2H1zm0 4h8v2H1z"/></svg>
|
|
705
|
+
Modules
|
|
706
|
+
</h3>
|
|
132
707
|
<div id="mod-list"></div>
|
|
133
|
-
<h3 style="margin-top:12px"
|
|
708
|
+
<h3 style="margin-top:12px">
|
|
709
|
+
<svg viewBox="0 0 16 16" fill="var(--accent)"><rect x="4" y="2" width="8" height="3" rx="1"/><rect x="3" y="5" width="10" height="6" rx="2" opacity=".8"/><rect x="5" y="11" width="2" height="2" opacity=".6"/><rect x="9" y="11" width="2" height="2" opacity=".6"/></svg>
|
|
710
|
+
Agents
|
|
711
|
+
</h3>
|
|
134
712
|
<div id="agent-sidebar"></div>
|
|
135
713
|
</aside>
|
|
136
714
|
|
|
137
715
|
<main class="main">
|
|
138
|
-
<
|
|
716
|
+
<div class="view" id="graph-view">
|
|
717
|
+
<canvas id="graph-canvas"></canvas>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="view hidden" id="pixel-view">
|
|
720
|
+
<div class="pixel-stage">
|
|
721
|
+
<img class="asset desk-asset" src="./assets/star/desk-v3.webp" alt="desk">
|
|
722
|
+
<img class="asset server-asset" src="./assets/botreview/server.gif" alt="server">
|
|
723
|
+
<img class="asset coffee-asset" src="./assets/botreview/coffee-machine.gif" alt="coffee">
|
|
724
|
+
<img class="asset walls-asset" src="./assets/botreview/walls.png" alt="walls">
|
|
725
|
+
<div class="pixel-kpis">
|
|
726
|
+
<div class="pixel-kpi"><div class="t">Working</div><div class="v" id="kpi-working">0</div></div>
|
|
727
|
+
<div class="pixel-kpi errors"><div class="t">Errors</div><div class="v" id="kpi-errors">0</div></div>
|
|
728
|
+
<div class="pixel-kpi done"><div class="t">Done</div><div class="v" id="kpi-done">0</div></div>
|
|
729
|
+
</div>
|
|
730
|
+
<div class="pixel-agent-layer" id="pixel-agent-layer"></div>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
139
733
|
<div class="tooltip" id="tooltip"></div>
|
|
140
734
|
</main>
|
|
141
735
|
|
|
142
736
|
<div class="log-panel">
|
|
143
737
|
<div class="panel-tabs">
|
|
144
|
-
<button class="tab active" data-tab="log"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
738
|
+
<button class="tab active" data-tab="log">
|
|
739
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="1" y="2" width="14" height="12" rx="2" opacity=".2"/><line x1="4" y1="5" x2="12" y2="5" stroke="currentColor" stroke-width="1.2"/><line x1="4" y1="8" x2="10" y2="8" stroke="currentColor" stroke-width="1.2"/><line x1="4" y1="11" x2="8" y2="11" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
740
|
+
Log
|
|
741
|
+
</button>
|
|
742
|
+
<button class="tab" data-tab="files">
|
|
743
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M3 1h7l3 3v11H3V1z" opacity=".3"/><path d="M10 1v3h3" fill="none" stroke="currentColor" stroke-width="1"/></svg>
|
|
744
|
+
Tests <span id="file-badge" class="tab-badge" style="display:none">0</span>
|
|
745
|
+
</button>
|
|
746
|
+
<button class="tab" data-tab="results">
|
|
747
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M3 8l3 3 7-7"/></svg>
|
|
748
|
+
Results <span id="result-badge" class="tab-badge" style="display:none">0</span>
|
|
749
|
+
</button>
|
|
750
|
+
<button class="tab" data-tab="reports">
|
|
751
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" opacity=".7"><rect x="1" y="3" width="5" height="10" rx="1"/><rect x="7" y="1" width="8" height="14" rx="1"/></svg>
|
|
752
|
+
Reports
|
|
753
|
+
</button>
|
|
148
754
|
</div>
|
|
149
755
|
<div class="log-list" id="log-list"></div>
|
|
150
756
|
<div class="file-list" id="file-list" style="display:none"></div>
|
|
@@ -160,14 +766,144 @@
|
|
|
160
766
|
<pre id="fp-code"></pre>
|
|
161
767
|
</div>
|
|
162
768
|
|
|
769
|
+
<!-- Keyboard shortcut legend -->
|
|
770
|
+
<div class="shortcut-legend" id="shortcut-legend">
|
|
771
|
+
<kbd>1</kbd><span>Dashboard</span>
|
|
772
|
+
<kbd>2</kbd><span>Pixel Office</span>
|
|
773
|
+
<kbd>S</kbd><span>Scan</span>
|
|
774
|
+
<kbd>P</kbd><span>Pipeline</span>
|
|
775
|
+
<kbd>T</kbd><span>Run Tests</span>
|
|
776
|
+
<kbd>R</kbd><span>Reports</span>
|
|
777
|
+
<kbd>X</kbd><span>Reset</span>
|
|
778
|
+
<kbd>D</kbd><span>Dark/Light</span>
|
|
779
|
+
<kbd>?</kbd><span>Show shortcuts</span>
|
|
780
|
+
<kbd>Esc</kbd><span>Close panel</span>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
163
783
|
<script>
|
|
784
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
785
|
+
SVG Icon Library (replaces emoji) โ inspired by OpenClaw PixelSvg
|
|
786
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
787
|
+
const ICONS = {
|
|
788
|
+
croc: `<svg viewBox="0 0 16 16" fill="none"><rect x="4" y="1" width="8" height="3" rx="1" fill="var(--accent)"/><rect x="3" y="4" width="10" height="7" rx="2" fill="var(--accent)" opacity=".85"/><rect x="5" y="5" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="9" y="5" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="6" y="8" width="4" height="1" fill="var(--bg)" opacity=".4"/><rect x="5" y="11" width="2" height="3" rx="1" fill="var(--accent)" opacity=".6"/><rect x="9" y="11" width="2" height="3" rx="1" fill="var(--accent)" opacity=".6"/></svg>`,
|
|
789
|
+
parser: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.3"><rect x="2" y="2" width="12" height="12" rx="2"/><line x1="5" y1="6" x2="11" y2="6"/><line x1="5" y1="9" x2="9" y2="9"/></svg>`,
|
|
790
|
+
analyzer: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--purple)" stroke-width="1.3"><rect x="1" y="11" width="3" height="4"/><rect x="5" y="7" width="3" height="8"/><rect x="9" y="3" width="3" height="12"/><rect x="13" y="1" width="2" height="14"/></svg>`,
|
|
791
|
+
tester: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.3"><path d="M6 2v4l-3 8h10l-3-8V2"/><line x1="4" y1="2" x2="12" y2="2"/></svg>`,
|
|
792
|
+
healer: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--orange)" stroke-width="1.5"><path d="M5 3l-2 5h4l-2 5 7-7h-4l2-3z"/></svg>`,
|
|
793
|
+
planner: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.3"><rect x="2" y="1" width="12" height="14" rx="2"/><line x1="5" y1="5" x2="11" y2="5"/><line x1="5" y1="8" x2="11" y2="8"/><line x1="5" y1="11" x2="8" y2="11"/></svg>`,
|
|
794
|
+
reporter: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.3"><rect x="1" y="2" width="14" height="12" rx="2"/><polyline points="4,10 6,6 8,9 10,4 12,7"/></svg>`,
|
|
795
|
+
};
|
|
796
|
+
const ROLE_ICONS = { parser:ICONS.parser, analyzer:ICONS.analyzer, tester:ICONS.tester, healer:ICONS.healer, planner:ICONS.planner, reporter:ICONS.reporter };
|
|
797
|
+
|
|
798
|
+
/* Bubble messages (Star-Office style) */
|
|
799
|
+
const BUBBLE_TEXTS = {
|
|
800
|
+
working: ['ๆญฃๅจๆง่ก...', 'ๅฟซไบๅฟซไบ', 'ๅค็ไธญ...', 'ๅ ๆฒน ๐ช'],
|
|
801
|
+
testing: ['่ทๆต่ฏไธญ...', '้ช่ฏ API...', '็ญ็ปๆ...'],
|
|
802
|
+
thinking: ['่ฎฉๆๆณๆณ...', 'ๅๆไธญ...', 'ๆจ็...', '๐ค'],
|
|
803
|
+
error: ['ๅบ้ไบ!', 'ไฟฎๅคไธญ...', '็ณ็ณ...', 'ๆๆฅ้ฎ้ข...'],
|
|
804
|
+
idle: ['ๆธ้ฑผไธญ~', '็ญไปปๅก...', 'โ ๅๅๅก', 'zzZ'],
|
|
805
|
+
done: ['ๆๅฎ!', 'ๅฎๆ โ', 'ไธไธไธช!'],
|
|
806
|
+
passed: ['ๅ
จ็ปฟ โ', 'ๆต่ฏ้่ฟ!'],
|
|
807
|
+
failed: ['ๆๅคฑ่ดฅ...', '้่ฆไฟฎๅค'],
|
|
808
|
+
};
|
|
809
|
+
|
|
164
810
|
const S = {
|
|
165
811
|
project:null, graph:{nodes:[],edges:[]}, agents:[], ws:null,
|
|
166
812
|
pan:{x:0,y:0}, zoom:1, dragging:false, dragStart:{x:0,y:0},
|
|
167
813
|
nodePos:new Map(), hoveredNode:null, running:false, _userPanned:false,
|
|
168
|
-
generatedFiles:[], testMetrics:null, testQuality:null, reports:[], runMode:'auto'
|
|
814
|
+
generatedFiles:[], testMetrics:null, testQuality:null, reports:[], runMode:'auto',
|
|
815
|
+
currentView:'dashboard', theme:'dark', bubbleTimers:new Map(),
|
|
816
|
+
spriteSheets:new Map(), spriteAnimFrames:new Map(), spriteAnimTimers:new Map()
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
820
|
+
Spritesheet Animation Engine (inspired by Star-Office Phaser)
|
|
821
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
822
|
+
const SPRITE_CONFIG = {
|
|
823
|
+
// Each character sprite: image src, frame grid, frame count, FPS per status
|
|
824
|
+
chars: [
|
|
825
|
+
{ key:'char_0', src:'./assets/botreview/char_0.png', cols:1, rows:1, total:1 },
|
|
826
|
+
{ key:'char_1', src:'./assets/botreview/char_1.png', cols:1, rows:1, total:1 },
|
|
827
|
+
{ key:'char_2', src:'./assets/botreview/char_2.png', cols:1, rows:1, total:1 },
|
|
828
|
+
],
|
|
829
|
+
// FPS by status โ active states get faster animation
|
|
830
|
+
fps: { idle:2, working:8, testing:8, thinking:4, error:10, failed:10, done:4, passed:4 },
|
|
169
831
|
};
|
|
170
832
|
|
|
833
|
+
// Preload sprite images
|
|
834
|
+
function preloadSprites(){
|
|
835
|
+
SPRITE_CONFIG.chars.forEach(cfg=>{
|
|
836
|
+
if(S.spriteSheets.has(cfg.key)) return;
|
|
837
|
+
const img=new Image(); img.src=cfg.src;
|
|
838
|
+
img.onload=()=>{ S.spriteSheets.set(cfg.key, {img,cols:cfg.cols,rows:cfg.rows,total:cfg.total}); };
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Render a single frame of a sprite to a canvas element
|
|
843
|
+
function renderSpriteFrame(canvas, spriteKey, frameIdx){
|
|
844
|
+
const sheet=S.spriteSheets.get(spriteKey);
|
|
845
|
+
if(!sheet||!canvas) return;
|
|
846
|
+
const ctx=canvas.getContext('2d');
|
|
847
|
+
const fw=sheet.img.naturalWidth/sheet.cols;
|
|
848
|
+
const fh=sheet.img.naturalHeight/sheet.rows;
|
|
849
|
+
const col=frameIdx%sheet.cols;
|
|
850
|
+
const row=Math.floor(frameIdx/sheet.cols);
|
|
851
|
+
ctx.clearRect(0,0,canvas.width,canvas.height);
|
|
852
|
+
ctx.imageSmoothingEnabled=false; // pixel-art crisp
|
|
853
|
+
ctx.drawImage(sheet.img, col*fw,row*fh,fw,fh, 0,0,canvas.width,canvas.height);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Start/manage sprite animation loop for an agent
|
|
857
|
+
function startSpriteAnim(canvasId, spriteKey, status){
|
|
858
|
+
// Stop existing timer
|
|
859
|
+
if(S.spriteAnimTimers.has(canvasId)){
|
|
860
|
+
clearInterval(S.spriteAnimTimers.get(canvasId));
|
|
861
|
+
}
|
|
862
|
+
const sheet=S.spriteSheets.get(spriteKey);
|
|
863
|
+
if(!sheet) return;
|
|
864
|
+
const fps=SPRITE_CONFIG.fps[status]||SPRITE_CONFIG.fps.idle;
|
|
865
|
+
let frame=S.spriteAnimFrames.get(canvasId)||0;
|
|
866
|
+
const canvas=document.getElementById(canvasId);
|
|
867
|
+
if(!canvas) return;
|
|
868
|
+
renderSpriteFrame(canvas, spriteKey, frame);
|
|
869
|
+
if(sheet.total<=1){ S.spriteAnimTimers.delete(canvasId); return; }
|
|
870
|
+
const timer=setInterval(()=>{
|
|
871
|
+
frame=(frame+1)%sheet.total;
|
|
872
|
+
S.spriteAnimFrames.set(canvasId, frame);
|
|
873
|
+
const c=document.getElementById(canvasId);
|
|
874
|
+
if(c) renderSpriteFrame(c, spriteKey, frame);
|
|
875
|
+
else { clearInterval(timer); S.spriteAnimTimers.delete(canvasId); }
|
|
876
|
+
}, 1000/fps);
|
|
877
|
+
S.spriteAnimTimers.set(canvasId, timer);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
881
|
+
Typewriter Effect (inspired by Star-Office)
|
|
882
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
883
|
+
function typewriterLog(msg, level, charDelay){
|
|
884
|
+
level=level||'info';
|
|
885
|
+
charDelay=charDelay||35;
|
|
886
|
+
const el=document.getElementById('log-list');
|
|
887
|
+
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
888
|
+
const ts=document.createElement('span'); ts.className='timestamp';
|
|
889
|
+
ts.textContent=new Date().toLocaleTimeString();
|
|
890
|
+
d.appendChild(ts);
|
|
891
|
+
const textSpan=document.createElement('span');
|
|
892
|
+
const cursor=document.createElement('span'); cursor.className='typewriter-cursor';
|
|
893
|
+
d.appendChild(textSpan); d.appendChild(cursor);
|
|
894
|
+
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
895
|
+
let i=0;
|
|
896
|
+
const interval=setInterval(()=>{
|
|
897
|
+
if(i<msg.length){
|
|
898
|
+
textSpan.textContent+=msg[i]; i++;
|
|
899
|
+
el.scrollTop=el.scrollHeight;
|
|
900
|
+
}else{
|
|
901
|
+
clearInterval(interval); cursor.remove();
|
|
902
|
+
}
|
|
903
|
+
}, charDelay);
|
|
904
|
+
return d;
|
|
905
|
+
}
|
|
906
|
+
|
|
171
907
|
async function fetchProject(){
|
|
172
908
|
try{
|
|
173
909
|
const r=await fetch('/api/project'); S.project=await r.json();
|
|
@@ -177,23 +913,23 @@ async function fetchProject(){
|
|
|
177
913
|
}
|
|
178
914
|
async function doScan(){
|
|
179
915
|
if(S.running)return; S.running=true; updateBtns();
|
|
180
|
-
addLog('๐ Starting scan...');
|
|
916
|
+
addLog('๐ Starting scan...','info',true);
|
|
181
917
|
try{await fetch('/api/scan',{method:'POST'});}
|
|
182
918
|
catch(e){addLog('Scan failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
183
919
|
}
|
|
184
920
|
async function doPipeline(){
|
|
185
921
|
if(S.running)return; S.running=true; updateBtns();
|
|
186
|
-
addLog('โถ Starting pipeline...');
|
|
922
|
+
addLog('โถ Starting pipeline...','info',true);
|
|
187
923
|
try{await fetch('/api/pipeline',{method:'POST'});}
|
|
188
924
|
catch(e){addLog('Pipeline failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
189
925
|
}
|
|
190
926
|
async function doReset(){
|
|
191
927
|
try{await fetch('/api/reset',{method:'POST'});}catch(e){addLog('Reset failed','error');}
|
|
192
|
-
S.running=false; updateBtns(); addLog('โน Agents reset');
|
|
928
|
+
S.running=false; updateBtns(); addLog('โน Agents reset','info',true);
|
|
193
929
|
}
|
|
194
930
|
async function doRunTests(){
|
|
195
931
|
if(S.running)return; S.running=true; updateBtns();
|
|
196
|
-
addLog('๐งช Starting test execution ('+S.runMode+')...');
|
|
932
|
+
addLog('๐งช Starting test execution ('+S.runMode+')...','info',true);
|
|
197
933
|
try{
|
|
198
934
|
const r=await fetch('/api/run-tests',{
|
|
199
935
|
method:'POST',
|
|
@@ -209,7 +945,7 @@ async function doRunTests(){
|
|
|
209
945
|
}
|
|
210
946
|
async function doReports(){
|
|
211
947
|
if(S.running)return; S.running=true; updateBtns();
|
|
212
|
-
addLog('๐ Generating reports...');
|
|
948
|
+
addLog('๐ Generating reports...','info',true);
|
|
213
949
|
try{await fetch('/api/reports/generate',{method:'POST'});}
|
|
214
950
|
catch(e){addLog('Report gen failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
215
951
|
}
|
|
@@ -229,7 +965,7 @@ function connectWS(){
|
|
|
229
965
|
try{
|
|
230
966
|
const m=JSON.parse(e.data);
|
|
231
967
|
if(m.type==='agent:update'&&Array.isArray(m.payload)){
|
|
232
|
-
S.agents=m.payload; renderOffice(); renderAgentSB();
|
|
968
|
+
S.agents=m.payload; renderOffice(); renderAgentSB(); renderPixelOffice();
|
|
233
969
|
}else if(m.type==='graph:update'){
|
|
234
970
|
S.graph=m.payload; layoutGraph(); renderCanvas(); renderModList(); updateStats();
|
|
235
971
|
}else if(m.type==='log'){
|
|
@@ -242,8 +978,8 @@ function connectWS(){
|
|
|
242
978
|
badge.textContent=S.generatedFiles.length;badge.style.display='inline';
|
|
243
979
|
}else if(m.type==='pipeline:complete'){
|
|
244
980
|
S.running=false; updateBtns();
|
|
245
|
-
if(m.payload.status==='success') addLog('โ
Pipeline complete!');
|
|
246
|
-
else addLog('โ Pipeline failed: '+(m.payload.error||''),'error');
|
|
981
|
+
if(m.payload.status==='success') addLog('โ
Pipeline complete!','info',true);
|
|
982
|
+
else addLog('โ Pipeline failed: '+(m.payload.error||''),'error',true);
|
|
247
983
|
setTimeout(fetchProject,500);
|
|
248
984
|
}else if(m.type==='test:complete'){
|
|
249
985
|
S.running=false; S.testMetrics=m.payload.metrics||null; S.testQuality=m.payload.quality||null; updateBtns(); renderResults();
|
|
@@ -258,7 +994,7 @@ function connectWS(){
|
|
|
258
994
|
}
|
|
259
995
|
const rb=document.getElementById('result-badge');
|
|
260
996
|
rb.textContent=m.payload.total||0; rb.style.display='inline';
|
|
261
|
-
rb.
|
|
997
|
+
rb.className='tab-badge'+((met&&met.failed>0)?' alert':'');
|
|
262
998
|
}else if(m.type==='reports:generated'){
|
|
263
999
|
S.running=false; S.reports=m.payload||[]; updateBtns(); renderReports();
|
|
264
1000
|
addLog('๐ '+S.reports.length+' reports generated');
|
|
@@ -268,11 +1004,17 @@ function connectWS(){
|
|
|
268
1004
|
S.ws.onclose=()=>{document.getElementById('conn-dot').classList.remove('on');setTimeout(connectWS,3000);};
|
|
269
1005
|
}
|
|
270
1006
|
|
|
271
|
-
function addLog(msg,level){
|
|
1007
|
+
function addLog(msg,level,useTypewriter){
|
|
272
1008
|
level=level||'info';
|
|
1009
|
+
// Use typewriter for important messages (pipeline, scan, reset, test)
|
|
1010
|
+
if(useTypewriter){
|
|
1011
|
+
typewriterLog(msg,level,30);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
273
1014
|
const el=document.getElementById('log-list');
|
|
274
1015
|
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
275
|
-
|
|
1016
|
+
const ts=document.createElement('span'); ts.className='timestamp'; ts.textContent=new Date().toLocaleTimeString();
|
|
1017
|
+
d.appendChild(ts); d.appendChild(document.createTextNode(msg));
|
|
276
1018
|
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
277
1019
|
}
|
|
278
1020
|
function updateStats(){
|
|
@@ -364,79 +1106,81 @@ function renderCanvas(){
|
|
|
364
1106
|
canvas.width=canvas.clientWidth*dpr; canvas.height=canvas.clientHeight*dpr;
|
|
365
1107
|
ctx.scale(dpr,dpr);
|
|
366
1108
|
const w=canvas.clientWidth,h=canvas.clientHeight;
|
|
367
|
-
|
|
1109
|
+
const isDark=S.theme==='dark';
|
|
1110
|
+
ctx.clearRect(0,0,w,h);
|
|
1111
|
+
// Background fill
|
|
1112
|
+
ctx.fillStyle=isDark?'#0a0f1a':'#f8fafc';
|
|
1113
|
+
ctx.fillRect(0,0,w,h);
|
|
1114
|
+
ctx.save(); ctx.translate(S.pan.x,S.pan.y); ctx.scale(S.zoom,S.zoom);
|
|
368
1115
|
// Subtle grid
|
|
369
|
-
ctx.strokeStyle='
|
|
1116
|
+
ctx.strokeStyle=isDark?'rgba(148,163,184,.06)':'rgba(100,116,139,.08)';ctx.lineWidth=.5;
|
|
370
1117
|
const gridStep=40;
|
|
371
1118
|
for(let x=-2000;x<4000;x+=gridStep){ctx.beginPath();ctx.moveTo(x,-2000);ctx.lineTo(x,4000);ctx.stroke();}
|
|
372
1119
|
for(let y=-2000;y<4000;y+=gridStep){ctx.beginPath();ctx.moveTo(-2000,y);ctx.lineTo(4000,y);ctx.stroke();}
|
|
373
1120
|
|
|
374
1121
|
const edges=S.graph.edges||[],nodes=S.graph.nodes||[];
|
|
375
|
-
const largeGraph=nodes.length>80;
|
|
376
1122
|
|
|
377
1123
|
// Draw module cluster backgrounds
|
|
378
1124
|
if(S.modMeta){
|
|
379
1125
|
for(const[name,m] of S.modMeta){
|
|
380
1126
|
ctx.beginPath(); ctx.arc(m.cx,m.cy,m.radius,0,Math.PI*2);
|
|
381
|
-
ctx.fillStyle=m.color+'
|
|
382
|
-
ctx.strokeStyle=m.color+'
|
|
383
|
-
|
|
384
|
-
ctx.
|
|
385
|
-
ctx.fillText(name+' ('+m.count+')',m.cx,m.cy-m.radius-
|
|
1127
|
+
ctx.fillStyle=m.color+(isDark?'0a':'08'); ctx.fill();
|
|
1128
|
+
ctx.strokeStyle=m.color+(isDark?'25':'20'); ctx.lineWidth=1.5; ctx.setLineDash([6,4]); ctx.stroke(); ctx.setLineDash([]);
|
|
1129
|
+
ctx.font='600 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
1130
|
+
ctx.fillStyle=m.color+(isDark?'cc':'aa'); ctx.textAlign='center'; ctx.textBaseline='bottom';
|
|
1131
|
+
ctx.fillText(name+' ('+m.count+')',m.cx,m.cy-m.radius-8);
|
|
386
1132
|
}
|
|
387
1133
|
}
|
|
388
1134
|
|
|
389
|
-
// Draw
|
|
1135
|
+
// Draw edges with curved lines + gradient
|
|
390
1136
|
for(const e of edges){
|
|
391
|
-
if(e.relation==='contains') continue;
|
|
1137
|
+
if(e.relation==='contains') continue;
|
|
392
1138
|
const s=S.nodePos.get(e.source),t=S.nodePos.get(e.target);if(!s||!t)continue;
|
|
393
|
-
const dx=t.x-s.x,dy=t.y-s.y
|
|
394
|
-
// Curved edge via quadratic bezier, perpendicular offset
|
|
1139
|
+
const dx=t.x-s.x,dy=t.y-s.y;
|
|
395
1140
|
const mx=(s.x+t.x)/2+dy*0.15, my=(s.y+t.y)/2-dx*0.15;
|
|
396
1141
|
const grad=ctx.createLinearGradient(s.x,s.y,t.x,t.y);
|
|
397
|
-
grad.addColorStop(0,'rgba(
|
|
398
|
-
grad.addColorStop(1,'rgba(
|
|
399
|
-
ctx.strokeStyle=grad;ctx.lineWidth=
|
|
1142
|
+
grad.addColorStop(0,isDark?'rgba(248,113,113,.35)':'rgba(220,38,38,.25)');
|
|
1143
|
+
grad.addColorStop(1,isDark?'rgba(52,211,153,.35)':'rgba(5,150,105,.25)');
|
|
1144
|
+
ctx.strokeStyle=grad;ctx.lineWidth=1.5;
|
|
400
1145
|
ctx.beginPath();ctx.moveTo(s.x,s.y);ctx.quadraticCurveTo(mx,my,t.x,t.y);ctx.stroke();
|
|
401
|
-
// Arrowhead
|
|
402
1146
|
const t2=0.95,at2x=(1-t2)*(1-t2)*s.x+2*(1-t2)*t2*mx+t2*t2*t.x,at2y=(1-t2)*(1-t2)*s.y+2*(1-t2)*t2*my+t2*t2*t.y;
|
|
403
|
-
const a=Math.atan2(t.y-at2y,t.x-at2x),al=
|
|
404
|
-
ctx.fillStyle='rgba(
|
|
1147
|
+
const a=Math.atan2(t.y-at2y,t.x-at2x),al=7;
|
|
1148
|
+
ctx.fillStyle=isDark?'rgba(52,211,153,.5)':'rgba(5,150,105,.4)';ctx.beginPath();ctx.moveTo(t.x,t.y);
|
|
405
1149
|
ctx.lineTo(t.x-al*Math.cos(a-.4),t.y-al*Math.sin(a-.4));
|
|
406
1150
|
ctx.lineTo(t.x-al*Math.cos(a+.4),t.y-al*Math.sin(a+.4));ctx.closePath();ctx.fill();
|
|
407
1151
|
}
|
|
408
1152
|
|
|
409
1153
|
// Draw nodes
|
|
410
|
-
const tc={model:'#
|
|
411
|
-
const sc={idle:'#
|
|
1154
|
+
const tc={model:'#34d399',controller:'#f87171',api:'#fbbf24',dto:'#60a5fa',module:'#a78bfa'};
|
|
1155
|
+
const sc={idle:isDark?'#475569':'#94a3b8',testing:'#fbbf24',passed:'#34d399',failed:'#f87171'};
|
|
412
1156
|
for(const n of nodes){
|
|
413
|
-
if(n.type==='module') continue;
|
|
1157
|
+
if(n.type==='module') continue;
|
|
414
1158
|
const p=S.nodePos.get(n.id);if(!p)continue;
|
|
415
|
-
const sz=
|
|
416
|
-
|
|
417
|
-
if(n.status==='
|
|
418
|
-
else if(n.status==='
|
|
419
|
-
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=12;}
|
|
420
|
-
// Node circle instead of rect for cleaner look
|
|
1159
|
+
const sz=9,c=tc[n.type]||'#888',ol=sc[n.status]||sc.idle,hov=S.hoveredNode===n.id;
|
|
1160
|
+
if(n.status==='testing'){ctx.shadowColor=sc.testing;ctx.shadowBlur=12;}
|
|
1161
|
+
else if(n.status==='passed'){ctx.shadowColor=sc.passed;ctx.shadowBlur=8;}
|
|
1162
|
+
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=10;}
|
|
421
1163
|
ctx.beginPath();ctx.arc(p.x,p.y,sz,0,Math.PI*2);
|
|
422
1164
|
ctx.fillStyle=c;ctx.fill();
|
|
423
1165
|
ctx.shadowBlur=0;
|
|
424
|
-
ctx.strokeStyle=hov?'#fff':ol;ctx.lineWidth=hov?
|
|
425
|
-
//
|
|
426
|
-
ctx.font=(sz)+'px serif';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
427
|
-
ctx.fillText(n.type==='model'?'๐ฆ':'๐ฎ',p.x,p.y);
|
|
428
|
-
// Label โ show at higher zoom or on hover
|
|
1166
|
+
ctx.strokeStyle=hov?(isDark?'#fff':'#0f172a'):ol;ctx.lineWidth=hov?2.5:1.2;ctx.stroke();
|
|
1167
|
+
// Label
|
|
429
1168
|
if(S.zoom>0.4||hov){
|
|
430
|
-
ctx.font='9px "
|
|
431
|
-
ctx.
|
|
1169
|
+
ctx.font='500 9px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
1170
|
+
ctx.fillStyle=hov?(isDark?'#fff':'#0f172a'):(isDark?'#94a3b8':'#64748b');
|
|
1171
|
+
ctx.textAlign='center';ctx.textBaseline='top';
|
|
1172
|
+
ctx.fillText((n.label||n.id.split(':').pop()).substring(0,18),p.x,p.y+sz+4);
|
|
432
1173
|
}
|
|
433
1174
|
}
|
|
434
1175
|
ctx.restore();
|
|
435
1176
|
|
|
436
1177
|
// HUD legend
|
|
437
|
-
ctx.font='10px "
|
|
438
|
-
const
|
|
439
|
-
|
|
1178
|
+
ctx.font='500 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';ctx.textAlign='left';
|
|
1179
|
+
const legBg=isDark?'rgba(10,15,26,.8)':'rgba(255,255,255,.85)';
|
|
1180
|
+
ctx.fillStyle=legBg; ctx.beginPath(); ctx.roundRect(6,h-56,120,48,6); ctx.fill();
|
|
1181
|
+
ctx.strokeStyle=isDark?'rgba(148,163,184,.15)':'rgba(100,116,139,.2)';ctx.lineWidth=1;ctx.stroke();
|
|
1182
|
+
const leg=[['โ Model','#34d399'],['โ Controller','#f87171'],['โ uses',isDark?'rgba(200,150,160,.7)':'rgba(150,80,100,.6)']];
|
|
1183
|
+
for(let i=0;i<leg.length;i++){ctx.fillStyle=leg[i][1];ctx.fillText(leg[i][0],14,h-42+i*14);}
|
|
440
1184
|
}
|
|
441
1185
|
|
|
442
1186
|
function setupCanvas(){
|
|
@@ -467,12 +1211,11 @@ function updateAll(){
|
|
|
467
1211
|
document.getElementById('s-mdl').textContent=S.project.stats?.models||0;
|
|
468
1212
|
document.getElementById('s-api').textContent=S.project.stats?.endpoints||0;
|
|
469
1213
|
}
|
|
470
|
-
renderModList();renderOffice();renderAgentSB();renderCanvas();
|
|
1214
|
+
renderModList();renderOffice();renderAgentSB();renderPixelOffice();renderCanvas();
|
|
471
1215
|
}
|
|
472
1216
|
function renderModList(){
|
|
473
1217
|
const el=document.getElementById('mod-list'),mods=S.graph.nodes.filter(n=>n.type==='module');
|
|
474
|
-
if(!mods.length){el.innerHTML='<div style="padding:
|
|
475
|
-
// Sort by member count descending
|
|
1218
|
+
if(!mods.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No modules found</div>';return;}
|
|
476
1219
|
const sorted=mods.sort((a,b)=>{
|
|
477
1220
|
const ca=S.modMeta&&S.modMeta.get(a.label)?S.modMeta.get(a.label).count:0;
|
|
478
1221
|
const cb=S.modMeta&&S.modMeta.get(b.label)?S.modMeta.get(b.label).count:0;
|
|
@@ -481,10 +1224,10 @@ function renderModList(){
|
|
|
481
1224
|
el.innerHTML=sorted.map(m=>{
|
|
482
1225
|
const meta=S.modMeta&&S.modMeta.get(m.label);
|
|
483
1226
|
const cnt=meta?meta.count:'';
|
|
484
|
-
const col=meta?meta.color:'
|
|
485
|
-
return '<div class="mod-item" data-mod="'+esc(m.label)+'" style="border-left:3px solid '+col+'
|
|
1227
|
+
const col=meta?meta.color:'var(--text-subtle)';
|
|
1228
|
+
return '<div class="mod-item" data-mod="'+esc(m.label)+'" style="border-left:3px solid '+col+'">'
|
|
486
1229
|
+'<div class="dot '+m.status+'"></div>'
|
|
487
|
-
+esc(m.label)+'
|
|
1230
|
+
+esc(m.label)+'<span class="mod-count">'+cnt+'</span></div>';
|
|
488
1231
|
}).join('');
|
|
489
1232
|
// Click to navigate
|
|
490
1233
|
el.querySelectorAll('.mod-item').forEach(item=>{
|
|
@@ -503,21 +1246,116 @@ function renderModList(){
|
|
|
503
1246
|
}
|
|
504
1247
|
function renderAgentSB(){
|
|
505
1248
|
document.getElementById('agent-sidebar').innerHTML=S.agents.map(a=>
|
|
506
|
-
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+'
|
|
1249
|
+
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+'<span class="mod-count">'+a.status+'</span></div>'
|
|
507
1250
|
).join('');
|
|
508
1251
|
}
|
|
509
1252
|
function renderOffice(){
|
|
510
|
-
const dd={parser:'๐ป',analyzer:'๐',tester:'๐งช',healer:'๐ง',planner:'๐',reporter:'๐'};
|
|
511
1253
|
document.getElementById('croc-office').innerHTML=S.agents.map(a=>{
|
|
512
1254
|
const prog=typeof a.progress==='number'?a.progress:0;
|
|
1255
|
+
const roleIcon=ROLE_ICONS[a.role]||ICONS.croc;
|
|
513
1256
|
return '<div class="desk '+a.status+'"><div class="badge dot '+a.status+'"></div>'+
|
|
514
|
-
'<div class="croc-sprite"
|
|
1257
|
+
'<div class="croc-sprite">'+ICONS.croc+'</div><div class="croc-name">'+esc(a.name)+'</div>'+
|
|
515
1258
|
'<div class="croc-role">'+esc(a.role)+'</div>'+
|
|
516
1259
|
'<div class="croc-task">'+(a.currentTask?esc(a.currentTask):'')+'</div>'+
|
|
517
1260
|
'<div class="progress-bar"><div class="fill" style="width:'+prog+'%"></div></div>'+
|
|
518
|
-
'<div class="desk-
|
|
1261
|
+
'<div class="desk-icon">'+roleIcon+'</div></div>';
|
|
519
1262
|
}).join('');
|
|
520
1263
|
}
|
|
1264
|
+
function renderPixelOffice(){
|
|
1265
|
+
const el=document.getElementById('pixel-agent-layer');
|
|
1266
|
+
const stageWidth=(document.getElementById('pixel-view').clientWidth||900)-80;
|
|
1267
|
+
const stageHeight=(document.getElementById('pixel-view').clientHeight||520)-120;
|
|
1268
|
+
const presets=[
|
|
1269
|
+
{x:.18,y:.64},{x:.3,y:.66},{x:.42,y:.62},{x:.56,y:.6},{x:.72,y:.63},{x:.82,y:.52},
|
|
1270
|
+
{x:.22,y:.45},{x:.48,y:.42},{x:.66,y:.4},{x:.78,y:.72}
|
|
1271
|
+
];
|
|
1272
|
+
const roles=['char_0','char_1','char_2'];
|
|
1273
|
+
const working=S.agents.filter(a=>a.status==='working'||a.status==='testing').length;
|
|
1274
|
+
const errors=S.agents.filter(a=>a.status==='error'||a.status==='failed').length;
|
|
1275
|
+
const done=S.agents.filter(a=>a.status==='done'||a.status==='passed').length;
|
|
1276
|
+
document.getElementById('kpi-working').textContent=String(working);
|
|
1277
|
+
document.getElementById('kpi-errors').textContent=String(errors);
|
|
1278
|
+
document.getElementById('kpi-done').textContent=String(done);
|
|
1279
|
+
if(!S.agents.length){el.innerHTML='';return;}
|
|
1280
|
+
|
|
1281
|
+
// Stop all existing sprite timers
|
|
1282
|
+
for(const [id,timer] of S.spriteAnimTimers){ clearInterval(timer); }
|
|
1283
|
+
S.spriteAnimTimers.clear();
|
|
1284
|
+
|
|
1285
|
+
el.innerHTML=S.agents.map((a,i)=>{
|
|
1286
|
+
const p=presets[i%presets.length];
|
|
1287
|
+
const x=Math.max(16,Math.round(stageWidth*p.x));
|
|
1288
|
+
const y=Math.max(20,Math.round(stageHeight*p.y));
|
|
1289
|
+
const roleSprite=roles[i%roles.length];
|
|
1290
|
+
const labelY=Math.max(16,y-12);
|
|
1291
|
+
const statusClass=(['working','testing'].includes(a.status)?'working':
|
|
1292
|
+
['done','passed'].includes(a.status)?'done':
|
|
1293
|
+
['error','failed'].includes(a.status)?'error':
|
|
1294
|
+
a.status==='thinking'?'thinking':'');
|
|
1295
|
+
const canvasId='sprite-'+i;
|
|
1296
|
+
// Use canvas element for spritesheet-based rendering
|
|
1297
|
+
return '<canvas id="'+canvasId+'" class="pixel-sprite-canvas '+a.status+'" width="48" height="48" style="left:'+x+'px;top:'+y+'px"></canvas>'
|
|
1298
|
+
+'<div class="pixel-label" style="left:'+(x+24)+'px;top:'+labelY+'px">'+esc(a.name)
|
|
1299
|
+
+(statusClass?'<span class="status-tag '+statusClass+'">'+esc(a.status)+'</span>':'')+'</div>';
|
|
1300
|
+
}).join('');
|
|
1301
|
+
|
|
1302
|
+
// Start sprite animations for each agent
|
|
1303
|
+
S.agents.forEach((a,i)=>{
|
|
1304
|
+
const roleSprite=roles[i%roles.length];
|
|
1305
|
+
const canvasId='sprite-'+i;
|
|
1306
|
+
// Delay to ensure DOM is ready
|
|
1307
|
+
requestAnimationFrame(()=>startSpriteAnim(canvasId, roleSprite, a.status));
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
// Trigger bubbles for active agents (Star-Office style)
|
|
1311
|
+
scheduleBubbles();
|
|
1312
|
+
}
|
|
1313
|
+
function scheduleBubbles(){
|
|
1314
|
+
S.agents.forEach((a,i)=>{
|
|
1315
|
+
if(S.bubbleTimers.has(a.name)) return;
|
|
1316
|
+
const texts=BUBBLE_TEXTS[a.status]||BUBBLE_TEXTS.idle;
|
|
1317
|
+
const show=()=>{
|
|
1318
|
+
const el=document.getElementById('pixel-agent-layer');
|
|
1319
|
+
if(!el||S.currentView!=='office') return;
|
|
1320
|
+
const presets=[
|
|
1321
|
+
{x:.18,y:.64},{x:.3,y:.66},{x:.42,y:.62},{x:.56,y:.6},{x:.72,y:.63},{x:.82,y:.52},
|
|
1322
|
+
{x:.22,y:.45},{x:.48,y:.42},{x:.66,y:.4},{x:.78,y:.72}
|
|
1323
|
+
];
|
|
1324
|
+
const stageW=(document.getElementById('pixel-view').clientWidth||900)-80;
|
|
1325
|
+
const stageH=(document.getElementById('pixel-view').clientHeight||520)-120;
|
|
1326
|
+
const p=presets[i%presets.length];
|
|
1327
|
+
const x=Math.max(16,Math.round(stageW*p.x));
|
|
1328
|
+
const y=Math.max(20,Math.round(stageH*p.y));
|
|
1329
|
+
const bubble=document.createElement('div');
|
|
1330
|
+
bubble.className='pixel-bubble';
|
|
1331
|
+
bubble.textContent=texts[Math.floor(Math.random()*texts.length)];
|
|
1332
|
+
bubble.style.left=(x+50)+'px'; bubble.style.top=(y-20)+'px';
|
|
1333
|
+
el.appendChild(bubble);
|
|
1334
|
+
setTimeout(()=>bubble.remove(),3000);
|
|
1335
|
+
};
|
|
1336
|
+
const interval=setInterval(show, 6000+Math.random()*8000);
|
|
1337
|
+
S.bubbleTimers.set(a.name, interval);
|
|
1338
|
+
setTimeout(show, 1000+Math.random()*3000);
|
|
1339
|
+
});
|
|
1340
|
+
// Clear timers for agents that no longer exist
|
|
1341
|
+
for(const [name,timer] of S.bubbleTimers){
|
|
1342
|
+
if(!S.agents.find(a=>a.name===name)){clearInterval(timer);S.bubbleTimers.delete(name);}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function setView(view){
|
|
1346
|
+
S.currentView=view;
|
|
1347
|
+
const dashboard=view==='dashboard';
|
|
1348
|
+
document.getElementById('graph-view').classList.toggle('hidden',!dashboard);
|
|
1349
|
+
document.getElementById('pixel-view').classList.toggle('hidden',dashboard);
|
|
1350
|
+
document.getElementById('view-dashboard').classList.toggle('active',dashboard);
|
|
1351
|
+
document.getElementById('view-office').classList.toggle('active',!dashboard);
|
|
1352
|
+
document.getElementById('tooltip').classList.remove('visible');
|
|
1353
|
+
if(dashboard){
|
|
1354
|
+
setTimeout(()=>{layoutGraph();renderCanvas();},0);
|
|
1355
|
+
}else{
|
|
1356
|
+
renderPixelOffice();
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
521
1359
|
function esc(s){return s?s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'):'';}
|
|
522
1360
|
|
|
523
1361
|
document.getElementById('btn-scan').addEventListener('click',doScan);
|
|
@@ -526,6 +1364,8 @@ document.getElementById('btn-reset').addEventListener('click',doReset);
|
|
|
526
1364
|
document.getElementById('btn-run-tests').addEventListener('click',doRunTests);
|
|
527
1365
|
document.getElementById('btn-reports').addEventListener('click',doReports);
|
|
528
1366
|
document.getElementById('run-mode').addEventListener('change',e=>{S.runMode=e.target.value;});
|
|
1367
|
+
document.getElementById('view-dashboard').addEventListener('click',()=>setView('dashboard'));
|
|
1368
|
+
document.getElementById('view-office').addEventListener('click',()=>setView('office'));
|
|
529
1369
|
|
|
530
1370
|
// Tab switching
|
|
531
1371
|
document.querySelectorAll('.panel-tabs .tab').forEach(tab=>{
|
|
@@ -543,7 +1383,7 @@ document.querySelectorAll('.panel-tabs .tab').forEach(tab=>{
|
|
|
543
1383
|
// File list rendering
|
|
544
1384
|
function renderFileList(){
|
|
545
1385
|
const el=document.getElementById('file-list');
|
|
546
|
-
if(!S.generatedFiles.length){el.innerHTML='<div style="padding:12px;color
|
|
1386
|
+
if(!S.generatedFiles.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No test files generated yet. Run Pipeline first.</div>';return;}
|
|
547
1387
|
el.innerHTML=S.generatedFiles.map((f,i)=>
|
|
548
1388
|
'<div class="file-item" data-idx="'+i+'"><div class="fname">'+esc(f.filePath.split('/').pop()||f.filePath)+'</div>'+
|
|
549
1389
|
'<div class="fmeta">'+esc(f.module)+' / '+esc(f.chain)+' โ '+f.lines+' lines</div></div>'
|
|
@@ -572,12 +1412,12 @@ document.addEventListener('keydown',e=>{
|
|
|
572
1412
|
// Test Results rendering
|
|
573
1413
|
function renderResults(){
|
|
574
1414
|
const el=document.getElementById('results-panel');
|
|
575
|
-
if(!S.testMetrics&&!S.testQuality){el.innerHTML='<div style="padding:12px;color
|
|
1415
|
+
if(!S.testMetrics&&!S.testQuality){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No test results yet. Run Tests first.</div>';return;}
|
|
576
1416
|
const q=S.testQuality;
|
|
577
1417
|
if(!S.testMetrics&&q){
|
|
578
1418
|
el.innerHTML='<div style="padding:10px">'
|
|
579
1419
|
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐งช Test Execution Results</div>'
|
|
580
|
-
+'<div style="padding:10px;border-radius:
|
|
1420
|
+
+'<div style="padding:10px;border-radius:var(--radius-sm);background:var(--red-bg);color:var(--red);font-size:11px;margin-bottom:10px">Setup failed before test execution.</div>'
|
|
581
1421
|
+'<div style="font-size:11px;margin-bottom:6px">Gate Level: <span style="color:'+(q.level==='fail'?'var(--red)':q.level==='warn'?'var(--orange)':'var(--accent)')+';font-weight:bold">'+q.level.toUpperCase()+'</span></div>'
|
|
582
1422
|
+'<div style="font-size:10px;color:var(--text-dim)">Auth: '+q.authStatus+' | Backend: '+q.backendStatus+'</div>'
|
|
583
1423
|
+'<div style="font-size:10px;color:var(--text-dim);margin-top:4px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
@@ -590,15 +1430,15 @@ function renderResults(){
|
|
|
590
1430
|
let qualityHtml='';
|
|
591
1431
|
if(q){
|
|
592
1432
|
const gateColor=q.level==='fail'?'var(--red)':q.level==='warn'?'var(--orange)':'var(--accent)';
|
|
593
|
-
qualityHtml='<div style="margin-top:12px;padding-top:10px;border-top:1px solid
|
|
1433
|
+
qualityHtml='<div style="margin-top:12px;padding-top:10px;border-top:1px solid var(--border)">'
|
|
594
1434
|
+'<div style="font-size:12px;font-weight:bold;margin-bottom:8px">๐งญ Execution Quality</div>'
|
|
595
|
-
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:
|
|
596
|
-
+'<div style="background
|
|
597
|
-
+'<div style="background
|
|
598
|
-
+'<div style="background
|
|
599
|
-
+'<div style="background
|
|
600
|
-
+'<div style="background
|
|
601
|
-
+'<div style="background
|
|
1435
|
+
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:11px">'
|
|
1436
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Gate: <span style="color:'+gateColor+';font-weight:600">'+q.level.toUpperCase()+'</span></div>'
|
|
1437
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Setup Fail: '+q.setupFail+'</div>'
|
|
1438
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Skip Ratio: '+Math.round((q.skipRatio||0)*100)+'%</div>'
|
|
1439
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Auth Fail Ratio: '+Math.round((q.authFailRatio||0)*100)+'%</div>'
|
|
1440
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Effective Rate: '+Math.round((q.effectiveExecutionRate||0)*100)+'%</div>'
|
|
1441
|
+
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Auth/Backend: '+q.authStatus+' / '+q.backendStatus+'</div>'
|
|
602
1442
|
+'</div>'
|
|
603
1443
|
+'<div style="font-size:10px;color:var(--text-dim);margin-top:6px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
604
1444
|
+'</div>';
|
|
@@ -606,12 +1446,12 @@ function renderResults(){
|
|
|
606
1446
|
el.innerHTML='<div style="padding:10px">'
|
|
607
1447
|
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐งช Test Execution Results</div>'
|
|
608
1448
|
+'<div style="display:flex;gap:12px;margin-bottom:10px">'
|
|
609
|
-
+'<div style="flex:1;text-align:center;background
|
|
610
|
-
+'<div style="flex:1;text-align:center;background
|
|
611
|
-
+'<div style="flex:1;text-align:center;background
|
|
612
|
-
+'<div style="flex:1;text-align:center;background
|
|
1449
|
+
+'<div style="flex:1;text-align:center;background:var(--accent-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--accent)">'+m.passed+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">PASSED</div></div>'
|
|
1450
|
+
+'<div style="flex:1;text-align:center;background:var(--red-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--red)">'+m.failed+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">FAILED</div></div>'
|
|
1451
|
+
+'<div style="flex:1;text-align:center;background:var(--orange-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--orange)">'+m.skipped+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">SKIPPED</div></div>'
|
|
1452
|
+
+'<div style="flex:1;text-align:center;background:var(--blue-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--blue)">'+m.timedOut+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">TIMEOUT</div></div>'
|
|
613
1453
|
+'</div>'
|
|
614
|
-
+'<div style="background
|
|
1454
|
+
+'<div style="background:var(--bg-hover);border-radius:4px;height:8px;overflow:hidden">'
|
|
615
1455
|
+'<div style="height:100%;width:'+passRate+'%;background:'+barColor+';transition:width .5s"></div></div>'
|
|
616
1456
|
+'<div style="text-align:center;font-size:10px;color:var(--text-dim);margin-top:4px">Pass Rate: '+passRate+'% ('+total+' total)</div>'
|
|
617
1457
|
+qualityHtml
|
|
@@ -621,7 +1461,7 @@ function renderResults(){
|
|
|
621
1461
|
// Reports rendering
|
|
622
1462
|
function renderReports(){
|
|
623
1463
|
const el=document.getElementById('reports-panel');
|
|
624
|
-
if(!S.reports.length){el.innerHTML='<div style="padding:12px;color
|
|
1464
|
+
if(!S.reports.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No reports generated yet. Click Reports to generate.</div>';return;}
|
|
625
1465
|
el.innerHTML='<div style="padding:10px"><div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐ Generated Reports</div>'
|
|
626
1466
|
+S.reports.map(r=>{
|
|
627
1467
|
const icon=r.format==='html'?'๐':r.format==='json'?'๐':'๐';
|
|
@@ -649,8 +1489,80 @@ function renderReports(){
|
|
|
649
1489
|
});
|
|
650
1490
|
}
|
|
651
1491
|
|
|
652
|
-
|
|
653
|
-
|
|
1492
|
+
// Theme toggle (borrowed from OpenClaw)
|
|
1493
|
+
function toggleTheme(){
|
|
1494
|
+
S.theme=S.theme==='dark'?'light':'dark';
|
|
1495
|
+
document.documentElement.setAttribute('data-theme',S.theme);
|
|
1496
|
+
localStorage.setItem('opencroc-theme',S.theme);
|
|
1497
|
+
document.getElementById('theme-icon-dark').style.display=S.theme==='dark'?'':'none';
|
|
1498
|
+
document.getElementById('theme-icon-light').style.display=S.theme==='light'?'':'none';
|
|
1499
|
+
renderCanvas();
|
|
1500
|
+
}
|
|
1501
|
+
document.getElementById('theme-toggle').addEventListener('click',toggleTheme);
|
|
1502
|
+
|
|
1503
|
+
// Restore saved theme
|
|
1504
|
+
(function initTheme(){
|
|
1505
|
+
const saved=localStorage.getItem('opencroc-theme');
|
|
1506
|
+
if(saved&&saved==='light'){S.theme='light';document.documentElement.setAttribute('data-theme','light');
|
|
1507
|
+
document.getElementById('theme-icon-dark').style.display='none';
|
|
1508
|
+
document.getElementById('theme-icon-light').style.display='';
|
|
1509
|
+
}
|
|
1510
|
+
})();
|
|
1511
|
+
|
|
1512
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1513
|
+
Keyboard Shortcuts System
|
|
1514
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
1515
|
+
let shortcutLegendTimer=null;
|
|
1516
|
+
function showShortcutLegend(){
|
|
1517
|
+
const el=document.getElementById('shortcut-legend');
|
|
1518
|
+
el.classList.add('visible');
|
|
1519
|
+
if(shortcutLegendTimer) clearTimeout(shortcutLegendTimer);
|
|
1520
|
+
shortcutLegendTimer=setTimeout(()=>el.classList.remove('visible'), 4000);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
document.addEventListener('keydown',e=>{
|
|
1524
|
+
// Don't capture when typing in inputs/selects
|
|
1525
|
+
const tag=e.target.tagName.toLowerCase();
|
|
1526
|
+
if(tag==='input'||tag==='textarea'||tag==='select') return;
|
|
1527
|
+
|
|
1528
|
+
const key=e.key.toLowerCase();
|
|
1529
|
+
|
|
1530
|
+
// Escape โ close file preview, close shortcut legend
|
|
1531
|
+
if(e.key==='Escape'){
|
|
1532
|
+
document.getElementById('file-preview').classList.remove('visible');
|
|
1533
|
+
document.getElementById('shortcut-legend').classList.remove('visible');
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
// ? โ Show shortcut legend
|
|
1537
|
+
if(key==='?'||e.key==='/'&&e.shiftKey){
|
|
1538
|
+
e.preventDefault(); showShortcutLegend(); return;
|
|
1539
|
+
}
|
|
1540
|
+
// 1 โ Dashboard view
|
|
1541
|
+
if(key==='1'){ e.preventDefault(); setView('dashboard'); return; }
|
|
1542
|
+
// 2 โ Pixel Office view
|
|
1543
|
+
if(key==='2'){ e.preventDefault(); setView('office'); return; }
|
|
1544
|
+
// S โ Scan
|
|
1545
|
+
if(key==='s'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doScan(); return; }
|
|
1546
|
+
// P โ Pipeline
|
|
1547
|
+
if(key==='p'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doPipeline(); return; }
|
|
1548
|
+
// T โ Tests
|
|
1549
|
+
if(key==='t'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doRunTests(); return; }
|
|
1550
|
+
// R โ Reports
|
|
1551
|
+
if(key==='r'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doReports(); return; }
|
|
1552
|
+
// X โ Reset
|
|
1553
|
+
if(key==='x'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doReset(); return; }
|
|
1554
|
+
// D โ Dark/Light toggle
|
|
1555
|
+
if(key==='d'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); toggleTheme(); return; }
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
(async()=>{
|
|
1559
|
+
preloadSprites();
|
|
1560
|
+
setupCanvas();
|
|
1561
|
+
await fetchProject();
|
|
1562
|
+
connectWS();
|
|
1563
|
+
addLog('OpenCroc Studio ready โ press ? for shortcuts','info',true);
|
|
1564
|
+
window.addEventListener('resize',()=>{layoutGraph();renderCanvas();renderPixelOffice();});
|
|
1565
|
+
})();
|
|
654
1566
|
</script>
|
|
655
1567
|
</body>
|
|
656
1568
|
</html>
|