loki-mode 4.2.0
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/LICENSE +21 -0
- package/README.md +691 -0
- package/SKILL.md +191 -0
- package/VERSION +1 -0
- package/autonomy/.loki/dashboard/index.html +2634 -0
- package/autonomy/CONSTITUTION.md +508 -0
- package/autonomy/README.md +201 -0
- package/autonomy/config.example.yaml +152 -0
- package/autonomy/loki +526 -0
- package/autonomy/run.sh +3636 -0
- package/bin/loki-mode.js +26 -0
- package/bin/postinstall.js +60 -0
- package/docs/ACKNOWLEDGEMENTS.md +234 -0
- package/docs/COMPARISON.md +325 -0
- package/docs/COMPETITIVE-ANALYSIS.md +333 -0
- package/docs/INSTALLATION.md +547 -0
- package/docs/auto-claude-comparison.md +276 -0
- package/docs/cursor-comparison.md +225 -0
- package/docs/dashboard-guide.md +355 -0
- package/docs/screenshots/README.md +149 -0
- package/docs/screenshots/dashboard-agents.png +0 -0
- package/docs/screenshots/dashboard-tasks.png +0 -0
- package/docs/thick2thin.md +173 -0
- package/package.json +48 -0
- package/references/advanced-patterns.md +453 -0
- package/references/agent-types.md +243 -0
- package/references/agents.md +1043 -0
- package/references/business-ops.md +550 -0
- package/references/competitive-analysis.md +216 -0
- package/references/confidence-routing.md +371 -0
- package/references/core-workflow.md +275 -0
- package/references/cursor-learnings.md +207 -0
- package/references/deployment.md +604 -0
- package/references/lab-research-patterns.md +534 -0
- package/references/mcp-integration.md +186 -0
- package/references/memory-system.md +467 -0
- package/references/openai-patterns.md +647 -0
- package/references/production-patterns.md +568 -0
- package/references/prompt-repetition.md +192 -0
- package/references/quality-control.md +437 -0
- package/references/sdlc-phases.md +410 -0
- package/references/task-queue.md +361 -0
- package/references/tool-orchestration.md +691 -0
- package/skills/00-index.md +120 -0
- package/skills/agents.md +249 -0
- package/skills/artifacts.md +174 -0
- package/skills/github-integration.md +218 -0
- package/skills/model-selection.md +125 -0
- package/skills/parallel-workflows.md +526 -0
- package/skills/patterns-advanced.md +188 -0
- package/skills/production.md +292 -0
- package/skills/quality-gates.md +180 -0
- package/skills/testing.md +149 -0
- package/skills/troubleshooting.md +109 -0
|
@@ -0,0 +1,2634 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Loki Mode Dashboard - Multi-agent autonomous system monitor">
|
|
7
|
+
<meta name="theme-color" content="#d97757">
|
|
8
|
+
<title>Loki Mode Dashboard</title>
|
|
9
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect fill='%23d97757' rx='15' width='100' height='100'/><text x='50' y='72' font-size='60' font-weight='bold' text-anchor='middle' fill='white'>L</text></svg>">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
13
|
+
<style>
|
|
14
|
+
/* ============================================
|
|
15
|
+
Anthropic Design Language - Light & Dark
|
|
16
|
+
Based on anthropic.com color system
|
|
17
|
+
============================================ */
|
|
18
|
+
:root {
|
|
19
|
+
/* Light Mode (default) - Anthropic cream theme */
|
|
20
|
+
--bg-primary: #faf9f0;
|
|
21
|
+
--bg-secondary: #f5f4eb;
|
|
22
|
+
--bg-tertiary: #eeeddf;
|
|
23
|
+
--bg-card: #ffffff;
|
|
24
|
+
--bg-hover: #f0efe6;
|
|
25
|
+
--accent: #d97757;
|
|
26
|
+
--accent-light: #e8956f;
|
|
27
|
+
--accent-muted: rgba(217, 119, 87, 0.12);
|
|
28
|
+
--text-primary: #1a1a1a;
|
|
29
|
+
--text-secondary: #5c5c5c;
|
|
30
|
+
--text-muted: #8a8a8a;
|
|
31
|
+
--border: #e5e3de;
|
|
32
|
+
--border-light: #d4d2cb;
|
|
33
|
+
|
|
34
|
+
/* Status Colors */
|
|
35
|
+
--green: #16a34a;
|
|
36
|
+
--green-muted: rgba(22, 163, 74, 0.12);
|
|
37
|
+
--yellow: #ca8a04;
|
|
38
|
+
--yellow-muted: rgba(202, 138, 4, 0.12);
|
|
39
|
+
--red: #dc2626;
|
|
40
|
+
--red-muted: rgba(220, 38, 38, 0.12);
|
|
41
|
+
--blue: #2563eb;
|
|
42
|
+
--blue-muted: rgba(37, 99, 235, 0.12);
|
|
43
|
+
--purple: #9333ea;
|
|
44
|
+
--purple-muted: rgba(147, 51, 234, 0.12);
|
|
45
|
+
|
|
46
|
+
/* Model Colors */
|
|
47
|
+
--opus: #d97706;
|
|
48
|
+
--sonnet: #4f46e5;
|
|
49
|
+
--haiku: #059669;
|
|
50
|
+
|
|
51
|
+
/* Transition */
|
|
52
|
+
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[data-theme="dark"] {
|
|
56
|
+
/* Dark Mode - Anthropic dark theme */
|
|
57
|
+
--bg-primary: #131314;
|
|
58
|
+
--bg-secondary: #1a1a1b;
|
|
59
|
+
--bg-tertiary: #232325;
|
|
60
|
+
--bg-card: #1e1e20;
|
|
61
|
+
--bg-hover: #2a2a2d;
|
|
62
|
+
--accent: #d97757;
|
|
63
|
+
--accent-light: #e8956f;
|
|
64
|
+
--accent-muted: rgba(217, 119, 87, 0.15);
|
|
65
|
+
--text-primary: #f5f5f5;
|
|
66
|
+
--text-secondary: #a1a1a6;
|
|
67
|
+
--text-muted: #6b6b70;
|
|
68
|
+
--border: #2d2d30;
|
|
69
|
+
--border-light: #3d3d42;
|
|
70
|
+
|
|
71
|
+
--green: #22c55e;
|
|
72
|
+
--green-muted: rgba(34, 197, 94, 0.15);
|
|
73
|
+
--yellow: #eab308;
|
|
74
|
+
--yellow-muted: rgba(234, 179, 8, 0.15);
|
|
75
|
+
--red: #ef4444;
|
|
76
|
+
--red-muted: rgba(239, 68, 68, 0.15);
|
|
77
|
+
--blue: #3b82f6;
|
|
78
|
+
--blue-muted: rgba(59, 130, 246, 0.15);
|
|
79
|
+
--purple: #a855f7;
|
|
80
|
+
--purple-muted: rgba(168, 85, 247, 0.15);
|
|
81
|
+
|
|
82
|
+
--opus: #f59e0b;
|
|
83
|
+
--sonnet: #6366f1;
|
|
84
|
+
--haiku: #10b981;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
* {
|
|
88
|
+
margin: 0;
|
|
89
|
+
padding: 0;
|
|
90
|
+
box-sizing: border-box;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
body {
|
|
94
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
95
|
+
background: var(--bg-primary);
|
|
96
|
+
color: var(--text-primary);
|
|
97
|
+
min-height: 100vh;
|
|
98
|
+
line-height: 1.5;
|
|
99
|
+
-webkit-font-smoothing: antialiased;
|
|
100
|
+
transition: background var(--transition), color var(--transition);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Layout */
|
|
104
|
+
.dashboard {
|
|
105
|
+
display: grid;
|
|
106
|
+
grid-template-columns: 260px 1fr;
|
|
107
|
+
min-height: 100vh;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Sidebar */
|
|
111
|
+
.sidebar {
|
|
112
|
+
background: var(--bg-secondary);
|
|
113
|
+
border-right: 1px solid var(--border);
|
|
114
|
+
padding: 20px 12px;
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: 20px;
|
|
118
|
+
position: sticky;
|
|
119
|
+
top: 0;
|
|
120
|
+
height: 100vh;
|
|
121
|
+
overflow-y: auto;
|
|
122
|
+
transition: background var(--transition), border-color var(--transition);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.logo {
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 10px;
|
|
129
|
+
padding: 0 8px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.logo-icon {
|
|
133
|
+
width: 36px;
|
|
134
|
+
height: 36px;
|
|
135
|
+
background: linear-gradient(135deg, var(--accent), var(--accent-light));
|
|
136
|
+
border-radius: 8px;
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
font-weight: 700;
|
|
141
|
+
font-size: 16px;
|
|
142
|
+
color: white;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.logo-text {
|
|
146
|
+
font-size: 18px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.logo-version {
|
|
151
|
+
font-size: 10px;
|
|
152
|
+
color: var(--text-muted);
|
|
153
|
+
font-weight: 400;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Theme Toggle */
|
|
157
|
+
.theme-toggle {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 8px;
|
|
161
|
+
padding: 8px 12px;
|
|
162
|
+
background: var(--bg-tertiary);
|
|
163
|
+
border-radius: 8px;
|
|
164
|
+
font-size: 12px;
|
|
165
|
+
color: var(--text-secondary);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.theme-toggle-btn {
|
|
169
|
+
width: 44px;
|
|
170
|
+
height: 24px;
|
|
171
|
+
background: var(--border);
|
|
172
|
+
border-radius: 12px;
|
|
173
|
+
border: none;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
position: relative;
|
|
176
|
+
transition: background var(--transition);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.theme-toggle-btn::after {
|
|
180
|
+
content: '';
|
|
181
|
+
position: absolute;
|
|
182
|
+
top: 2px;
|
|
183
|
+
left: 2px;
|
|
184
|
+
width: 20px;
|
|
185
|
+
height: 20px;
|
|
186
|
+
background: var(--bg-card);
|
|
187
|
+
border-radius: 50%;
|
|
188
|
+
transition: transform var(--transition);
|
|
189
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[data-theme="dark"] .theme-toggle-btn {
|
|
193
|
+
background: var(--accent);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[data-theme="dark"] .theme-toggle-btn::after {
|
|
197
|
+
transform: translateX(20px);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Connection Status */
|
|
201
|
+
.connection-status {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 6px;
|
|
205
|
+
padding: 6px 12px;
|
|
206
|
+
background: var(--bg-tertiary);
|
|
207
|
+
border-radius: 6px;
|
|
208
|
+
font-size: 11px;
|
|
209
|
+
color: var(--text-muted);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.connection-dot {
|
|
213
|
+
width: 6px;
|
|
214
|
+
height: 6px;
|
|
215
|
+
border-radius: 50%;
|
|
216
|
+
background: var(--red);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.connection-dot.connected {
|
|
220
|
+
background: var(--green);
|
|
221
|
+
animation: pulse 2s infinite;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@keyframes pulse {
|
|
225
|
+
0%, 100% { opacity: 1; }
|
|
226
|
+
50% { opacity: 0.5; }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Sidebar Navigation */
|
|
230
|
+
.sidebar-section {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex-direction: column;
|
|
233
|
+
gap: 4px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.sidebar-title {
|
|
237
|
+
font-size: 10px;
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
text-transform: uppercase;
|
|
240
|
+
letter-spacing: 0.5px;
|
|
241
|
+
color: var(--text-muted);
|
|
242
|
+
padding: 0 12px 4px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.sidebar-item {
|
|
246
|
+
display: flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
gap: 10px;
|
|
249
|
+
padding: 8px 12px;
|
|
250
|
+
border-radius: 6px;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
transition: all var(--transition);
|
|
253
|
+
color: var(--text-secondary);
|
|
254
|
+
font-size: 13px;
|
|
255
|
+
font-weight: 500;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.sidebar-item:hover {
|
|
259
|
+
background: var(--bg-hover);
|
|
260
|
+
color: var(--text-primary);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.sidebar-item.active {
|
|
264
|
+
background: var(--accent-muted);
|
|
265
|
+
color: var(--accent);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.sidebar-icon {
|
|
269
|
+
width: 18px;
|
|
270
|
+
height: 18px;
|
|
271
|
+
stroke: currentColor;
|
|
272
|
+
stroke-width: 1.5;
|
|
273
|
+
fill: none;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Status Panel */
|
|
277
|
+
.status-panel {
|
|
278
|
+
background: var(--bg-tertiary);
|
|
279
|
+
border-radius: 10px;
|
|
280
|
+
padding: 14px;
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
gap: 10px;
|
|
284
|
+
transition: background var(--transition);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.status-row {
|
|
288
|
+
display: flex;
|
|
289
|
+
justify-content: space-between;
|
|
290
|
+
align-items: center;
|
|
291
|
+
font-size: 12px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.status-label {
|
|
295
|
+
color: var(--text-secondary);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.status-value {
|
|
299
|
+
font-weight: 500;
|
|
300
|
+
display: flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 6px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.status-dot {
|
|
306
|
+
width: 8px;
|
|
307
|
+
height: 8px;
|
|
308
|
+
border-radius: 50%;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.status-dot.active { background: var(--green); animation: pulse 2s infinite; }
|
|
312
|
+
.status-dot.idle { background: var(--text-muted); }
|
|
313
|
+
.status-dot.paused { background: var(--yellow); }
|
|
314
|
+
.status-dot.stopped { background: var(--red); }
|
|
315
|
+
.status-dot.error { background: var(--red); }
|
|
316
|
+
.status-dot.offline { background: var(--text-muted); }
|
|
317
|
+
|
|
318
|
+
/* Intervention Controls */
|
|
319
|
+
.intervention-controls {
|
|
320
|
+
display: flex;
|
|
321
|
+
gap: 6px;
|
|
322
|
+
margin-top: 6px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.intervention-btn {
|
|
326
|
+
flex: 1;
|
|
327
|
+
padding: 6px 10px;
|
|
328
|
+
border-radius: 6px;
|
|
329
|
+
border: 1px solid var(--border);
|
|
330
|
+
background: var(--bg-card);
|
|
331
|
+
color: var(--text-secondary);
|
|
332
|
+
font-size: 11px;
|
|
333
|
+
font-weight: 500;
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
transition: all var(--transition);
|
|
336
|
+
display: flex;
|
|
337
|
+
align-items: center;
|
|
338
|
+
justify-content: center;
|
|
339
|
+
gap: 4px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.intervention-btn:hover {
|
|
343
|
+
background: var(--bg-hover);
|
|
344
|
+
color: var(--text-primary);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.intervention-btn.pause:hover {
|
|
348
|
+
background: var(--yellow-muted);
|
|
349
|
+
color: var(--yellow);
|
|
350
|
+
border-color: var(--yellow);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.intervention-btn.stop:hover {
|
|
354
|
+
background: var(--red-muted);
|
|
355
|
+
color: var(--red);
|
|
356
|
+
border-color: var(--red);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* Main Content */
|
|
360
|
+
.main-content {
|
|
361
|
+
padding: 20px 24px;
|
|
362
|
+
overflow-y: auto;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.header {
|
|
366
|
+
display: flex;
|
|
367
|
+
justify-content: space-between;
|
|
368
|
+
align-items: center;
|
|
369
|
+
margin-bottom: 24px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.header-title {
|
|
373
|
+
font-size: 22px;
|
|
374
|
+
font-weight: 600;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.header-actions {
|
|
378
|
+
display: flex;
|
|
379
|
+
gap: 10px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.btn {
|
|
383
|
+
padding: 8px 14px;
|
|
384
|
+
border-radius: 6px;
|
|
385
|
+
font-size: 13px;
|
|
386
|
+
font-weight: 500;
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
transition: all var(--transition);
|
|
389
|
+
display: flex;
|
|
390
|
+
align-items: center;
|
|
391
|
+
gap: 6px;
|
|
392
|
+
border: none;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.btn-primary {
|
|
396
|
+
background: var(--accent);
|
|
397
|
+
color: white;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.btn-primary:hover {
|
|
401
|
+
background: var(--accent-light);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.btn-secondary {
|
|
405
|
+
background: var(--bg-tertiary);
|
|
406
|
+
color: var(--text-primary);
|
|
407
|
+
border: 1px solid var(--border);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.btn-secondary:hover {
|
|
411
|
+
background: var(--bg-hover);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* Stats Row */
|
|
415
|
+
.stats-row {
|
|
416
|
+
display: grid;
|
|
417
|
+
grid-template-columns: repeat(5, 1fr);
|
|
418
|
+
gap: 12px;
|
|
419
|
+
margin-bottom: 24px;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.stat-card {
|
|
423
|
+
background: var(--bg-card);
|
|
424
|
+
border: 1px solid var(--border);
|
|
425
|
+
border-radius: 10px;
|
|
426
|
+
padding: 16px;
|
|
427
|
+
transition: all var(--transition);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.stat-label {
|
|
431
|
+
font-size: 12px;
|
|
432
|
+
color: var(--text-secondary);
|
|
433
|
+
margin-bottom: 6px;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.stat-value {
|
|
437
|
+
font-size: 24px;
|
|
438
|
+
font-weight: 600;
|
|
439
|
+
font-family: 'JetBrains Mono', monospace;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.stat-change {
|
|
443
|
+
font-size: 11px;
|
|
444
|
+
margin-top: 6px;
|
|
445
|
+
display: flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
gap: 4px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.stat-change.positive { color: var(--green); }
|
|
451
|
+
.stat-change.negative { color: var(--red); }
|
|
452
|
+
|
|
453
|
+
/* Kanban Board */
|
|
454
|
+
.kanban-container {
|
|
455
|
+
margin-bottom: 24px;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.section-header {
|
|
459
|
+
display: flex;
|
|
460
|
+
justify-content: space-between;
|
|
461
|
+
align-items: center;
|
|
462
|
+
margin-bottom: 16px;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.section-title {
|
|
466
|
+
font-size: 16px;
|
|
467
|
+
font-weight: 600;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.kanban-board {
|
|
471
|
+
display: grid;
|
|
472
|
+
grid-template-columns: repeat(4, 1fr);
|
|
473
|
+
gap: 12px;
|
|
474
|
+
min-height: 350px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.kanban-column {
|
|
478
|
+
background: var(--bg-secondary);
|
|
479
|
+
border-radius: 10px;
|
|
480
|
+
padding: 12px;
|
|
481
|
+
display: flex;
|
|
482
|
+
flex-direction: column;
|
|
483
|
+
transition: background var(--transition);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.kanban-column-header {
|
|
487
|
+
display: flex;
|
|
488
|
+
justify-content: space-between;
|
|
489
|
+
align-items: center;
|
|
490
|
+
margin-bottom: 12px;
|
|
491
|
+
padding-bottom: 10px;
|
|
492
|
+
border-bottom: 2px solid var(--border);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.kanban-column.pending .kanban-column-header { border-color: var(--text-muted); }
|
|
496
|
+
.kanban-column.in-progress .kanban-column-header { border-color: var(--blue); }
|
|
497
|
+
.kanban-column.review .kanban-column-header { border-color: var(--purple); }
|
|
498
|
+
.kanban-column.completed .kanban-column-header { border-color: var(--green); }
|
|
499
|
+
|
|
500
|
+
.kanban-column-title {
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
font-weight: 600;
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
gap: 6px;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.kanban-column-count {
|
|
509
|
+
background: var(--bg-tertiary);
|
|
510
|
+
padding: 2px 8px;
|
|
511
|
+
border-radius: 10px;
|
|
512
|
+
font-size: 11px;
|
|
513
|
+
font-weight: 600;
|
|
514
|
+
font-family: 'JetBrains Mono', monospace;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.kanban-tasks {
|
|
518
|
+
flex: 1;
|
|
519
|
+
display: flex;
|
|
520
|
+
flex-direction: column;
|
|
521
|
+
gap: 8px;
|
|
522
|
+
min-height: 80px;
|
|
523
|
+
transition: background var(--transition);
|
|
524
|
+
border-radius: 6px;
|
|
525
|
+
padding: 4px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.kanban-tasks.drag-over {
|
|
529
|
+
background: var(--bg-hover);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.task-card {
|
|
533
|
+
background: var(--bg-card);
|
|
534
|
+
border: 1px solid var(--border);
|
|
535
|
+
border-radius: 6px;
|
|
536
|
+
padding: 10px;
|
|
537
|
+
cursor: grab;
|
|
538
|
+
transition: all var(--transition);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.task-card:hover {
|
|
542
|
+
border-color: var(--border-light);
|
|
543
|
+
transform: translateY(-1px);
|
|
544
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
[data-theme="dark"] .task-card:hover {
|
|
548
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.task-card.dragging {
|
|
552
|
+
opacity: 0.5;
|
|
553
|
+
cursor: grabbing;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.task-card.from-server {
|
|
557
|
+
border-left: 3px solid var(--accent);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.task-card-header {
|
|
561
|
+
display: flex;
|
|
562
|
+
justify-content: space-between;
|
|
563
|
+
align-items: flex-start;
|
|
564
|
+
margin-bottom: 6px;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.task-id {
|
|
568
|
+
font-size: 11px;
|
|
569
|
+
font-weight: 600;
|
|
570
|
+
font-family: 'JetBrains Mono', monospace;
|
|
571
|
+
color: var(--accent);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.task-priority {
|
|
575
|
+
font-size: 9px;
|
|
576
|
+
padding: 2px 5px;
|
|
577
|
+
border-radius: 3px;
|
|
578
|
+
font-weight: 500;
|
|
579
|
+
text-transform: uppercase;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.task-priority.high {
|
|
583
|
+
background: var(--red-muted);
|
|
584
|
+
color: var(--red);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.task-priority.medium {
|
|
588
|
+
background: var(--yellow-muted);
|
|
589
|
+
color: var(--yellow);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.task-priority.low {
|
|
593
|
+
background: var(--green-muted);
|
|
594
|
+
color: var(--green);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.task-title {
|
|
598
|
+
font-size: 12px;
|
|
599
|
+
font-weight: 500;
|
|
600
|
+
margin-bottom: 6px;
|
|
601
|
+
line-height: 1.4;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.task-meta {
|
|
605
|
+
display: flex;
|
|
606
|
+
justify-content: space-between;
|
|
607
|
+
align-items: center;
|
|
608
|
+
font-size: 10px;
|
|
609
|
+
color: var(--text-muted);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.task-type {
|
|
613
|
+
background: var(--bg-tertiary);
|
|
614
|
+
padding: 2px 6px;
|
|
615
|
+
border-radius: 3px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.add-task-btn {
|
|
619
|
+
background: transparent;
|
|
620
|
+
border: 1px dashed var(--border);
|
|
621
|
+
border-radius: 6px;
|
|
622
|
+
padding: 10px;
|
|
623
|
+
color: var(--text-muted);
|
|
624
|
+
font-size: 12px;
|
|
625
|
+
cursor: pointer;
|
|
626
|
+
transition: all var(--transition);
|
|
627
|
+
display: flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
justify-content: center;
|
|
630
|
+
gap: 6px;
|
|
631
|
+
margin-top: 8px;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.add-task-btn:hover {
|
|
635
|
+
border-color: var(--accent);
|
|
636
|
+
color: var(--accent);
|
|
637
|
+
background: var(--accent-muted);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* Agents Grid */
|
|
641
|
+
.agents-section {
|
|
642
|
+
margin-bottom: 24px;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.agents-grid {
|
|
646
|
+
display: grid;
|
|
647
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
648
|
+
gap: 12px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.agent-card {
|
|
652
|
+
background: var(--bg-card);
|
|
653
|
+
border: 1px solid var(--border);
|
|
654
|
+
border-radius: 10px;
|
|
655
|
+
padding: 16px;
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
transition: all var(--transition);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.agent-card:hover {
|
|
661
|
+
border-color: var(--border-light);
|
|
662
|
+
transform: translateY(-1px);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.agent-card.from-server {
|
|
666
|
+
border-left: 3px solid var(--green);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.agent-header {
|
|
670
|
+
display: flex;
|
|
671
|
+
justify-content: space-between;
|
|
672
|
+
align-items: flex-start;
|
|
673
|
+
margin-bottom: 10px;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.agent-info h3 {
|
|
677
|
+
font-size: 13px;
|
|
678
|
+
font-weight: 600;
|
|
679
|
+
margin-bottom: 2px;
|
|
680
|
+
font-family: 'JetBrains Mono', monospace;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.agent-type {
|
|
684
|
+
font-size: 11px;
|
|
685
|
+
color: var(--text-secondary);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.model-badge {
|
|
689
|
+
padding: 3px 8px;
|
|
690
|
+
border-radius: 10px;
|
|
691
|
+
font-size: 10px;
|
|
692
|
+
font-weight: 600;
|
|
693
|
+
text-transform: uppercase;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.model-badge.opus {
|
|
697
|
+
background: rgba(217, 119, 6, 0.12);
|
|
698
|
+
color: var(--opus);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.model-badge.sonnet {
|
|
702
|
+
background: rgba(79, 70, 229, 0.12);
|
|
703
|
+
color: var(--sonnet);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.model-badge.haiku {
|
|
707
|
+
background: rgba(5, 150, 105, 0.12);
|
|
708
|
+
color: var(--haiku);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.agent-work {
|
|
712
|
+
font-size: 12px;
|
|
713
|
+
color: var(--text-secondary);
|
|
714
|
+
margin-bottom: 12px;
|
|
715
|
+
line-height: 1.5;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.agent-stats {
|
|
719
|
+
display: flex;
|
|
720
|
+
gap: 12px;
|
|
721
|
+
font-size: 11px;
|
|
722
|
+
color: var(--text-muted);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.agent-stat {
|
|
726
|
+
display: flex;
|
|
727
|
+
align-items: center;
|
|
728
|
+
gap: 4px;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.agent-status {
|
|
732
|
+
display: flex;
|
|
733
|
+
align-items: center;
|
|
734
|
+
gap: 6px;
|
|
735
|
+
margin-top: 10px;
|
|
736
|
+
padding-top: 10px;
|
|
737
|
+
border-top: 1px solid var(--border);
|
|
738
|
+
font-size: 11px;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.agent-status.active { color: var(--green); }
|
|
742
|
+
.agent-status.idle { color: var(--text-muted); }
|
|
743
|
+
.agent-status.completed { color: var(--purple); }
|
|
744
|
+
.agent-status.error { color: var(--red); }
|
|
745
|
+
|
|
746
|
+
/* System Grid */
|
|
747
|
+
.system-grid {
|
|
748
|
+
display: grid;
|
|
749
|
+
grid-template-columns: repeat(3, 1fr);
|
|
750
|
+
gap: 12px;
|
|
751
|
+
margin-bottom: 24px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.system-card {
|
|
755
|
+
background: var(--bg-card);
|
|
756
|
+
border: 1px solid var(--border);
|
|
757
|
+
border-radius: 10px;
|
|
758
|
+
padding: 16px;
|
|
759
|
+
transition: all var(--transition);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.system-card-header {
|
|
763
|
+
display: flex;
|
|
764
|
+
justify-content: space-between;
|
|
765
|
+
align-items: center;
|
|
766
|
+
margin-bottom: 12px;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.system-card-title {
|
|
770
|
+
font-size: 13px;
|
|
771
|
+
font-weight: 600;
|
|
772
|
+
display: flex;
|
|
773
|
+
align-items: center;
|
|
774
|
+
gap: 6px;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.system-card-status {
|
|
778
|
+
font-size: 10px;
|
|
779
|
+
padding: 3px 8px;
|
|
780
|
+
border-radius: 10px;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.system-card-status.healthy {
|
|
784
|
+
background: var(--green-muted);
|
|
785
|
+
color: var(--green);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.system-card-status.warning {
|
|
789
|
+
background: var(--yellow-muted);
|
|
790
|
+
color: var(--yellow);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/* RARV Cycle */
|
|
794
|
+
.rarv-cycle {
|
|
795
|
+
display: flex;
|
|
796
|
+
justify-content: space-between;
|
|
797
|
+
gap: 8px;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.rarv-step {
|
|
801
|
+
flex: 1;
|
|
802
|
+
text-align: center;
|
|
803
|
+
padding: 10px 8px;
|
|
804
|
+
background: var(--bg-tertiary);
|
|
805
|
+
border-radius: 6px;
|
|
806
|
+
font-size: 11px;
|
|
807
|
+
font-weight: 500;
|
|
808
|
+
position: relative;
|
|
809
|
+
transition: all var(--transition);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.rarv-step.active {
|
|
813
|
+
background: var(--accent-muted);
|
|
814
|
+
color: var(--accent);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.rarv-step::after {
|
|
818
|
+
content: '';
|
|
819
|
+
position: absolute;
|
|
820
|
+
right: -6px;
|
|
821
|
+
top: 50%;
|
|
822
|
+
transform: translateY(-50%);
|
|
823
|
+
width: 0;
|
|
824
|
+
height: 0;
|
|
825
|
+
border-left: 4px solid var(--text-muted);
|
|
826
|
+
border-top: 4px solid transparent;
|
|
827
|
+
border-bottom: 4px solid transparent;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.rarv-step:last-child::after {
|
|
831
|
+
display: none;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* Memory Bars */
|
|
835
|
+
.memory-bars {
|
|
836
|
+
display: flex;
|
|
837
|
+
flex-direction: column;
|
|
838
|
+
gap: 10px;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.memory-bar {
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-direction: column;
|
|
844
|
+
gap: 4px;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.memory-bar-header {
|
|
848
|
+
display: flex;
|
|
849
|
+
justify-content: space-between;
|
|
850
|
+
font-size: 11px;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.memory-bar-label {
|
|
854
|
+
color: var(--text-secondary);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.memory-bar-value {
|
|
858
|
+
font-family: 'JetBrains Mono', monospace;
|
|
859
|
+
color: var(--text-muted);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.memory-bar-track {
|
|
863
|
+
height: 5px;
|
|
864
|
+
background: var(--bg-tertiary);
|
|
865
|
+
border-radius: 3px;
|
|
866
|
+
overflow: hidden;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.memory-bar-fill {
|
|
870
|
+
height: 100%;
|
|
871
|
+
border-radius: 3px;
|
|
872
|
+
transition: width 0.3s ease;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.memory-bar-fill.episodic { background: var(--blue); }
|
|
876
|
+
.memory-bar-fill.semantic { background: var(--purple); }
|
|
877
|
+
.memory-bar-fill.procedural { background: var(--green); }
|
|
878
|
+
|
|
879
|
+
/* Quality Gates */
|
|
880
|
+
.quality-gates {
|
|
881
|
+
display: grid;
|
|
882
|
+
grid-template-columns: repeat(2, 1fr);
|
|
883
|
+
gap: 6px;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.quality-gate {
|
|
887
|
+
display: flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
gap: 6px;
|
|
890
|
+
padding: 6px 10px;
|
|
891
|
+
background: var(--bg-tertiary);
|
|
892
|
+
border-radius: 5px;
|
|
893
|
+
font-size: 11px;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.gate-icon {
|
|
897
|
+
width: 14px;
|
|
898
|
+
height: 14px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.gate-icon.pass { color: var(--green); }
|
|
902
|
+
.gate-icon.fail { color: var(--red); }
|
|
903
|
+
.gate-icon.pending { color: var(--yellow); }
|
|
904
|
+
|
|
905
|
+
/* Modal */
|
|
906
|
+
.modal-overlay {
|
|
907
|
+
position: fixed;
|
|
908
|
+
top: 0;
|
|
909
|
+
left: 0;
|
|
910
|
+
right: 0;
|
|
911
|
+
bottom: 0;
|
|
912
|
+
background: rgba(0, 0, 0, 0.5);
|
|
913
|
+
display: flex;
|
|
914
|
+
align-items: center;
|
|
915
|
+
justify-content: center;
|
|
916
|
+
z-index: 1000;
|
|
917
|
+
opacity: 0;
|
|
918
|
+
visibility: hidden;
|
|
919
|
+
transition: all var(--transition);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
[data-theme="dark"] .modal-overlay {
|
|
923
|
+
background: rgba(0, 0, 0, 0.7);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.modal-overlay.active {
|
|
927
|
+
opacity: 1;
|
|
928
|
+
visibility: visible;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.modal {
|
|
932
|
+
background: var(--bg-secondary);
|
|
933
|
+
border: 1px solid var(--border);
|
|
934
|
+
border-radius: 12px;
|
|
935
|
+
width: 90%;
|
|
936
|
+
max-width: 480px;
|
|
937
|
+
padding: 20px;
|
|
938
|
+
transform: scale(0.95);
|
|
939
|
+
transition: transform var(--transition);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.modal-overlay.active .modal {
|
|
943
|
+
transform: scale(1);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.modal-header {
|
|
947
|
+
display: flex;
|
|
948
|
+
justify-content: space-between;
|
|
949
|
+
align-items: center;
|
|
950
|
+
margin-bottom: 16px;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.modal-title {
|
|
954
|
+
font-size: 16px;
|
|
955
|
+
font-weight: 600;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.modal-close {
|
|
959
|
+
background: none;
|
|
960
|
+
border: none;
|
|
961
|
+
color: var(--text-muted);
|
|
962
|
+
cursor: pointer;
|
|
963
|
+
padding: 4px;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.modal-close:hover {
|
|
967
|
+
color: var(--text-primary);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.form-group {
|
|
971
|
+
margin-bottom: 14px;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.form-label {
|
|
975
|
+
display: block;
|
|
976
|
+
font-size: 12px;
|
|
977
|
+
font-weight: 500;
|
|
978
|
+
margin-bottom: 6px;
|
|
979
|
+
color: var(--text-secondary);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.form-input, .form-select, .form-textarea {
|
|
983
|
+
width: 100%;
|
|
984
|
+
padding: 8px 10px;
|
|
985
|
+
background: var(--bg-tertiary);
|
|
986
|
+
border: 1px solid var(--border);
|
|
987
|
+
border-radius: 6px;
|
|
988
|
+
color: var(--text-primary);
|
|
989
|
+
font-size: 13px;
|
|
990
|
+
font-family: inherit;
|
|
991
|
+
transition: border-color var(--transition);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
|
995
|
+
outline: none;
|
|
996
|
+
border-color: var(--accent);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.form-textarea {
|
|
1000
|
+
min-height: 70px;
|
|
1001
|
+
resize: vertical;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.modal-actions {
|
|
1005
|
+
display: flex;
|
|
1006
|
+
justify-content: flex-end;
|
|
1007
|
+
gap: 10px;
|
|
1008
|
+
margin-top: 20px;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* Empty State */
|
|
1012
|
+
.empty-state {
|
|
1013
|
+
text-align: center;
|
|
1014
|
+
padding: 24px;
|
|
1015
|
+
color: var(--text-muted);
|
|
1016
|
+
font-size: 12px;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/* Responsive */
|
|
1020
|
+
@media (max-width: 1400px) {
|
|
1021
|
+
.stats-row { grid-template-columns: repeat(3, 1fr); }
|
|
1022
|
+
.system-grid { grid-template-columns: repeat(2, 1fr); }
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
@media (max-width: 1200px) {
|
|
1026
|
+
.kanban-board { grid-template-columns: repeat(2, 1fr); }
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
@media (max-width: 1024px) {
|
|
1030
|
+
.dashboard { grid-template-columns: 1fr; }
|
|
1031
|
+
.sidebar { display: none; }
|
|
1032
|
+
.mobile-header { display: flex !important; }
|
|
1033
|
+
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.mobile-header {
|
|
1037
|
+
display: none;
|
|
1038
|
+
justify-content: space-between;
|
|
1039
|
+
align-items: center;
|
|
1040
|
+
padding: 12px 16px;
|
|
1041
|
+
background: var(--bg-secondary);
|
|
1042
|
+
border-bottom: 1px solid var(--border);
|
|
1043
|
+
position: sticky;
|
|
1044
|
+
top: 0;
|
|
1045
|
+
z-index: 100;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.mobile-logo {
|
|
1049
|
+
display: flex;
|
|
1050
|
+
align-items: center;
|
|
1051
|
+
gap: 8px;
|
|
1052
|
+
font-weight: 600;
|
|
1053
|
+
font-size: 16px;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
.mobile-logo-icon {
|
|
1057
|
+
width: 28px;
|
|
1058
|
+
height: 28px;
|
|
1059
|
+
background: linear-gradient(135deg, var(--accent), var(--accent-light));
|
|
1060
|
+
border-radius: 6px;
|
|
1061
|
+
display: flex;
|
|
1062
|
+
align-items: center;
|
|
1063
|
+
justify-content: center;
|
|
1064
|
+
font-weight: 700;
|
|
1065
|
+
font-size: 14px;
|
|
1066
|
+
color: white;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.mobile-controls {
|
|
1070
|
+
display: flex;
|
|
1071
|
+
align-items: center;
|
|
1072
|
+
gap: 12px;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.mobile-status {
|
|
1076
|
+
display: flex;
|
|
1077
|
+
align-items: center;
|
|
1078
|
+
gap: 6px;
|
|
1079
|
+
font-size: 11px;
|
|
1080
|
+
color: var(--text-muted);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
@media (max-width: 768px) {
|
|
1084
|
+
.main-content { padding: 16px; }
|
|
1085
|
+
.kanban-board { grid-template-columns: 1fr; }
|
|
1086
|
+
.system-grid { grid-template-columns: 1fr; }
|
|
1087
|
+
.stats-row { grid-template-columns: 1fr; }
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/* Scrollbar */
|
|
1091
|
+
::-webkit-scrollbar { width: 6px; }
|
|
1092
|
+
::-webkit-scrollbar-track { background: var(--bg-primary); }
|
|
1093
|
+
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
1094
|
+
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
|
|
1095
|
+
|
|
1096
|
+
/* Terminal Output Panel */
|
|
1097
|
+
.terminal-section {
|
|
1098
|
+
margin-bottom: 24px;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.terminal-container {
|
|
1102
|
+
background: #1a1a1b;
|
|
1103
|
+
border: 1px solid var(--border);
|
|
1104
|
+
border-radius: 10px;
|
|
1105
|
+
overflow: hidden;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.terminal-header {
|
|
1109
|
+
display: flex;
|
|
1110
|
+
justify-content: space-between;
|
|
1111
|
+
align-items: center;
|
|
1112
|
+
padding: 10px 14px;
|
|
1113
|
+
background: #232325;
|
|
1114
|
+
border-bottom: 1px solid var(--border);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.terminal-title {
|
|
1118
|
+
display: flex;
|
|
1119
|
+
align-items: center;
|
|
1120
|
+
gap: 8px;
|
|
1121
|
+
font-size: 12px;
|
|
1122
|
+
font-weight: 600;
|
|
1123
|
+
color: #a1a1a6;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.terminal-dots {
|
|
1127
|
+
display: flex;
|
|
1128
|
+
gap: 6px;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.terminal-dot {
|
|
1132
|
+
width: 10px;
|
|
1133
|
+
height: 10px;
|
|
1134
|
+
border-radius: 50%;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.terminal-dot.red { background: #ff5f56; }
|
|
1138
|
+
.terminal-dot.yellow { background: #ffbd2e; }
|
|
1139
|
+
.terminal-dot.green { background: #27c93f; }
|
|
1140
|
+
|
|
1141
|
+
.terminal-controls {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
gap: 8px;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.terminal-btn {
|
|
1147
|
+
padding: 4px 10px;
|
|
1148
|
+
background: #2a2a2d;
|
|
1149
|
+
border: 1px solid #3d3d42;
|
|
1150
|
+
border-radius: 4px;
|
|
1151
|
+
color: #a1a1a6;
|
|
1152
|
+
font-size: 11px;
|
|
1153
|
+
cursor: pointer;
|
|
1154
|
+
transition: all var(--transition);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.terminal-btn:hover {
|
|
1158
|
+
background: #3d3d42;
|
|
1159
|
+
color: #f5f5f5;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.terminal-btn.active {
|
|
1163
|
+
background: var(--accent);
|
|
1164
|
+
border-color: var(--accent);
|
|
1165
|
+
color: white;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.terminal-output {
|
|
1169
|
+
padding: 14px;
|
|
1170
|
+
max-height: 350px;
|
|
1171
|
+
overflow-y: auto;
|
|
1172
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1173
|
+
font-size: 12px;
|
|
1174
|
+
line-height: 1.6;
|
|
1175
|
+
color: #e5e5e5;
|
|
1176
|
+
background: #1a1a1b;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.terminal-line {
|
|
1180
|
+
display: flex;
|
|
1181
|
+
gap: 10px;
|
|
1182
|
+
white-space: pre-wrap;
|
|
1183
|
+
word-break: break-all;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.terminal-line .timestamp {
|
|
1187
|
+
color: #6b6b70;
|
|
1188
|
+
flex-shrink: 0;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.terminal-line .level-info { color: #3b82f6; }
|
|
1192
|
+
.terminal-line .level-success { color: #22c55e; }
|
|
1193
|
+
.terminal-line .level-warning { color: #eab308; }
|
|
1194
|
+
.terminal-line .level-error { color: #ef4444; }
|
|
1195
|
+
.terminal-line .level-step { color: #a855f7; }
|
|
1196
|
+
.terminal-line .level-agent { color: #d97757; }
|
|
1197
|
+
|
|
1198
|
+
.terminal-line .message {
|
|
1199
|
+
flex: 1;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.terminal-empty {
|
|
1203
|
+
color: #6b6b70;
|
|
1204
|
+
text-align: center;
|
|
1205
|
+
padding: 40px;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/* Quick Actions Bar */
|
|
1209
|
+
.quick-actions {
|
|
1210
|
+
display: flex;
|
|
1211
|
+
gap: 10px;
|
|
1212
|
+
margin-bottom: 20px;
|
|
1213
|
+
padding: 12px;
|
|
1214
|
+
background: var(--bg-secondary);
|
|
1215
|
+
border: 1px solid var(--border);
|
|
1216
|
+
border-radius: 10px;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.quick-action-btn {
|
|
1220
|
+
flex: 1;
|
|
1221
|
+
display: flex;
|
|
1222
|
+
align-items: center;
|
|
1223
|
+
justify-content: center;
|
|
1224
|
+
gap: 8px;
|
|
1225
|
+
padding: 10px 14px;
|
|
1226
|
+
background: var(--bg-card);
|
|
1227
|
+
border: 1px solid var(--border);
|
|
1228
|
+
border-radius: 8px;
|
|
1229
|
+
color: var(--text-secondary);
|
|
1230
|
+
font-size: 12px;
|
|
1231
|
+
font-weight: 500;
|
|
1232
|
+
cursor: pointer;
|
|
1233
|
+
transition: all var(--transition);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
.quick-action-btn:hover {
|
|
1237
|
+
background: var(--bg-hover);
|
|
1238
|
+
color: var(--text-primary);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.quick-action-btn.pause:hover {
|
|
1242
|
+
background: var(--yellow-muted);
|
|
1243
|
+
color: var(--yellow);
|
|
1244
|
+
border-color: var(--yellow);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.quick-action-btn.resume:hover {
|
|
1248
|
+
background: var(--green-muted);
|
|
1249
|
+
color: var(--green);
|
|
1250
|
+
border-color: var(--green);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.quick-action-btn.github:hover {
|
|
1254
|
+
background: var(--purple-muted);
|
|
1255
|
+
color: var(--purple);
|
|
1256
|
+
border-color: var(--purple);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.quick-action-btn.export:hover {
|
|
1260
|
+
background: var(--blue-muted);
|
|
1261
|
+
color: var(--blue);
|
|
1262
|
+
border-color: var(--blue);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/* GitHub Import Modal */
|
|
1266
|
+
.github-form .form-row {
|
|
1267
|
+
display: grid;
|
|
1268
|
+
grid-template-columns: 1fr 1fr;
|
|
1269
|
+
gap: 12px;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.github-preview {
|
|
1273
|
+
margin-top: 14px;
|
|
1274
|
+
padding: 12px;
|
|
1275
|
+
background: var(--bg-tertiary);
|
|
1276
|
+
border-radius: 6px;
|
|
1277
|
+
font-size: 12px;
|
|
1278
|
+
max-height: 150px;
|
|
1279
|
+
overflow-y: auto;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.github-preview-title {
|
|
1283
|
+
font-weight: 600;
|
|
1284
|
+
margin-bottom: 8px;
|
|
1285
|
+
color: var(--text-secondary);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
.github-issue-item {
|
|
1289
|
+
display: flex;
|
|
1290
|
+
align-items: center;
|
|
1291
|
+
gap: 8px;
|
|
1292
|
+
padding: 6px 0;
|
|
1293
|
+
border-bottom: 1px solid var(--border);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.github-issue-item:last-child {
|
|
1297
|
+
border-bottom: none;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.github-issue-number {
|
|
1301
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1302
|
+
color: var(--accent);
|
|
1303
|
+
font-weight: 500;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.github-issue-title {
|
|
1307
|
+
flex: 1;
|
|
1308
|
+
overflow: hidden;
|
|
1309
|
+
text-overflow: ellipsis;
|
|
1310
|
+
white-space: nowrap;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
@media (max-width: 768px) {
|
|
1314
|
+
.quick-actions {
|
|
1315
|
+
flex-wrap: wrap;
|
|
1316
|
+
}
|
|
1317
|
+
.quick-action-btn {
|
|
1318
|
+
flex: 1 1 45%;
|
|
1319
|
+
}
|
|
1320
|
+
.github-form .form-row {
|
|
1321
|
+
grid-template-columns: 1fr;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
</style>
|
|
1325
|
+
</head>
|
|
1326
|
+
<body>
|
|
1327
|
+
<!-- Mobile Header (visible on small screens) -->
|
|
1328
|
+
<div class="mobile-header">
|
|
1329
|
+
<div class="mobile-logo">
|
|
1330
|
+
<div class="mobile-logo-icon">L</div>
|
|
1331
|
+
Loki Mode
|
|
1332
|
+
</div>
|
|
1333
|
+
<div class="mobile-controls">
|
|
1334
|
+
<div class="mobile-status">
|
|
1335
|
+
<span class="connection-dot" id="mobile-connection-dot"></span>
|
|
1336
|
+
<span id="mobile-mode-text">--</span>
|
|
1337
|
+
</div>
|
|
1338
|
+
<button class="theme-toggle-btn" id="mobile-theme-toggle" aria-label="Toggle theme"></button>
|
|
1339
|
+
</div>
|
|
1340
|
+
</div>
|
|
1341
|
+
|
|
1342
|
+
<div class="dashboard">
|
|
1343
|
+
<!-- Sidebar -->
|
|
1344
|
+
<aside class="sidebar">
|
|
1345
|
+
<div class="logo">
|
|
1346
|
+
<div class="logo-icon">L</div>
|
|
1347
|
+
<div>
|
|
1348
|
+
<div class="logo-text">Loki Mode</div>
|
|
1349
|
+
<div class="logo-version" id="version">v4.1.0</div>
|
|
1350
|
+
</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
|
|
1353
|
+
<!-- Theme Toggle -->
|
|
1354
|
+
<div class="theme-toggle">
|
|
1355
|
+
<span>Theme</span>
|
|
1356
|
+
<button class="theme-toggle-btn" id="theme-toggle" aria-label="Toggle theme"></button>
|
|
1357
|
+
</div>
|
|
1358
|
+
|
|
1359
|
+
<!-- Connection Status -->
|
|
1360
|
+
<div class="connection-status">
|
|
1361
|
+
<span class="connection-dot" id="connection-dot"></span>
|
|
1362
|
+
<span id="connection-text">Connecting...</span>
|
|
1363
|
+
</div>
|
|
1364
|
+
<div class="connection-status" style="font-size: 10px; margin-top: -10px;">
|
|
1365
|
+
<span id="last-sync">Last sync: --</span>
|
|
1366
|
+
</div>
|
|
1367
|
+
|
|
1368
|
+
<!-- Navigation -->
|
|
1369
|
+
<nav class="sidebar-section">
|
|
1370
|
+
<div class="sidebar-title">Navigation</div>
|
|
1371
|
+
<div class="sidebar-item active" data-section="kanban">
|
|
1372
|
+
<svg class="sidebar-icon" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
1373
|
+
Kanban Board
|
|
1374
|
+
</div>
|
|
1375
|
+
<div class="sidebar-item" data-section="agents">
|
|
1376
|
+
<svg class="sidebar-icon" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M20 21a8 8 0 10-16 0"/></svg>
|
|
1377
|
+
Active Agents
|
|
1378
|
+
</div>
|
|
1379
|
+
<div class="sidebar-item" data-section="system">
|
|
1380
|
+
<svg class="sidebar-icon" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
|
1381
|
+
System Status
|
|
1382
|
+
</div>
|
|
1383
|
+
<div class="sidebar-item" data-section="terminal">
|
|
1384
|
+
<svg class="sidebar-icon" viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
|
|
1385
|
+
Terminal Output
|
|
1386
|
+
</div>
|
|
1387
|
+
</nav>
|
|
1388
|
+
|
|
1389
|
+
<!-- Status Panel -->
|
|
1390
|
+
<div class="status-panel">
|
|
1391
|
+
<div class="sidebar-title" style="padding: 0; margin-bottom: 4px;">System Status</div>
|
|
1392
|
+
<div class="status-row">
|
|
1393
|
+
<span class="status-label">Mode</span>
|
|
1394
|
+
<span class="status-value">
|
|
1395
|
+
<span class="status-dot offline" id="mode-dot"></span>
|
|
1396
|
+
<span id="mode-text">OFFLINE</span>
|
|
1397
|
+
</span>
|
|
1398
|
+
</div>
|
|
1399
|
+
<div class="status-row">
|
|
1400
|
+
<span class="status-label">Phase</span>
|
|
1401
|
+
<span class="status-value" id="phase-text">--</span>
|
|
1402
|
+
</div>
|
|
1403
|
+
<div class="status-row">
|
|
1404
|
+
<span class="status-label">Complexity</span>
|
|
1405
|
+
<span class="status-value" id="complexity-text">--</span>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div class="status-row">
|
|
1408
|
+
<span class="status-label">Iteration</span>
|
|
1409
|
+
<span class="status-value" id="iteration-text">--</span>
|
|
1410
|
+
</div>
|
|
1411
|
+
<div class="intervention-controls">
|
|
1412
|
+
<button class="intervention-btn pause" onclick="triggerPause()">
|
|
1413
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
|
1414
|
+
PAUSE
|
|
1415
|
+
</button>
|
|
1416
|
+
<button class="intervention-btn stop" onclick="triggerStop()">
|
|
1417
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
|
|
1418
|
+
STOP
|
|
1419
|
+
</button>
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
|
|
1423
|
+
<!-- Quick Stats -->
|
|
1424
|
+
<div class="sidebar-section">
|
|
1425
|
+
<div class="sidebar-title">Resources</div>
|
|
1426
|
+
<div class="status-panel" style="gap: 6px;">
|
|
1427
|
+
<div class="status-row">
|
|
1428
|
+
<span class="status-label">CPU</span>
|
|
1429
|
+
<span class="status-value" id="cpu-usage">--%</span>
|
|
1430
|
+
</div>
|
|
1431
|
+
<div class="status-row">
|
|
1432
|
+
<span class="status-label">Memory</span>
|
|
1433
|
+
<span class="status-value" id="mem-usage">--%</span>
|
|
1434
|
+
</div>
|
|
1435
|
+
</div>
|
|
1436
|
+
</div>
|
|
1437
|
+
</aside>
|
|
1438
|
+
|
|
1439
|
+
<!-- Main Content -->
|
|
1440
|
+
<main class="main-content">
|
|
1441
|
+
<div class="header">
|
|
1442
|
+
<h1 class="header-title">Dashboard</h1>
|
|
1443
|
+
<div class="header-actions">
|
|
1444
|
+
<button class="btn btn-secondary" onclick="exportTasks()">
|
|
1445
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
|
1446
|
+
Export
|
|
1447
|
+
</button>
|
|
1448
|
+
<button class="btn btn-primary" onclick="openAddTaskModal()">
|
|
1449
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
1450
|
+
Add Task
|
|
1451
|
+
</button>
|
|
1452
|
+
</div>
|
|
1453
|
+
</div>
|
|
1454
|
+
|
|
1455
|
+
<!-- Stats Row -->
|
|
1456
|
+
<div class="stats-row">
|
|
1457
|
+
<div class="stat-card">
|
|
1458
|
+
<div class="stat-label">Total Tasks</div>
|
|
1459
|
+
<div class="stat-value" id="stat-total">0</div>
|
|
1460
|
+
</div>
|
|
1461
|
+
<div class="stat-card">
|
|
1462
|
+
<div class="stat-label">In Progress</div>
|
|
1463
|
+
<div class="stat-value" id="stat-progress">0</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
<div class="stat-card">
|
|
1466
|
+
<div class="stat-label">Completed</div>
|
|
1467
|
+
<div class="stat-value" id="stat-completed">0</div>
|
|
1468
|
+
</div>
|
|
1469
|
+
<div class="stat-card">
|
|
1470
|
+
<div class="stat-label">Active Agents</div>
|
|
1471
|
+
<div class="stat-value" id="stat-agents">0</div>
|
|
1472
|
+
</div>
|
|
1473
|
+
<div class="stat-card">
|
|
1474
|
+
<div class="stat-label">Failed</div>
|
|
1475
|
+
<div class="stat-value" id="stat-failed">0</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
</div>
|
|
1478
|
+
|
|
1479
|
+
<!-- Quick Actions -->
|
|
1480
|
+
<div class="quick-actions">
|
|
1481
|
+
<button class="quick-action-btn pause" onclick="triggerPause()">
|
|
1482
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
|
1483
|
+
Pause All
|
|
1484
|
+
</button>
|
|
1485
|
+
<button class="quick-action-btn resume" onclick="triggerResume()">
|
|
1486
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
1487
|
+
Resume
|
|
1488
|
+
</button>
|
|
1489
|
+
<button class="quick-action-btn github" onclick="openGitHubModal()">
|
|
1490
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
|
1491
|
+
Import GitHub
|
|
1492
|
+
</button>
|
|
1493
|
+
<button class="quick-action-btn export" onclick="exportTasks()">
|
|
1494
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
|
1495
|
+
Export Report
|
|
1496
|
+
</button>
|
|
1497
|
+
</div>
|
|
1498
|
+
|
|
1499
|
+
<!-- Kanban Board -->
|
|
1500
|
+
<div class="kanban-container" id="section-kanban">
|
|
1501
|
+
<div class="section-header">
|
|
1502
|
+
<h2 class="section-title">Task Queue</h2>
|
|
1503
|
+
</div>
|
|
1504
|
+
<div class="kanban-board" id="kanban-board">
|
|
1505
|
+
<div class="kanban-column pending" data-status="pending">
|
|
1506
|
+
<div class="kanban-column-header">
|
|
1507
|
+
<span class="kanban-column-title">
|
|
1508
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg>
|
|
1509
|
+
Pending
|
|
1510
|
+
</span>
|
|
1511
|
+
<span class="kanban-column-count" id="pending-count">0</span>
|
|
1512
|
+
</div>
|
|
1513
|
+
<div class="kanban-tasks" id="pending-tasks"></div>
|
|
1514
|
+
<button class="add-task-btn" onclick="openAddTaskModal('pending')">+ Add Task</button>
|
|
1515
|
+
</div>
|
|
1516
|
+
<div class="kanban-column in-progress" data-status="in-progress">
|
|
1517
|
+
<div class="kanban-column-header">
|
|
1518
|
+
<span class="kanban-column-title">
|
|
1519
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--blue)" stroke-width="2" fill="none"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
|
|
1520
|
+
In Progress
|
|
1521
|
+
</span>
|
|
1522
|
+
<span class="kanban-column-count" id="in-progress-count">0</span>
|
|
1523
|
+
</div>
|
|
1524
|
+
<div class="kanban-tasks" id="in-progress-tasks"></div>
|
|
1525
|
+
</div>
|
|
1526
|
+
<div class="kanban-column review" data-status="review">
|
|
1527
|
+
<div class="kanban-column-header">
|
|
1528
|
+
<span class="kanban-column-title">
|
|
1529
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--purple)" stroke-width="2" fill="none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
1530
|
+
In Review
|
|
1531
|
+
</span>
|
|
1532
|
+
<span class="kanban-column-count" id="review-count">0</span>
|
|
1533
|
+
</div>
|
|
1534
|
+
<div class="kanban-tasks" id="review-tasks"></div>
|
|
1535
|
+
</div>
|
|
1536
|
+
<div class="kanban-column completed" data-status="completed">
|
|
1537
|
+
<div class="kanban-column-header">
|
|
1538
|
+
<span class="kanban-column-title">
|
|
1539
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--green)" stroke-width="2" fill="none"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
1540
|
+
Completed
|
|
1541
|
+
</span>
|
|
1542
|
+
<span class="kanban-column-count" id="completed-count">0</span>
|
|
1543
|
+
</div>
|
|
1544
|
+
<div class="kanban-tasks" id="completed-tasks"></div>
|
|
1545
|
+
</div>
|
|
1546
|
+
</div>
|
|
1547
|
+
</div>
|
|
1548
|
+
|
|
1549
|
+
<!-- Active Agents -->
|
|
1550
|
+
<div class="agents-section" id="section-agents">
|
|
1551
|
+
<div class="section-header">
|
|
1552
|
+
<h2 class="section-title">Active Agents</h2>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div class="agents-grid" id="agents-grid">
|
|
1555
|
+
<div class="empty-state">No active agents. Agents will appear when Loki Mode is running.</div>
|
|
1556
|
+
</div>
|
|
1557
|
+
</div>
|
|
1558
|
+
|
|
1559
|
+
<!-- System Components -->
|
|
1560
|
+
<div class="system-grid" id="section-system">
|
|
1561
|
+
<!-- RARV Cycle -->
|
|
1562
|
+
<div class="system-card">
|
|
1563
|
+
<div class="system-card-header">
|
|
1564
|
+
<span class="system-card-title">
|
|
1565
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
|
|
1566
|
+
RARV Cycle
|
|
1567
|
+
</span>
|
|
1568
|
+
<span class="system-card-status healthy" id="rarv-status">Active</span>
|
|
1569
|
+
</div>
|
|
1570
|
+
<div class="rarv-cycle">
|
|
1571
|
+
<div class="rarv-step" id="rarv-reason">Reason</div>
|
|
1572
|
+
<div class="rarv-step" id="rarv-act">Act</div>
|
|
1573
|
+
<div class="rarv-step" id="rarv-reflect">Reflect</div>
|
|
1574
|
+
<div class="rarv-step" id="rarv-verify">Verify</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
|
|
1578
|
+
<!-- Memory System -->
|
|
1579
|
+
<div class="system-card">
|
|
1580
|
+
<div class="system-card-header">
|
|
1581
|
+
<span class="system-card-title">
|
|
1582
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
|
1583
|
+
Memory System
|
|
1584
|
+
</span>
|
|
1585
|
+
<span class="system-card-status healthy">Healthy</span>
|
|
1586
|
+
</div>
|
|
1587
|
+
<div class="memory-bars">
|
|
1588
|
+
<div class="memory-bar">
|
|
1589
|
+
<div class="memory-bar-header">
|
|
1590
|
+
<span class="memory-bar-label">Episodic</span>
|
|
1591
|
+
<span class="memory-bar-value" id="memory-episodic">0 entries</span>
|
|
1592
|
+
</div>
|
|
1593
|
+
<div class="memory-bar-track">
|
|
1594
|
+
<div class="memory-bar-fill episodic" id="memory-episodic-bar" style="width: 0%;"></div>
|
|
1595
|
+
</div>
|
|
1596
|
+
</div>
|
|
1597
|
+
<div class="memory-bar">
|
|
1598
|
+
<div class="memory-bar-header">
|
|
1599
|
+
<span class="memory-bar-label">Semantic</span>
|
|
1600
|
+
<span class="memory-bar-value" id="memory-semantic">0 patterns</span>
|
|
1601
|
+
</div>
|
|
1602
|
+
<div class="memory-bar-track">
|
|
1603
|
+
<div class="memory-bar-fill semantic" id="memory-semantic-bar" style="width: 0%;"></div>
|
|
1604
|
+
</div>
|
|
1605
|
+
</div>
|
|
1606
|
+
<div class="memory-bar">
|
|
1607
|
+
<div class="memory-bar-header">
|
|
1608
|
+
<span class="memory-bar-label">Procedural</span>
|
|
1609
|
+
<span class="memory-bar-value" id="memory-procedural">0 skills</span>
|
|
1610
|
+
</div>
|
|
1611
|
+
<div class="memory-bar-track">
|
|
1612
|
+
<div class="memory-bar-fill procedural" id="memory-procedural-bar" style="width: 0%;"></div>
|
|
1613
|
+
</div>
|
|
1614
|
+
</div>
|
|
1615
|
+
</div>
|
|
1616
|
+
</div>
|
|
1617
|
+
|
|
1618
|
+
<!-- Quality Gates -->
|
|
1619
|
+
<div class="system-card">
|
|
1620
|
+
<div class="system-card-header">
|
|
1621
|
+
<span class="system-card-title">
|
|
1622
|
+
<svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
1623
|
+
Quality Gates
|
|
1624
|
+
</span>
|
|
1625
|
+
<span class="system-card-status healthy" id="quality-status">--</span>
|
|
1626
|
+
</div>
|
|
1627
|
+
<div class="quality-gates" id="quality-gates">
|
|
1628
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Static Analysis</div>
|
|
1629
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> 3-Reviewer</div>
|
|
1630
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Anti-Sycophancy</div>
|
|
1631
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Test Coverage</div>
|
|
1632
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Security Scan</div>
|
|
1633
|
+
<div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Performance</div>
|
|
1634
|
+
</div>
|
|
1635
|
+
</div>
|
|
1636
|
+
</div>
|
|
1637
|
+
|
|
1638
|
+
<!-- Terminal Output -->
|
|
1639
|
+
<div class="terminal-section" id="section-terminal">
|
|
1640
|
+
<div class="section-header">
|
|
1641
|
+
<h2 class="section-title">Terminal Output</h2>
|
|
1642
|
+
</div>
|
|
1643
|
+
<div class="terminal-container">
|
|
1644
|
+
<div class="terminal-header">
|
|
1645
|
+
<div class="terminal-title">
|
|
1646
|
+
<div class="terminal-dots">
|
|
1647
|
+
<span class="terminal-dot red"></span>
|
|
1648
|
+
<span class="terminal-dot yellow"></span>
|
|
1649
|
+
<span class="terminal-dot green"></span>
|
|
1650
|
+
</div>
|
|
1651
|
+
loki-mode -- agent output
|
|
1652
|
+
</div>
|
|
1653
|
+
<div class="terminal-controls">
|
|
1654
|
+
<button class="terminal-btn active" id="terminal-auto-scroll" onclick="toggleAutoScroll()">Auto-scroll</button>
|
|
1655
|
+
<button class="terminal-btn" onclick="clearTerminal()">Clear</button>
|
|
1656
|
+
<button class="terminal-btn" onclick="downloadLogs()">Download</button>
|
|
1657
|
+
</div>
|
|
1658
|
+
</div>
|
|
1659
|
+
<div class="terminal-output" id="terminal-output">
|
|
1660
|
+
<div class="terminal-empty">No log output yet. Terminal will update when Loki Mode is running.</div>
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
</div>
|
|
1664
|
+
</main>
|
|
1665
|
+
</div>
|
|
1666
|
+
|
|
1667
|
+
<!-- Add Task Modal -->
|
|
1668
|
+
<div class="modal-overlay" id="add-task-modal">
|
|
1669
|
+
<div class="modal">
|
|
1670
|
+
<div class="modal-header">
|
|
1671
|
+
<h3 class="modal-title">Add New Task</h3>
|
|
1672
|
+
<button class="modal-close" onclick="closeAddTaskModal()">
|
|
1673
|
+
<svg width="18" height="18" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
1674
|
+
</button>
|
|
1675
|
+
</div>
|
|
1676
|
+
<form onsubmit="addTask(event)">
|
|
1677
|
+
<div class="form-group">
|
|
1678
|
+
<label class="form-label">Task Title</label>
|
|
1679
|
+
<input type="text" class="form-input" id="task-title" placeholder="e.g., Implement user authentication" required>
|
|
1680
|
+
</div>
|
|
1681
|
+
<div class="form-group">
|
|
1682
|
+
<label class="form-label">Description</label>
|
|
1683
|
+
<textarea class="form-textarea" id="task-description" placeholder="Describe what needs to be done..."></textarea>
|
|
1684
|
+
</div>
|
|
1685
|
+
<div class="form-group">
|
|
1686
|
+
<label class="form-label">Type</label>
|
|
1687
|
+
<select class="form-select" id="task-type">
|
|
1688
|
+
<option value="eng-backend">Engineering - Backend</option>
|
|
1689
|
+
<option value="eng-frontend">Engineering - Frontend</option>
|
|
1690
|
+
<option value="eng-database">Engineering - Database</option>
|
|
1691
|
+
<option value="qa-testing">QA - Testing</option>
|
|
1692
|
+
<option value="ops-devops">Operations - DevOps</option>
|
|
1693
|
+
<option value="security">Security</option>
|
|
1694
|
+
<option value="docs">Documentation</option>
|
|
1695
|
+
</select>
|
|
1696
|
+
</div>
|
|
1697
|
+
<div class="form-group">
|
|
1698
|
+
<label class="form-label">Priority</label>
|
|
1699
|
+
<select class="form-select" id="task-priority">
|
|
1700
|
+
<option value="medium">Medium</option>
|
|
1701
|
+
<option value="high">High</option>
|
|
1702
|
+
<option value="low">Low</option>
|
|
1703
|
+
</select>
|
|
1704
|
+
</div>
|
|
1705
|
+
<input type="hidden" id="task-initial-status" value="pending">
|
|
1706
|
+
<div class="modal-actions">
|
|
1707
|
+
<button type="button" class="btn btn-secondary" onclick="closeAddTaskModal()">Cancel</button>
|
|
1708
|
+
<button type="submit" class="btn btn-primary">Add Task</button>
|
|
1709
|
+
</div>
|
|
1710
|
+
</form>
|
|
1711
|
+
</div>
|
|
1712
|
+
</div>
|
|
1713
|
+
|
|
1714
|
+
<!-- GitHub Import Modal -->
|
|
1715
|
+
<div class="modal-overlay" id="github-modal">
|
|
1716
|
+
<div class="modal">
|
|
1717
|
+
<div class="modal-header">
|
|
1718
|
+
<h3 class="modal-title">Import from GitHub</h3>
|
|
1719
|
+
<button class="modal-close" onclick="closeGitHubModal()">
|
|
1720
|
+
<svg width="18" height="18" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
1721
|
+
</button>
|
|
1722
|
+
</div>
|
|
1723
|
+
<form class="github-form" onsubmit="importGitHubIssues(event)">
|
|
1724
|
+
<div class="form-group">
|
|
1725
|
+
<label class="form-label">Repository</label>
|
|
1726
|
+
<input type="text" class="form-input" id="github-repo" placeholder="owner/repo (leave empty for auto-detect)">
|
|
1727
|
+
</div>
|
|
1728
|
+
<div class="form-row">
|
|
1729
|
+
<div class="form-group">
|
|
1730
|
+
<label class="form-label">Labels (comma-separated)</label>
|
|
1731
|
+
<input type="text" class="form-input" id="github-labels" placeholder="bug,enhancement">
|
|
1732
|
+
</div>
|
|
1733
|
+
<div class="form-group">
|
|
1734
|
+
<label class="form-label">Milestone</label>
|
|
1735
|
+
<input type="text" class="form-input" id="github-milestone" placeholder="v1.0.0">
|
|
1736
|
+
</div>
|
|
1737
|
+
</div>
|
|
1738
|
+
<div class="form-row">
|
|
1739
|
+
<div class="form-group">
|
|
1740
|
+
<label class="form-label">Assignee</label>
|
|
1741
|
+
<input type="text" class="form-input" id="github-assignee" placeholder="@me or username">
|
|
1742
|
+
</div>
|
|
1743
|
+
<div class="form-group">
|
|
1744
|
+
<label class="form-label">Limit</label>
|
|
1745
|
+
<input type="number" class="form-input" id="github-limit" value="20" min="1" max="100">
|
|
1746
|
+
</div>
|
|
1747
|
+
</div>
|
|
1748
|
+
<div class="form-group">
|
|
1749
|
+
<label class="form-label">State</label>
|
|
1750
|
+
<select class="form-select" id="github-state">
|
|
1751
|
+
<option value="open">Open issues only</option>
|
|
1752
|
+
<option value="closed">Closed issues only</option>
|
|
1753
|
+
<option value="all">All issues</option>
|
|
1754
|
+
</select>
|
|
1755
|
+
</div>
|
|
1756
|
+
<div class="github-preview" id="github-preview" style="display: none;">
|
|
1757
|
+
<div class="github-preview-title">Preview: <span id="github-preview-count">0</span> issues found</div>
|
|
1758
|
+
<div id="github-preview-list"></div>
|
|
1759
|
+
</div>
|
|
1760
|
+
<div class="modal-actions">
|
|
1761
|
+
<button type="button" class="btn btn-secondary" onclick="previewGitHubIssues()">Preview</button>
|
|
1762
|
+
<button type="button" class="btn btn-secondary" onclick="closeGitHubModal()">Cancel</button>
|
|
1763
|
+
<button type="submit" class="btn btn-primary">Import</button>
|
|
1764
|
+
</div>
|
|
1765
|
+
</form>
|
|
1766
|
+
</div>
|
|
1767
|
+
</div>
|
|
1768
|
+
|
|
1769
|
+
<script>
|
|
1770
|
+
// ============================================
|
|
1771
|
+
// Configuration
|
|
1772
|
+
// ============================================
|
|
1773
|
+
const CONFIG = {
|
|
1774
|
+
POLL_INTERVAL: 2000, // Poll every 2 seconds
|
|
1775
|
+
STATE_FILE: '../dashboard-state.json',
|
|
1776
|
+
LOCAL_STORAGE_KEY: 'loki-dashboard-local'
|
|
1777
|
+
};
|
|
1778
|
+
|
|
1779
|
+
// ============================================
|
|
1780
|
+
// State Management
|
|
1781
|
+
// ============================================
|
|
1782
|
+
let serverState = null;
|
|
1783
|
+
let localState = {
|
|
1784
|
+
tasks: [],
|
|
1785
|
+
lastUpdated: null
|
|
1786
|
+
};
|
|
1787
|
+
let isConnected = false;
|
|
1788
|
+
let pollInterval = null;
|
|
1789
|
+
|
|
1790
|
+
// ============================================
|
|
1791
|
+
// Theme Management
|
|
1792
|
+
// ============================================
|
|
1793
|
+
function initTheme() {
|
|
1794
|
+
const saved = localStorage.getItem('loki-theme');
|
|
1795
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1796
|
+
const theme = saved || (prefersDark ? 'dark' : 'light');
|
|
1797
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function toggleTheme() {
|
|
1801
|
+
const current = document.documentElement.getAttribute('data-theme');
|
|
1802
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
1803
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
1804
|
+
localStorage.setItem('loki-theme', next);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// ============================================
|
|
1808
|
+
// File-Based Sync (Realtime Polling)
|
|
1809
|
+
// ============================================
|
|
1810
|
+
async function fetchServerState() {
|
|
1811
|
+
try {
|
|
1812
|
+
const response = await fetch(CONFIG.STATE_FILE + '?t=' + Date.now());
|
|
1813
|
+
if (!response.ok) {
|
|
1814
|
+
throw new Error('State file not found');
|
|
1815
|
+
}
|
|
1816
|
+
const data = await response.json();
|
|
1817
|
+
serverState = data;
|
|
1818
|
+
setConnected(true);
|
|
1819
|
+
updateUI();
|
|
1820
|
+
return data;
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
setConnected(false);
|
|
1823
|
+
// Keep showing local state if server unavailable
|
|
1824
|
+
if (localState.tasks.length > 0) {
|
|
1825
|
+
renderKanban();
|
|
1826
|
+
}
|
|
1827
|
+
return null;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function setConnected(connected) {
|
|
1832
|
+
isConnected = connected;
|
|
1833
|
+
const dot = document.getElementById('connection-dot');
|
|
1834
|
+
const text = document.getElementById('connection-text');
|
|
1835
|
+
const syncText = document.getElementById('last-sync');
|
|
1836
|
+
const mobileDot = document.getElementById('mobile-connection-dot');
|
|
1837
|
+
const mobileModeText = document.getElementById('mobile-mode-text');
|
|
1838
|
+
|
|
1839
|
+
if (connected) {
|
|
1840
|
+
dot.classList.add('connected');
|
|
1841
|
+
mobileDot?.classList.add('connected');
|
|
1842
|
+
text.textContent = 'Live - syncing';
|
|
1843
|
+
syncText.textContent = 'Last sync: ' + new Date().toLocaleTimeString();
|
|
1844
|
+
if (mobileModeText && serverState) {
|
|
1845
|
+
mobileModeText.textContent = serverState.mode?.toUpperCase() || 'LIVE';
|
|
1846
|
+
}
|
|
1847
|
+
} else {
|
|
1848
|
+
dot.classList.remove('connected');
|
|
1849
|
+
mobileDot?.classList.remove('connected');
|
|
1850
|
+
text.textContent = 'Offline - local only';
|
|
1851
|
+
if (mobileModeText) {
|
|
1852
|
+
mobileModeText.textContent = 'OFFLINE';
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function startPolling() {
|
|
1858
|
+
fetchServerState();
|
|
1859
|
+
fetchTerminalLogs();
|
|
1860
|
+
pollInterval = setInterval(() => {
|
|
1861
|
+
fetchServerState();
|
|
1862
|
+
fetchTerminalLogs();
|
|
1863
|
+
}, CONFIG.POLL_INTERVAL);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function stopPolling() {
|
|
1867
|
+
if (pollInterval) {
|
|
1868
|
+
clearInterval(pollInterval);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// ============================================
|
|
1873
|
+
// Local Storage
|
|
1874
|
+
// ============================================
|
|
1875
|
+
function loadLocalState() {
|
|
1876
|
+
const saved = localStorage.getItem(CONFIG.LOCAL_STORAGE_KEY);
|
|
1877
|
+
if (saved) {
|
|
1878
|
+
localState = JSON.parse(saved);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function saveLocalState() {
|
|
1883
|
+
localState.lastUpdated = Date.now();
|
|
1884
|
+
localStorage.setItem(CONFIG.LOCAL_STORAGE_KEY, JSON.stringify(localState));
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// ============================================
|
|
1888
|
+
// UI Update Functions
|
|
1889
|
+
// ============================================
|
|
1890
|
+
function updateUI() {
|
|
1891
|
+
if (!serverState) return;
|
|
1892
|
+
|
|
1893
|
+
// Update version
|
|
1894
|
+
document.getElementById('version').textContent = 'v' + (serverState.version || '4.1.0');
|
|
1895
|
+
|
|
1896
|
+
// Update status panel
|
|
1897
|
+
updateStatusPanel();
|
|
1898
|
+
|
|
1899
|
+
// Update stats
|
|
1900
|
+
updateStats();
|
|
1901
|
+
|
|
1902
|
+
// Render kanban with merged tasks
|
|
1903
|
+
renderKanban();
|
|
1904
|
+
|
|
1905
|
+
// Render agents
|
|
1906
|
+
renderAgents();
|
|
1907
|
+
|
|
1908
|
+
// Update system components
|
|
1909
|
+
updateSystemComponents();
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
function updateStatusPanel() {
|
|
1913
|
+
if (!serverState) return;
|
|
1914
|
+
|
|
1915
|
+
const modeMap = {
|
|
1916
|
+
'autonomous': { text: 'AUTONOMOUS', class: 'active' },
|
|
1917
|
+
'paused': { text: 'PAUSED', class: 'paused' },
|
|
1918
|
+
'stopped': { text: 'STOPPED', class: 'stopped' }
|
|
1919
|
+
};
|
|
1920
|
+
|
|
1921
|
+
const mode = modeMap[serverState.mode] || modeMap['autonomous'];
|
|
1922
|
+
document.getElementById('mode-text').textContent = mode.text;
|
|
1923
|
+
document.getElementById('mode-dot').className = 'status-dot ' + mode.class;
|
|
1924
|
+
|
|
1925
|
+
document.getElementById('phase-text').textContent = serverState.phase || '--';
|
|
1926
|
+
document.getElementById('complexity-text').textContent = (serverState.complexity || '--').toUpperCase();
|
|
1927
|
+
document.getElementById('iteration-text').textContent = serverState.iteration || '--';
|
|
1928
|
+
|
|
1929
|
+
// Resources
|
|
1930
|
+
document.getElementById('cpu-usage').textContent = (serverState.metrics?.cpuUsage || 0) + '%';
|
|
1931
|
+
document.getElementById('mem-usage').textContent = (serverState.metrics?.memoryUsage || 0) + '%';
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function updateStats() {
|
|
1935
|
+
const tasks = serverState?.tasks || {};
|
|
1936
|
+
const pending = tasks.pending?.length || 0;
|
|
1937
|
+
const inProgress = tasks.inProgress?.length || 0;
|
|
1938
|
+
const review = tasks.review?.length || 0;
|
|
1939
|
+
const completed = tasks.completed?.length || 0;
|
|
1940
|
+
const failed = tasks.failed?.length || 0;
|
|
1941
|
+
const total = pending + inProgress + review + completed + failed + localState.tasks.length;
|
|
1942
|
+
|
|
1943
|
+
document.getElementById('stat-total').textContent = total;
|
|
1944
|
+
document.getElementById('stat-progress').textContent = inProgress;
|
|
1945
|
+
document.getElementById('stat-completed').textContent = completed;
|
|
1946
|
+
document.getElementById('stat-failed').textContent = failed;
|
|
1947
|
+
document.getElementById('stat-agents').textContent = serverState?.agents?.length || 0;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// ============================================
|
|
1951
|
+
// Kanban Rendering
|
|
1952
|
+
// ============================================
|
|
1953
|
+
function renderKanban() {
|
|
1954
|
+
const columns = {
|
|
1955
|
+
pending: document.getElementById('pending-tasks'),
|
|
1956
|
+
'in-progress': document.getElementById('in-progress-tasks'),
|
|
1957
|
+
review: document.getElementById('review-tasks'),
|
|
1958
|
+
completed: document.getElementById('completed-tasks')
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
// Clear columns
|
|
1962
|
+
Object.values(columns).forEach(col => col.innerHTML = '');
|
|
1963
|
+
|
|
1964
|
+
// Render server tasks
|
|
1965
|
+
if (serverState?.tasks) {
|
|
1966
|
+
renderTasksToColumn(serverState.tasks.pending || [], columns.pending, true);
|
|
1967
|
+
renderTasksToColumn(serverState.tasks.inProgress || [], columns['in-progress'], true);
|
|
1968
|
+
renderTasksToColumn(serverState.tasks.review || [], columns.review, true);
|
|
1969
|
+
renderTasksToColumn(serverState.tasks.completed || [], columns.completed, true);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// Render local tasks
|
|
1973
|
+
localState.tasks.forEach(task => {
|
|
1974
|
+
const column = columns[task.status];
|
|
1975
|
+
if (column) {
|
|
1976
|
+
renderTaskCard(task, column, false);
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
// Update counts
|
|
1981
|
+
updateCounts();
|
|
1982
|
+
|
|
1983
|
+
// Setup drag-drop
|
|
1984
|
+
setupDragDrop();
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
function renderTasksToColumn(tasks, column, fromServer) {
|
|
1988
|
+
tasks.forEach(task => renderTaskCard(task, column, fromServer));
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function renderTaskCard(task, column, fromServer) {
|
|
1992
|
+
const card = document.createElement('div');
|
|
1993
|
+
card.className = 'task-card' + (fromServer ? ' from-server' : '');
|
|
1994
|
+
card.draggable = !fromServer; // Only local tasks are draggable
|
|
1995
|
+
card.dataset.taskId = task.id;
|
|
1996
|
+
card.dataset.fromServer = fromServer;
|
|
1997
|
+
|
|
1998
|
+
const priority = task.priority || 'medium';
|
|
1999
|
+
const type = task.type || 'unknown';
|
|
2000
|
+
|
|
2001
|
+
card.innerHTML = `
|
|
2002
|
+
<div class="task-card-header">
|
|
2003
|
+
<span class="task-id">${task.id || 'local-' + Date.now()}</span>
|
|
2004
|
+
<span class="task-priority ${priority}">${priority}</span>
|
|
2005
|
+
</div>
|
|
2006
|
+
<div class="task-title">${task.title || task.description || 'Untitled'}</div>
|
|
2007
|
+
<div class="task-meta">
|
|
2008
|
+
<span class="task-type">${type}</span>
|
|
2009
|
+
${task.agent ? `<span>${task.agent}</span>` : ''}
|
|
2010
|
+
</div>
|
|
2011
|
+
`;
|
|
2012
|
+
|
|
2013
|
+
if (!fromServer) {
|
|
2014
|
+
card.addEventListener('dragstart', handleDragStart);
|
|
2015
|
+
card.addEventListener('dragend', handleDragEnd);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
column.appendChild(card);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function updateCounts() {
|
|
2022
|
+
const serverTasks = serverState?.tasks || {};
|
|
2023
|
+
const localByStatus = {
|
|
2024
|
+
pending: localState.tasks.filter(t => t.status === 'pending').length,
|
|
2025
|
+
'in-progress': localState.tasks.filter(t => t.status === 'in-progress').length,
|
|
2026
|
+
review: localState.tasks.filter(t => t.status === 'review').length,
|
|
2027
|
+
completed: localState.tasks.filter(t => t.status === 'completed').length
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
document.getElementById('pending-count').textContent =
|
|
2031
|
+
(serverTasks.pending?.length || 0) + localByStatus.pending;
|
|
2032
|
+
document.getElementById('in-progress-count').textContent =
|
|
2033
|
+
(serverTasks.inProgress?.length || 0) + localByStatus['in-progress'];
|
|
2034
|
+
document.getElementById('review-count').textContent =
|
|
2035
|
+
(serverTasks.review?.length || 0) + localByStatus.review;
|
|
2036
|
+
document.getElementById('completed-count').textContent =
|
|
2037
|
+
(serverTasks.completed?.length || 0) + localByStatus.completed;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// ============================================
|
|
2041
|
+
// Drag and Drop
|
|
2042
|
+
// ============================================
|
|
2043
|
+
let draggedTask = null;
|
|
2044
|
+
|
|
2045
|
+
function handleDragStart(e) {
|
|
2046
|
+
draggedTask = e.target;
|
|
2047
|
+
e.target.classList.add('dragging');
|
|
2048
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
2049
|
+
e.dataTransfer.setData('text/plain', e.target.dataset.taskId);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function handleDragEnd(e) {
|
|
2053
|
+
e.target.classList.remove('dragging');
|
|
2054
|
+
document.querySelectorAll('.kanban-tasks').forEach(col => col.classList.remove('drag-over'));
|
|
2055
|
+
draggedTask = null;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function setupDragDrop() {
|
|
2059
|
+
document.querySelectorAll('.kanban-tasks').forEach(zone => {
|
|
2060
|
+
zone.addEventListener('dragover', e => {
|
|
2061
|
+
e.preventDefault();
|
|
2062
|
+
e.dataTransfer.dropEffect = 'move';
|
|
2063
|
+
});
|
|
2064
|
+
zone.addEventListener('dragenter', e => {
|
|
2065
|
+
e.preventDefault();
|
|
2066
|
+
zone.classList.add('drag-over');
|
|
2067
|
+
});
|
|
2068
|
+
zone.addEventListener('dragleave', e => {
|
|
2069
|
+
if (!zone.contains(e.relatedTarget)) {
|
|
2070
|
+
zone.classList.remove('drag-over');
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
2073
|
+
zone.addEventListener('drop', e => {
|
|
2074
|
+
e.preventDefault();
|
|
2075
|
+
zone.classList.remove('drag-over');
|
|
2076
|
+
|
|
2077
|
+
const taskId = e.dataTransfer.getData('text/plain');
|
|
2078
|
+
const newStatus = zone.closest('.kanban-column').dataset.status;
|
|
2079
|
+
|
|
2080
|
+
// Update local task status
|
|
2081
|
+
const task = localState.tasks.find(t => t.id === taskId);
|
|
2082
|
+
if (task && task.status !== newStatus) {
|
|
2083
|
+
task.status = newStatus;
|
|
2084
|
+
saveLocalState();
|
|
2085
|
+
renderKanban();
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// ============================================
|
|
2092
|
+
// Agents Rendering
|
|
2093
|
+
// ============================================
|
|
2094
|
+
function renderAgents() {
|
|
2095
|
+
const grid = document.getElementById('agents-grid');
|
|
2096
|
+
const agents = serverState?.agents || [];
|
|
2097
|
+
|
|
2098
|
+
if (agents.length === 0) {
|
|
2099
|
+
grid.innerHTML = '<div class="empty-state">No active agents. Agents will appear when Loki Mode is running.</div>';
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
grid.innerHTML = agents.map(agent => `
|
|
2104
|
+
<div class="agent-card from-server">
|
|
2105
|
+
<div class="agent-header">
|
|
2106
|
+
<div class="agent-info">
|
|
2107
|
+
<h3>${agent.id || 'unknown'}</h3>
|
|
2108
|
+
<div class="agent-type">${agent.type || 'Agent'}</div>
|
|
2109
|
+
</div>
|
|
2110
|
+
<span class="model-badge ${(agent.model || 'sonnet').toLowerCase()}">${agent.model || 'sonnet'}</span>
|
|
2111
|
+
</div>
|
|
2112
|
+
<div class="agent-work">${agent.work || agent.currentTask || (agent.status === 'idle' ? 'Waiting for tasks...' : 'Working...')}</div>
|
|
2113
|
+
<div class="agent-stats">
|
|
2114
|
+
<span class="agent-stat">
|
|
2115
|
+
<svg width="10" height="10" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
2116
|
+
${agent.runtime || '--'}
|
|
2117
|
+
</span>
|
|
2118
|
+
<span class="agent-stat">
|
|
2119
|
+
<svg width="10" height="10" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
2120
|
+
${agent.tasks || 0} tasks
|
|
2121
|
+
</span>
|
|
2122
|
+
</div>
|
|
2123
|
+
<div class="agent-status ${agent.status || 'active'}">
|
|
2124
|
+
<span class="status-dot ${agent.status || 'active'}"></span>
|
|
2125
|
+
${agent.status === 'active' ? 'Active' : agent.status === 'idle' ? 'Idle' : agent.status === 'error' ? 'Error' : 'Completed'}
|
|
2126
|
+
</div>
|
|
2127
|
+
</div>
|
|
2128
|
+
`).join('');
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// ============================================
|
|
2132
|
+
// System Components
|
|
2133
|
+
// ============================================
|
|
2134
|
+
function updateSystemComponents() {
|
|
2135
|
+
if (!serverState) return;
|
|
2136
|
+
|
|
2137
|
+
// RARV Cycle - supports both string step name and numeric index
|
|
2138
|
+
const rarvStepMap = { 'REASON': 0, 'ACT': 1, 'REFLECT': 2, 'VERIFY': 3 };
|
|
2139
|
+
let rarvStep = 0;
|
|
2140
|
+
if (serverState.rarv) {
|
|
2141
|
+
if (typeof serverState.rarv.currentStep === 'number') {
|
|
2142
|
+
rarvStep = serverState.rarv.currentStep;
|
|
2143
|
+
} else if (typeof serverState.rarv.step === 'string') {
|
|
2144
|
+
rarvStep = rarvStepMap[serverState.rarv.step.toUpperCase()] || 0;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
const rarvIds = ['rarv-reason', 'rarv-act', 'rarv-reflect', 'rarv-verify'];
|
|
2148
|
+
rarvIds.forEach((id, i) => {
|
|
2149
|
+
document.getElementById(id).classList.toggle('active', i === rarvStep);
|
|
2150
|
+
});
|
|
2151
|
+
|
|
2152
|
+
// Memory System
|
|
2153
|
+
const memory = serverState.memory || {};
|
|
2154
|
+
const maxMemory = 100; // Assume max for percentage
|
|
2155
|
+
|
|
2156
|
+
document.getElementById('memory-episodic').textContent = (memory.episodic || 0) + ' entries';
|
|
2157
|
+
document.getElementById('memory-episodic-bar').style.width = Math.min((memory.episodic || 0) / maxMemory * 100, 100) + '%';
|
|
2158
|
+
|
|
2159
|
+
document.getElementById('memory-semantic').textContent = (memory.semantic || 0) + ' patterns';
|
|
2160
|
+
document.getElementById('memory-semantic-bar').style.width = Math.min((memory.semantic || 0) / maxMemory * 100, 100) + '%';
|
|
2161
|
+
|
|
2162
|
+
document.getElementById('memory-procedural').textContent = (memory.procedural || 0) + ' skills';
|
|
2163
|
+
document.getElementById('memory-procedural-bar').style.width = Math.min((memory.procedural || 0) / maxMemory * 100, 100) + '%';
|
|
2164
|
+
|
|
2165
|
+
// Quality Gates - render actual status from server
|
|
2166
|
+
const gates = serverState.qualityGates || {};
|
|
2167
|
+
const gateContainer = document.getElementById('quality-gates');
|
|
2168
|
+
const gateConfigs = [
|
|
2169
|
+
{ key: 'staticAnalysis', label: 'Static Analysis' },
|
|
2170
|
+
{ key: 'codeReview', label: '3-Reviewer' },
|
|
2171
|
+
{ key: 'antiSycophancy', label: 'Anti-Sycophancy' },
|
|
2172
|
+
{ key: 'testCoverage', label: 'Test Coverage' },
|
|
2173
|
+
{ key: 'securityScan', label: 'Security Scan' },
|
|
2174
|
+
{ key: 'performance', label: 'Performance' }
|
|
2175
|
+
];
|
|
2176
|
+
|
|
2177
|
+
gateContainer.innerHTML = gateConfigs.map(gate => {
|
|
2178
|
+
const status = gates[gate.key] || 'pending';
|
|
2179
|
+
const statusClass = status === 'passed' ? 'pass' :
|
|
2180
|
+
status === 'failed' ? 'fail' : 'pending';
|
|
2181
|
+
const icon = status === 'passed'
|
|
2182
|
+
? '<polyline points="22 4 12 14.01 9 11.01"/><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>'
|
|
2183
|
+
: status === 'failed'
|
|
2184
|
+
? '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>'
|
|
2185
|
+
: '<circle cx="12" cy="12" r="10"/>';
|
|
2186
|
+
return `<div class="quality-gate"><svg class="gate-icon ${statusClass}" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none">${icon}</svg> ${gate.label}</div>`;
|
|
2187
|
+
}).join('');
|
|
2188
|
+
|
|
2189
|
+
// Update overall quality status
|
|
2190
|
+
const passedCount = Object.values(gates).filter(s => s === 'passed').length;
|
|
2191
|
+
const failedCount = Object.values(gates).filter(s => s === 'failed').length;
|
|
2192
|
+
const statusEl = document.getElementById('quality-status');
|
|
2193
|
+
if (failedCount > 0) {
|
|
2194
|
+
statusEl.textContent = failedCount + ' Failed';
|
|
2195
|
+
statusEl.className = 'system-card-status warning';
|
|
2196
|
+
} else if (passedCount === gateConfigs.length) {
|
|
2197
|
+
statusEl.textContent = 'All Passed';
|
|
2198
|
+
statusEl.className = 'system-card-status healthy';
|
|
2199
|
+
} else {
|
|
2200
|
+
statusEl.textContent = passedCount + '/' + gateConfigs.length;
|
|
2201
|
+
statusEl.className = 'system-card-status healthy';
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// ============================================
|
|
2206
|
+
// Task Management
|
|
2207
|
+
// ============================================
|
|
2208
|
+
function openAddTaskModal(initialStatus = 'pending') {
|
|
2209
|
+
document.getElementById('task-initial-status').value = initialStatus;
|
|
2210
|
+
document.getElementById('add-task-modal').classList.add('active');
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function closeAddTaskModal() {
|
|
2214
|
+
document.getElementById('add-task-modal').classList.remove('active');
|
|
2215
|
+
document.getElementById('task-title').value = '';
|
|
2216
|
+
document.getElementById('task-description').value = '';
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
function addTask(e) {
|
|
2220
|
+
e.preventDefault();
|
|
2221
|
+
|
|
2222
|
+
const newTask = {
|
|
2223
|
+
id: 'local-' + Date.now(),
|
|
2224
|
+
title: document.getElementById('task-title').value,
|
|
2225
|
+
description: document.getElementById('task-description').value,
|
|
2226
|
+
type: document.getElementById('task-type').value,
|
|
2227
|
+
priority: document.getElementById('task-priority').value,
|
|
2228
|
+
status: document.getElementById('task-initial-status').value,
|
|
2229
|
+
createdAt: Date.now(),
|
|
2230
|
+
local: true
|
|
2231
|
+
};
|
|
2232
|
+
|
|
2233
|
+
localState.tasks.push(newTask);
|
|
2234
|
+
saveLocalState();
|
|
2235
|
+
renderKanban();
|
|
2236
|
+
updateStats();
|
|
2237
|
+
closeAddTaskModal();
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// ============================================
|
|
2241
|
+
// Human Intervention
|
|
2242
|
+
// ============================================
|
|
2243
|
+
function triggerPause() {
|
|
2244
|
+
alert('To pause Loki Mode, create this file:\ntouch .loki/PAUSE\n\nOr press Ctrl+C once in the terminal.');
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
function triggerStop() {
|
|
2248
|
+
alert('To stop Loki Mode, create this file:\ntouch .loki/STOP\n\nOr press Ctrl+C twice in the terminal.');
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function triggerResume() {
|
|
2252
|
+
alert('To resume Loki Mode, remove the pause file:\nrm .loki/PAUSE\n\nOr use: loki resume');
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// ============================================
|
|
2256
|
+
// Terminal Output
|
|
2257
|
+
// ============================================
|
|
2258
|
+
const MAX_TERMINAL_LINES = 1000;
|
|
2259
|
+
let terminalLines = [];
|
|
2260
|
+
let autoScroll = true;
|
|
2261
|
+
let lastLogPosition = 0;
|
|
2262
|
+
|
|
2263
|
+
function toggleAutoScroll() {
|
|
2264
|
+
autoScroll = !autoScroll;
|
|
2265
|
+
const btn = document.getElementById('terminal-auto-scroll');
|
|
2266
|
+
btn.classList.toggle('active', autoScroll);
|
|
2267
|
+
if (autoScroll) {
|
|
2268
|
+
scrollTerminalToBottom();
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
function scrollTerminalToBottom() {
|
|
2273
|
+
const output = document.getElementById('terminal-output');
|
|
2274
|
+
output.scrollTop = output.scrollHeight;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
function clearTerminal() {
|
|
2278
|
+
terminalLines = [];
|
|
2279
|
+
renderTerminal();
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
function downloadLogs() {
|
|
2283
|
+
const content = terminalLines.map(line =>
|
|
2284
|
+
`[${line.timestamp}] [${line.level.toUpperCase()}] ${line.message}`
|
|
2285
|
+
).join('\n');
|
|
2286
|
+
const blob = new Blob([content], { type: 'text/plain' });
|
|
2287
|
+
const url = URL.createObjectURL(blob);
|
|
2288
|
+
const a = document.createElement('a');
|
|
2289
|
+
a.href = url;
|
|
2290
|
+
a.download = 'loki-mode-logs-' + Date.now() + '.txt';
|
|
2291
|
+
a.click();
|
|
2292
|
+
URL.revokeObjectURL(url);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function parseLogLine(line) {
|
|
2296
|
+
// Parse log format: [TIMESTAMP] [LEVEL] MESSAGE or similar
|
|
2297
|
+
const patterns = [
|
|
2298
|
+
/^\[(\d{2}:\d{2}:\d{2})\]\s*\[(\w+)\]\s*(.*)$/,
|
|
2299
|
+
/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s*\[(\w+)\]\s*(.*)$/,
|
|
2300
|
+
/^\[(\w+)\]\s*(.*)$/
|
|
2301
|
+
];
|
|
2302
|
+
|
|
2303
|
+
for (const pattern of patterns) {
|
|
2304
|
+
const match = line.match(pattern);
|
|
2305
|
+
if (match) {
|
|
2306
|
+
if (match.length === 4) {
|
|
2307
|
+
return { timestamp: match[1], level: match[2].toLowerCase(), message: match[3] };
|
|
2308
|
+
} else if (match.length === 3) {
|
|
2309
|
+
return { timestamp: new Date().toLocaleTimeString(), level: match[1].toLowerCase(), message: match[2] };
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// Default: treat as info
|
|
2315
|
+
return { timestamp: new Date().toLocaleTimeString(), level: 'info', message: line };
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
function getLevelClass(level) {
|
|
2319
|
+
const levelMap = {
|
|
2320
|
+
'info': 'level-info',
|
|
2321
|
+
'success': 'level-success',
|
|
2322
|
+
'warn': 'level-warning',
|
|
2323
|
+
'warning': 'level-warning',
|
|
2324
|
+
'error': 'level-error',
|
|
2325
|
+
'err': 'level-error',
|
|
2326
|
+
'step': 'level-step',
|
|
2327
|
+
'agent': 'level-agent',
|
|
2328
|
+
'debug': 'level-info'
|
|
2329
|
+
};
|
|
2330
|
+
return levelMap[level] || 'level-info';
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
function renderTerminal() {
|
|
2334
|
+
const output = document.getElementById('terminal-output');
|
|
2335
|
+
|
|
2336
|
+
if (terminalLines.length === 0) {
|
|
2337
|
+
output.innerHTML = '<div class="terminal-empty">No log output yet. Terminal will update when Loki Mode is running.</div>';
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
output.innerHTML = terminalLines.map(line => `
|
|
2342
|
+
<div class="terminal-line">
|
|
2343
|
+
<span class="timestamp">[${line.timestamp}]</span>
|
|
2344
|
+
<span class="${getLevelClass(line.level)}">[${line.level.toUpperCase()}]</span>
|
|
2345
|
+
<span class="message">${escapeHtml(line.message)}</span>
|
|
2346
|
+
</div>
|
|
2347
|
+
`).join('');
|
|
2348
|
+
|
|
2349
|
+
if (autoScroll) {
|
|
2350
|
+
scrollTerminalToBottom();
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function escapeHtml(text) {
|
|
2355
|
+
const div = document.createElement('div');
|
|
2356
|
+
div.textContent = text;
|
|
2357
|
+
return div.innerHTML;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
async function fetchTerminalLogs() {
|
|
2361
|
+
try {
|
|
2362
|
+
const response = await fetch('../logs/agent.log?t=' + Date.now());
|
|
2363
|
+
if (!response.ok) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
const text = await response.text();
|
|
2367
|
+
const lines = text.split('\n').filter(line => line.trim());
|
|
2368
|
+
|
|
2369
|
+
// Only add new lines
|
|
2370
|
+
if (lines.length > terminalLines.length) {
|
|
2371
|
+
const newLines = lines.slice(terminalLines.length);
|
|
2372
|
+
newLines.forEach(line => {
|
|
2373
|
+
terminalLines.push(parseLogLine(line));
|
|
2374
|
+
});
|
|
2375
|
+
|
|
2376
|
+
// Limit memory growth - keep only the last MAX_TERMINAL_LINES
|
|
2377
|
+
if (terminalLines.length > MAX_TERMINAL_LINES) {
|
|
2378
|
+
terminalLines = terminalLines.slice(-MAX_TERMINAL_LINES);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
renderTerminal();
|
|
2382
|
+
}
|
|
2383
|
+
} catch (error) {
|
|
2384
|
+
// Silently fail - log file may not exist yet
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
// ============================================
|
|
2389
|
+
// GitHub Import
|
|
2390
|
+
// ============================================
|
|
2391
|
+
let githubPreviewIssues = [];
|
|
2392
|
+
|
|
2393
|
+
// Sanitize shell arguments to prevent command injection
|
|
2394
|
+
function sanitizeShellArg(input) {
|
|
2395
|
+
if (!input) return '';
|
|
2396
|
+
// Remove dangerous shell metacharacters
|
|
2397
|
+
return input.replace(/[`$"\\!;&|><(){}[\]\n\r]/g, '').trim();
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// Validate repository format (owner/repo)
|
|
2401
|
+
function validateRepoFormat(repo) {
|
|
2402
|
+
if (!repo) return true; // Empty is valid (auto-detect)
|
|
2403
|
+
return /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
function openGitHubModal() {
|
|
2407
|
+
document.getElementById('github-modal').classList.add('active');
|
|
2408
|
+
document.getElementById('github-preview').style.display = 'none';
|
|
2409
|
+
githubPreviewIssues = [];
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function closeGitHubModal() {
|
|
2413
|
+
document.getElementById('github-modal').classList.remove('active');
|
|
2414
|
+
document.getElementById('github-repo').value = '';
|
|
2415
|
+
document.getElementById('github-labels').value = '';
|
|
2416
|
+
document.getElementById('github-milestone').value = '';
|
|
2417
|
+
document.getElementById('github-assignee').value = '';
|
|
2418
|
+
document.getElementById('github-preview').style.display = 'none';
|
|
2419
|
+
githubPreviewIssues = [];
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
function buildGitHubCommand() {
|
|
2423
|
+
const repo = sanitizeShellArg(document.getElementById('github-repo').value);
|
|
2424
|
+
const labels = sanitizeShellArg(document.getElementById('github-labels').value);
|
|
2425
|
+
const milestone = sanitizeShellArg(document.getElementById('github-milestone').value);
|
|
2426
|
+
const assignee = sanitizeShellArg(document.getElementById('github-assignee').value);
|
|
2427
|
+
const limitVal = parseInt(document.getElementById('github-limit').value, 10);
|
|
2428
|
+
const limit = Math.max(1, Math.min(100, isNaN(limitVal) ? 20 : limitVal));
|
|
2429
|
+
const state = document.getElementById('github-state').value;
|
|
2430
|
+
|
|
2431
|
+
// Validate repo format if provided
|
|
2432
|
+
if (repo && !validateRepoFormat(repo)) {
|
|
2433
|
+
return { error: 'Invalid repository format. Use: owner/repo' };
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
let cmd = 'gh issue list --json number,title,body,labels,assignees,milestone --state ' + state + ' --limit ' + limit;
|
|
2437
|
+
|
|
2438
|
+
if (repo) {
|
|
2439
|
+
cmd += ' --repo ' + repo;
|
|
2440
|
+
}
|
|
2441
|
+
if (labels) {
|
|
2442
|
+
labels.split(',').forEach(label => {
|
|
2443
|
+
const sanitizedLabel = sanitizeShellArg(label);
|
|
2444
|
+
if (sanitizedLabel) {
|
|
2445
|
+
cmd += ' --label "' + sanitizedLabel + '"';
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
if (milestone) {
|
|
2450
|
+
cmd += ' --milestone "' + milestone + '"';
|
|
2451
|
+
}
|
|
2452
|
+
if (assignee) {
|
|
2453
|
+
cmd += ' --assignee "' + assignee + '"';
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
return cmd;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
async function previewGitHubIssues() {
|
|
2460
|
+
const previewDiv = document.getElementById('github-preview');
|
|
2461
|
+
const listDiv = document.getElementById('github-preview-list');
|
|
2462
|
+
const countSpan = document.getElementById('github-preview-count');
|
|
2463
|
+
|
|
2464
|
+
// Show loading
|
|
2465
|
+
previewDiv.style.display = 'block';
|
|
2466
|
+
listDiv.innerHTML = '<div style="color: var(--text-muted);">Loading issues... (requires gh CLI)</div>';
|
|
2467
|
+
|
|
2468
|
+
// In a real implementation, this would call the backend
|
|
2469
|
+
// For the dashboard, we show instructions
|
|
2470
|
+
const cmd = buildGitHubCommand();
|
|
2471
|
+
|
|
2472
|
+
// Check for validation error
|
|
2473
|
+
if (cmd && cmd.error) {
|
|
2474
|
+
listDiv.innerHTML = `<div style="color: var(--red);">${escapeHtml(cmd.error)}</div>`;
|
|
2475
|
+
countSpan.textContent = '0';
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
listDiv.innerHTML = `
|
|
2480
|
+
<div style="color: var(--text-muted); font-size: 11px;">
|
|
2481
|
+
<p style="margin-bottom: 8px;">To preview issues, run this command in terminal:</p>
|
|
2482
|
+
<code style="display: block; background: #2a2a2d; padding: 8px; border-radius: 4px; word-break: break-all; color: #e5e5e5;">${escapeHtml(cmd)}</code>
|
|
2483
|
+
<p style="margin-top: 8px;">Or use the CLI: <code>loki import --preview</code></p>
|
|
2484
|
+
</div>
|
|
2485
|
+
`;
|
|
2486
|
+
countSpan.textContent = '?';
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
function importGitHubIssues(e) {
|
|
2490
|
+
e.preventDefault();
|
|
2491
|
+
|
|
2492
|
+
// Sanitize all inputs to prevent command injection
|
|
2493
|
+
const repo = sanitizeShellArg(document.getElementById('github-repo').value);
|
|
2494
|
+
const labels = sanitizeShellArg(document.getElementById('github-labels').value);
|
|
2495
|
+
const milestone = sanitizeShellArg(document.getElementById('github-milestone').value);
|
|
2496
|
+
const assignee = sanitizeShellArg(document.getElementById('github-assignee').value);
|
|
2497
|
+
const limitVal = parseInt(document.getElementById('github-limit').value, 10);
|
|
2498
|
+
const limit = Math.max(1, Math.min(100, isNaN(limitVal) ? 20 : limitVal));
|
|
2499
|
+
const state = document.getElementById('github-state').value;
|
|
2500
|
+
|
|
2501
|
+
// Validate repo format
|
|
2502
|
+
if (repo && !validateRepoFormat(repo)) {
|
|
2503
|
+
alert('Invalid repository format. Use: owner/repo');
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
// Build the import command for the user
|
|
2508
|
+
let cliCmd = 'loki import';
|
|
2509
|
+
if (repo) cliCmd += ' --repo ' + repo;
|
|
2510
|
+
if (labels) cliCmd += ' --labels "' + labels + '"';
|
|
2511
|
+
if (milestone) cliCmd += ' --milestone "' + milestone + '"';
|
|
2512
|
+
if (assignee) cliCmd += ' --assignee "' + assignee + '"';
|
|
2513
|
+
if (limit !== 20) cliCmd += ' --limit ' + limit;
|
|
2514
|
+
if (state !== 'open') cliCmd += ' --state ' + state;
|
|
2515
|
+
|
|
2516
|
+
// Also build env vars for run.sh
|
|
2517
|
+
let envSetup = 'export LOKI_GITHUB_IMPORT=true';
|
|
2518
|
+
if (repo) envSetup += '\nexport LOKI_GITHUB_REPO="' + repo + '"';
|
|
2519
|
+
if (labels) envSetup += '\nexport LOKI_GITHUB_LABELS="' + labels + '"';
|
|
2520
|
+
if (milestone) envSetup += '\nexport LOKI_GITHUB_MILESTONE="' + milestone + '"';
|
|
2521
|
+
if (assignee) envSetup += '\nexport LOKI_GITHUB_ASSIGNEE="' + assignee + '"';
|
|
2522
|
+
if (limit !== 20) envSetup += '\nexport LOKI_GITHUB_LIMIT=' + limit;
|
|
2523
|
+
|
|
2524
|
+
const message = `To import GitHub issues, run one of these commands:
|
|
2525
|
+
|
|
2526
|
+
Option 1 - Using CLI:
|
|
2527
|
+
${cliCmd}
|
|
2528
|
+
|
|
2529
|
+
Option 2 - Using environment variables:
|
|
2530
|
+
${envSetup}
|
|
2531
|
+
|
|
2532
|
+
Then start Loki Mode and issues will be imported automatically.`;
|
|
2533
|
+
|
|
2534
|
+
alert(message);
|
|
2535
|
+
closeGitHubModal();
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// ============================================
|
|
2539
|
+
// Export
|
|
2540
|
+
// ============================================
|
|
2541
|
+
function exportTasks() {
|
|
2542
|
+
const exportData = {
|
|
2543
|
+
serverState: serverState,
|
|
2544
|
+
localState: localState,
|
|
2545
|
+
exportedAt: new Date().toISOString()
|
|
2546
|
+
};
|
|
2547
|
+
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
2548
|
+
const url = URL.createObjectURL(blob);
|
|
2549
|
+
const a = document.createElement('a');
|
|
2550
|
+
a.href = url;
|
|
2551
|
+
a.download = 'loki-mode-export-' + Date.now() + '.json';
|
|
2552
|
+
a.click();
|
|
2553
|
+
URL.revokeObjectURL(url);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// ============================================
|
|
2557
|
+
// Initialize
|
|
2558
|
+
// ============================================
|
|
2559
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2560
|
+
initTheme();
|
|
2561
|
+
loadLocalState();
|
|
2562
|
+
startPolling();
|
|
2563
|
+
|
|
2564
|
+
// Theme toggle (both desktop and mobile)
|
|
2565
|
+
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
|
|
2566
|
+
document.getElementById('mobile-theme-toggle').addEventListener('click', toggleTheme);
|
|
2567
|
+
|
|
2568
|
+
// Sidebar navigation - scroll to sections
|
|
2569
|
+
document.querySelectorAll('.sidebar-item[data-section]').forEach(item => {
|
|
2570
|
+
item.addEventListener('click', () => {
|
|
2571
|
+
const section = item.dataset.section;
|
|
2572
|
+
const targetElement = document.getElementById('section-' + section);
|
|
2573
|
+
|
|
2574
|
+
if (targetElement) {
|
|
2575
|
+
targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
2576
|
+
// Update active state
|
|
2577
|
+
document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
|
|
2578
|
+
item.classList.add('active');
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
// Scroll spy - update sidebar active state based on scroll position
|
|
2584
|
+
const sections = ['kanban', 'agents', 'system', 'terminal'];
|
|
2585
|
+
const mainContent = document.querySelector('.main-content');
|
|
2586
|
+
if (mainContent) {
|
|
2587
|
+
mainContent.addEventListener('scroll', () => {
|
|
2588
|
+
let currentSection = 'kanban';
|
|
2589
|
+
sections.forEach(section => {
|
|
2590
|
+
const el = document.getElementById('section-' + section);
|
|
2591
|
+
if (el && el.getBoundingClientRect().top < 200) {
|
|
2592
|
+
currentSection = section;
|
|
2593
|
+
}
|
|
2594
|
+
});
|
|
2595
|
+
document.querySelectorAll('.sidebar-item[data-section]').forEach(item => {
|
|
2596
|
+
item.classList.toggle('active', item.dataset.section === currentSection);
|
|
2597
|
+
});
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// Modal backdrop click
|
|
2602
|
+
document.getElementById('add-task-modal').addEventListener('click', e => {
|
|
2603
|
+
if (e.target.classList.contains('modal-overlay')) {
|
|
2604
|
+
closeAddTaskModal();
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
document.getElementById('github-modal').addEventListener('click', e => {
|
|
2609
|
+
if (e.target.classList.contains('modal-overlay')) {
|
|
2610
|
+
closeGitHubModal();
|
|
2611
|
+
}
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2614
|
+
// Keyboard shortcuts
|
|
2615
|
+
document.addEventListener('keydown', e => {
|
|
2616
|
+
if (e.key === 'Escape') {
|
|
2617
|
+
closeAddTaskModal();
|
|
2618
|
+
closeGitHubModal();
|
|
2619
|
+
}
|
|
2620
|
+
if (e.key === 'n' && (e.metaKey || e.ctrlKey)) {
|
|
2621
|
+
e.preventDefault();
|
|
2622
|
+
openAddTaskModal();
|
|
2623
|
+
}
|
|
2624
|
+
});
|
|
2625
|
+
|
|
2626
|
+
// Initial render with local data
|
|
2627
|
+
renderKanban();
|
|
2628
|
+
});
|
|
2629
|
+
|
|
2630
|
+
// Cleanup on page unload
|
|
2631
|
+
window.addEventListener('beforeunload', stopPolling);
|
|
2632
|
+
</script>
|
|
2633
|
+
</body>
|
|
2634
|
+
</html>
|