@wipal/agent-team 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/server/api/projects.js +71 -0
- package/src/server/index.js +2 -0
- package/src/ui/agents.html +126 -51
- package/src/ui/css/styles.css +49 -1
- package/src/ui/index.html +86 -8
package/package.json
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects API Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Router } from 'express';
|
|
6
|
+
import { listValidProjects, getProject, updateProjectAccess } from '../../utils/global-registry.js';
|
|
7
|
+
|
|
8
|
+
const router = Router();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all registered projects
|
|
12
|
+
*/
|
|
13
|
+
router.get('/', async (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const projects = await listValidProjects();
|
|
16
|
+
res.json({ projects });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
res.status(500).json({ error: error.message });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current project
|
|
24
|
+
*/
|
|
25
|
+
router.get('/current', async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const projectRoot = req.app.get('projectRoot');
|
|
28
|
+
const project = await getProject(projectRoot);
|
|
29
|
+
|
|
30
|
+
res.json({
|
|
31
|
+
path: projectRoot,
|
|
32
|
+
...project
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
res.status(500).json({ error: error.message });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Switch to a different project
|
|
41
|
+
*/
|
|
42
|
+
router.post('/switch', async (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
const { path: projectPath } = req.body;
|
|
45
|
+
|
|
46
|
+
if (!projectPath) {
|
|
47
|
+
return res.status(400).json({ error: 'Project path is required' });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const project = await getProject(projectPath);
|
|
51
|
+
if (!project) {
|
|
52
|
+
return res.status(404).json({ error: 'Project not found in registry' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update app's project root
|
|
56
|
+
req.app.set('projectRoot', projectPath);
|
|
57
|
+
await updateProjectAccess(projectPath);
|
|
58
|
+
|
|
59
|
+
res.json({
|
|
60
|
+
success: true,
|
|
61
|
+
project: {
|
|
62
|
+
path: projectPath,
|
|
63
|
+
...project
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
res.status(500).json({ error: error.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export default router;
|
package/src/server/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import open from 'open';
|
|
|
10
10
|
import agentsRouter from './api/agents.js';
|
|
11
11
|
import rolesRouter from './api/roles.js';
|
|
12
12
|
import skillsRouter from './api/skills.js';
|
|
13
|
+
import projectsRouter from './api/projects.js';
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = path.dirname(__filename);
|
|
@@ -37,6 +38,7 @@ export function createApp(projectRoot = process.cwd()) {
|
|
|
37
38
|
app.use('/api/agents', agentsRouter);
|
|
38
39
|
app.use('/api/roles', rolesRouter);
|
|
39
40
|
app.use('/api/skills', skillsRouter);
|
|
41
|
+
app.use('/api/projects', projectsRouter);
|
|
40
42
|
|
|
41
43
|
// Main page redirect
|
|
42
44
|
app.get('/', (req, res) => {
|
package/src/ui/agents.html
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
<!-- Add Agent Button -->
|
|
28
28
|
<div class="actions-bar">
|
|
29
|
-
<button class="btn btn-primary" @click="
|
|
29
|
+
<button class="btn btn-primary" @click="openAddModal()">
|
|
30
30
|
➕ Add New Agent
|
|
31
31
|
</button>
|
|
32
32
|
</div>
|
|
@@ -49,42 +49,55 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<div class="modal-body">
|
|
52
|
-
<!-- Step 1:
|
|
52
|
+
<!-- Step 1: Agent Name -->
|
|
53
53
|
<div class="form-group">
|
|
54
|
-
<label>Agent Name
|
|
54
|
+
<label>Agent Name *</label>
|
|
55
55
|
<input type="text" x-model="newAgent.name" placeholder="my-agent"
|
|
56
56
|
class="input" pattern="[a-z0-9-]+">
|
|
57
57
|
<small class="hint">Use lowercase letters, numbers, and hyphens</small>
|
|
58
58
|
</div>
|
|
59
59
|
|
|
60
|
-
<!-- Step 2: Select Role -->
|
|
60
|
+
<!-- Step 2: Select Role (Dropdown) -->
|
|
61
61
|
<div class="form-group">
|
|
62
|
-
<label>Role
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
|
|
62
|
+
<label>Role *</label>
|
|
63
|
+
<select x-model="newAgent.role" class="input" @change="onRoleChange()" :disabled="loadingRoles">
|
|
64
|
+
<option value="">-- Select Role --</option>
|
|
65
|
+
<template x-for="role in roles" :key="role.name">
|
|
66
|
+
<option :value="role.name" x-text="role.name"></option>
|
|
67
|
+
</template>
|
|
68
|
+
</select>
|
|
69
|
+
<small class="hint" x-show="selectedRoleDescription" x-text="selectedRoleDescription"></small>
|
|
66
70
|
</div>
|
|
67
71
|
|
|
68
|
-
<!-- Step 3: Select Variants (
|
|
69
|
-
<div x-show="newAgent.role" x-cloak class="variants-section">
|
|
70
|
-
<h3>Select Variants</h3>
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
<!-- Step 3: Select Variants (Dropdowns) -->
|
|
73
|
+
<div x-show="newAgent.role && Object.keys(variantCategories).length > 0" x-cloak class="variants-section">
|
|
74
|
+
<h3>Select Variants (Optional)</h3>
|
|
75
|
+
<template x-for="(category, key) in variantCategories" :key="key">
|
|
76
|
+
<div class="form-group">
|
|
77
|
+
<label x-text="category.label"></label>
|
|
78
|
+
<select class="input" @change="selectVariant(key, $event.target.value)">
|
|
79
|
+
<option value="">-- Select --</option>
|
|
80
|
+
<template x-for="option in category.options" :key="option.value">
|
|
81
|
+
<option :value="option.value"
|
|
82
|
+
:selected="newAgent.variants[key] === option.value"
|
|
83
|
+
x-text="option.label"></option>
|
|
84
|
+
</template>
|
|
85
|
+
</select>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
77
88
|
</div>
|
|
78
89
|
|
|
79
90
|
<!-- Step 4: Preview Skills -->
|
|
80
91
|
<div x-show="newAgent.role" x-cloak class="skills-preview">
|
|
81
92
|
<h3>Skills Preview</h3>
|
|
82
|
-
<div
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
<div x-show="loadingSkills" class="loading">Calculating skills...</div>
|
|
94
|
+
<div x-show="!loadingSkills && previewSkills.length > 0" class="skills-list">
|
|
95
|
+
<template x-for="skill in previewSkills" :key="skill">
|
|
96
|
+
<span class="skill-tag" x-text="skill"></span>
|
|
97
|
+
</template>
|
|
98
|
+
</div>
|
|
99
|
+
<div x-show="!loadingSkills && previewSkills.length === 0" class="hint">
|
|
100
|
+
Select variants to see included skills
|
|
88
101
|
</div>
|
|
89
102
|
</div>
|
|
90
103
|
</div>
|
|
@@ -92,8 +105,9 @@
|
|
|
92
105
|
<div class="modal-footer">
|
|
93
106
|
<button class="btn btn-secondary" @click="showAddModal = false">Cancel</button>
|
|
94
107
|
<button class="btn btn-primary" @click="createAgent()"
|
|
95
|
-
:disabled="!newAgent.name || !newAgent.role">
|
|
96
|
-
Create Agent
|
|
108
|
+
:disabled="!newAgent.name || !newAgent.role || creating">
|
|
109
|
+
<span x-show="!creating">Create Agent</span>
|
|
110
|
+
<span x-show="creating">Creating...</span>
|
|
97
111
|
</button>
|
|
98
112
|
</div>
|
|
99
113
|
</div>
|
|
@@ -104,27 +118,102 @@
|
|
|
104
118
|
function agentApp() {
|
|
105
119
|
return {
|
|
106
120
|
showAddModal: false,
|
|
121
|
+
loadingRoles: false,
|
|
122
|
+
loadingSkills: false,
|
|
123
|
+
creating: false,
|
|
124
|
+
roles: [],
|
|
125
|
+
variantCategories: {},
|
|
126
|
+
previewSkills: [],
|
|
107
127
|
newAgent: {
|
|
108
128
|
name: '',
|
|
109
129
|
role: '',
|
|
110
130
|
variants: {}
|
|
111
131
|
},
|
|
112
132
|
|
|
113
|
-
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
htmx.trigger(document.body, 'roleSelected', { role });
|
|
133
|
+
get selectedRoleDescription() {
|
|
134
|
+
const role = this.roles.find(r => r.name === this.newAgent.role);
|
|
135
|
+
return role ? role.description : '';
|
|
117
136
|
},
|
|
118
137
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
async openAddModal() {
|
|
139
|
+
this.showAddModal = true;
|
|
140
|
+
await this.loadRoles();
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async loadRoles() {
|
|
144
|
+
if (this.roles.length > 0) return; // Already loaded
|
|
145
|
+
|
|
146
|
+
this.loadingRoles = true;
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch('/api/agents/meta/roles');
|
|
149
|
+
this.roles = await response.json();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Failed to load roles:', error);
|
|
152
|
+
} finally {
|
|
153
|
+
this.loadingRoles = false;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async onRoleChange() {
|
|
158
|
+
// Reset variants and skills
|
|
159
|
+
this.newAgent.variants = {};
|
|
160
|
+
this.previewSkills = [];
|
|
161
|
+
this.variantCategories = {};
|
|
162
|
+
|
|
163
|
+
if (!this.newAgent.role) return;
|
|
164
|
+
|
|
165
|
+
// Load variants for selected role
|
|
166
|
+
await this.loadVariants(this.newAgent.role);
|
|
167
|
+
// Load skills preview
|
|
168
|
+
await this.loadSkillsPreview();
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
async loadVariants(role) {
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch(`/api/agents/meta/variants/${role}`);
|
|
174
|
+
const data = await response.json();
|
|
175
|
+
this.variantCategories = data.categories || data;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('Failed to load variants:', error);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
async selectVariant(category, value) {
|
|
182
|
+
if (value) {
|
|
123
183
|
this.newAgent.variants[category] = value;
|
|
184
|
+
} else {
|
|
185
|
+
delete this.newAgent.variants[category];
|
|
186
|
+
}
|
|
187
|
+
// Update skills preview
|
|
188
|
+
await this.loadSkillsPreview();
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
async loadSkillsPreview() {
|
|
192
|
+
if (!this.newAgent.role) return;
|
|
193
|
+
|
|
194
|
+
this.loadingSkills = true;
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch('/api/agents/preview-skills', {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
role: this.newAgent.role,
|
|
201
|
+
variants: this.newAgent.variants
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
const data = await response.json();
|
|
205
|
+
this.previewSkills = data.skills || data.core?.concat(data.role || []) || [];
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Failed to load skills preview:', error);
|
|
208
|
+
} finally {
|
|
209
|
+
this.loadingSkills = false;
|
|
124
210
|
}
|
|
125
211
|
},
|
|
126
212
|
|
|
127
213
|
async createAgent() {
|
|
214
|
+
if (!this.newAgent.name || !this.newAgent.role) return;
|
|
215
|
+
|
|
216
|
+
this.creating = true;
|
|
128
217
|
try {
|
|
129
218
|
const response = await fetch('/api/agents', {
|
|
130
219
|
method: 'POST',
|
|
@@ -143,32 +232,18 @@
|
|
|
143
232
|
}
|
|
144
233
|
} catch (error) {
|
|
145
234
|
alert('Error creating agent: ' + error.message);
|
|
235
|
+
} finally {
|
|
236
|
+
this.creating = false;
|
|
146
237
|
}
|
|
147
238
|
},
|
|
148
239
|
|
|
149
240
|
resetForm() {
|
|
150
241
|
this.newAgent = { name: '', role: '', variants: {} };
|
|
242
|
+
this.variantCategories = {};
|
|
243
|
+
this.previewSkills = [];
|
|
151
244
|
}
|
|
152
245
|
}
|
|
153
246
|
}
|
|
154
|
-
|
|
155
|
-
// Transform role list to radio buttons
|
|
156
|
-
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
|
157
|
-
if (evt.detail.pathInfo.requestPath.includes('/meta/roles')) {
|
|
158
|
-
try {
|
|
159
|
-
const roles = JSON.parse(evt.detail.xhr.response);
|
|
160
|
-
evt.detail.xhr.response = roles.map(r => `
|
|
161
|
-
<label class="role-option" :class="{'selected': newAgent.role === '${r.name}'}">
|
|
162
|
-
<input type="radio" name="role" value="${r.name}" @click="selectRole('${r.name}')">
|
|
163
|
-
<div class="role-info">
|
|
164
|
-
<strong>${r.name}</strong>
|
|
165
|
-
<p>${r.description}</p>
|
|
166
|
-
</div>
|
|
167
|
-
</label>
|
|
168
|
-
`).join('');
|
|
169
|
-
} catch (e) {}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
247
|
</script>
|
|
173
248
|
</body>
|
|
174
249
|
</html>
|
package/src/ui/css/styles.css
CHANGED
|
@@ -36,15 +36,63 @@ body {
|
|
|
36
36
|
|
|
37
37
|
/* Header */
|
|
38
38
|
.header {
|
|
39
|
-
text-align: center;
|
|
40
39
|
margin-bottom: 2rem;
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
.header-row {
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
align-items: flex-start;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
gap: 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.header-row > div:first-child {
|
|
51
|
+
text-align: left;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
.header h1 {
|
|
44
55
|
font-size: 2rem;
|
|
45
56
|
margin-bottom: 0.5rem;
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
/* Project Selector */
|
|
60
|
+
.project-selector {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 0.5rem;
|
|
64
|
+
padding: 0.75rem 1rem;
|
|
65
|
+
background: var(--bg-card);
|
|
66
|
+
border-radius: 0.5rem;
|
|
67
|
+
box-shadow: var(--shadow);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.project-selector label {
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
color: var(--text-muted);
|
|
73
|
+
font-size: 0.875rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.project-selector select {
|
|
77
|
+
padding: 0.5rem 0.75rem;
|
|
78
|
+
border: 1px solid var(--border);
|
|
79
|
+
border-radius: 0.375rem;
|
|
80
|
+
font-size: 0.875rem;
|
|
81
|
+
min-width: 200px;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.project-selector select:focus {
|
|
86
|
+
outline: none;
|
|
87
|
+
border-color: var(--primary);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.loading-indicator {
|
|
91
|
+
font-size: 0.75rem;
|
|
92
|
+
color: var(--primary);
|
|
93
|
+
font-style: italic;
|
|
94
|
+
}
|
|
95
|
+
|
|
48
96
|
.subtitle {
|
|
49
97
|
color: var(--text-muted);
|
|
50
98
|
}
|
package/src/ui/index.html
CHANGED
|
@@ -10,10 +10,26 @@
|
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div class="container">
|
|
13
|
-
<!-- Header -->
|
|
13
|
+
<!-- Header with Project Selector -->
|
|
14
14
|
<header class="header">
|
|
15
|
-
<
|
|
16
|
-
|
|
15
|
+
<div class="header-row">
|
|
16
|
+
<div>
|
|
17
|
+
<h1>🤖 Agent Team Dashboard</h1>
|
|
18
|
+
<p class="subtitle">Manage AI agents for your project</p>
|
|
19
|
+
</div>
|
|
20
|
+
<!-- Project Selector -->
|
|
21
|
+
<div class="project-selector" x-data="projectSelector()">
|
|
22
|
+
<label>Project:</label>
|
|
23
|
+
<select @change="switchProject($event)" x-model="currentProject" :disabled="loading">
|
|
24
|
+
<template x-for="project in projects" :key="project.path">
|
|
25
|
+
<option :value="project.path" :selected="project.path === currentProject">
|
|
26
|
+
<span x-text="project.name"></span>
|
|
27
|
+
</option>
|
|
28
|
+
</template>
|
|
29
|
+
</select>
|
|
30
|
+
<span x-show="loading" class="loading-indicator">Switching...</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
17
33
|
</header>
|
|
18
34
|
|
|
19
35
|
<!-- Navigation -->
|
|
@@ -25,7 +41,7 @@
|
|
|
25
41
|
</nav>
|
|
26
42
|
|
|
27
43
|
<!-- Stats Cards -->
|
|
28
|
-
<div class="stats-grid" hx-get="/api/agents" hx-trigger="load" hx-swap="innerHTML">
|
|
44
|
+
<div class="stats-grid" hx-get="/api/agents" hx-trigger="load, projectChanged from:body" hx-swap="innerHTML">
|
|
29
45
|
<!-- Loaded via HTMX -->
|
|
30
46
|
<div class="loading">Loading...</div>
|
|
31
47
|
</div>
|
|
@@ -49,7 +65,7 @@
|
|
|
49
65
|
<!-- Recent Agents -->
|
|
50
66
|
<section class="section">
|
|
51
67
|
<h2>Your Agents</h2>
|
|
52
|
-
<div id="agents-list" hx-get="/api/agents" hx-trigger="load">
|
|
68
|
+
<div id="agents-list" hx-get="/api/agents" hx-trigger="load, projectChanged from:body">
|
|
53
69
|
<div class="loading">Loading agents...</div>
|
|
54
70
|
</div>
|
|
55
71
|
</section>
|
|
@@ -64,13 +80,75 @@
|
|
|
64
80
|
</div>
|
|
65
81
|
|
|
66
82
|
<script>
|
|
83
|
+
// Project Selector Alpine Component
|
|
84
|
+
function projectSelector() {
|
|
85
|
+
return {
|
|
86
|
+
projects: [],
|
|
87
|
+
currentProject: '',
|
|
88
|
+
loading: false,
|
|
89
|
+
|
|
90
|
+
async init() {
|
|
91
|
+
await this.loadProjects();
|
|
92
|
+
await this.loadCurrentProject();
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async loadProjects() {
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch('/api/projects');
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
this.projects = data.projects.filter(p => p.valid);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Failed to load projects:', error);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async loadCurrentProject() {
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch('/api/projects/current');
|
|
108
|
+
const project = await response.json();
|
|
109
|
+
this.currentProject = project.path;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Failed to load current project:', error);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async switchProject(event) {
|
|
116
|
+
const newPath = event.target.value;
|
|
117
|
+
if (newPath === this.currentProject) return;
|
|
118
|
+
|
|
119
|
+
this.loading = true;
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch('/api/projects/switch', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify({ path: newPath })
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
this.currentProject = newPath;
|
|
129
|
+
// Trigger HTMX refresh
|
|
130
|
+
document.body.dispatchEvent(new CustomEvent('projectChanged'));
|
|
131
|
+
} else {
|
|
132
|
+
const error = await response.json();
|
|
133
|
+
alert('Failed to switch project: ' + error.error);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
alert('Failed to switch project: ' + error.message);
|
|
137
|
+
} finally {
|
|
138
|
+
this.loading = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
67
144
|
// Transform stats response
|
|
68
145
|
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
|
69
146
|
if (evt.detail.pathInfo.requestPath === '/api/agents') {
|
|
70
147
|
// First load - render as stats
|
|
71
148
|
if (evt.detail.target.matches('.stats-grid')) {
|
|
72
149
|
try {
|
|
73
|
-
const
|
|
150
|
+
const data = JSON.parse(evt.detail.xhr.response);
|
|
151
|
+
const agents = data.agents || [];
|
|
74
152
|
const statsHtml = renderStats(agents);
|
|
75
153
|
evt.detail.swap = 'innerHTML';
|
|
76
154
|
evt.detail.xhr.response = statsHtml;
|
|
@@ -93,11 +171,11 @@
|
|
|
93
171
|
<div class="stat-label">Roles Used</div>
|
|
94
172
|
</div>
|
|
95
173
|
<div class="stat-card">
|
|
96
|
-
<div class="stat-value">
|
|
174
|
+
<div class="stat-value">50</div>
|
|
97
175
|
<div class="stat-label">Skills Available</div>
|
|
98
176
|
</div>
|
|
99
177
|
<div class="stat-card">
|
|
100
|
-
<div class="stat-value">
|
|
178
|
+
<div class="stat-value">9</div>
|
|
101
179
|
<div class="stat-label">Roles Available</div>
|
|
102
180
|
</div>
|
|
103
181
|
`;
|