@wipal/agent-team 1.2.1 → 1.2.3
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/ui/agents.html +131 -17
- package/src/ui/css/styles.css +125 -0
- package/src/ui/index.html +110 -40
- package/src/utils/skill-resolver.js +13 -6
package/package.json
CHANGED
package/src/ui/agents.html
CHANGED
|
@@ -33,8 +33,38 @@
|
|
|
33
33
|
|
|
34
34
|
<!-- Agents List -->
|
|
35
35
|
<section class="section">
|
|
36
|
-
<div id="agents-grid"
|
|
37
|
-
<div class="loading">Loading agents...</div>
|
|
36
|
+
<div id="agents-grid">
|
|
37
|
+
<div x-show="loadingAgents" class="loading">Loading agents...</div>
|
|
38
|
+
<div x-show="!loadingAgents && agents.length === 0" class="empty-state">
|
|
39
|
+
<p>No agents yet. <a href="#" @click.prevent="openAddModal()">Add your first agent</a></p>
|
|
40
|
+
</div>
|
|
41
|
+
<template x-for="agent in agents" :key="agent.name">
|
|
42
|
+
<div class="agent-card">
|
|
43
|
+
<div class="agent-header">
|
|
44
|
+
<span class="agent-name" x-text="agent.name"></span>
|
|
45
|
+
<span class="agent-role" x-text="agent.variants?.base_role || 'unknown'"></span>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="agent-variants">
|
|
48
|
+
<template x-for="(value, key) in agent.variants" :key="key + 'variant-' + key">
|
|
49
|
+
<template x-if="Array.isArray(value)">
|
|
50
|
+
<template x-for="v in value" :key="v">
|
|
51
|
+
<span class="variant-tag" x-text="key + ': ' + v"></span>
|
|
52
|
+
</template>
|
|
53
|
+
<template x-else>
|
|
54
|
+
<span class="variant-tag" x-text="key + ': ' + value"></span>
|
|
55
|
+
</template>
|
|
56
|
+
</template>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="agent-skills" x-show="agent.skills?.length">
|
|
59
|
+
<template x-for="skill in agent.skills" :key="skill">
|
|
60
|
+
<span class="skill-tag" x-text="skill"></span>
|
|
61
|
+
</template>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="agent-actions">
|
|
64
|
+
<button class="btn btn-danger" @click="deleteAgent(agent.name)">Delete</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
38
68
|
</div>
|
|
39
69
|
</section>
|
|
40
70
|
|
|
@@ -69,20 +99,39 @@
|
|
|
69
99
|
<small class="hint" x-show="selectedRoleDescription" x-text="selectedRoleDescription"></small>
|
|
70
100
|
</div>
|
|
71
101
|
|
|
72
|
-
<!-- Step 3: Select Variants (Dropdowns) -->
|
|
102
|
+
<!-- Step 3: Select Variants (Dropdowns or Checkboxes) -->
|
|
73
103
|
<div x-show="newAgent.role && Object.keys(variantCategories).length > 0" x-cloak class="variants-section">
|
|
74
104
|
<h3>Select Variants (Optional)</h3>
|
|
75
105
|
<template x-for="(category, key) in variantCategories" :key="key">
|
|
76
106
|
<div class="form-group">
|
|
77
107
|
<label x-text="category.label"></label>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
|
|
109
|
+
<!-- Multi-select (checkbox type) -->
|
|
110
|
+
<template x-if="category.type === 'checkbox'">
|
|
111
|
+
<div class="checkbox-group">
|
|
112
|
+
<template x-for="option in category.options" :key="option.value">
|
|
113
|
+
<label class="checkbox-label">
|
|
114
|
+
<input type="checkbox"
|
|
115
|
+
:value="option.value"
|
|
116
|
+
@change="selectVariant(key, option.value, true)"
|
|
117
|
+
:checked="isVariantSelected(key, option.value)">
|
|
118
|
+
<span x-text="option.label"></span>
|
|
119
|
+
</label>
|
|
120
|
+
</template>
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
123
|
+
|
|
124
|
+
<!-- Single-select (select type) -->
|
|
125
|
+
<template x-if="category.type !== 'checkbox'">
|
|
126
|
+
<select class="input" @change="selectVariant(key, $event.target.value, false)">
|
|
127
|
+
<option value="">-- Select --</option>
|
|
128
|
+
<template x-for="option in category.options" :key="option.value">
|
|
129
|
+
<option :value="option.value"
|
|
130
|
+
:selected="newAgent.variants[key] === option.value"
|
|
131
|
+
x-text="option.label"></option>
|
|
132
|
+
</template>
|
|
133
|
+
</select>
|
|
134
|
+
</template>
|
|
86
135
|
</div>
|
|
87
136
|
</template>
|
|
88
137
|
</div>
|
|
@@ -118,9 +167,11 @@
|
|
|
118
167
|
function agentApp() {
|
|
119
168
|
return {
|
|
120
169
|
showAddModal: false,
|
|
170
|
+
loadingAgents: true,
|
|
121
171
|
loadingRoles: false,
|
|
122
172
|
loadingSkills: false,
|
|
123
173
|
creating: false,
|
|
174
|
+
agents: [],
|
|
124
175
|
roles: [],
|
|
125
176
|
variantCategories: {},
|
|
126
177
|
previewSkills: [],
|
|
@@ -130,6 +181,38 @@
|
|
|
130
181
|
variants: {}
|
|
131
182
|
},
|
|
132
183
|
|
|
184
|
+
async init() {
|
|
185
|
+
await this.loadAgents();
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
async loadAgents() {
|
|
189
|
+
this.loadingAgents = true;
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch('/api/agents');
|
|
192
|
+
const data = await response.json();
|
|
193
|
+
this.agents = data.agents || [];
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Failed to load agents:', error);
|
|
196
|
+
} finally {
|
|
197
|
+
this.loadingAgents = false;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async deleteAgent(name) {
|
|
202
|
+
if (!confirm(`Delete agent "${name}"?`)) return;
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(`/api/agents/${name}`, { method: 'DELETE' });
|
|
205
|
+
if (response.ok) {
|
|
206
|
+
await this.loadAgents();
|
|
207
|
+
} else {
|
|
208
|
+
const error = await response.json();
|
|
209
|
+
alert('Failed to delete: ' + error.error);
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
alert('Failed to delete agent: ' + error.message);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
133
216
|
get selectedRoleDescription() {
|
|
134
217
|
const role = this.roles.find(r => r.name === this.newAgent.role);
|
|
135
218
|
return role ? role.description : '';
|
|
@@ -178,12 +261,41 @@
|
|
|
178
261
|
}
|
|
179
262
|
},
|
|
180
263
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
264
|
+
isVariantSelected(category, value) {
|
|
265
|
+
const selected = this.newAgent.variants[category];
|
|
266
|
+
if (Array.isArray(selected)) {
|
|
267
|
+
return selected.includes(value);
|
|
268
|
+
}
|
|
269
|
+
return selected === value;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
async selectVariant(category, value, isMulti = false) {
|
|
273
|
+
if (isMulti) {
|
|
274
|
+
// Multi-select: toggle value in array
|
|
275
|
+
if (!this.newAgent.variants[category]) {
|
|
276
|
+
this.newAgent.variants[category] = [];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const index = this.newAgent.variants[category].indexOf(value);
|
|
280
|
+
if (index > -1) {
|
|
281
|
+
// Remove if already selected
|
|
282
|
+
this.newAgent.variants[category].splice(index, 1);
|
|
283
|
+
if (this.newAgent.variants[category].length === 0) {
|
|
284
|
+
delete this.newAgent.variants[category];
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Add if not selected
|
|
288
|
+
this.newAgent.variants[category].push(value);
|
|
289
|
+
}
|
|
184
290
|
} else {
|
|
185
|
-
|
|
291
|
+
// Single select: replace value
|
|
292
|
+
if (value) {
|
|
293
|
+
this.newAgent.variants[category] = value
|
|
294
|
+
} else {
|
|
295
|
+
delete this.newAgent.variants[category];
|
|
296
|
+
}
|
|
186
297
|
}
|
|
298
|
+
|
|
187
299
|
// Update skills preview
|
|
188
300
|
await this.loadSkillsPreview();
|
|
189
301
|
},
|
|
@@ -224,7 +336,7 @@
|
|
|
224
336
|
if (response.ok) {
|
|
225
337
|
this.showAddModal = false;
|
|
226
338
|
this.resetForm();
|
|
227
|
-
|
|
339
|
+
await this.loadAgents();
|
|
228
340
|
alert('Agent created successfully!');
|
|
229
341
|
} else {
|
|
230
342
|
const error = await response.json();
|
|
@@ -241,7 +353,9 @@
|
|
|
241
353
|
this.newAgent = { name: '', role: '', variants: {} };
|
|
242
354
|
this.variantCategories = {};
|
|
243
355
|
this.previewSkills = [];
|
|
244
|
-
|
|
356
|
+
// Force Alpine to re-render by reassigning
|
|
357
|
+
this.$forceUpdate();
|
|
358
|
+
},
|
|
245
359
|
}
|
|
246
360
|
}
|
|
247
361
|
</script>
|
package/src/ui/css/styles.css
CHANGED
|
@@ -497,6 +497,131 @@ body {
|
|
|
497
497
|
/* Utility */
|
|
498
498
|
[x-cloak] { display: none !important; }
|
|
499
499
|
|
|
500
|
+
/* Empty State */
|
|
501
|
+
.empty-state {
|
|
502
|
+
text-align: center;
|
|
503
|
+
padding: 2rem;
|
|
504
|
+
color: var(--text-muted);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.empty-state a {
|
|
508
|
+
color: var(--primary);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* Compact agent card for dashboard */
|
|
512
|
+
.agent-card-compact {
|
|
513
|
+
display: flex;
|
|
514
|
+
justify-content: space-between;
|
|
515
|
+
align-items: center;
|
|
516
|
+
padding: 0.75rem 1rem;
|
|
517
|
+
background: var(--bg);
|
|
518
|
+
border-radius: 0.375rem;
|
|
519
|
+
margin-bottom: 0.5rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.agent-card-compact .agent-name {
|
|
523
|
+
font-weight: 500;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.agent-card-compact .agent-role {
|
|
527
|
+
font-size: 0.75rem;
|
|
528
|
+
color: var(--text-muted);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* Roles preview grid */
|
|
532
|
+
.roles-grid-preview {
|
|
533
|
+
display: grid;
|
|
534
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
535
|
+
gap: 0.75rem;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.role-chip {
|
|
539
|
+
padding: 0.75rem 1rem;
|
|
540
|
+
background: var(--bg);
|
|
541
|
+
border-radius: 0.375rem;
|
|
542
|
+
border: 1px solid var(--border);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.role-chip strong {
|
|
546
|
+
display: block;
|
|
547
|
+
margin-bottom: 0.25rem;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.role-chip span {
|
|
551
|
+
font-size: 0.75rem;
|
|
552
|
+
color: var(--text-muted);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* Agent variants in card */
|
|
556
|
+
.agent-variants {
|
|
557
|
+
display: flex;
|
|
558
|
+
flex-wrap: wrap;
|
|
559
|
+
gap: 0.25rem;
|
|
560
|
+
margin: 0.5rem 0;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.variant-tag {
|
|
564
|
+
background: var(--bg);
|
|
565
|
+
padding: 0.125rem 0.5rem;
|
|
566
|
+
border-radius: 0.25rem;
|
|
567
|
+
font-size: 0.7rem;
|
|
568
|
+
color: var(--text-muted);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* Checkbox group for multi-select variants */
|
|
572
|
+
.checkbox-group {
|
|
573
|
+
display: flex;
|
|
574
|
+
flex-wrap: wrap;
|
|
575
|
+
gap: 0.5rem;
|
|
576
|
+
margin-top: 0.5rem;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.checkbox-label {
|
|
580
|
+
display: flex;
|
|
581
|
+
align-items: center;
|
|
582
|
+
gap: 0.375rem;
|
|
583
|
+
padding: 0.5rem 0.75rem;
|
|
584
|
+
background: var(--bg);
|
|
585
|
+
border: 1px solid var(--border);
|
|
586
|
+
border-radius: 0.375rem;
|
|
587
|
+
cursor: pointer;
|
|
588
|
+
font-size: 0.875rem;
|
|
589
|
+
transition: all 0.2s;
|
|
590
|
+
user-select: none;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.checkbox-label:hover {
|
|
594
|
+
border-color: var(--primary);
|
|
595
|
+
background: rgba(59, 130, 246, 0.05);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.checkbox-label.checked {
|
|
599
|
+
background: var(--primary);
|
|
600
|
+
border-color: var(--primary);
|
|
601
|
+
color: white;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.checkbox-label input[type="checkbox"] {
|
|
605
|
+
position: absolute;
|
|
606
|
+
opacity: 0;
|
|
607
|
+
pointer-events: none;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/* Variant type badge */
|
|
611
|
+
.variant-type-badge {
|
|
612
|
+
font-size: 0.65rem;
|
|
613
|
+
padding: 0.125rem 0.375rem;
|
|
614
|
+
border-radius: 0.25rem;
|
|
615
|
+
background: var(--border);
|
|
616
|
+
color: var(--text-muted);
|
|
617
|
+
margin-left: 0.5rem;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.variant-type-badge.multi {
|
|
621
|
+
background: var(--success);
|
|
622
|
+
color: white;
|
|
623
|
+
}
|
|
624
|
+
|
|
500
625
|
/* Responsive */
|
|
501
626
|
@media (max-width: 768px) {
|
|
502
627
|
.container {
|
package/src/ui/index.html
CHANGED
|
@@ -41,9 +41,18 @@
|
|
|
41
41
|
</nav>
|
|
42
42
|
|
|
43
43
|
<!-- Stats Cards -->
|
|
44
|
-
<div class="stats-grid"
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
<div class="stats-grid" x-data="statsCards()">
|
|
45
|
+
<template x-if="loading">
|
|
46
|
+
<div class="stat-card"><div class="loading">Loading...</div></div>
|
|
47
|
+
</template>
|
|
48
|
+
<template x-if="!loading">
|
|
49
|
+
<template x-for="stat in stats" :key="stat.label">
|
|
50
|
+
<div class="stat-card">
|
|
51
|
+
<div class="stat-value" x-text="stat.value"></div>
|
|
52
|
+
<div class="stat-label" x-text="stat.label"></div>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
</template>
|
|
47
56
|
</div>
|
|
48
57
|
|
|
49
58
|
<!-- Quick Actions -->
|
|
@@ -65,16 +74,36 @@
|
|
|
65
74
|
<!-- Recent Agents -->
|
|
66
75
|
<section class="section">
|
|
67
76
|
<h2>Your Agents</h2>
|
|
68
|
-
<div id="agents-list"
|
|
69
|
-
<div class="loading">Loading agents...</div>
|
|
77
|
+
<div id="agents-list" x-data="dashboardAgents()">
|
|
78
|
+
<div x-show="loading" class="loading">Loading agents...</div>
|
|
79
|
+
<div x-show="!loading && agents.length === 0" class="empty-state">
|
|
80
|
+
<p>No agents configured yet. <a href="/ui/agents.html?action=add">Add your first agent</a></p>
|
|
81
|
+
</div>
|
|
82
|
+
<template x-for="agent in agents" :key="agent.name">
|
|
83
|
+
<div class="agent-card-compact">
|
|
84
|
+
<span class="agent-name" x-text="agent.name"></span>
|
|
85
|
+
<span class="agent-role" x-text="agent.variants?.base_role || 'unknown'"></span>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
<div x-show="!loading && agents.length > 0" style="margin-top: 1rem;">
|
|
89
|
+
<a href="/ui/agents.html" class="btn btn-secondary">Manage Agents</a>
|
|
90
|
+
</div>
|
|
70
91
|
</div>
|
|
71
92
|
</section>
|
|
72
93
|
|
|
73
94
|
<!-- Available Roles -->
|
|
74
95
|
<section class="section">
|
|
75
96
|
<h2>Available Roles</h2>
|
|
76
|
-
<div id="roles-list"
|
|
77
|
-
<div class="loading">Loading roles...</div>
|
|
97
|
+
<div id="roles-list" x-data="rolesPreview()">
|
|
98
|
+
<div x-show="loading" class="loading">Loading roles...</div>
|
|
99
|
+
<div class="roles-grid-preview">
|
|
100
|
+
<template x-for="role in roles" :key="role.name">
|
|
101
|
+
<div class="role-chip">
|
|
102
|
+
<strong x-text="role.name"></strong>
|
|
103
|
+
<span x-text="role.description"></span>
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|
|
106
|
+
</div>
|
|
78
107
|
</div>
|
|
79
108
|
</section>
|
|
80
109
|
</div>
|
|
@@ -141,44 +170,85 @@
|
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
173
|
+
// Dashboard Agents Alpine Component
|
|
174
|
+
function dashboardAgents() {
|
|
175
|
+
return {
|
|
176
|
+
agents: [],
|
|
177
|
+
loading: true,
|
|
178
|
+
|
|
179
|
+
async init() {
|
|
180
|
+
await this.loadAgents();
|
|
181
|
+
// Listen for project changes
|
|
182
|
+
document.body.addEventListener('projectChanged', () => this.loadAgents());
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
async loadAgents() {
|
|
186
|
+
this.loading = true;
|
|
149
187
|
try {
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
188
|
+
const response = await fetch('/api/agents');
|
|
189
|
+
const data = await response.json();
|
|
190
|
+
this.agents = data.agents || [];
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Failed to load agents:', error);
|
|
193
|
+
} finally {
|
|
194
|
+
this.loading = false;
|
|
195
|
+
}
|
|
156
196
|
}
|
|
157
197
|
}
|
|
158
|
-
}
|
|
198
|
+
}
|
|
159
199
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
200
|
+
// Roles Preview Alpine Component
|
|
201
|
+
function rolesPreview() {
|
|
202
|
+
return {
|
|
203
|
+
roles: [],
|
|
204
|
+
loading: true,
|
|
163
205
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
async init() {
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch('/api/agents/meta/roles');
|
|
209
|
+
this.roles = await response.json();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to load roles:', error);
|
|
212
|
+
} finally {
|
|
213
|
+
this.loading = false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Stats Cards Alpine Component
|
|
220
|
+
function statsCards() {
|
|
221
|
+
return {
|
|
222
|
+
stats: [],
|
|
223
|
+
loading: true,
|
|
224
|
+
|
|
225
|
+
async init() {
|
|
226
|
+
await this.loadStats();
|
|
227
|
+
// Listen for project changes
|
|
228
|
+
document.body.addEventListener('projectChanged', () => this.loadStats());
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async loadStats() {
|
|
232
|
+
this.loading = true;
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch('/api/agents');
|
|
235
|
+
const data = await response.json();
|
|
236
|
+
const agents = data.agents || [];
|
|
237
|
+
const roles = [...new Set(agents.map(a => a.variants?.base_role).filter(Boolean))];
|
|
238
|
+
|
|
239
|
+
this.stats = [
|
|
240
|
+
{ value: agents.length, label: 'Total Agents' },
|
|
241
|
+
{ value: roles.length, label: 'Roles Used' },
|
|
242
|
+
{ value: 50, label: 'Skills Available' },
|
|
243
|
+
{ value: 9, label: 'Roles Available' }
|
|
244
|
+
];
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Failed to load stats:', error);
|
|
247
|
+
} finally {
|
|
248
|
+
this.loading = false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
182
252
|
}
|
|
183
253
|
</script>
|
|
184
254
|
</body>
|
|
@@ -542,14 +542,21 @@ export function resolveSkills(role, variants = {}) {
|
|
|
542
542
|
result.role = [...ROLE_SKILL_MAP[role]];
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
// Add variant-specific skills
|
|
545
|
+
// Add variant-specific skills (supports both single values and arrays for multi-select)
|
|
546
546
|
for (const [category, value] of Object.entries(variants)) {
|
|
547
547
|
if (value) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
548
|
+
// Handle both single value and array of values
|
|
549
|
+
const values = Array.isArray(value) ? value : [value];
|
|
550
|
+
|
|
551
|
+
for (const v of values) {
|
|
552
|
+
if (v) {
|
|
553
|
+
const key = `${category}:${v}`;
|
|
554
|
+
if (VARIANT_SKILL_MAP[key]) {
|
|
555
|
+
for (const skill of VARIANT_SKILL_MAP[key]) {
|
|
556
|
+
if (!result.variants.includes(skill) && !result.role.includes(skill)) {
|
|
557
|
+
result.variants.push(skill);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
553
560
|
}
|
|
554
561
|
}
|
|
555
562
|
}
|