@reallyartificial/grain 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder-server.d.ts +2 -0
- package/dist/builder-server.d.ts.map +1 -0
- package/dist/builder-server.js +156 -0
- package/dist/builder-server.js.map +1 -0
- package/dist/builder.html +1387 -0
- package/dist/cli.js +31 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1387 @@
|
|
|
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">
|
|
6
|
+
<title>Grain Builder</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌾</text></svg>">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--bg: #f5f0e8;
|
|
13
|
+
--surface: #fff;
|
|
14
|
+
--surface-alt: #ebe5d9;
|
|
15
|
+
--ink: #1a1a1a;
|
|
16
|
+
--ink-dim: #6b6560;
|
|
17
|
+
--ink-faint: #a09890;
|
|
18
|
+
--red: #c0392b;
|
|
19
|
+
--green: #1a7a4c;
|
|
20
|
+
--blue: #1a4a7a;
|
|
21
|
+
--border: #1a1a1a;
|
|
22
|
+
--border-light: #ccc5b8;
|
|
23
|
+
--mono: "IBM Plex Mono", "Menlo", monospace;
|
|
24
|
+
--sans: "Space Grotesk", -apple-system, system-ui, sans-serif;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
28
|
+
::selection { background: #1a1a1a; color: #f5f0e8; }
|
|
29
|
+
|
|
30
|
+
body {
|
|
31
|
+
font-family: var(--sans);
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
color: var(--ink);
|
|
34
|
+
line-height: 1.55;
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* NAV */
|
|
40
|
+
.nav {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 14px 24px;
|
|
45
|
+
border-bottom: 2px solid var(--border);
|
|
46
|
+
position: sticky;
|
|
47
|
+
top: 0;
|
|
48
|
+
background: var(--bg);
|
|
49
|
+
z-index: 100;
|
|
50
|
+
}
|
|
51
|
+
.nav-title {
|
|
52
|
+
font-family: var(--mono);
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
font-size: 0.9rem;
|
|
55
|
+
letter-spacing: -0.02em;
|
|
56
|
+
}
|
|
57
|
+
.nav-actions {
|
|
58
|
+
display: flex;
|
|
59
|
+
gap: 8px;
|
|
60
|
+
}
|
|
61
|
+
.nav-btn {
|
|
62
|
+
font-family: var(--mono);
|
|
63
|
+
font-size: 0.72rem;
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
letter-spacing: 0.06em;
|
|
66
|
+
padding: 6px 14px;
|
|
67
|
+
border: 1.5px solid var(--border);
|
|
68
|
+
background: var(--surface);
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
color: var(--ink);
|
|
71
|
+
}
|
|
72
|
+
.nav-btn:hover { background: var(--ink); color: var(--bg); }
|
|
73
|
+
|
|
74
|
+
/* LAYOUT */
|
|
75
|
+
.layout {
|
|
76
|
+
display: grid;
|
|
77
|
+
grid-template-columns: 420px 1fr;
|
|
78
|
+
min-height: calc(100vh - 52px);
|
|
79
|
+
}
|
|
80
|
+
@media (max-width: 900px) {
|
|
81
|
+
.layout { grid-template-columns: 1fr; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* FORM */
|
|
85
|
+
.form-panel {
|
|
86
|
+
border-right: 2px solid var(--border);
|
|
87
|
+
overflow-y: auto;
|
|
88
|
+
max-height: calc(100vh - 52px);
|
|
89
|
+
}
|
|
90
|
+
@media (max-width: 900px) {
|
|
91
|
+
.form-panel { border-right: none; border-bottom: 2px solid var(--border); max-height: none; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
details {
|
|
95
|
+
border-bottom: 1px solid var(--border-light);
|
|
96
|
+
}
|
|
97
|
+
details[open] > summary { border-bottom: 1px solid var(--border-light); }
|
|
98
|
+
summary {
|
|
99
|
+
font-family: var(--mono);
|
|
100
|
+
font-size: 0.72rem;
|
|
101
|
+
font-weight: 600;
|
|
102
|
+
text-transform: uppercase;
|
|
103
|
+
letter-spacing: 0.08em;
|
|
104
|
+
padding: 14px 20px;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
user-select: none;
|
|
107
|
+
color: var(--ink-dim);
|
|
108
|
+
background: var(--surface-alt);
|
|
109
|
+
}
|
|
110
|
+
summary:hover { color: var(--ink); }
|
|
111
|
+
summary::marker { color: var(--ink-faint); }
|
|
112
|
+
|
|
113
|
+
.form-section {
|
|
114
|
+
padding: 16px 20px;
|
|
115
|
+
background: var(--surface);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.field {
|
|
119
|
+
margin-bottom: 14px;
|
|
120
|
+
}
|
|
121
|
+
.field:last-child { margin-bottom: 0; }
|
|
122
|
+
.field-label {
|
|
123
|
+
font-family: var(--mono);
|
|
124
|
+
font-size: 0.68rem;
|
|
125
|
+
text-transform: uppercase;
|
|
126
|
+
letter-spacing: 0.06em;
|
|
127
|
+
color: var(--ink-dim);
|
|
128
|
+
margin-bottom: 5px;
|
|
129
|
+
display: block;
|
|
130
|
+
}
|
|
131
|
+
.field input[type="text"],
|
|
132
|
+
.field input[type="number"],
|
|
133
|
+
.field textarea,
|
|
134
|
+
.field select {
|
|
135
|
+
width: 100%;
|
|
136
|
+
font-family: var(--mono);
|
|
137
|
+
font-size: 0.8rem;
|
|
138
|
+
padding: 8px 12px;
|
|
139
|
+
border: 1.5px solid var(--border-light);
|
|
140
|
+
background: var(--surface);
|
|
141
|
+
color: var(--ink);
|
|
142
|
+
outline: none;
|
|
143
|
+
}
|
|
144
|
+
.field input:focus,
|
|
145
|
+
.field textarea:focus,
|
|
146
|
+
.field select:focus {
|
|
147
|
+
border-color: var(--ink);
|
|
148
|
+
}
|
|
149
|
+
.field textarea { resize: vertical; min-height: 60px; line-height: 1.5; }
|
|
150
|
+
.field select { appearance: none; cursor: pointer; }
|
|
151
|
+
|
|
152
|
+
/* SLIDER */
|
|
153
|
+
.slider-row {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 10px;
|
|
157
|
+
margin-bottom: 8px;
|
|
158
|
+
}
|
|
159
|
+
.slider-label {
|
|
160
|
+
font-family: var(--mono);
|
|
161
|
+
font-size: 0.68rem;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
letter-spacing: 0.04em;
|
|
164
|
+
color: var(--ink-dim);
|
|
165
|
+
width: 100px;
|
|
166
|
+
flex-shrink: 0;
|
|
167
|
+
}
|
|
168
|
+
.slider-row input[type="range"] {
|
|
169
|
+
flex: 1;
|
|
170
|
+
height: 4px;
|
|
171
|
+
-webkit-appearance: none;
|
|
172
|
+
appearance: none;
|
|
173
|
+
background: var(--border-light);
|
|
174
|
+
outline: none;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
}
|
|
177
|
+
.slider-row input[type="range"]::-webkit-slider-thumb {
|
|
178
|
+
-webkit-appearance: none;
|
|
179
|
+
width: 12px;
|
|
180
|
+
height: 12px;
|
|
181
|
+
background: var(--ink);
|
|
182
|
+
border-radius: 0;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
}
|
|
185
|
+
.slider-row input[type="range"]::-moz-range-thumb {
|
|
186
|
+
width: 12px;
|
|
187
|
+
height: 12px;
|
|
188
|
+
background: var(--ink);
|
|
189
|
+
border: none;
|
|
190
|
+
border-radius: 0;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
}
|
|
193
|
+
.slider-val {
|
|
194
|
+
font-family: var(--mono);
|
|
195
|
+
font-size: 0.75rem;
|
|
196
|
+
font-weight: 600;
|
|
197
|
+
width: 32px;
|
|
198
|
+
text-align: right;
|
|
199
|
+
}
|
|
200
|
+
.slider-directive {
|
|
201
|
+
font-size: 0.72rem;
|
|
202
|
+
color: var(--ink-faint);
|
|
203
|
+
margin-left: 110px;
|
|
204
|
+
margin-bottom: 10px;
|
|
205
|
+
line-height: 1.4;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* PRESETS */
|
|
209
|
+
.presets {
|
|
210
|
+
display: flex;
|
|
211
|
+
gap: 6px;
|
|
212
|
+
flex-wrap: wrap;
|
|
213
|
+
margin-bottom: 14px;
|
|
214
|
+
}
|
|
215
|
+
.preset-btn {
|
|
216
|
+
font-family: var(--mono);
|
|
217
|
+
font-size: 0.68rem;
|
|
218
|
+
text-transform: uppercase;
|
|
219
|
+
letter-spacing: 0.04em;
|
|
220
|
+
padding: 4px 10px;
|
|
221
|
+
border: 1px solid var(--border-light);
|
|
222
|
+
background: var(--surface);
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
color: var(--ink-dim);
|
|
225
|
+
}
|
|
226
|
+
.preset-btn:hover { background: var(--ink); color: var(--bg); }
|
|
227
|
+
|
|
228
|
+
/* REPEATABLE */
|
|
229
|
+
.repeatable-item {
|
|
230
|
+
border: 1px solid var(--border-light);
|
|
231
|
+
padding: 12px;
|
|
232
|
+
margin-bottom: 8px;
|
|
233
|
+
position: relative;
|
|
234
|
+
background: var(--surface);
|
|
235
|
+
}
|
|
236
|
+
.remove-btn {
|
|
237
|
+
position: absolute;
|
|
238
|
+
top: 8px;
|
|
239
|
+
right: 8px;
|
|
240
|
+
font-family: var(--mono);
|
|
241
|
+
font-size: 0.68rem;
|
|
242
|
+
padding: 2px 8px;
|
|
243
|
+
border: 1px solid var(--border-light);
|
|
244
|
+
background: var(--surface);
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
color: var(--red);
|
|
247
|
+
}
|
|
248
|
+
.remove-btn:hover { background: var(--red); color: white; border-color: var(--red); }
|
|
249
|
+
.add-btn {
|
|
250
|
+
font-family: var(--mono);
|
|
251
|
+
font-size: 0.7rem;
|
|
252
|
+
text-transform: uppercase;
|
|
253
|
+
letter-spacing: 0.04em;
|
|
254
|
+
padding: 6px 12px;
|
|
255
|
+
border: 1.5px dashed var(--border-light);
|
|
256
|
+
background: transparent;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
color: var(--ink-dim);
|
|
259
|
+
width: 100%;
|
|
260
|
+
margin-top: 8px;
|
|
261
|
+
}
|
|
262
|
+
.add-btn:hover { border-color: var(--ink); color: var(--ink); }
|
|
263
|
+
|
|
264
|
+
/* OUTPUT PANEL */
|
|
265
|
+
.output-panel {
|
|
266
|
+
display: flex;
|
|
267
|
+
flex-direction: column;
|
|
268
|
+
position: sticky;
|
|
269
|
+
top: 52px;
|
|
270
|
+
max-height: calc(100vh - 52px);
|
|
271
|
+
}
|
|
272
|
+
@media (max-width: 900px) {
|
|
273
|
+
.output-panel { position: static; max-height: none; }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.output-tabs {
|
|
277
|
+
display: flex;
|
|
278
|
+
border-bottom: 1px solid var(--border-light);
|
|
279
|
+
background: var(--surface-alt);
|
|
280
|
+
}
|
|
281
|
+
.output-tab {
|
|
282
|
+
font-family: var(--mono);
|
|
283
|
+
font-size: 0.72rem;
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
letter-spacing: 0.06em;
|
|
286
|
+
padding: 10px 16px;
|
|
287
|
+
border: none;
|
|
288
|
+
background: transparent;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
color: var(--ink-dim);
|
|
291
|
+
border-right: 1px solid var(--border-light);
|
|
292
|
+
}
|
|
293
|
+
.output-tab.active { background: var(--ink); color: var(--bg); }
|
|
294
|
+
.output-tab:hover:not(.active) { background: var(--surface); }
|
|
295
|
+
|
|
296
|
+
.output-body {
|
|
297
|
+
flex: 1;
|
|
298
|
+
overflow: auto;
|
|
299
|
+
background: var(--surface);
|
|
300
|
+
}
|
|
301
|
+
.output-body pre {
|
|
302
|
+
font-family: var(--mono);
|
|
303
|
+
font-size: 0.78rem;
|
|
304
|
+
line-height: 1.75;
|
|
305
|
+
padding: 20px;
|
|
306
|
+
white-space: pre-wrap;
|
|
307
|
+
word-wrap: break-word;
|
|
308
|
+
color: var(--ink);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.output-footer {
|
|
312
|
+
padding: 10px 20px;
|
|
313
|
+
border-top: 1px solid var(--border-light);
|
|
314
|
+
background: var(--surface-alt);
|
|
315
|
+
font-family: var(--mono);
|
|
316
|
+
font-size: 0.72rem;
|
|
317
|
+
}
|
|
318
|
+
.output-footer.valid { color: var(--green); }
|
|
319
|
+
.output-footer.invalid { color: var(--red); }
|
|
320
|
+
|
|
321
|
+
/* CHANNEL SELECTOR in output */
|
|
322
|
+
.channel-select {
|
|
323
|
+
margin-left: auto;
|
|
324
|
+
font-family: var(--mono);
|
|
325
|
+
font-size: 0.68rem;
|
|
326
|
+
padding: 4px 8px;
|
|
327
|
+
border: 1px solid var(--border-light);
|
|
328
|
+
background: var(--surface);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* CHECKBOX */
|
|
332
|
+
.checkbox-row {
|
|
333
|
+
display: flex;
|
|
334
|
+
align-items: center;
|
|
335
|
+
gap: 8px;
|
|
336
|
+
margin-bottom: 8px;
|
|
337
|
+
}
|
|
338
|
+
.checkbox-row input[type="checkbox"] {
|
|
339
|
+
width: 14px;
|
|
340
|
+
height: 14px;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
}
|
|
343
|
+
.checkbox-row label {
|
|
344
|
+
font-family: var(--mono);
|
|
345
|
+
font-size: 0.72rem;
|
|
346
|
+
color: var(--ink-dim);
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
}
|
|
349
|
+
</style>
|
|
350
|
+
</head>
|
|
351
|
+
<body>
|
|
352
|
+
|
|
353
|
+
<div class="nav">
|
|
354
|
+
<span class="nav-title">grain builder</span>
|
|
355
|
+
<div class="nav-actions">
|
|
356
|
+
<button class="nav-btn" id="btn-download">Download YAML</button>
|
|
357
|
+
<button class="nav-btn" id="btn-copy">Copy</button>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<div class="layout">
|
|
362
|
+
<!-- FORM PANEL -->
|
|
363
|
+
<div class="form-panel">
|
|
364
|
+
|
|
365
|
+
<!-- META -->
|
|
366
|
+
<details open>
|
|
367
|
+
<summary>Meta</summary>
|
|
368
|
+
<div class="form-section">
|
|
369
|
+
<div class="field">
|
|
370
|
+
<label class="field-label">ID</label>
|
|
371
|
+
<input type="text" data-path="id" placeholder="my-agent">
|
|
372
|
+
</div>
|
|
373
|
+
<div class="field">
|
|
374
|
+
<label class="field-label">Name</label>
|
|
375
|
+
<input type="text" data-path="meta.name" placeholder="My Agent">
|
|
376
|
+
</div>
|
|
377
|
+
<div class="field">
|
|
378
|
+
<label class="field-label">Description</label>
|
|
379
|
+
<textarea data-path="meta.description" placeholder="What does this agent do?"></textarea>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="field">
|
|
382
|
+
<label class="field-label">Version</label>
|
|
383
|
+
<input type="text" data-path="version" placeholder="1.0.0">
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</details>
|
|
387
|
+
|
|
388
|
+
<!-- IDENTITY -->
|
|
389
|
+
<details open>
|
|
390
|
+
<summary>Identity</summary>
|
|
391
|
+
<div class="form-section">
|
|
392
|
+
<div class="field">
|
|
393
|
+
<label class="field-label">Name</label>
|
|
394
|
+
<input type="text" data-path="identity.name" placeholder="Alex">
|
|
395
|
+
</div>
|
|
396
|
+
<div class="field">
|
|
397
|
+
<label class="field-label">Role</label>
|
|
398
|
+
<input type="text" data-path="identity.role" placeholder="Senior Support Specialist">
|
|
399
|
+
</div>
|
|
400
|
+
<div class="field">
|
|
401
|
+
<label class="field-label">Primary Purpose</label>
|
|
402
|
+
<textarea data-path="identity.purpose.primary" placeholder="Resolve customer issues quickly"></textarea>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="field">
|
|
405
|
+
<label class="field-label">Secondary Purposes</label>
|
|
406
|
+
<div id="secondary-purposes"></div>
|
|
407
|
+
<button class="add-btn" data-add="secondary-purpose">+ Add Secondary Purpose</button>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="field">
|
|
410
|
+
<label class="field-label">Expertise</label>
|
|
411
|
+
<div id="expertise-list"></div>
|
|
412
|
+
<button class="add-btn" data-add="expertise">+ Add Expertise</button>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
</details>
|
|
416
|
+
|
|
417
|
+
<!-- PERSONALITY -->
|
|
418
|
+
<details open>
|
|
419
|
+
<summary>Personality</summary>
|
|
420
|
+
<div class="form-section">
|
|
421
|
+
<div class="presets">
|
|
422
|
+
<button class="preset-btn" data-preset="professional">Professional</button>
|
|
423
|
+
<button class="preset-btn" data-preset="friendly">Friendly</button>
|
|
424
|
+
<button class="preset-btn" data-preset="expert">Expert</button>
|
|
425
|
+
<button class="preset-btn" data-preset="creative">Creative</button>
|
|
426
|
+
<button class="preset-btn" data-preset="executor">Executor</button>
|
|
427
|
+
</div>
|
|
428
|
+
<div id="personality-sliders"></div>
|
|
429
|
+
</div>
|
|
430
|
+
</details>
|
|
431
|
+
|
|
432
|
+
<!-- LANGUAGE -->
|
|
433
|
+
<details>
|
|
434
|
+
<summary>Language Style</summary>
|
|
435
|
+
<div class="form-section">
|
|
436
|
+
<div class="field">
|
|
437
|
+
<label class="field-label">Locale</label>
|
|
438
|
+
<input type="text" data-path="voice.language.locale" placeholder="en">
|
|
439
|
+
</div>
|
|
440
|
+
<div class="field">
|
|
441
|
+
<label class="field-label">Sentence Length</label>
|
|
442
|
+
<select data-path="voice.language.sentenceLength">
|
|
443
|
+
<option value="short">Short</option>
|
|
444
|
+
<option value="medium" selected>Medium</option>
|
|
445
|
+
<option value="long">Long</option>
|
|
446
|
+
</select>
|
|
447
|
+
</div>
|
|
448
|
+
<div class="field">
|
|
449
|
+
<label class="field-label">Jargon Level</label>
|
|
450
|
+
<select data-path="voice.language.jargonLevel">
|
|
451
|
+
<option value="none">None</option>
|
|
452
|
+
<option value="light" selected>Light</option>
|
|
453
|
+
<option value="moderate">Moderate</option>
|
|
454
|
+
<option value="heavy">Heavy</option>
|
|
455
|
+
</select>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="field">
|
|
458
|
+
<label class="field-label">Emoji Usage</label>
|
|
459
|
+
<select data-path="voice.language.emojiUsage">
|
|
460
|
+
<option value="never">Never</option>
|
|
461
|
+
<option value="sparingly" selected>Sparingly</option>
|
|
462
|
+
<option value="frequently">Frequently</option>
|
|
463
|
+
</select>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="field">
|
|
466
|
+
<label class="field-label">Structure</label>
|
|
467
|
+
<select data-path="voice.language.structure">
|
|
468
|
+
<option value="bullets">Bullets</option>
|
|
469
|
+
<option value="mixed" selected>Mixed</option>
|
|
470
|
+
<option value="prose">Prose</option>
|
|
471
|
+
</select>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="checkbox-row">
|
|
474
|
+
<input type="checkbox" id="uses-analogies" data-path="voice.language.usesAnalogies">
|
|
475
|
+
<label for="uses-analogies">Uses Analogies</label>
|
|
476
|
+
</div>
|
|
477
|
+
<div class="field">
|
|
478
|
+
<label class="field-label">Addressing</label>
|
|
479
|
+
<select data-path="voice.language.addressing">
|
|
480
|
+
<option value="first-person" selected>First Person</option>
|
|
481
|
+
<option value="second-person">Second Person</option>
|
|
482
|
+
<option value="third-person">Third Person</option>
|
|
483
|
+
</select>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
</details>
|
|
487
|
+
|
|
488
|
+
<!-- CHANNEL OVERRIDES -->
|
|
489
|
+
<details>
|
|
490
|
+
<summary>Channel Overrides</summary>
|
|
491
|
+
<div class="form-section">
|
|
492
|
+
<div id="channel-overrides"></div>
|
|
493
|
+
<button class="add-btn" data-add="channel">+ Add Channel</button>
|
|
494
|
+
</div>
|
|
495
|
+
</details>
|
|
496
|
+
|
|
497
|
+
<!-- BEHAVIOR RULES -->
|
|
498
|
+
<details>
|
|
499
|
+
<summary>Behavior Rules</summary>
|
|
500
|
+
<div class="form-section">
|
|
501
|
+
<div id="rules-list"></div>
|
|
502
|
+
<button class="add-btn" data-add="rule">+ Add Rule</button>
|
|
503
|
+
</div>
|
|
504
|
+
</details>
|
|
505
|
+
|
|
506
|
+
<!-- BOUNDARIES -->
|
|
507
|
+
<details>
|
|
508
|
+
<summary>Boundaries</summary>
|
|
509
|
+
<div class="form-section">
|
|
510
|
+
<div id="boundaries-list"></div>
|
|
511
|
+
<button class="add-btn" data-add="boundary">+ Add Boundary</button>
|
|
512
|
+
</div>
|
|
513
|
+
</details>
|
|
514
|
+
|
|
515
|
+
<!-- TOOLS -->
|
|
516
|
+
<details>
|
|
517
|
+
<summary>Tools</summary>
|
|
518
|
+
<div class="form-section">
|
|
519
|
+
<div id="tools-list"></div>
|
|
520
|
+
<button class="add-btn" data-add="tool">+ Add Tool</button>
|
|
521
|
+
</div>
|
|
522
|
+
</details>
|
|
523
|
+
|
|
524
|
+
<!-- SKILLS -->
|
|
525
|
+
<details>
|
|
526
|
+
<summary>Skills</summary>
|
|
527
|
+
<div class="form-section">
|
|
528
|
+
<div id="skills-list"></div>
|
|
529
|
+
<button class="add-btn" data-add="skill">+ Add Skill</button>
|
|
530
|
+
</div>
|
|
531
|
+
</details>
|
|
532
|
+
|
|
533
|
+
<!-- COGNITION -->
|
|
534
|
+
<details>
|
|
535
|
+
<summary>Cognition</summary>
|
|
536
|
+
<div class="form-section">
|
|
537
|
+
<div class="field">
|
|
538
|
+
<label class="field-label">Reasoning Style</label>
|
|
539
|
+
<select data-path="cognition.reasoningStyle.primary">
|
|
540
|
+
<option value="analytical" selected>Analytical</option>
|
|
541
|
+
<option value="intuitive">Intuitive</option>
|
|
542
|
+
<option value="creative">Creative</option>
|
|
543
|
+
<option value="systematic">Systematic</option>
|
|
544
|
+
<option value="lateral">Lateral</option>
|
|
545
|
+
</select>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="checkbox-row">
|
|
548
|
+
<input type="checkbox" id="show-reasoning" data-path="cognition.reasoningStyle.showReasoning">
|
|
549
|
+
<label for="show-reasoning">Show Reasoning</label>
|
|
550
|
+
</div>
|
|
551
|
+
<div class="field">
|
|
552
|
+
<label class="field-label">Analysis Depth</label>
|
|
553
|
+
<select data-path="cognition.reasoningStyle.analysisDepth">
|
|
554
|
+
<option value="surface">Surface</option>
|
|
555
|
+
<option value="moderate" selected>Moderate</option>
|
|
556
|
+
<option value="deep">Deep</option>
|
|
557
|
+
<option value="exhaustive">Exhaustive</option>
|
|
558
|
+
</select>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="checkbox-row">
|
|
561
|
+
<input type="checkbox" id="multi-perspective" data-path="cognition.reasoningStyle.multiPerspective">
|
|
562
|
+
<label for="multi-perspective">Multi-Perspective</label>
|
|
563
|
+
</div>
|
|
564
|
+
<div class="field">
|
|
565
|
+
<label class="field-label">Autonomy</label>
|
|
566
|
+
<select data-path="cognition.decisionMaking.autonomy">
|
|
567
|
+
<option value="fully-autonomous">Fully Autonomous</option>
|
|
568
|
+
<option value="ask-for-major" selected>Ask for Major</option>
|
|
569
|
+
<option value="always-ask">Always Ask</option>
|
|
570
|
+
</select>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="field">
|
|
573
|
+
<label class="field-label">Low Confidence Action</label>
|
|
574
|
+
<select data-path="cognition.uncertainty.lowConfidenceAction">
|
|
575
|
+
<option value="ask" selected>Ask</option>
|
|
576
|
+
<option value="flag">Flag</option>
|
|
577
|
+
<option value="proceed-with-caveat">Proceed with Caveat</option>
|
|
578
|
+
<option value="refuse">Refuse</option>
|
|
579
|
+
</select>
|
|
580
|
+
</div>
|
|
581
|
+
<div class="slider-row">
|
|
582
|
+
<span class="slider-label">Conf. Floor</span>
|
|
583
|
+
<input type="range" min="0" max="1" step="0.05" value="0.4" data-path="cognition.uncertainty.confidenceFloor">
|
|
584
|
+
<span class="slider-val">0.40</span>
|
|
585
|
+
</div>
|
|
586
|
+
<div class="checkbox-row">
|
|
587
|
+
<input type="checkbox" id="communicate-uncertainty" data-path="cognition.uncertainty.communicateUncertainty" checked>
|
|
588
|
+
<label for="communicate-uncertainty">Communicate Uncertainty</label>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
</details>
|
|
592
|
+
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<!-- OUTPUT PANEL -->
|
|
596
|
+
<div class="output-panel">
|
|
597
|
+
<div class="output-tabs">
|
|
598
|
+
<button class="output-tab active" data-tab="yaml">YAML</button>
|
|
599
|
+
<button class="output-tab" data-tab="typescript">TypeScript</button>
|
|
600
|
+
<button class="output-tab" data-tab="python">Python</button>
|
|
601
|
+
<button class="output-tab" data-tab="prompt">Prompt</button>
|
|
602
|
+
</div>
|
|
603
|
+
<div class="output-body">
|
|
604
|
+
<pre id="output-content"></pre>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="output-footer valid" id="output-status">Valid</div>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
|
|
610
|
+
<script>
|
|
611
|
+
// ============================================================================
|
|
612
|
+
// STATE
|
|
613
|
+
// ============================================================================
|
|
614
|
+
|
|
615
|
+
const PERSONALITY_DIMS = ['formality','warmth','humor','assertiveness','verbosity','confidence','concreteness','urgency'];
|
|
616
|
+
|
|
617
|
+
const PERSONALITY_DIRECTIVES = {
|
|
618
|
+
formality: [
|
|
619
|
+
[0.2, "Be very casual. Use slang and contractions freely."],
|
|
620
|
+
[0.4, "Be conversational and approachable."],
|
|
621
|
+
[0.6, "Balanced tone - professional but approachable."],
|
|
622
|
+
[0.8, "Be professional and polished."],
|
|
623
|
+
[1.01, "Highly formal, precise tone."],
|
|
624
|
+
],
|
|
625
|
+
warmth: [
|
|
626
|
+
[0.2, "Direct and clinical. Facts only."],
|
|
627
|
+
[0.4, "Polite but task-focused."],
|
|
628
|
+
[0.6, "Friendly. Basic courtesy."],
|
|
629
|
+
[0.8, "Warm and empathetic."],
|
|
630
|
+
[1.01, "Lead with empathy. Mirror emotional state."],
|
|
631
|
+
],
|
|
632
|
+
humor: [
|
|
633
|
+
[0.2, "Serious and focused. No jokes."],
|
|
634
|
+
[0.4, "Mostly serious. Light tone OK."],
|
|
635
|
+
[0.6, "A touch of wit when it serves clarity."],
|
|
636
|
+
[0.8, "Use humor and wit naturally."],
|
|
637
|
+
[1.01, "Playful and witty throughout."],
|
|
638
|
+
],
|
|
639
|
+
assertiveness: [
|
|
640
|
+
[0.2, "Passive and deferential."],
|
|
641
|
+
[0.4, "Reactive. Offer options, let user drive."],
|
|
642
|
+
[0.6, "Balance initiative with deference."],
|
|
643
|
+
[0.8, "Proactive. Clear recommendations."],
|
|
644
|
+
[1.01, "Highly assertive. Take initiative."],
|
|
645
|
+
],
|
|
646
|
+
verbosity: [
|
|
647
|
+
[0.2, "Extremely terse. Single sentences."],
|
|
648
|
+
[0.4, "Brief and focused."],
|
|
649
|
+
[0.6, "Moderate detail."],
|
|
650
|
+
[0.8, "Detailed, thorough explanations."],
|
|
651
|
+
[1.01, "Comprehensive. Rich context and examples."],
|
|
652
|
+
],
|
|
653
|
+
confidence: [
|
|
654
|
+
[0.2, "Hedge heavily. Use qualifiers."],
|
|
655
|
+
[0.4, "Show appropriate uncertainty."],
|
|
656
|
+
[0.6, "Moderately confident."],
|
|
657
|
+
[0.8, "Decisive. No excessive hedging."],
|
|
658
|
+
[1.01, "Maximally decisive. Project authority."],
|
|
659
|
+
],
|
|
660
|
+
concreteness: [
|
|
661
|
+
[0.2, "Abstract and conceptual."],
|
|
662
|
+
[0.4, "Lean toward concepts and theory."],
|
|
663
|
+
[0.6, "Mix abstract with concrete examples."],
|
|
664
|
+
[0.8, "Concrete examples and specific numbers."],
|
|
665
|
+
[1.01, "Hyper-concrete. Every point gets an example."],
|
|
666
|
+
],
|
|
667
|
+
urgency: [
|
|
668
|
+
[0.2, "Thorough. Prioritize completeness."],
|
|
669
|
+
[0.4, "Measured and deliberate."],
|
|
670
|
+
[0.6, "Balance thoroughness with efficiency."],
|
|
671
|
+
[0.8, "Move quickly. Bias toward action."],
|
|
672
|
+
[1.01, "Maximum urgency. Get to the answer."],
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const PRESETS = {
|
|
677
|
+
professional: { formality: 0.7, warmth: 0.5, humor: 0.1, assertiveness: 0.6, verbosity: 0.5, confidence: 0.7, concreteness: 0.7, urgency: 0.4 },
|
|
678
|
+
friendly: { formality: 0.3, warmth: 0.8, humor: 0.4, assertiveness: 0.4, verbosity: 0.6, confidence: 0.6, concreteness: 0.6, urgency: 0.3 },
|
|
679
|
+
expert: { formality: 0.5, warmth: 0.3, humor: 0.1, assertiveness: 0.8, verbosity: 0.4, confidence: 0.9, concreteness: 0.9, urgency: 0.5 },
|
|
680
|
+
creative: { formality: 0.2, warmth: 0.7, humor: 0.7, assertiveness: 0.6, verbosity: 0.7, confidence: 0.6, concreteness: 0.4, urgency: 0.3 },
|
|
681
|
+
executor: { formality: 0.4, warmth: 0.2, humor: 0.0, assertiveness: 0.9, verbosity: 0.1, confidence: 0.9, concreteness: 1.0, urgency: 0.8 },
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
let state = {
|
|
685
|
+
specVersion: "1.0",
|
|
686
|
+
id: "",
|
|
687
|
+
version: "1.0.0",
|
|
688
|
+
meta: { name: "", description: "" },
|
|
689
|
+
identity: { name: "", role: "", purpose: { primary: "", secondary: [] }, expertise: [] },
|
|
690
|
+
voice: {
|
|
691
|
+
personality: { formality: 0.5, warmth: 0.5, humor: 0.3, assertiveness: 0.5, verbosity: 0.5, confidence: 0.6, concreteness: 0.6, urgency: 0.4 },
|
|
692
|
+
language: { locale: "en", sentenceLength: "medium", jargonLevel: "light", emojiUsage: "sparingly", structure: "mixed", usesAnalogies: false, addressing: "first-person" },
|
|
693
|
+
channelOverrides: {}
|
|
694
|
+
},
|
|
695
|
+
cognition: {
|
|
696
|
+
reasoningStyle: { primary: "analytical", showReasoning: false, analysisDepth: "moderate", multiPerspective: false },
|
|
697
|
+
decisionMaking: { autonomy: "ask-for-major" },
|
|
698
|
+
uncertainty: { lowConfidenceAction: "ask", confidenceFloor: 0.4, communicateUncertainty: true }
|
|
699
|
+
},
|
|
700
|
+
behavior: { rules: [], boundaries: [] },
|
|
701
|
+
capabilities: { tools: [], skills: [] }
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
let activeTab = "yaml";
|
|
705
|
+
let outputChannel = "";
|
|
706
|
+
let lastOutput = { yaml: "", prompt: "", codeTs: "", codePy: "" };
|
|
707
|
+
let renderTimer = null;
|
|
708
|
+
|
|
709
|
+
// ============================================================================
|
|
710
|
+
// STATE HELPERS
|
|
711
|
+
// ============================================================================
|
|
712
|
+
|
|
713
|
+
function setPath(obj, path, value) {
|
|
714
|
+
const parts = path.split(".");
|
|
715
|
+
let cur = obj;
|
|
716
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
717
|
+
if (!(parts[i] in cur)) cur[parts[i]] = {};
|
|
718
|
+
cur = cur[parts[i]];
|
|
719
|
+
}
|
|
720
|
+
cur[parts[parts.length - 1]] = value;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function getPath(obj, path) {
|
|
724
|
+
const parts = path.split(".");
|
|
725
|
+
let cur = obj;
|
|
726
|
+
for (const p of parts) {
|
|
727
|
+
if (cur == null) return undefined;
|
|
728
|
+
cur = cur[p];
|
|
729
|
+
}
|
|
730
|
+
return cur;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function getDirective(dim, val) {
|
|
734
|
+
const levels = PERSONALITY_DIRECTIVES[dim];
|
|
735
|
+
if (!levels) return "";
|
|
736
|
+
for (const [max, text] of levels) {
|
|
737
|
+
if (val < max || max > 1) return text;
|
|
738
|
+
}
|
|
739
|
+
return "";
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ============================================================================
|
|
743
|
+
// RENDER & API
|
|
744
|
+
// ============================================================================
|
|
745
|
+
|
|
746
|
+
function scheduleRender() {
|
|
747
|
+
if (renderTimer) clearTimeout(renderTimer);
|
|
748
|
+
renderTimer = setTimeout(doRender, 300);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function buildSpec() {
|
|
752
|
+
const spec = JSON.parse(JSON.stringify(state));
|
|
753
|
+
// Clean up empty fields
|
|
754
|
+
if (!spec.id) delete spec.id;
|
|
755
|
+
if (!spec.meta.name && !spec.meta.description) delete spec.meta;
|
|
756
|
+
if (!spec.identity.name && !spec.identity.role && !spec.identity.purpose.primary) delete spec.identity;
|
|
757
|
+
if (spec.identity) {
|
|
758
|
+
if (!spec.identity.purpose.secondary?.length) delete spec.identity.purpose.secondary;
|
|
759
|
+
if (!spec.identity.expertise?.length) delete spec.identity.expertise;
|
|
760
|
+
}
|
|
761
|
+
if (!Object.keys(spec.voice.channelOverrides).length) delete spec.voice.channelOverrides;
|
|
762
|
+
if (!spec.behavior.rules.length) delete spec.behavior.rules;
|
|
763
|
+
if (!spec.behavior.boundaries.length) delete spec.behavior.boundaries;
|
|
764
|
+
if (!spec.behavior.rules && !spec.behavior.boundaries) delete spec.behavior;
|
|
765
|
+
if (!spec.capabilities.tools.length && !spec.capabilities.skills.length) delete spec.capabilities;
|
|
766
|
+
else {
|
|
767
|
+
if (!spec.capabilities.tools.length) delete spec.capabilities.tools;
|
|
768
|
+
if (!spec.capabilities.skills.length) delete spec.capabilities.skills;
|
|
769
|
+
}
|
|
770
|
+
return spec;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function doRender() {
|
|
774
|
+
const spec = buildSpec();
|
|
775
|
+
try {
|
|
776
|
+
const res = await fetch("/api/build", {
|
|
777
|
+
method: "POST",
|
|
778
|
+
headers: { "Content-Type": "application/json" },
|
|
779
|
+
body: JSON.stringify({ spec, channel: outputChannel || undefined })
|
|
780
|
+
});
|
|
781
|
+
const data = await res.json();
|
|
782
|
+
lastOutput = data;
|
|
783
|
+
updateOutputDisplay();
|
|
784
|
+
updateValidationStatus(data);
|
|
785
|
+
} catch (e) {
|
|
786
|
+
document.getElementById("output-content").textContent = "// Error connecting to server";
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function updateOutputDisplay() {
|
|
791
|
+
const el = document.getElementById("output-content");
|
|
792
|
+
switch (activeTab) {
|
|
793
|
+
case "yaml": el.textContent = lastOutput.yaml || ""; break;
|
|
794
|
+
case "typescript": el.textContent = lastOutput.codeTs || ""; break;
|
|
795
|
+
case "python": el.textContent = lastOutput.codePy || ""; break;
|
|
796
|
+
case "prompt": el.textContent = lastOutput.prompt || ""; break;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function updateValidationStatus(data) {
|
|
801
|
+
const el = document.getElementById("output-status");
|
|
802
|
+
if (data.valid) {
|
|
803
|
+
el.textContent = "Valid";
|
|
804
|
+
el.className = "output-footer valid";
|
|
805
|
+
} else {
|
|
806
|
+
const count = data.errors?.length || 0;
|
|
807
|
+
el.textContent = `${count} error${count !== 1 ? 's' : ''}: ${data.errors?.[0]?.message || ''}`;
|
|
808
|
+
el.className = "output-footer invalid";
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ============================================================================
|
|
813
|
+
// PERSONALITY SLIDERS
|
|
814
|
+
// ============================================================================
|
|
815
|
+
|
|
816
|
+
function renderPersonalitySliders() {
|
|
817
|
+
const container = document.getElementById("personality-sliders");
|
|
818
|
+
container.innerHTML = "";
|
|
819
|
+
for (const dim of PERSONALITY_DIMS) {
|
|
820
|
+
const val = state.voice.personality[dim] || 0.5;
|
|
821
|
+
const row = document.createElement("div");
|
|
822
|
+
row.innerHTML = `
|
|
823
|
+
<div class="slider-row">
|
|
824
|
+
<span class="slider-label">${dim}</span>
|
|
825
|
+
<input type="range" min="0" max="1" step="0.05" value="${val}" data-dim="${dim}">
|
|
826
|
+
<span class="slider-val">${val.toFixed(2)}</span>
|
|
827
|
+
</div>
|
|
828
|
+
<div class="slider-directive">${getDirective(dim, val)}</div>
|
|
829
|
+
`;
|
|
830
|
+
container.appendChild(row);
|
|
831
|
+
|
|
832
|
+
const input = row.querySelector('input[type="range"]');
|
|
833
|
+
const valEl = row.querySelector('.slider-val');
|
|
834
|
+
const dirEl = row.querySelector('.slider-directive');
|
|
835
|
+
input.addEventListener("input", () => {
|
|
836
|
+
const v = parseFloat(input.value);
|
|
837
|
+
state.voice.personality[dim] = v;
|
|
838
|
+
valEl.textContent = v.toFixed(2);
|
|
839
|
+
dirEl.textContent = getDirective(dim, v);
|
|
840
|
+
scheduleRender();
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ============================================================================
|
|
846
|
+
// DYNAMIC LISTS
|
|
847
|
+
// ============================================================================
|
|
848
|
+
|
|
849
|
+
function renderSecondaryPurposes() {
|
|
850
|
+
const container = document.getElementById("secondary-purposes");
|
|
851
|
+
container.innerHTML = "";
|
|
852
|
+
(state.identity.purpose.secondary || []).forEach((val, i) => {
|
|
853
|
+
const item = document.createElement("div");
|
|
854
|
+
item.className = "repeatable-item";
|
|
855
|
+
item.innerHTML = `
|
|
856
|
+
<input type="text" value="${escHtml(val)}" style="width:calc(100% - 60px)">
|
|
857
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
858
|
+
`;
|
|
859
|
+
item.querySelector("input").addEventListener("input", (e) => {
|
|
860
|
+
state.identity.purpose.secondary[i] = e.target.value;
|
|
861
|
+
scheduleRender();
|
|
862
|
+
});
|
|
863
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
864
|
+
state.identity.purpose.secondary.splice(i, 1);
|
|
865
|
+
renderSecondaryPurposes();
|
|
866
|
+
scheduleRender();
|
|
867
|
+
});
|
|
868
|
+
container.appendChild(item);
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function renderExpertise() {
|
|
873
|
+
const container = document.getElementById("expertise-list");
|
|
874
|
+
container.innerHTML = "";
|
|
875
|
+
(state.identity.expertise || []).forEach((exp, i) => {
|
|
876
|
+
const item = document.createElement("div");
|
|
877
|
+
item.className = "repeatable-item";
|
|
878
|
+
item.innerHTML = `
|
|
879
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
880
|
+
<div class="field" style="margin-bottom:8px">
|
|
881
|
+
<label class="field-label">Domain</label>
|
|
882
|
+
<input type="text" value="${escHtml(exp.domain || '')}">
|
|
883
|
+
</div>
|
|
884
|
+
<div class="slider-row">
|
|
885
|
+
<span class="slider-label">Proficiency</span>
|
|
886
|
+
<input type="range" min="0" max="1" step="0.05" value="${exp.proficiency || 0.5}">
|
|
887
|
+
<span class="slider-val">${(exp.proficiency || 0.5).toFixed(2)}</span>
|
|
888
|
+
</div>
|
|
889
|
+
`;
|
|
890
|
+
item.querySelector('input[type="text"]').addEventListener("input", (e) => {
|
|
891
|
+
state.identity.expertise[i].domain = e.target.value;
|
|
892
|
+
scheduleRender();
|
|
893
|
+
});
|
|
894
|
+
const slider = item.querySelector('input[type="range"]');
|
|
895
|
+
const sliderVal = item.querySelector('.slider-val');
|
|
896
|
+
slider.addEventListener("input", () => {
|
|
897
|
+
state.identity.expertise[i].proficiency = parseFloat(slider.value);
|
|
898
|
+
sliderVal.textContent = parseFloat(slider.value).toFixed(2);
|
|
899
|
+
scheduleRender();
|
|
900
|
+
});
|
|
901
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
902
|
+
state.identity.expertise.splice(i, 1);
|
|
903
|
+
renderExpertise();
|
|
904
|
+
scheduleRender();
|
|
905
|
+
});
|
|
906
|
+
container.appendChild(item);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function renderChannelOverrides() {
|
|
911
|
+
const container = document.getElementById("channel-overrides");
|
|
912
|
+
container.innerHTML = "";
|
|
913
|
+
for (const [name, overrides] of Object.entries(state.voice.channelOverrides)) {
|
|
914
|
+
const item = document.createElement("div");
|
|
915
|
+
item.className = "repeatable-item";
|
|
916
|
+
let html = `<button class="remove-btn" data-channel="${escHtml(name)}">X</button>
|
|
917
|
+
<div class="field" style="margin-bottom:10px">
|
|
918
|
+
<label class="field-label">Channel Name</label>
|
|
919
|
+
<input type="text" value="${escHtml(name)}" data-old-name="${escHtml(name)}" class="channel-name-input">
|
|
920
|
+
</div>`;
|
|
921
|
+
for (const dim of PERSONALITY_DIMS) {
|
|
922
|
+
const val = overrides[dim] !== undefined ? overrides[dim] : "";
|
|
923
|
+
html += `<div class="slider-row">
|
|
924
|
+
<span class="slider-label">${dim}</span>
|
|
925
|
+
<input type="range" min="0" max="1" step="0.05" value="${val || 0.5}" data-dim="${dim}" data-channel="${escHtml(name)}">
|
|
926
|
+
<span class="slider-val">${val !== "" ? parseFloat(val).toFixed(2) : "-"}</span>
|
|
927
|
+
</div>`;
|
|
928
|
+
}
|
|
929
|
+
item.innerHTML = html;
|
|
930
|
+
|
|
931
|
+
item.querySelector(".channel-name-input").addEventListener("change", (e) => {
|
|
932
|
+
const oldName = e.target.dataset.oldName;
|
|
933
|
+
const newName = e.target.value;
|
|
934
|
+
if (newName && newName !== oldName) {
|
|
935
|
+
state.voice.channelOverrides[newName] = state.voice.channelOverrides[oldName];
|
|
936
|
+
delete state.voice.channelOverrides[oldName];
|
|
937
|
+
renderChannelOverrides();
|
|
938
|
+
scheduleRender();
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
item.querySelectorAll('input[type="range"]').forEach(slider => {
|
|
943
|
+
slider.addEventListener("input", () => {
|
|
944
|
+
const ch = slider.dataset.channel;
|
|
945
|
+
const dim = slider.dataset.dim;
|
|
946
|
+
state.voice.channelOverrides[ch][dim] = parseFloat(slider.value);
|
|
947
|
+
slider.nextElementSibling.textContent = parseFloat(slider.value).toFixed(2);
|
|
948
|
+
scheduleRender();
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
953
|
+
delete state.voice.channelOverrides[name];
|
|
954
|
+
renderChannelOverrides();
|
|
955
|
+
scheduleRender();
|
|
956
|
+
});
|
|
957
|
+
container.appendChild(item);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function renderRules() {
|
|
962
|
+
const container = document.getElementById("rules-list");
|
|
963
|
+
container.innerHTML = "";
|
|
964
|
+
state.behavior.rules.forEach((rule, i) => {
|
|
965
|
+
const item = document.createElement("div");
|
|
966
|
+
item.className = "repeatable-item";
|
|
967
|
+
item.innerHTML = `
|
|
968
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
969
|
+
<div class="field" style="margin-bottom:8px">
|
|
970
|
+
<label class="field-label">ID</label>
|
|
971
|
+
<input type="text" value="${escHtml(rule.id || '')}" data-field="id">
|
|
972
|
+
</div>
|
|
973
|
+
<div class="field" style="margin-bottom:8px">
|
|
974
|
+
<label class="field-label">Priority</label>
|
|
975
|
+
<input type="number" value="${rule.priority || 0}" data-field="priority" style="width:80px">
|
|
976
|
+
</div>
|
|
977
|
+
<div class="field" style="margin-bottom:8px">
|
|
978
|
+
<label class="field-label">Condition Type</label>
|
|
979
|
+
<select data-field="condition.type">
|
|
980
|
+
<option value="always" ${rule.condition?.type === 'always' ? 'selected' : ''}>Always</option>
|
|
981
|
+
<option value="when-topic" ${rule.condition?.type === 'when-topic' ? 'selected' : ''}>When Topic</option>
|
|
982
|
+
<option value="when-sentiment" ${rule.condition?.type === 'when-sentiment' ? 'selected' : ''}>When Sentiment</option>
|
|
983
|
+
<option value="when-confidence-below" ${rule.condition?.type === 'when-confidence-below' ? 'selected' : ''}>When Confidence Below</option>
|
|
984
|
+
</select>
|
|
985
|
+
</div>
|
|
986
|
+
${rule.condition?.type === 'when-topic' ? `<div class="field" style="margin-bottom:8px">
|
|
987
|
+
<label class="field-label">Topics (comma-separated)</label>
|
|
988
|
+
<input type="text" value="${escHtml((rule.condition.topics || []).join(', '))}" data-field="condition.topics">
|
|
989
|
+
</div>` : ''}
|
|
990
|
+
${rule.condition?.type === 'when-sentiment' ? `<div class="field" style="margin-bottom:8px">
|
|
991
|
+
<label class="field-label">Sentiment</label>
|
|
992
|
+
<select data-field="condition.sentiment">
|
|
993
|
+
<option value="frustrated" ${rule.condition.sentiment === 'frustrated' ? 'selected' : ''}>Frustrated</option>
|
|
994
|
+
<option value="confused" ${rule.condition.sentiment === 'confused' ? 'selected' : ''}>Confused</option>
|
|
995
|
+
<option value="angry" ${rule.condition.sentiment === 'angry' ? 'selected' : ''}>Angry</option>
|
|
996
|
+
<option value="happy" ${rule.condition.sentiment === 'happy' ? 'selected' : ''}>Happy</option>
|
|
997
|
+
</select>
|
|
998
|
+
</div>` : ''}
|
|
999
|
+
${rule.condition?.type === 'when-confidence-below' ? `<div class="slider-row" style="margin-bottom:8px">
|
|
1000
|
+
<span class="slider-label">Threshold</span>
|
|
1001
|
+
<input type="range" min="0" max="1" step="0.05" value="${rule.condition.threshold || 0.3}" data-field="condition.threshold">
|
|
1002
|
+
<span class="slider-val">${(rule.condition.threshold || 0.3).toFixed(2)}</span>
|
|
1003
|
+
</div>` : ''}
|
|
1004
|
+
<div class="field" style="margin-bottom:8px">
|
|
1005
|
+
<label class="field-label">Action Type</label>
|
|
1006
|
+
<select data-field="action.type">
|
|
1007
|
+
<option value="respond-with-style" ${rule.action?.type === 'respond-with-style' ? 'selected' : ''}>Respond With Style</option>
|
|
1008
|
+
<option value="escalate" ${rule.action?.type === 'escalate' ? 'selected' : ''}>Escalate</option>
|
|
1009
|
+
<option value="require-confirmation" ${rule.action?.type === 'require-confirmation' ? 'selected' : ''}>Require Confirmation</option>
|
|
1010
|
+
<option value="use-tool" ${rule.action?.type === 'use-tool' ? 'selected' : ''}>Use Tool</option>
|
|
1011
|
+
<option value="refuse" ${rule.action?.type === 'refuse' ? 'selected' : ''}>Refuse</option>
|
|
1012
|
+
</select>
|
|
1013
|
+
</div>
|
|
1014
|
+
${rule.action?.type === 'escalate' ? `
|
|
1015
|
+
<div class="field" style="margin-bottom:8px">
|
|
1016
|
+
<label class="field-label">Target</label>
|
|
1017
|
+
<input type="text" value="${escHtml(rule.action.target || '')}" data-field="action.target">
|
|
1018
|
+
</div>
|
|
1019
|
+
<div class="field" style="margin-bottom:8px">
|
|
1020
|
+
<label class="field-label">Reason</label>
|
|
1021
|
+
<input type="text" value="${escHtml(rule.action.reason || '')}" data-field="action.reason">
|
|
1022
|
+
</div>` : ''}
|
|
1023
|
+
${rule.action?.type === 'refuse' ? `<div class="field" style="margin-bottom:8px">
|
|
1024
|
+
<label class="field-label">Message</label>
|
|
1025
|
+
<input type="text" value="${escHtml(rule.action.message || '')}" data-field="action.message">
|
|
1026
|
+
</div>` : ''}
|
|
1027
|
+
${rule.action?.type === 'use-tool' ? `<div class="field" style="margin-bottom:8px">
|
|
1028
|
+
<label class="field-label">Tool</label>
|
|
1029
|
+
<input type="text" value="${escHtml(rule.action.tool || '')}" data-field="action.tool">
|
|
1030
|
+
</div>` : ''}
|
|
1031
|
+
`;
|
|
1032
|
+
|
|
1033
|
+
// Event handlers
|
|
1034
|
+
item.querySelectorAll("input[type='text'], input[type='number'], select").forEach(el => {
|
|
1035
|
+
const field = el.dataset.field;
|
|
1036
|
+
if (!field) return;
|
|
1037
|
+
el.addEventListener("change", () => {
|
|
1038
|
+
if (field === "id") { state.behavior.rules[i].id = el.value; }
|
|
1039
|
+
else if (field === "priority") { state.behavior.rules[i].priority = parseInt(el.value) || 0; }
|
|
1040
|
+
else if (field === "condition.type") {
|
|
1041
|
+
state.behavior.rules[i].condition = { type: el.value };
|
|
1042
|
+
renderRules();
|
|
1043
|
+
}
|
|
1044
|
+
else if (field === "condition.topics") { state.behavior.rules[i].condition.topics = el.value.split(",").map(s => s.trim()).filter(Boolean); }
|
|
1045
|
+
else if (field === "condition.sentiment") { state.behavior.rules[i].condition.sentiment = el.value; }
|
|
1046
|
+
else if (field === "action.type") {
|
|
1047
|
+
state.behavior.rules[i].action = { type: el.value };
|
|
1048
|
+
renderRules();
|
|
1049
|
+
}
|
|
1050
|
+
else if (field === "action.target") { state.behavior.rules[i].action.target = el.value; }
|
|
1051
|
+
else if (field === "action.reason") { state.behavior.rules[i].action.reason = el.value; }
|
|
1052
|
+
else if (field === "action.message") { state.behavior.rules[i].action.message = el.value; }
|
|
1053
|
+
else if (field === "action.tool") { state.behavior.rules[i].action.tool = el.value; }
|
|
1054
|
+
scheduleRender();
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
item.querySelectorAll('input[type="range"]').forEach(slider => {
|
|
1059
|
+
slider.addEventListener("input", () => {
|
|
1060
|
+
const field = slider.dataset.field;
|
|
1061
|
+
if (field === "condition.threshold") {
|
|
1062
|
+
state.behavior.rules[i].condition.threshold = parseFloat(slider.value);
|
|
1063
|
+
slider.nextElementSibling.textContent = parseFloat(slider.value).toFixed(2);
|
|
1064
|
+
}
|
|
1065
|
+
scheduleRender();
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
1070
|
+
state.behavior.rules.splice(i, 1);
|
|
1071
|
+
renderRules();
|
|
1072
|
+
scheduleRender();
|
|
1073
|
+
});
|
|
1074
|
+
container.appendChild(item);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function renderBoundaries() {
|
|
1079
|
+
const container = document.getElementById("boundaries-list");
|
|
1080
|
+
container.innerHTML = "";
|
|
1081
|
+
state.behavior.boundaries.forEach((b, i) => {
|
|
1082
|
+
const item = document.createElement("div");
|
|
1083
|
+
item.className = "repeatable-item";
|
|
1084
|
+
item.innerHTML = `
|
|
1085
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
1086
|
+
<div class="field" style="margin-bottom:8px">
|
|
1087
|
+
<label class="field-label">Description</label>
|
|
1088
|
+
<textarea data-field="description">${escHtml(b.description || '')}</textarea>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="field" style="margin-bottom:8px">
|
|
1091
|
+
<label class="field-label">Category</label>
|
|
1092
|
+
<select data-field="category">
|
|
1093
|
+
<option value="content" ${b.category === 'content' ? 'selected' : ''}>Content</option>
|
|
1094
|
+
<option value="action" ${b.category === 'action' ? 'selected' : ''}>Action</option>
|
|
1095
|
+
<option value="data" ${b.category === 'data' ? 'selected' : ''}>Data</option>
|
|
1096
|
+
<option value="scope" ${b.category === 'scope' ? 'selected' : ''}>Scope</option>
|
|
1097
|
+
<option value="safety" ${b.category === 'safety' ? 'selected' : ''}>Safety</option>
|
|
1098
|
+
</select>
|
|
1099
|
+
</div>
|
|
1100
|
+
<div class="field" style="margin-bottom:8px">
|
|
1101
|
+
<label class="field-label">Enforcement</label>
|
|
1102
|
+
<select data-field="enforcement">
|
|
1103
|
+
<option value="hard" ${b.enforcement === 'hard' ? 'selected' : ''}>Hard</option>
|
|
1104
|
+
<option value="soft" ${b.enforcement === 'soft' ? 'selected' : ''}>Soft</option>
|
|
1105
|
+
</select>
|
|
1106
|
+
</div>
|
|
1107
|
+
<div class="field" style="margin-bottom:8px">
|
|
1108
|
+
<label class="field-label">On Violation</label>
|
|
1109
|
+
<select data-field="onViolation">
|
|
1110
|
+
<option value="refuse" ${b.onViolation === 'refuse' ? 'selected' : ''}>Refuse</option>
|
|
1111
|
+
<option value="warn" ${b.onViolation === 'warn' ? 'selected' : ''}>Warn</option>
|
|
1112
|
+
<option value="redirect" ${b.onViolation === 'redirect' ? 'selected' : ''}>Redirect</option>
|
|
1113
|
+
<option value="escalate" ${b.onViolation === 'escalate' ? 'selected' : ''}>Escalate</option>
|
|
1114
|
+
</select>
|
|
1115
|
+
</div>
|
|
1116
|
+
<div class="field">
|
|
1117
|
+
<label class="field-label">Message (optional)</label>
|
|
1118
|
+
<input type="text" value="${escHtml(b.message || '')}" data-field="message">
|
|
1119
|
+
</div>
|
|
1120
|
+
`;
|
|
1121
|
+
item.querySelectorAll("textarea, select, input[type='text']").forEach(el => {
|
|
1122
|
+
el.addEventListener("change", () => {
|
|
1123
|
+
state.behavior.boundaries[i][el.dataset.field] = el.value;
|
|
1124
|
+
scheduleRender();
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
1128
|
+
state.behavior.boundaries.splice(i, 1);
|
|
1129
|
+
renderBoundaries();
|
|
1130
|
+
scheduleRender();
|
|
1131
|
+
});
|
|
1132
|
+
container.appendChild(item);
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function renderTools() {
|
|
1137
|
+
const container = document.getElementById("tools-list");
|
|
1138
|
+
container.innerHTML = "";
|
|
1139
|
+
(state.capabilities.tools || []).forEach((t, i) => {
|
|
1140
|
+
const item = document.createElement("div");
|
|
1141
|
+
item.className = "repeatable-item";
|
|
1142
|
+
item.innerHTML = `
|
|
1143
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
1144
|
+
<div class="field" style="margin-bottom:8px">
|
|
1145
|
+
<label class="field-label">ID</label>
|
|
1146
|
+
<input type="text" value="${escHtml(t.id || '')}" data-field="id">
|
|
1147
|
+
</div>
|
|
1148
|
+
<div class="field" style="margin-bottom:8px">
|
|
1149
|
+
<label class="field-label">Usage</label>
|
|
1150
|
+
<input type="text" value="${escHtml(t.usage || '')}" data-field="usage">
|
|
1151
|
+
</div>
|
|
1152
|
+
<div class="checkbox-row">
|
|
1153
|
+
<input type="checkbox" ${t.requiresConfirmation ? 'checked' : ''} data-field="requiresConfirmation">
|
|
1154
|
+
<label>Requires Confirmation</label>
|
|
1155
|
+
</div>
|
|
1156
|
+
`;
|
|
1157
|
+
item.querySelectorAll("input[type='text']").forEach(el => {
|
|
1158
|
+
el.addEventListener("change", () => {
|
|
1159
|
+
state.capabilities.tools[i][el.dataset.field] = el.value;
|
|
1160
|
+
scheduleRender();
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
item.querySelector("input[type='checkbox']").addEventListener("change", (e) => {
|
|
1164
|
+
state.capabilities.tools[i].requiresConfirmation = e.target.checked;
|
|
1165
|
+
scheduleRender();
|
|
1166
|
+
});
|
|
1167
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
1168
|
+
state.capabilities.tools.splice(i, 1);
|
|
1169
|
+
renderTools();
|
|
1170
|
+
scheduleRender();
|
|
1171
|
+
});
|
|
1172
|
+
container.appendChild(item);
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function renderSkills() {
|
|
1177
|
+
const container = document.getElementById("skills-list");
|
|
1178
|
+
container.innerHTML = "";
|
|
1179
|
+
(state.capabilities.skills || []).forEach((s, i) => {
|
|
1180
|
+
const item = document.createElement("div");
|
|
1181
|
+
item.className = "repeatable-item";
|
|
1182
|
+
item.innerHTML = `
|
|
1183
|
+
<button class="remove-btn" data-idx="${i}">X</button>
|
|
1184
|
+
<div class="field" style="margin-bottom:8px">
|
|
1185
|
+
<label class="field-label">Name</label>
|
|
1186
|
+
<input type="text" value="${escHtml(s.name || '')}" data-field="name">
|
|
1187
|
+
</div>
|
|
1188
|
+
<div class="field" style="margin-bottom:8px">
|
|
1189
|
+
<label class="field-label">Description</label>
|
|
1190
|
+
<input type="text" value="${escHtml(s.description || '')}" data-field="description">
|
|
1191
|
+
</div>
|
|
1192
|
+
<div class="field" style="margin-bottom:8px">
|
|
1193
|
+
<label class="field-label">Required Tools (comma-separated)</label>
|
|
1194
|
+
<input type="text" value="${escHtml((s.requiredTools || []).join(', '))}" data-field="requiredTools">
|
|
1195
|
+
</div>
|
|
1196
|
+
<div class="field" style="margin-bottom:8px">
|
|
1197
|
+
<label class="field-label">Steps (comma-separated)</label>
|
|
1198
|
+
<input type="text" value="${escHtml((s.steps || []).join(', '))}" data-field="steps">
|
|
1199
|
+
</div>
|
|
1200
|
+
<div class="field">
|
|
1201
|
+
<label class="field-label">Success Criteria (comma-separated)</label>
|
|
1202
|
+
<input type="text" value="${escHtml((s.successCriteria || []).join(', '))}" data-field="successCriteria">
|
|
1203
|
+
</div>
|
|
1204
|
+
`;
|
|
1205
|
+
item.querySelectorAll("input[type='text']").forEach(el => {
|
|
1206
|
+
el.addEventListener("change", () => {
|
|
1207
|
+
const field = el.dataset.field;
|
|
1208
|
+
if (field === "requiredTools" || field === "steps" || field === "successCriteria") {
|
|
1209
|
+
state.capabilities.skills[i][field] = el.value.split(",").map(s => s.trim()).filter(Boolean);
|
|
1210
|
+
} else {
|
|
1211
|
+
state.capabilities.skills[i][field] = el.value;
|
|
1212
|
+
}
|
|
1213
|
+
scheduleRender();
|
|
1214
|
+
});
|
|
1215
|
+
});
|
|
1216
|
+
item.querySelector(".remove-btn").addEventListener("click", () => {
|
|
1217
|
+
state.capabilities.skills.splice(i, 1);
|
|
1218
|
+
renderSkills();
|
|
1219
|
+
scheduleRender();
|
|
1220
|
+
});
|
|
1221
|
+
container.appendChild(item);
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// ============================================================================
|
|
1226
|
+
// UTILS
|
|
1227
|
+
// ============================================================================
|
|
1228
|
+
|
|
1229
|
+
function escHtml(s) {
|
|
1230
|
+
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// ============================================================================
|
|
1234
|
+
// EVENT BINDING
|
|
1235
|
+
// ============================================================================
|
|
1236
|
+
|
|
1237
|
+
// Simple data-path inputs
|
|
1238
|
+
document.querySelectorAll("[data-path]").forEach(el => {
|
|
1239
|
+
const path = el.dataset.path;
|
|
1240
|
+
const initial = getPath(state, path);
|
|
1241
|
+
if (el.type === "checkbox") {
|
|
1242
|
+
el.checked = !!initial;
|
|
1243
|
+
el.addEventListener("change", () => {
|
|
1244
|
+
setPath(state, path, el.checked);
|
|
1245
|
+
scheduleRender();
|
|
1246
|
+
});
|
|
1247
|
+
} else if (el.tagName === "SELECT") {
|
|
1248
|
+
if (initial) el.value = initial;
|
|
1249
|
+
el.addEventListener("change", () => {
|
|
1250
|
+
setPath(state, path, el.value);
|
|
1251
|
+
scheduleRender();
|
|
1252
|
+
});
|
|
1253
|
+
} else if (el.type === "range") {
|
|
1254
|
+
if (initial !== undefined) el.value = initial;
|
|
1255
|
+
el.addEventListener("input", () => {
|
|
1256
|
+
const v = parseFloat(el.value);
|
|
1257
|
+
setPath(state, path, v);
|
|
1258
|
+
el.nextElementSibling.textContent = v.toFixed(2);
|
|
1259
|
+
scheduleRender();
|
|
1260
|
+
});
|
|
1261
|
+
} else {
|
|
1262
|
+
if (initial) el.value = initial;
|
|
1263
|
+
el.addEventListener("input", () => {
|
|
1264
|
+
setPath(state, path, el.value);
|
|
1265
|
+
scheduleRender();
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
// Presets
|
|
1271
|
+
document.querySelectorAll(".preset-btn").forEach(btn => {
|
|
1272
|
+
btn.addEventListener("click", () => {
|
|
1273
|
+
const preset = PRESETS[btn.dataset.preset];
|
|
1274
|
+
if (preset) {
|
|
1275
|
+
state.voice.personality = { ...preset };
|
|
1276
|
+
renderPersonalitySliders();
|
|
1277
|
+
scheduleRender();
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// Add buttons
|
|
1283
|
+
document.querySelectorAll("[data-add]").forEach(btn => {
|
|
1284
|
+
btn.addEventListener("click", () => {
|
|
1285
|
+
switch (btn.dataset.add) {
|
|
1286
|
+
case "secondary-purpose":
|
|
1287
|
+
if (!state.identity.purpose.secondary) state.identity.purpose.secondary = [];
|
|
1288
|
+
state.identity.purpose.secondary.push("");
|
|
1289
|
+
renderSecondaryPurposes();
|
|
1290
|
+
break;
|
|
1291
|
+
case "expertise":
|
|
1292
|
+
if (!state.identity.expertise) state.identity.expertise = [];
|
|
1293
|
+
state.identity.expertise.push({ domain: "", proficiency: 0.5 });
|
|
1294
|
+
renderExpertise();
|
|
1295
|
+
break;
|
|
1296
|
+
case "channel":
|
|
1297
|
+
const name = prompt("Channel name (e.g., slack, email):");
|
|
1298
|
+
if (name) {
|
|
1299
|
+
state.voice.channelOverrides[name] = {};
|
|
1300
|
+
renderChannelOverrides();
|
|
1301
|
+
}
|
|
1302
|
+
break;
|
|
1303
|
+
case "rule":
|
|
1304
|
+
state.behavior.rules.push({ id: "", priority: 0, condition: { type: "always" }, action: { type: "respond-with-style" } });
|
|
1305
|
+
renderRules();
|
|
1306
|
+
break;
|
|
1307
|
+
case "boundary":
|
|
1308
|
+
state.behavior.boundaries.push({ description: "", category: "content", enforcement: "hard", onViolation: "refuse" });
|
|
1309
|
+
renderBoundaries();
|
|
1310
|
+
break;
|
|
1311
|
+
case "tool":
|
|
1312
|
+
if (!state.capabilities.tools) state.capabilities.tools = [];
|
|
1313
|
+
state.capabilities.tools.push({ id: "", usage: "", requiresConfirmation: false });
|
|
1314
|
+
renderTools();
|
|
1315
|
+
break;
|
|
1316
|
+
case "skill":
|
|
1317
|
+
if (!state.capabilities.skills) state.capabilities.skills = [];
|
|
1318
|
+
state.capabilities.skills.push({ name: "", description: "", requiredTools: [], steps: [], successCriteria: [] });
|
|
1319
|
+
renderSkills();
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
scheduleRender();
|
|
1323
|
+
});
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// Output tabs
|
|
1327
|
+
document.querySelectorAll(".output-tab").forEach(btn => {
|
|
1328
|
+
btn.addEventListener("click", () => {
|
|
1329
|
+
document.querySelectorAll(".output-tab").forEach(b => b.classList.remove("active"));
|
|
1330
|
+
btn.classList.add("active");
|
|
1331
|
+
activeTab = btn.dataset.tab;
|
|
1332
|
+
updateOutputDisplay();
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
// Download
|
|
1337
|
+
document.getElementById("btn-download").addEventListener("click", () => {
|
|
1338
|
+
let content, filename;
|
|
1339
|
+
switch (activeTab) {
|
|
1340
|
+
case "yaml": content = lastOutput.yaml; filename = `${state.id || 'agent'}.agent.yaml`; break;
|
|
1341
|
+
case "typescript": content = lastOutput.codeTs; filename = "agent.ts"; break;
|
|
1342
|
+
case "python": content = lastOutput.codePy; filename = "agent.py"; break;
|
|
1343
|
+
case "prompt": content = lastOutput.prompt; filename = "prompt.txt"; break;
|
|
1344
|
+
default: content = lastOutput.yaml; filename = "agent.yaml";
|
|
1345
|
+
}
|
|
1346
|
+
const blob = new Blob([content], { type: "text/plain" });
|
|
1347
|
+
const url = URL.createObjectURL(blob);
|
|
1348
|
+
const a = document.createElement("a");
|
|
1349
|
+
a.href = url;
|
|
1350
|
+
a.download = filename;
|
|
1351
|
+
a.click();
|
|
1352
|
+
URL.revokeObjectURL(url);
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
// Copy
|
|
1356
|
+
document.getElementById("btn-copy").addEventListener("click", () => {
|
|
1357
|
+
let content;
|
|
1358
|
+
switch (activeTab) {
|
|
1359
|
+
case "yaml": content = lastOutput.yaml; break;
|
|
1360
|
+
case "typescript": content = lastOutput.codeTs; break;
|
|
1361
|
+
case "python": content = lastOutput.codePy; break;
|
|
1362
|
+
case "prompt": content = lastOutput.prompt; break;
|
|
1363
|
+
default: content = lastOutput.yaml;
|
|
1364
|
+
}
|
|
1365
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
1366
|
+
const btn = document.getElementById("btn-copy");
|
|
1367
|
+
btn.textContent = "Copied!";
|
|
1368
|
+
setTimeout(() => { btn.textContent = "Copy"; }, 1500);
|
|
1369
|
+
});
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
// ============================================================================
|
|
1373
|
+
// INIT
|
|
1374
|
+
// ============================================================================
|
|
1375
|
+
|
|
1376
|
+
renderPersonalitySliders();
|
|
1377
|
+
renderSecondaryPurposes();
|
|
1378
|
+
renderExpertise();
|
|
1379
|
+
renderChannelOverrides();
|
|
1380
|
+
renderRules();
|
|
1381
|
+
renderBoundaries();
|
|
1382
|
+
renderTools();
|
|
1383
|
+
renderSkills();
|
|
1384
|
+
doRender();
|
|
1385
|
+
</script>
|
|
1386
|
+
</body>
|
|
1387
|
+
</html>
|