opencroc 1.3.1 โ 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +188 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/web/index.html +275 -661
- package/package.json +1 -1
package/dist/web/index.html
CHANGED
|
@@ -14,721 +14,335 @@
|
|
|
14
14
|
--red: #e94560;
|
|
15
15
|
--orange: #f39c12;
|
|
16
16
|
--blue: #3498db;
|
|
17
|
+
--purple: #9b59b6;
|
|
17
18
|
--text: #e0e0e0;
|
|
18
19
|
--text-dim: #888;
|
|
19
20
|
--pixel-border: 2px solid #333;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
.sidebar {
|
|
86
|
-
background: var(--bg-panel);
|
|
87
|
-
border-right: var(--pixel-border);
|
|
88
|
-
overflow-y: auto;
|
|
89
|
-
padding: 8px;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.sidebar h3 {
|
|
93
|
-
font-size: 11px;
|
|
94
|
-
text-transform: uppercase;
|
|
95
|
-
color: var(--text-dim);
|
|
96
|
-
padding: 8px 4px 4px;
|
|
97
|
-
letter-spacing: 1px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.sidebar .module-item {
|
|
101
|
-
padding: 6px 8px;
|
|
102
|
-
border-radius: 4px;
|
|
103
|
-
font-size: 12px;
|
|
104
|
-
cursor: pointer;
|
|
105
|
-
display: flex;
|
|
106
|
-
align-items: center;
|
|
107
|
-
gap: 6px;
|
|
108
|
-
transition: background 0.15s;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.sidebar .module-item:hover { background: var(--bg-card); }
|
|
112
|
-
.sidebar .module-item .dot {
|
|
113
|
-
width: 6px; height: 6px;
|
|
114
|
-
border-radius: 50%;
|
|
115
|
-
background: var(--text-dim);
|
|
116
|
-
}
|
|
117
|
-
.sidebar .module-item .dot.idle { background: var(--text-dim); }
|
|
118
|
-
.sidebar .module-item .dot.testing { background: var(--orange); animation: blink 0.8s infinite; }
|
|
119
|
-
.sidebar .module-item .dot.passed { background: var(--accent); }
|
|
120
|
-
.sidebar .module-item .dot.failed { background: var(--red); }
|
|
121
|
-
|
|
122
|
-
@keyframes blink { 50% { opacity: 0.3; } }
|
|
123
|
-
|
|
124
|
-
/* ===== Main Canvas Area ===== */
|
|
125
|
-
.main {
|
|
126
|
-
position: relative;
|
|
127
|
-
overflow: hidden;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
#graph-canvas {
|
|
131
|
-
width: 100%;
|
|
132
|
-
height: 100%;
|
|
133
|
-
display: block;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* ===== Bottom Panel: Croc Office ===== */
|
|
137
|
-
.office-panel {
|
|
138
|
-
grid-column: 1 / -1;
|
|
139
|
-
background: var(--bg-panel);
|
|
140
|
-
border-top: var(--pixel-border);
|
|
141
|
-
display: flex;
|
|
142
|
-
overflow-x: auto;
|
|
143
|
-
padding: 8px;
|
|
144
|
-
gap: 8px;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.croc-desk {
|
|
148
|
-
flex: 0 0 200px;
|
|
149
|
-
background: var(--bg-card);
|
|
150
|
-
border: 1px solid #333;
|
|
151
|
-
border-radius: 4px;
|
|
152
|
-
padding: 8px;
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-direction: column;
|
|
155
|
-
align-items: center;
|
|
156
|
-
gap: 4px;
|
|
157
|
-
position: relative;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.croc-desk .croc-sprite {
|
|
161
|
-
font-size: 48px;
|
|
162
|
-
image-rendering: pixelated;
|
|
163
|
-
transition: transform 0.3s;
|
|
164
|
-
position: relative;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.croc-desk.working .croc-sprite {
|
|
168
|
-
animation: croc-work 0.6s infinite alternate;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.croc-desk.thinking .croc-sprite {
|
|
172
|
-
animation: croc-think 1s infinite;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@keyframes croc-work {
|
|
176
|
-
from { transform: translateY(0) rotate(-2deg); }
|
|
177
|
-
to { transform: translateY(-4px) rotate(2deg); }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@keyframes croc-think {
|
|
181
|
-
0%, 100% { transform: scale(1); }
|
|
182
|
-
50% { transform: scale(1.05); }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.croc-desk .croc-name {
|
|
186
|
-
font-size: 11px;
|
|
187
|
-
font-weight: bold;
|
|
188
|
-
color: var(--accent);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.croc-desk .croc-role {
|
|
192
|
-
font-size: 9px;
|
|
193
|
-
color: var(--text-dim);
|
|
194
|
-
text-transform: uppercase;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.croc-desk .croc-task {
|
|
198
|
-
font-size: 9px;
|
|
199
|
-
color: var(--orange);
|
|
200
|
-
text-align: center;
|
|
201
|
-
max-width: 180px;
|
|
202
|
-
overflow: hidden;
|
|
203
|
-
text-overflow: ellipsis;
|
|
204
|
-
white-space: nowrap;
|
|
205
|
-
min-height: 14px;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.croc-desk .status-badge {
|
|
209
|
-
position: absolute;
|
|
210
|
-
top: 4px;
|
|
211
|
-
right: 4px;
|
|
212
|
-
width: 8px;
|
|
213
|
-
height: 8px;
|
|
214
|
-
border-radius: 50%;
|
|
215
|
-
background: var(--text-dim);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.croc-desk .status-badge.idle { background: var(--text-dim); }
|
|
219
|
-
.croc-desk .status-badge.working { background: var(--orange); animation: blink 0.6s infinite; }
|
|
220
|
-
.croc-desk .status-badge.thinking { background: var(--blue); animation: blink 1s infinite; }
|
|
221
|
-
.croc-desk .status-badge.done { background: var(--accent); }
|
|
222
|
-
.croc-desk .status-badge.error { background: var(--red); }
|
|
223
|
-
|
|
224
|
-
/* ===== Tooltip ===== */
|
|
225
|
-
.tooltip {
|
|
226
|
-
position: absolute;
|
|
227
|
-
background: var(--bg-card);
|
|
228
|
-
border: 1px solid var(--accent-dim);
|
|
229
|
-
border-radius: 4px;
|
|
230
|
-
padding: 8px 12px;
|
|
231
|
-
font-size: 11px;
|
|
232
|
-
pointer-events: none;
|
|
233
|
-
z-index: 100;
|
|
234
|
-
display: none;
|
|
235
|
-
max-width: 300px;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.tooltip.visible { display: block; }
|
|
239
|
-
|
|
240
|
-
/* ===== Action Buttons ===== */
|
|
241
|
-
.actions {
|
|
242
|
-
display: flex;
|
|
243
|
-
gap: 8px;
|
|
244
|
-
align-items: center;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.btn {
|
|
248
|
-
background: var(--accent-dim);
|
|
249
|
-
color: #fff;
|
|
250
|
-
border: none;
|
|
251
|
-
padding: 6px 14px;
|
|
252
|
-
font-family: inherit;
|
|
253
|
-
font-size: 11px;
|
|
254
|
-
border-radius: 4px;
|
|
255
|
-
cursor: pointer;
|
|
256
|
-
transition: background 0.2s;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.btn:hover { background: var(--accent); }
|
|
260
|
-
.btn.danger { background: #8b2035; }
|
|
261
|
-
.btn.danger:hover { background: var(--red); }
|
|
262
|
-
|
|
263
|
-
/* Pixel art decorations */
|
|
264
|
-
.pixel-floor {
|
|
265
|
-
position: absolute;
|
|
266
|
-
bottom: 0;
|
|
267
|
-
left: 0;
|
|
268
|
-
right: 0;
|
|
269
|
-
height: 4px;
|
|
270
|
-
background: repeating-linear-gradient(90deg, #2a2a4a 0, #2a2a4a 8px, #1a1a3a 8px, #1a1a3a 16px);
|
|
271
|
-
}
|
|
22
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
23
|
+
body { background:var(--bg-dark); color:var(--text); font-family:'Courier New','Consolas',monospace; overflow:hidden; height:100vh; }
|
|
24
|
+
|
|
25
|
+
.app { display:grid; grid-template-rows:48px 1fr 220px; grid-template-columns:220px 1fr 280px; height:100vh; }
|
|
26
|
+
|
|
27
|
+
.header { grid-column:1/-1; background:var(--bg-panel); border-bottom:var(--pixel-border); display:flex; align-items:center; padding:0 16px; gap:12px; }
|
|
28
|
+
.header .logo { font-size:24px; }
|
|
29
|
+
.header h1 { font-size:15px; color:var(--accent); }
|
|
30
|
+
.header .actions { display:flex; gap:6px; }
|
|
31
|
+
.btn { background:var(--accent-dim); color:#fff; border:none; padding:5px 12px; font-family:inherit; font-size:11px; border-radius:3px; cursor:pointer; transition:all .2s; }
|
|
32
|
+
.btn:hover { background:var(--accent); transform:translateY(-1px); }
|
|
33
|
+
.btn:active { transform:translateY(0); }
|
|
34
|
+
.btn:disabled { opacity:.4; cursor:not-allowed; transform:none; }
|
|
35
|
+
.btn.danger { background:#8b2035; }
|
|
36
|
+
.btn.danger:hover { background:var(--red); }
|
|
37
|
+
.header .stats { margin-left:auto; display:flex; gap:14px; font-size:11px; color:var(--text-dim); }
|
|
38
|
+
.header .stats span { color:var(--accent); font-weight:bold; }
|
|
39
|
+
.conn-dot { width:8px; height:8px; border-radius:50%; background:var(--red); transition:background .3s; }
|
|
40
|
+
.conn-dot.on { background:var(--accent); }
|
|
41
|
+
|
|
42
|
+
.sidebar { background:var(--bg-panel); border-right:var(--pixel-border); overflow-y:auto; padding:8px; }
|
|
43
|
+
.sidebar h3 { font-size:10px; text-transform:uppercase; color:var(--text-dim); padding:8px 4px 4px; letter-spacing:1px; }
|
|
44
|
+
.mod-item { padding:5px 8px; border-radius:3px; font-size:11px; cursor:pointer; display:flex; align-items:center; gap:6px; transition:background .15s; }
|
|
45
|
+
.mod-item:hover { background:var(--bg-card); }
|
|
46
|
+
.dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; }
|
|
47
|
+
.dot.idle { background:var(--text-dim); }
|
|
48
|
+
.dot.testing,.dot.working { background:var(--orange); animation:blink .7s infinite; }
|
|
49
|
+
.dot.thinking { background:var(--blue); animation:blink 1s infinite; }
|
|
50
|
+
.dot.passed,.dot.done { background:var(--accent); }
|
|
51
|
+
.dot.failed,.dot.error { background:var(--red); }
|
|
52
|
+
@keyframes blink { 50%{opacity:.3} }
|
|
53
|
+
|
|
54
|
+
.main { position:relative; overflow:hidden; }
|
|
55
|
+
#graph-canvas { width:100%; height:100%; display:block; cursor:grab; }
|
|
56
|
+
.tooltip { position:absolute; background:var(--bg-card); border:1px solid var(--accent-dim); border-radius:4px; padding:8px 12px; font-size:11px; pointer-events:none; z-index:100; display:none; max-width:280px; }
|
|
57
|
+
.tooltip.visible { display:block; }
|
|
58
|
+
|
|
59
|
+
.log-panel { background:var(--bg-panel); border-left:var(--pixel-border); display:flex; flex-direction:column; overflow:hidden; }
|
|
60
|
+
.log-panel h3 { font-size:10px; text-transform:uppercase; color:var(--text-dim); padding:10px 10px 6px; letter-spacing:1px; border-bottom:1px solid #222; }
|
|
61
|
+
.log-list { flex:1; overflow-y:auto; padding:4px 8px; font-size:10px; line-height:1.6; }
|
|
62
|
+
.log-list .log-entry { padding:2px 0; border-bottom:1px solid #1a1a2e; word-break:break-all; }
|
|
63
|
+
.log-list .log-entry.warn { color:var(--orange); }
|
|
64
|
+
.log-list .log-entry.error { color:var(--red); }
|
|
65
|
+
|
|
66
|
+
.office { grid-column:1/-1; background:var(--bg-panel); border-top:var(--pixel-border); display:flex; overflow-x:auto; padding:8px; gap:8px; }
|
|
67
|
+
.desk { flex:0 0 180px; background:var(--bg-card); border:1px solid #333; border-radius:4px; padding:8px; display:flex; flex-direction:column; align-items:center; gap:2px; position:relative; overflow:hidden; }
|
|
68
|
+
.desk .badge { position:absolute; top:4px; right:4px; width:8px; height:8px; border-radius:50%; }
|
|
69
|
+
.desk .croc-sprite { font-size:40px; position:relative; z-index:1; }
|
|
70
|
+
.desk.idle .croc-sprite { animation:croc-idle 3s infinite; }
|
|
71
|
+
.desk.working .croc-sprite { animation:croc-work .5s infinite alternate; }
|
|
72
|
+
.desk.thinking .croc-sprite { animation:croc-think 1.2s infinite; }
|
|
73
|
+
.desk.done .croc-sprite { animation:croc-done .8s 1; }
|
|
74
|
+
.desk.error .croc-sprite { animation:croc-error .3s 3; }
|
|
75
|
+
@keyframes croc-idle { 0%,90%,100%{transform:translateY(0)} 95%{transform:translateY(-3px)} }
|
|
76
|
+
@keyframes croc-work { from{transform:translateY(0) rotate(-3deg)} to{transform:translateY(-5px) rotate(3deg)} }
|
|
77
|
+
@keyframes croc-think { 0%,100%{transform:scale(1) rotate(0)} 25%{transform:scale(1.05) rotate(-2deg)} 75%{transform:scale(1.05) rotate(2deg)} }
|
|
78
|
+
@keyframes croc-done { 0%{transform:scale(1)} 50%{transform:scale(1.2) translateY(-8px)} 100%{transform:scale(1)} }
|
|
79
|
+
@keyframes croc-error { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-4px)} 75%{transform:translateX(4px)} }
|
|
80
|
+
.desk .croc-name { font-size:11px; font-weight:bold; color:var(--accent); }
|
|
81
|
+
.desk .croc-role { font-size:9px; color:var(--text-dim); text-transform:uppercase; }
|
|
82
|
+
.desk .croc-task { font-size:9px; color:var(--orange); text-align:center; max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-height:13px; }
|
|
83
|
+
.desk .progress-bar { width:90%; height:3px; background:#222; border-radius:2px; margin-top:2px; overflow:hidden; }
|
|
84
|
+
.desk .progress-bar .fill { height:100%; background:var(--accent); transition:width .3s; border-radius:2px; }
|
|
85
|
+
.desk .desk-items { position:absolute; bottom:4px; right:8px; font-size:14px; opacity:.4; }
|
|
272
86
|
</style>
|
|
273
87
|
</head>
|
|
274
88
|
<body>
|
|
275
|
-
|
|
276
89
|
<div class="app">
|
|
277
|
-
<!-- Header -->
|
|
278
90
|
<header class="header">
|
|
279
91
|
<div class="logo">๐</div>
|
|
280
92
|
<h1>OpenCroc Studio</h1>
|
|
281
93
|
<div class="actions">
|
|
282
|
-
<button class="btn" id="btn-scan" title="
|
|
283
|
-
<button class="btn" id="btn-
|
|
94
|
+
<button class="btn" id="btn-scan" title="Scan project">๐ Scan</button>
|
|
95
|
+
<button class="btn" id="btn-pipeline" title="Run full pipeline">โถ Pipeline</button>
|
|
96
|
+
<button class="btn danger" id="btn-reset" title="Reset agents">โน Reset</button>
|
|
284
97
|
</div>
|
|
285
|
-
<div class="stats"
|
|
286
|
-
<div>Modules: <span id="
|
|
287
|
-
<div>Models: <span id="
|
|
288
|
-
<div>APIs: <span id="
|
|
98
|
+
<div class="stats">
|
|
99
|
+
<div>Modules: <span id="s-mod">-</span></div>
|
|
100
|
+
<div>Models: <span id="s-mdl">-</span></div>
|
|
101
|
+
<div>APIs: <span id="s-api">-</span></div>
|
|
289
102
|
</div>
|
|
290
|
-
<div class="conn-
|
|
103
|
+
<div class="conn-dot" id="conn-dot" title="WebSocket"></div>
|
|
291
104
|
</header>
|
|
292
105
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
<
|
|
297
|
-
<
|
|
298
|
-
<div id="agent-list-sidebar"></div>
|
|
106
|
+
<aside class="sidebar">
|
|
107
|
+
<h3>๐ Modules</h3>
|
|
108
|
+
<div id="mod-list"></div>
|
|
109
|
+
<h3 style="margin-top:12px">๐ Agents</h3>
|
|
110
|
+
<div id="agent-sidebar"></div>
|
|
299
111
|
</aside>
|
|
300
112
|
|
|
301
|
-
<!-- Main: Knowledge Graph Canvas -->
|
|
302
113
|
<main class="main">
|
|
303
114
|
<canvas id="graph-canvas"></canvas>
|
|
304
115
|
<div class="tooltip" id="tooltip"></div>
|
|
305
116
|
</main>
|
|
306
117
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
</
|
|
118
|
+
<div class="log-panel">
|
|
119
|
+
<h3>๐ Activity Log</h3>
|
|
120
|
+
<div class="log-list" id="log-list"></div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<section class="office" id="croc-office"></section>
|
|
311
124
|
</div>
|
|
312
125
|
|
|
313
126
|
<script>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const state = {
|
|
319
|
-
project: null,
|
|
320
|
-
graph: { nodes: [], edges: [] },
|
|
321
|
-
agents: [],
|
|
322
|
-
ws: null,
|
|
323
|
-
selectedNode: null,
|
|
324
|
-
// Canvas rendering state
|
|
325
|
-
pan: { x: 0, y: 0 },
|
|
326
|
-
zoom: 1,
|
|
327
|
-
dragging: false,
|
|
328
|
-
dragStart: { x: 0, y: 0 },
|
|
329
|
-
nodePositions: new Map(),
|
|
330
|
-
hoveredNode: null,
|
|
127
|
+
const S = {
|
|
128
|
+
project:null, graph:{nodes:[],edges:[]}, agents:[], ws:null,
|
|
129
|
+
pan:{x:0,y:0}, zoom:1, dragging:false, dragStart:{x:0,y:0},
|
|
130
|
+
nodePos:new Map(), hoveredNode:null, running:false, _userPanned:false
|
|
331
131
|
};
|
|
332
132
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
state.graph = state.project.graph || { nodes: [], edges: [] };
|
|
340
|
-
state.agents = state.project.agents || [];
|
|
341
|
-
layoutGraph();
|
|
342
|
-
updateUI();
|
|
343
|
-
} catch (e) {
|
|
344
|
-
console.error('Failed to fetch project:', e);
|
|
345
|
-
}
|
|
133
|
+
async function fetchProject(){
|
|
134
|
+
try{
|
|
135
|
+
const r=await fetch('/api/project'); S.project=await r.json();
|
|
136
|
+
S.graph=S.project.graph||{nodes:[],edges:[]}; S.agents=S.project.agents||[];
|
|
137
|
+
layoutGraph(); updateAll();
|
|
138
|
+
}catch(e){addLog('Failed to load: '+e.message,'error');}
|
|
346
139
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
140
|
+
async function doScan(){
|
|
141
|
+
if(S.running)return; S.running=true; updateBtns();
|
|
142
|
+
addLog('๐ Starting scan...');
|
|
143
|
+
try{await fetch('/api/scan',{method:'POST'});}
|
|
144
|
+
catch(e){addLog('Scan failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
145
|
+
}
|
|
146
|
+
async function doPipeline(){
|
|
147
|
+
if(S.running)return; S.running=true; updateBtns();
|
|
148
|
+
addLog('โถ Starting pipeline...');
|
|
149
|
+
try{await fetch('/api/pipeline',{method:'POST'});}
|
|
150
|
+
catch(e){addLog('Pipeline failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
151
|
+
}
|
|
152
|
+
async function doReset(){
|
|
153
|
+
try{await fetch('/api/reset',{method:'POST'});}catch(e){addLog('Reset failed','error');}
|
|
154
|
+
S.running=false; updateBtns(); addLog('โน Agents reset');
|
|
155
|
+
}
|
|
156
|
+
function updateBtns(){
|
|
157
|
+
document.getElementById('btn-scan').disabled=S.running;
|
|
158
|
+
document.getElementById('btn-pipeline').disabled=S.running;
|
|
355
159
|
}
|
|
356
160
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
state.graph = msg.payload;
|
|
376
|
-
layoutGraph();
|
|
377
|
-
renderCanvas();
|
|
161
|
+
function connectWS(){
|
|
162
|
+
const p=location.protocol==='https:'?'wss:':'ws:';
|
|
163
|
+
S.ws=new WebSocket(p+'//'+location.host+'/ws');
|
|
164
|
+
S.ws.onopen=()=>{document.getElementById('conn-dot').classList.add('on');};
|
|
165
|
+
S.ws.onmessage=(e)=>{
|
|
166
|
+
try{
|
|
167
|
+
const m=JSON.parse(e.data);
|
|
168
|
+
if(m.type==='agent:update'&&Array.isArray(m.payload)){
|
|
169
|
+
S.agents=m.payload; renderOffice(); renderAgentSB();
|
|
170
|
+
}else if(m.type==='graph:update'){
|
|
171
|
+
S.graph=m.payload; layoutGraph(); renderCanvas(); renderModList(); updateStats();
|
|
172
|
+
}else if(m.type==='log'){
|
|
173
|
+
addLog(m.payload.message, m.payload.level);
|
|
174
|
+
}else if(m.type==='pipeline:complete'){
|
|
175
|
+
S.running=false; updateBtns();
|
|
176
|
+
if(m.payload.status==='success') addLog('โ
Pipeline complete!');
|
|
177
|
+
else addLog('โ Pipeline failed: '+(m.payload.error||''),'error');
|
|
178
|
+
setTimeout(fetchProject,500);
|
|
378
179
|
}
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
state.ws.onclose = () => {
|
|
383
|
-
document.getElementById('conn-status').classList.remove('connected');
|
|
384
|
-
// Reconnect after 3s
|
|
385
|
-
setTimeout(connectWS, 3000);
|
|
180
|
+
}catch{}
|
|
386
181
|
};
|
|
182
|
+
S.ws.onclose=()=>{document.getElementById('conn-dot').classList.remove('on');setTimeout(connectWS,3000);};
|
|
387
183
|
}
|
|
388
184
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// Group by module
|
|
402
|
-
const modules = new Map();
|
|
403
|
-
for (const node of nodes) {
|
|
404
|
-
const m = node.module || node.id;
|
|
405
|
-
if (!modules.has(m)) modules.set(m, []);
|
|
406
|
-
modules.get(m).push(node);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const moduleKeys = [...modules.keys()];
|
|
410
|
-
const moduleCount = moduleKeys.length || 1;
|
|
411
|
-
|
|
412
|
-
for (let mi = 0; mi < moduleKeys.length; mi++) {
|
|
413
|
-
const modNodes = modules.get(moduleKeys[mi]);
|
|
414
|
-
const angle = (mi / moduleCount) * Math.PI * 2 - Math.PI / 2;
|
|
415
|
-
const radius = Math.min(w, h) * 0.3;
|
|
416
|
-
const modCx = cx + Math.cos(angle) * radius;
|
|
417
|
-
const modCy = cy + Math.sin(angle) * radius;
|
|
418
|
-
|
|
419
|
-
for (let ni = 0; ni < modNodes.length; ni++) {
|
|
420
|
-
const nAngle = (ni / modNodes.length) * Math.PI * 2;
|
|
421
|
-
const nRadius = 40 + modNodes.length * 10;
|
|
422
|
-
state.nodePositions.set(modNodes[ni].id, {
|
|
423
|
-
x: modCx + Math.cos(nAngle) * nRadius,
|
|
424
|
-
y: modCy + Math.sin(nAngle) * nRadius,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
185
|
+
function addLog(msg,level){
|
|
186
|
+
level=level||'info';
|
|
187
|
+
const el=document.getElementById('log-list');
|
|
188
|
+
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
189
|
+
d.textContent='['+new Date().toLocaleTimeString()+'] '+msg;
|
|
190
|
+
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
191
|
+
}
|
|
192
|
+
function updateStats(){
|
|
193
|
+
document.getElementById('s-mod').textContent=S.graph.nodes.filter(n=>n.type==='module').length;
|
|
194
|
+
document.getElementById('s-mdl').textContent=S.graph.nodes.filter(n=>n.type==='model').length;
|
|
195
|
+
document.getElementById('s-api').textContent=S.graph.nodes.filter(n=>n.type==='controller'||n.type==='api').length;
|
|
428
196
|
}
|
|
429
197
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
198
|
+
function layoutGraph(){
|
|
199
|
+
const nodes=S.graph.nodes; if(!nodes.length)return;
|
|
200
|
+
const c=document.getElementById('graph-canvas');
|
|
201
|
+
const w=c.clientWidth||800,h=c.clientHeight||500;
|
|
202
|
+
// Group nodes by module
|
|
203
|
+
const mods=new Map();
|
|
204
|
+
for(const n of nodes){
|
|
205
|
+
const m=n.module||'_none';
|
|
206
|
+
if(n.type==='module'){S.nodePos.set(n.id,{x:0,y:0});continue;}
|
|
207
|
+
if(!mods.has(m))mods.set(m,[]);mods.get(m).push(n);
|
|
208
|
+
}
|
|
209
|
+
const keys=[...mods.keys()]; const nMods=keys.length||1;
|
|
210
|
+
// Calculate total canvas size needed โ scale with node count
|
|
211
|
+
const totalNodes=nodes.length;
|
|
212
|
+
const canvasScale=Math.max(1, Math.sqrt(totalNodes/50));
|
|
213
|
+
const baseRadius=Math.min(w,h)*0.35*canvasScale;
|
|
214
|
+
// Place modules around a circle, each module's members in a sub-ring
|
|
215
|
+
for(let i=0;i<keys.length;i++){
|
|
216
|
+
const mn=mods.get(keys[i]); if(!mn)continue;
|
|
217
|
+
const modAngle=(i/nMods)*Math.PI*2-Math.PI/2;
|
|
218
|
+
const mcx=w/2+Math.cos(modAngle)*baseRadius;
|
|
219
|
+
const mcy=h/2+Math.sin(modAngle)*baseRadius;
|
|
220
|
+
// Place module node at group center
|
|
221
|
+
const modNodeId='module:'+keys[i];
|
|
222
|
+
S.nodePos.set(modNodeId,{x:mcx,y:mcy});
|
|
223
|
+
// Sub-ring radius scales with member count, min 60
|
|
224
|
+
const subRadius=Math.max(60, Math.sqrt(mn.length)*28);
|
|
225
|
+
for(let j=0;j<mn.length;j++){
|
|
226
|
+
const na=(j/mn.length)*Math.PI*2;
|
|
227
|
+
S.nodePos.set(mn[j].id,{x:mcx+Math.cos(na)*subRadius,y:mcy+Math.sin(na)*subRadius});
|
|
228
|
+
}
|
|
455
229
|
}
|
|
456
|
-
|
|
457
|
-
|
|
230
|
+
// Auto-center: reset pan to center of all nodes
|
|
231
|
+
if(totalNodes>0&&!S._userPanned){
|
|
232
|
+
let minX=Infinity,maxX=-Infinity,minY=Infinity,maxY=-Infinity;
|
|
233
|
+
for(const[,p] of S.nodePos){minX=Math.min(minX,p.x);maxX=Math.max(maxX,p.x);minY=Math.min(minY,p.y);maxY=Math.max(maxY,p.y);}
|
|
234
|
+
const gw=maxX-minX+200,gh=maxY-minY+200,gcx=minX+(maxX-minX)/2,gcy=minY+(maxY-minY)/2;
|
|
235
|
+
S.zoom=Math.min(1, Math.min(w/gw, h/gh));
|
|
236
|
+
S.pan.x=w/2-gcx*S.zoom;S.pan.y=h/2-gcy*S.zoom;
|
|
458
237
|
}
|
|
238
|
+
}
|
|
459
239
|
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
ctx.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
ctx.
|
|
478
|
-
ctx.stroke();
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
ctx.
|
|
491
|
-
ctx.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const statusOutline = {
|
|
504
|
-
idle: '#555',
|
|
505
|
-
testing: '#f39c12',
|
|
506
|
-
passed: '#4ecca3',
|
|
507
|
-
failed: '#e94560',
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
for (const node of nodes) {
|
|
511
|
-
const pos = state.nodePositions.get(node.id);
|
|
512
|
-
if (!pos) continue;
|
|
513
|
-
|
|
514
|
-
const size = node.type === 'module' ? 24 : 16;
|
|
515
|
-
const color = typeColors[node.type] || '#888';
|
|
516
|
-
const outline = statusOutline[node.status] || '#555';
|
|
517
|
-
const isHovered = state.hoveredNode === node.id;
|
|
518
|
-
|
|
519
|
-
// Shadow
|
|
520
|
-
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
|
521
|
-
ctx.fillRect(pos.x - size + 2, pos.y - size + 2, size * 2, size * 2);
|
|
522
|
-
|
|
523
|
-
// Main block
|
|
524
|
-
ctx.fillStyle = color;
|
|
525
|
-
ctx.fillRect(pos.x - size, pos.y - size, size * 2, size * 2);
|
|
526
|
-
|
|
527
|
-
// Pixel highlight (top-left)
|
|
528
|
-
ctx.fillStyle = 'rgba(255,255,255,0.2)';
|
|
529
|
-
ctx.fillRect(pos.x - size, pos.y - size, size * 2, 3);
|
|
530
|
-
ctx.fillRect(pos.x - size, pos.y - size, 3, size * 2);
|
|
531
|
-
|
|
532
|
-
// Status outline
|
|
533
|
-
ctx.strokeStyle = isHovered ? '#fff' : outline;
|
|
534
|
-
ctx.lineWidth = isHovered ? 3 : 2;
|
|
535
|
-
ctx.strokeRect(pos.x - size, pos.y - size, size * 2, size * 2);
|
|
536
|
-
|
|
537
|
-
// Icon (pixel emoji)
|
|
538
|
-
ctx.font = `${size}px serif`;
|
|
539
|
-
ctx.textAlign = 'center';
|
|
540
|
-
ctx.textBaseline = 'middle';
|
|
541
|
-
const icons = { model: '๐ฆ', controller: '๐ฎ', api: '๐', dto: '๐', module: '๐' };
|
|
542
|
-
ctx.fillText(icons[node.type] || 'โฌ', pos.x, pos.y);
|
|
543
|
-
|
|
544
|
-
// Label
|
|
545
|
-
ctx.font = '10px "Courier New"';
|
|
546
|
-
ctx.fillStyle = isHovered ? '#fff' : '#ccc';
|
|
547
|
-
ctx.textAlign = 'center';
|
|
548
|
-
ctx.textBaseline = 'top';
|
|
549
|
-
const label = node.label || node.id.split(':').pop();
|
|
550
|
-
ctx.fillText(label, pos.x, pos.y + size + 4);
|
|
240
|
+
function renderCanvas(){
|
|
241
|
+
const canvas=document.getElementById('graph-canvas'),ctx=canvas.getContext('2d');
|
|
242
|
+
const dpr=window.devicePixelRatio||1;
|
|
243
|
+
canvas.width=canvas.clientWidth*dpr; canvas.height=canvas.clientHeight*dpr;
|
|
244
|
+
ctx.scale(dpr,dpr);
|
|
245
|
+
const w=canvas.clientWidth,h=canvas.clientHeight;
|
|
246
|
+
ctx.clearRect(0,0,w,h); ctx.save(); ctx.translate(S.pan.x,S.pan.y); ctx.scale(S.zoom,S.zoom);
|
|
247
|
+
// Grid
|
|
248
|
+
ctx.strokeStyle='#151530';ctx.lineWidth=.5;
|
|
249
|
+
for(let x=0;x<w*2;x+=32){ctx.beginPath();ctx.moveTo(x,-h);ctx.lineTo(x,h*2);ctx.stroke();}
|
|
250
|
+
for(let y=0;y<h*2;y+=32){ctx.beginPath();ctx.moveTo(-w,y);ctx.lineTo(w*2,y);ctx.stroke();}
|
|
251
|
+
const edges=S.graph.edges||[],nodes=S.graph.nodes||[];
|
|
252
|
+
const largeGraph=nodes.length>100;
|
|
253
|
+
// Edges โ for large graphs, only draw 'uses' relations (skip 'contains' to reduce clutter)
|
|
254
|
+
for(const e of edges){
|
|
255
|
+
if(largeGraph && e.relation==='contains') continue;
|
|
256
|
+
const s=S.nodePos.get(e.source),t=S.nodePos.get(e.target);if(!s||!t)continue;
|
|
257
|
+
ctx.strokeStyle='rgba(78,204,163,.25)';ctx.lineWidth=1.5;
|
|
258
|
+
ctx.beginPath();ctx.moveTo(s.x,s.y);ctx.lineTo(t.x,t.y);ctx.stroke();
|
|
259
|
+
const a=Math.atan2(t.y-s.y,t.x-s.x),al=7,ax=t.x-Math.cos(a)*18,ay=t.y-Math.sin(a)*18;
|
|
260
|
+
ctx.fillStyle='rgba(78,204,163,.4)';ctx.beginPath();ctx.moveTo(ax,ay);
|
|
261
|
+
ctx.lineTo(ax-al*Math.cos(a-.4),ay-al*Math.sin(a-.4));
|
|
262
|
+
ctx.lineTo(ax-al*Math.cos(a+.4),ay-al*Math.sin(a+.4));ctx.closePath();ctx.fill();
|
|
263
|
+
}
|
|
264
|
+
const tc={model:'#4ecca3',controller:'#e94560',api:'#f39c12',dto:'#3498db',module:'#9b59b6'};
|
|
265
|
+
const sc={idle:'#444',testing:'#f39c12',passed:'#4ecca3',failed:'#e94560'};
|
|
266
|
+
const icons={model:'๐ฆ',controller:'๐ฎ',api:'๐',dto:'๐',module:'๐'};
|
|
267
|
+
for(const n of nodes){
|
|
268
|
+
const p=S.nodePos.get(n.id);if(!p)continue;
|
|
269
|
+
const sz=n.type==='module'?22:14,c=tc[n.type]||'#888',ol=sc[n.status]||'#444',hov=S.hoveredNode===n.id;
|
|
270
|
+
if(n.status==='testing'){ctx.shadowColor=sc.testing;ctx.shadowBlur=12;}
|
|
271
|
+
else if(n.status==='passed'){ctx.shadowColor=sc.passed;ctx.shadowBlur=8;}
|
|
272
|
+
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=10;}
|
|
273
|
+
ctx.fillStyle='rgba(0,0,0,.3)';ctx.fillRect(p.x-sz+2,p.y-sz+2,sz*2,sz*2);
|
|
274
|
+
ctx.fillStyle=c;ctx.fillRect(p.x-sz,p.y-sz,sz*2,sz*2);
|
|
275
|
+
ctx.fillStyle='rgba(255,255,255,.15)';ctx.fillRect(p.x-sz,p.y-sz,sz*2,3);ctx.fillRect(p.x-sz,p.y-sz,3,sz*2);
|
|
276
|
+
ctx.shadowBlur=0;ctx.strokeStyle=hov?'#fff':ol;ctx.lineWidth=hov?3:2;ctx.strokeRect(p.x-sz,p.y-sz,sz*2,sz*2);
|
|
277
|
+
ctx.font=sz+'px serif';ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(icons[n.type]||'โฌ',p.x,p.y);
|
|
278
|
+
if(!largeGraph||S.zoom>0.5||n.type==='module'||hov){
|
|
279
|
+
ctx.font='10px "Courier New"';ctx.fillStyle=hov?'#fff':'#bbb';ctx.textAlign='center';ctx.textBaseline='top';
|
|
280
|
+
ctx.fillText((n.label||n.id.split(':').pop()).substring(0,20),p.x,p.y+sz+4);
|
|
281
|
+
}
|
|
551
282
|
}
|
|
552
|
-
|
|
553
283
|
ctx.restore();
|
|
554
284
|
}
|
|
555
285
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// Hit-test nodes
|
|
574
|
-
const rect = canvas.getBoundingClientRect();
|
|
575
|
-
const mx = (e.clientX - rect.left - state.pan.x) / state.zoom;
|
|
576
|
-
const my = (e.clientY - rect.top - state.pan.y) / state.zoom;
|
|
577
|
-
|
|
578
|
-
let hit = null;
|
|
579
|
-
for (const node of state.graph.nodes) {
|
|
580
|
-
const pos = state.nodePositions.get(node.id);
|
|
581
|
-
if (!pos) continue;
|
|
582
|
-
const size = node.type === 'module' ? 24 : 16;
|
|
583
|
-
if (mx >= pos.x - size && mx <= pos.x + size && my >= pos.y - size && my <= pos.y + size) {
|
|
584
|
-
hit = node;
|
|
585
|
-
break;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const tooltip = document.getElementById('tooltip');
|
|
590
|
-
if (hit) {
|
|
591
|
-
state.hoveredNode = hit.id;
|
|
592
|
-
tooltip.innerHTML = `<b>${hit.label || hit.id}</b><br>Type: ${hit.type}<br>Status: ${hit.status}${hit.module ? '<br>Module: ' + hit.module : ''}`;
|
|
593
|
-
tooltip.style.left = (e.clientX + 12) + 'px';
|
|
594
|
-
tooltip.style.top = (e.clientY + 12) + 'px';
|
|
595
|
-
tooltip.classList.add('visible');
|
|
596
|
-
canvas.style.cursor = 'pointer';
|
|
597
|
-
} else {
|
|
598
|
-
if (state.hoveredNode) {
|
|
599
|
-
state.hoveredNode = null;
|
|
600
|
-
renderCanvas();
|
|
601
|
-
}
|
|
602
|
-
tooltip.classList.remove('visible');
|
|
603
|
-
canvas.style.cursor = state.dragging ? 'grabbing' : 'grab';
|
|
604
|
-
}
|
|
605
|
-
if (hit) renderCanvas();
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
canvas.addEventListener('mouseup', () => { state.dragging = false; });
|
|
609
|
-
canvas.addEventListener('mouseleave', () => {
|
|
610
|
-
state.dragging = false;
|
|
611
|
-
document.getElementById('tooltip').classList.remove('visible');
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
canvas.addEventListener('wheel', (e) => {
|
|
615
|
-
e.preventDefault();
|
|
616
|
-
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
617
|
-
state.zoom = Math.max(0.2, Math.min(3, state.zoom * delta));
|
|
286
|
+
function setupCanvas(){
|
|
287
|
+
const c=document.getElementById('graph-canvas');
|
|
288
|
+
c.addEventListener('mousedown',e=>{S.dragging=true;S._userPanned=true;S.dragStart={x:e.clientX-S.pan.x,y:e.clientY-S.pan.y};c.style.cursor='grabbing';});
|
|
289
|
+
c.addEventListener('mousemove',e=>{
|
|
290
|
+
if(S.dragging){S.pan.x=e.clientX-S.dragStart.x;S.pan.y=e.clientY-S.dragStart.y;renderCanvas();}
|
|
291
|
+
const rect=c.getBoundingClientRect(),mx=(e.clientX-rect.left-S.pan.x)/S.zoom,my=(e.clientY-rect.top-S.pan.y)/S.zoom;
|
|
292
|
+
let hit=null;
|
|
293
|
+
for(const n of S.graph.nodes){const p=S.nodePos.get(n.id);if(!p)continue;const sz=n.type==='module'?22:14;if(mx>=p.x-sz&&mx<=p.x+sz&&my>=p.y-sz&&my<=p.y+sz){hit=n;break;}}
|
|
294
|
+
const tt=document.getElementById('tooltip');
|
|
295
|
+
if(hit){
|
|
296
|
+
S.hoveredNode=hit.id;
|
|
297
|
+
const sc={idle:'#888',testing:'#f39c12',passed:'#4ecca3',failed:'#e94560'};
|
|
298
|
+
tt.innerHTML='<b>'+esc(hit.label||hit.id)+'</b><br>Type: '+hit.type+'<br>Status: <span style="color:'+(sc[hit.status]||'#888')+'">'+hit.status+'</span>'+(hit.module?'<br>Module: '+esc(hit.module):'');
|
|
299
|
+
tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY+12)+'px';tt.classList.add('visible');c.style.cursor='pointer';
|
|
300
|
+
}else{if(S.hoveredNode)S.hoveredNode=null;tt.classList.remove('visible');if(!S.dragging)c.style.cursor='grab';}
|
|
618
301
|
renderCanvas();
|
|
619
|
-
}
|
|
302
|
+
});
|
|
303
|
+
c.addEventListener('mouseup',()=>{S.dragging=false;c.style.cursor='grab';});
|
|
304
|
+
c.addEventListener('mouseleave',()=>{S.dragging=false;document.getElementById('tooltip').classList.remove('visible');});
|
|
305
|
+
c.addEventListener('wheel',e=>{e.preventDefault();S.zoom=Math.max(.2,Math.min(3,S.zoom*(e.deltaY>0?.92:1.08)));renderCanvas();},{passive:false});
|
|
620
306
|
}
|
|
621
307
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
document.getElementById('
|
|
627
|
-
document.getElementById('stat-models').textContent = state.project.stats?.models || 0;
|
|
628
|
-
document.getElementById('stat-endpoints').textContent = state.project.stats?.endpoints || 0;
|
|
308
|
+
function updateAll(){
|
|
309
|
+
if(S.project){
|
|
310
|
+
document.getElementById('s-mod').textContent=S.project.stats?.modules||0;
|
|
311
|
+
document.getElementById('s-mdl').textContent=S.project.stats?.models||0;
|
|
312
|
+
document.getElementById('s-api').textContent=S.project.stats?.endpoints||0;
|
|
629
313
|
}
|
|
630
|
-
|
|
631
|
-
renderModuleList();
|
|
632
|
-
renderCrocOffice();
|
|
633
|
-
renderAgentSidebar();
|
|
634
|
-
renderCanvas();
|
|
314
|
+
renderModList();renderOffice();renderAgentSB();renderCanvas();
|
|
635
315
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (!modules.length) {
|
|
642
|
-
list.innerHTML = '<div style="padding:8px;color:#666;font-size:11px">No modules found</div>';
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
list.innerHTML = modules.map(m => `
|
|
647
|
-
<div class="module-item" data-id="${esc(m.id)}">
|
|
648
|
-
<div class="dot ${m.status}"></div>
|
|
649
|
-
${esc(m.label || m.id.replace('module:', ''))}
|
|
650
|
-
</div>
|
|
651
|
-
`).join('');
|
|
316
|
+
function renderModList(){
|
|
317
|
+
const el=document.getElementById('mod-list'),mods=S.graph.nodes.filter(n=>n.type==='module');
|
|
318
|
+
if(!mods.length){el.innerHTML='<div style="padding:8px;color:#555;font-size:10px">No modules found</div>';return;}
|
|
319
|
+
el.innerHTML=mods.map(m=>'<div class="mod-item"><div class="dot '+m.status+'"></div>'+esc(m.label||m.id.replace('module:',''))+'</div>').join('');
|
|
652
320
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
<div class="module-item">
|
|
658
|
-
<div class="dot ${a.status}"></div>
|
|
659
|
-
${esc(a.name)} <span style="color:#666;font-size:9px">${a.status}</span>
|
|
660
|
-
</div>
|
|
661
|
-
`).join('');
|
|
321
|
+
function renderAgentSB(){
|
|
322
|
+
document.getElementById('agent-sidebar').innerHTML=S.agents.map(a=>
|
|
323
|
+
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+' <span style="color:#555;font-size:9px">'+a.status+'</span></div>'
|
|
324
|
+
).join('');
|
|
662
325
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
// Desk decorations per role
|
|
677
|
-
const deskDecor = {
|
|
678
|
-
parser: '๐ป',
|
|
679
|
-
analyzer: '๐',
|
|
680
|
-
tester: '๐งช',
|
|
681
|
-
healer: '๐ง',
|
|
682
|
-
planner: '๐',
|
|
683
|
-
reporter: '๐',
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
office.innerHTML = state.agents.map(a => `
|
|
687
|
-
<div class="croc-desk ${a.status}">
|
|
688
|
-
<div class="status-badge ${a.status}"></div>
|
|
689
|
-
<div class="croc-sprite">${sprites[a.role] || '๐'}</div>
|
|
690
|
-
<div class="croc-name">${esc(a.name)}</div>
|
|
691
|
-
<div class="croc-role">${esc(a.role)} ${deskDecor[a.role] || ''}</div>
|
|
692
|
-
<div class="croc-task">${a.currentTask ? esc(a.currentTask) : ''}</div>
|
|
693
|
-
<div class="pixel-floor"></div>
|
|
694
|
-
</div>
|
|
695
|
-
`).join('');
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function esc(s) {
|
|
699
|
-
if (!s) return '';
|
|
700
|
-
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
326
|
+
function renderOffice(){
|
|
327
|
+
const dd={parser:'๐ป',analyzer:'๐',tester:'๐งช',healer:'๐ง',planner:'๐',reporter:'๐'};
|
|
328
|
+
document.getElementById('croc-office').innerHTML=S.agents.map(a=>{
|
|
329
|
+
const prog=typeof a.progress==='number'?a.progress:0;
|
|
330
|
+
return '<div class="desk '+a.status+'"><div class="badge dot '+a.status+'"></div>'+
|
|
331
|
+
'<div class="croc-sprite">๐</div><div class="croc-name">'+esc(a.name)+'</div>'+
|
|
332
|
+
'<div class="croc-role">'+esc(a.role)+'</div>'+
|
|
333
|
+
'<div class="croc-task">'+(a.currentTask?esc(a.currentTask):'')+'</div>'+
|
|
334
|
+
'<div class="progress-bar"><div class="fill" style="width:'+prog+'%"></div></div>'+
|
|
335
|
+
'<div class="desk-items">'+(dd[a.role]||'')+'</div></div>';
|
|
336
|
+
}).join('');
|
|
701
337
|
}
|
|
338
|
+
function esc(s){return s?s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'):'';}
|
|
702
339
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
document.getElementById('btn-
|
|
706
|
-
document.getElementById('btn-test').addEventListener('click', async () => {
|
|
707
|
-
// M2: will trigger real test pipeline
|
|
708
|
-
try {
|
|
709
|
-
await fetch('/api/agents/tester-croc/task', {
|
|
710
|
-
method: 'POST',
|
|
711
|
-
headers: { 'Content-Type': 'application/json' },
|
|
712
|
-
body: JSON.stringify({ task: 'Running E2E tests...' }),
|
|
713
|
-
});
|
|
714
|
-
} catch (e) {
|
|
715
|
-
console.error('Failed to start test:', e);
|
|
716
|
-
}
|
|
717
|
-
});
|
|
340
|
+
document.getElementById('btn-scan').addEventListener('click',doScan);
|
|
341
|
+
document.getElementById('btn-pipeline').addEventListener('click',doPipeline);
|
|
342
|
+
document.getElementById('btn-reset').addEventListener('click',doReset);
|
|
718
343
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
(async () => {
|
|
722
|
-
setupCanvasInteraction();
|
|
723
|
-
await fetchProject();
|
|
724
|
-
connectWS();
|
|
725
|
-
|
|
726
|
-
// Handle resize
|
|
727
|
-
window.addEventListener('resize', () => {
|
|
728
|
-
layoutGraph();
|
|
729
|
-
renderCanvas();
|
|
730
|
-
});
|
|
731
|
-
})();
|
|
344
|
+
(async()=>{setupCanvas();await fetchProject();connectWS();addLog('๐ OpenCroc Studio ready');
|
|
345
|
+
window.addEventListener('resize',()=>{layoutGraph();renderCanvas();});})();
|
|
732
346
|
</script>
|
|
733
347
|
</body>
|
|
734
348
|
</html>
|