django-agent-studio 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,1443 @@
1
+ {% extends "django_agent_studio/base.html" %}
2
+
3
+ {% block title %}{% if is_new %}Create Agent{% else %}Edit {{ agent.name }}{% endif %} - Agent Studio{% endblock %}
4
+
5
+ {% block breadcrumbs %}
6
+ <nav class="flex items-center space-x-2 ml-4 text-sm">
7
+ <span class="text-gray-400">/</span>
8
+ <a href="{% url 'agent_studio:agent_list' %}" class="text-gray-500 hover:text-gray-700">My Agents</a>
9
+ <span class="text-gray-400">/</span>
10
+ <span class="text-gray-600">{% if is_new %}New Agent{% else %}{{ agent.name }}{% endif %}</span>
11
+ </nav>
12
+ {% endblock %}
13
+
14
+ {% block extra_head %}
15
+ <style>
16
+ /* Embedded chat widget styles */
17
+ .chat-pane {
18
+ height: 100%;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+ .chat-pane .chat-container {
23
+ flex: 1;
24
+ position: relative;
25
+ overflow: hidden;
26
+ }
27
+
28
+ /* Make chat widgets fill their containers */
29
+ #test-chat-container .cw-container,
30
+ #builder-chat-container .cw-container {
31
+ position: absolute !important;
32
+ top: 0;
33
+ left: 0;
34
+ right: 0;
35
+ bottom: 0;
36
+ width: 100% !important;
37
+ height: 100% !important;
38
+ max-height: none !important;
39
+ border-radius: 0 !important;
40
+ box-shadow: none !important;
41
+ }
42
+
43
+ #test-chat-container .cw-toggle-btn,
44
+ #builder-chat-container .cw-toggle-btn {
45
+ display: none !important;
46
+ }
47
+
48
+ #test-chat-container .cw-widget,
49
+ #builder-chat-container .cw-widget {
50
+ position: absolute !important;
51
+ top: 0;
52
+ left: 0;
53
+ right: 0;
54
+ bottom: 0;
55
+ width: 100% !important;
56
+ height: 100% !important;
57
+ max-height: none !important;
58
+ border-radius: 0 !important;
59
+ display: flex !important;
60
+ }
61
+
62
+ /* Schema Editor Modal Styles */
63
+ .schema-modal-overlay {
64
+ position: fixed;
65
+ inset: 0;
66
+ background: rgba(0, 0, 0, 0.5);
67
+ z-index: 1000;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ padding: 1rem;
72
+ }
73
+
74
+ .schema-modal {
75
+ background: white;
76
+ border-radius: 0.75rem;
77
+ width: 100%;
78
+ max-width: 1200px;
79
+ height: 90vh;
80
+ display: flex;
81
+ flex-direction: column;
82
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
83
+ }
84
+
85
+ .schema-modal-header {
86
+ padding: 1rem 1.5rem;
87
+ border-bottom: 1px solid #e5e7eb;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: space-between;
91
+ flex-shrink: 0;
92
+ }
93
+
94
+ .schema-modal-body {
95
+ flex: 1;
96
+ overflow: hidden;
97
+ display: flex;
98
+ flex-direction: column;
99
+ }
100
+
101
+ .schema-tabs {
102
+ display: flex;
103
+ border-bottom: 1px solid #e5e7eb;
104
+ padding: 0 1rem;
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ .schema-tab {
109
+ padding: 0.75rem 1rem;
110
+ cursor: pointer;
111
+ border-bottom: 2px solid transparent;
112
+ color: #6b7280;
113
+ font-size: 0.875rem;
114
+ font-weight: 500;
115
+ transition: all 0.15s;
116
+ }
117
+
118
+ .schema-tab:hover {
119
+ color: #374151;
120
+ }
121
+
122
+ .schema-tab.active {
123
+ color: #3b82f6;
124
+ border-bottom-color: #3b82f6;
125
+ }
126
+
127
+ .schema-content {
128
+ flex: 1;
129
+ overflow: auto;
130
+ padding: 1rem;
131
+ }
132
+
133
+ .schema-editor {
134
+ width: 100%;
135
+ height: 100%;
136
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
137
+ font-size: 13px;
138
+ line-height: 1.5;
139
+ padding: 1rem;
140
+ border: 1px solid #e5e7eb;
141
+ border-radius: 0.5rem;
142
+ resize: none;
143
+ background: #1e1e1e;
144
+ color: #d4d4d4;
145
+ }
146
+
147
+ .schema-editor:focus {
148
+ outline: none;
149
+ border-color: #3b82f6;
150
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
151
+ }
152
+
153
+ .schema-modal-footer {
154
+ padding: 1rem 1.5rem;
155
+ border-top: 1px solid #e5e7eb;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: space-between;
159
+ flex-shrink: 0;
160
+ background: #f9fafb;
161
+ border-radius: 0 0 0.75rem 0.75rem;
162
+ }
163
+
164
+ .schema-status {
165
+ font-size: 0.875rem;
166
+ color: #6b7280;
167
+ }
168
+
169
+ .schema-status.error {
170
+ color: #dc2626;
171
+ }
172
+
173
+ .schema-status.success {
174
+ color: #16a34a;
175
+ }
176
+
177
+ .btn-group {
178
+ display: flex;
179
+ gap: 0.5rem;
180
+ }
181
+ </style>
182
+ {% endblock %}
183
+
184
+ {% block content %}
185
+ <div class="h-full flex flex-col">
186
+ <!-- Top Bar: Agent Selector -->
187
+ <div class="px-4 py-2 bg-white border-b border-gray-200 flex items-center justify-between">
188
+ <div class="flex items-center space-x-4">
189
+ <!-- Agent Selector -->
190
+ <div class="flex items-center space-x-2">
191
+ <label class="text-sm font-medium text-gray-600">Agent:</label>
192
+ <select v-model="selectedAgentId"
193
+ @change="onAgentChange"
194
+ class="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 min-w-[200px]">
195
+ <option value="">-- Select an agent --</option>
196
+ <option v-for="agent in agents" :key="agent.id" :value="agent.id">
197
+ [[ agent.icon || '🤖' ]] [[ agent.name ]]
198
+ </option>
199
+ </select>
200
+ <button @click="createNewAgent"
201
+ class="px-3 py-1.5 text-sm text-green-600 border border-green-300 rounded hover:bg-green-50"
202
+ title="Create new agent">
203
+ <span>+ New</span>
204
+ </button>
205
+ <button v-if="selectedAgentId"
206
+ @click="deleteAgent"
207
+ class="px-2 py-1.5 text-sm text-red-600 border border-red-300 rounded hover:bg-red-50"
208
+ title="Delete this agent">
209
+ <span>🗑️</span>
210
+ </button>
211
+ </div>
212
+
213
+ <!-- Version Selector (shown when agent selected) -->
214
+ <div v-if="selectedAgentId && versions.length > 0" class="flex items-center space-x-2 border-l border-gray-200 pl-4">
215
+ <label class="text-sm font-medium text-gray-600">Version:</label>
216
+ <select v-model="selectedVersionId"
217
+ @change="onVersionChange"
218
+ class="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
219
+ <option v-for="version in versions" :key="version.id" :value="version.id">
220
+ [[ version.version ]] [[ version.is_active ? '(active)' : '' ]] [[ version.is_draft ? '(draft)' : '' ]]
221
+ </option>
222
+ </select>
223
+ <button v-if="selectedVersionId && !isActiveVersion"
224
+ @click="activateVersion"
225
+ class="px-2 py-1 text-xs text-blue-600 border border-blue-300 rounded hover:bg-blue-50"
226
+ title="Make this version active">
227
+ Activate
228
+ </button>
229
+ </div>
230
+
231
+ <!-- Systems Selector -->
232
+ <div class="flex items-center space-x-2 border-l border-gray-200 pl-4">
233
+ <label class="text-sm font-medium text-gray-600">System:</label>
234
+ <select v-model="selectedSystemId"
235
+ @change="onSystemChange"
236
+ class="px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500 min-w-[180px]">
237
+ <option value="">-- No system --</option>
238
+ <option v-for="system in systems" :key="system.id" :value="system.id">
239
+ 🔗 [[ system.name ]]
240
+ </option>
241
+ </select>
242
+ <button @click="openSystemsModal"
243
+ class="px-2 py-1.5 text-sm text-purple-600 border border-purple-300 rounded hover:bg-purple-50"
244
+ title="Manage multi-agent systems">
245
+ <span>⚙️</span>
246
+ </button>
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Agent Info & Actions -->
251
+ <div class="flex items-center space-x-3">
252
+ <div v-if="selectedAgent" class="text-sm text-gray-500">
253
+ <span class="font-mono text-xs bg-gray-100 px-2 py-0.5 rounded">[[ selectedAgent.slug ]]</span>
254
+ </div>
255
+ <button @click="openSchemaEditor"
256
+ class="flex items-center space-x-1 px-2 py-1 text-sm rounded border"
257
+ :class="selectedAgentId ? 'text-blue-600 border-blue-300 hover:bg-blue-50 hover:border-blue-400' : 'text-gray-400 border-gray-200 cursor-not-allowed'"
258
+ title="Edit Agent Schema (JSON)"
259
+ :disabled="!selectedAgentId">
260
+ <span>📋</span>
261
+ <span>Schema</span>
262
+ </button>
263
+ <button @click="loadAgents"
264
+ class="text-gray-500 hover:text-gray-700 p-1.5 rounded hover:bg-gray-100"
265
+ title="Refresh agent list">
266
+ <i class="pi pi-refresh"></i>
267
+ </button>
268
+ </div>
269
+ </div>
270
+
271
+ <!-- Main Content -->
272
+ <div class="flex-1 flex overflow-hidden">
273
+ <!-- Left Pane: Test Your Agent -->
274
+ <div class="w-1/2 border-r border-gray-200 flex flex-col">
275
+ <div class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
276
+ <div class="flex items-center space-x-2">
277
+ <span class="text-lg">🧪</span>
278
+ <h2 class="font-medium text-gray-700">Test Your Agent</h2>
279
+ </div>
280
+ <button @click="refreshTestAgent"
281
+ class="text-gray-500 hover:text-gray-700 p-1 rounded hover:bg-gray-200"
282
+ title="Refresh agent config">
283
+ <i class="pi pi-refresh text-sm"></i>
284
+ </button>
285
+ </div>
286
+ <div class="flex-1 relative bg-gray-100">
287
+ <div id="test-chat-container" class="absolute inset-0"></div>
288
+ </div>
289
+ </div>
290
+
291
+ <!-- Right Pane: Builder Agent -->
292
+ <div class="w-1/2 flex flex-col">
293
+ <div class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
294
+ <div class="flex items-center space-x-2">
295
+ <span class="text-lg">🛠️</span>
296
+ <h2 class="font-medium text-gray-700">Agent Builder</h2>
297
+ </div>
298
+ <span class="text-xs text-gray-500">Chat with the builder to customize your agent</span>
299
+ </div>
300
+ <div class="flex-1 relative bg-gray-50">
301
+ <div id="builder-chat-container" class="absolute inset-0"></div>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ <!-- Schema Editor Modal -->
308
+ <div v-if="schemaEditorOpen" class="schema-modal-overlay" @click.self="closeSchemaEditor">
309
+ <div class="schema-modal">
310
+ <div class="schema-modal-header">
311
+ <div class="flex items-center space-x-3">
312
+ <span class="text-xl">📋</span>
313
+ <div>
314
+ <h3 class="text-lg font-semibold text-gray-800">Agent Schema Editor</h3>
315
+ <p class="text-sm text-gray-500">View and edit the complete agent configuration</p>
316
+ </div>
317
+ </div>
318
+ <div class="flex items-center space-x-2">
319
+ <button @click="reloadSchema"
320
+ class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded"
321
+ :disabled="schemaLoading"
322
+ title="Reload schema from server">
323
+ <i class="pi pi-refresh" :class="{'pi-spin': schemaLoading}"></i>
324
+ <span class="ml-1">Reload</span>
325
+ </button>
326
+ <button @click="closeSchemaEditor"
327
+ class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
328
+ <i class="pi pi-times text-lg"></i>
329
+ </button>
330
+ </div>
331
+ </div>
332
+
333
+ <div class="schema-tabs">
334
+ <div class="schema-tab" :class="{active: schemaTab === 'full'}" @click="schemaTab = 'full'">
335
+ Full Schema
336
+ </div>
337
+ <div class="schema-tab" :class="{active: schemaTab === 'version'}" @click="schemaTab = 'version'">
338
+ Version Config
339
+ </div>
340
+ <div class="schema-tab" :class="{active: schemaTab === 'tools'}" @click="schemaTab = 'tools'">
341
+ Tools
342
+ </div>
343
+ <div class="schema-tab" :class="{active: schemaTab === 'dynamic_tools'}" @click="schemaTab = 'dynamic_tools'">
344
+ Dynamic Tools
345
+ </div>
346
+ <div class="schema-tab" :class="{active: schemaTab === 'sub_agents'}" @click="schemaTab = 'sub_agents'">
347
+ 🔗 Sub-Agents
348
+ </div>
349
+ <div class="schema-tab" :class="{active: schemaTab === 'knowledge'}" @click="schemaTab = 'knowledge'">
350
+ Knowledge
351
+ </div>
352
+ <div class="schema-tab" :class="{active: schemaTab === 'rag'}" @click="schemaTab = 'rag'">
353
+ RAG Config
354
+ </div>
355
+ <div class="schema-tab" :class="{active: schemaTab === 'functions'}" @click="schemaTab = 'functions'">
356
+ Available Functions
357
+ </div>
358
+ </div>
359
+
360
+ <div class="schema-modal-body">
361
+ <div class="schema-content">
362
+ <textarea
363
+ v-if="schemaTab === 'full'"
364
+ class="schema-editor"
365
+ v-model="schemaJson"
366
+ @input="validateSchema"
367
+ spellcheck="false"
368
+ ></textarea>
369
+ <textarea
370
+ v-else-if="schemaTab === 'version'"
371
+ class="schema-editor"
372
+ v-model="versionJson"
373
+ @input="validateSchema"
374
+ spellcheck="false"
375
+ ></textarea>
376
+ <textarea
377
+ v-else-if="schemaTab === 'tools'"
378
+ class="schema-editor"
379
+ v-model="toolsJson"
380
+ @input="validateSchema"
381
+ spellcheck="false"
382
+ ></textarea>
383
+ <textarea
384
+ v-else-if="schemaTab === 'dynamic_tools'"
385
+ class="schema-editor"
386
+ v-model="dynamicToolsJson"
387
+ @input="validateSchema"
388
+ spellcheck="false"
389
+ ></textarea>
390
+ <textarea
391
+ v-else-if="schemaTab === 'sub_agents'"
392
+ class="schema-editor"
393
+ v-model="subAgentToolsJson"
394
+ @input="validateSchema"
395
+ spellcheck="false"
396
+ ></textarea>
397
+ <textarea
398
+ v-else-if="schemaTab === 'knowledge'"
399
+ class="schema-editor"
400
+ v-model="knowledgeJson"
401
+ @input="validateSchema"
402
+ spellcheck="false"
403
+ ></textarea>
404
+ <textarea
405
+ v-else-if="schemaTab === 'rag'"
406
+ class="schema-editor"
407
+ v-model="ragConfigJson"
408
+ @input="validateSchema"
409
+ spellcheck="false"
410
+ ></textarea>
411
+ <textarea
412
+ v-else-if="schemaTab === 'functions'"
413
+ class="schema-editor"
414
+ v-model="functionsJson"
415
+ readonly
416
+ spellcheck="false"
417
+ ></textarea>
418
+ </div>
419
+ </div>
420
+
421
+ <div class="schema-modal-footer">
422
+ <div class="schema-status" :class="{error: schemaError, success: schemaSaved}">
423
+ <span v-if="schemaLoading"><i class="pi pi-spin pi-spinner"></i> Loading...</span>
424
+ <span v-else-if="schemaError"><i class="pi pi-exclamation-triangle"></i> [[ schemaError ]]</span>
425
+ <span v-else-if="schemaSaved"><i class="pi pi-check"></i> Saved successfully</span>
426
+ <span v-else-if="schemaModified"><i class="pi pi-pencil"></i> Modified (unsaved)</span>
427
+ <span v-else>Last loaded: [[ schemaLoadedAt ]]</span>
428
+ </div>
429
+ <div class="btn-group">
430
+ <button @click="copySchema"
431
+ class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
432
+ <i class="pi pi-copy mr-1"></i> Copy
433
+ </button>
434
+ <button @click="closeSchemaEditor"
435
+ class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
436
+ Cancel
437
+ </button>
438
+ <button @click="saveSchema"
439
+ class="px-4 py-2 text-sm text-white bg-blue-600 hover:bg-blue-700 rounded"
440
+ :disabled="schemaLoading || !!schemaError || schemaTab === 'functions'">
441
+ <i class="pi pi-save mr-1"></i> Save Changes
442
+ </button>
443
+ </div>
444
+ </div>
445
+ </div>
446
+ </div>
447
+
448
+ <!-- Systems Management Modal -->
449
+ <div v-if="systemsModalOpen" class="schema-modal-overlay" @click.self="closeSystemsModal">
450
+ <div class="schema-modal" style="max-width: 900px; height: 80vh;">
451
+ <div class="schema-modal-header">
452
+ <div class="flex items-center space-x-3">
453
+ <span class="text-xl">🔗</span>
454
+ <div>
455
+ <h3 class="text-lg font-semibold text-gray-800">Multi-Agent Systems</h3>
456
+ <p class="text-sm text-gray-500">Manage agent systems and their members</p>
457
+ </div>
458
+ </div>
459
+ <div class="flex items-center space-x-2">
460
+ <button @click="loadSystems"
461
+ class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded"
462
+ :disabled="systemsLoading"
463
+ title="Refresh systems">
464
+ <i class="pi pi-refresh" :class="{'pi-spin': systemsLoading}"></i>
465
+ </button>
466
+ <button @click="closeSystemsModal"
467
+ class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
468
+ <i class="pi pi-times text-lg"></i>
469
+ </button>
470
+ </div>
471
+ </div>
472
+
473
+ <div class="schema-modal-body" style="padding: 1rem;">
474
+ <!-- Create New System -->
475
+ <div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
476
+ <h4 class="text-sm font-medium text-gray-700 mb-3">Create New System</h4>
477
+ <div class="flex items-end space-x-3">
478
+ <div class="flex-1">
479
+ <label class="block text-xs text-gray-500 mb-1">Name</label>
480
+ <input v-model="newSystemName"
481
+ type="text"
482
+ placeholder="e.g., Customer Support"
483
+ class="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
484
+ </div>
485
+ <div class="flex-1">
486
+ <label class="block text-xs text-gray-500 mb-1">Slug</label>
487
+ <input v-model="newSystemSlug"
488
+ type="text"
489
+ placeholder="e.g., customer-support"
490
+ class="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
491
+ </div>
492
+ <div>
493
+ <label class="block text-xs text-gray-500 mb-1">Entry Agent</label>
494
+ <select v-model="newSystemEntryAgent"
495
+ class="px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
496
+ <option value="">-- Select --</option>
497
+ <option v-for="agent in agents" :key="agent.id" :value="agent.id">
498
+ [[ agent.name ]]
499
+ </option>
500
+ </select>
501
+ </div>
502
+ <button @click="createSystem"
503
+ :disabled="!newSystemName || !newSystemSlug"
504
+ class="px-4 py-2 text-sm text-white bg-purple-600 hover:bg-purple-700 rounded disabled:opacity-50 disabled:cursor-not-allowed">
505
+ Create
506
+ </button>
507
+ </div>
508
+ </div>
509
+
510
+ <!-- Systems List -->
511
+ <div class="space-y-3 overflow-y-auto" style="max-height: calc(80vh - 280px);">
512
+ <div v-if="systemsLoading" class="text-center py-8 text-gray-500">
513
+ <i class="pi pi-spin pi-spinner text-2xl"></i>
514
+ <p class="mt-2">Loading systems...</p>
515
+ </div>
516
+ <div v-else-if="systems.length === 0" class="text-center py-8 text-gray-500">
517
+ <span class="text-4xl">🔗</span>
518
+ <p class="mt-2">No systems yet. Create one above!</p>
519
+ </div>
520
+ <div v-else v-for="system in systems" :key="system.id"
521
+ class="p-4 bg-white border border-gray-200 rounded-lg hover:border-purple-300 transition-colors">
522
+ <div class="flex items-start justify-between">
523
+ <div class="flex-1">
524
+ <div class="flex items-center space-x-2">
525
+ <h4 class="font-medium text-gray-800">[[ system.name ]]</h4>
526
+ <span class="text-xs font-mono bg-gray-100 px-2 py-0.5 rounded">[[ system.slug ]]</span>
527
+ </div>
528
+ <p class="text-sm text-gray-500 mt-1">[[ system.description || 'No description' ]]</p>
529
+ <div class="flex items-center space-x-4 mt-2 text-xs text-gray-500">
530
+ <span v-if="system.entry_agent_slug">
531
+ Entry: <span class="font-mono">[[ system.entry_agent_slug ]]</span>
532
+ </span>
533
+ <span>[[ system.member_count || 0 ]] members</span>
534
+ <span v-if="system.active_version">
535
+ Active: v[[ system.active_version ]]
536
+ </span>
537
+ </div>
538
+ </div>
539
+ <div class="flex items-center space-x-2">
540
+ <button @click="viewSystemDetails(system)"
541
+ class="px-3 py-1.5 text-xs text-purple-600 border border-purple-300 rounded hover:bg-purple-50">
542
+ Details
543
+ </button>
544
+ <button @click="publishSystem(system)"
545
+ class="px-3 py-1.5 text-xs text-green-600 border border-green-300 rounded hover:bg-green-50">
546
+ Publish
547
+ </button>
548
+ <button @click="deleteSystem(system)"
549
+ class="px-3 py-1.5 text-xs text-red-600 border border-red-300 rounded hover:bg-red-50">
550
+ Delete
551
+ </button>
552
+ </div>
553
+ </div>
554
+
555
+ <!-- System Members (expandable) -->
556
+ <div v-if="expandedSystemId === system.id" class="mt-4 pt-4 border-t border-gray-100">
557
+ <div class="flex items-center justify-between mb-2">
558
+ <h5 class="text-sm font-medium text-gray-700">Members</h5>
559
+ <button @click="addMemberToSystem(system)"
560
+ class="text-xs text-purple-600 hover:text-purple-800">
561
+ + Add Member
562
+ </button>
563
+ </div>
564
+ <div v-if="system.members && system.members.length > 0" class="space-y-2">
565
+ <div v-for="member in system.members" :key="member.id"
566
+ class="flex items-center justify-between p-2 bg-gray-50 rounded text-sm">
567
+ <div class="flex items-center space-x-2">
568
+ <span>[[ member.agent_name ]]</span>
569
+ <span class="text-xs font-mono text-gray-500">([[ member.agent_slug ]])</span>
570
+ <span v-if="member.role" class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">
571
+ [[ member.role ]]
572
+ </span>
573
+ <span v-if="member.is_entry_point" class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">
574
+ Entry Point
575
+ </span>
576
+ </div>
577
+ <button @click="removeMemberFromSystem(system, member)"
578
+ class="text-red-500 hover:text-red-700">
579
+ <i class="pi pi-times text-xs"></i>
580
+ </button>
581
+ </div>
582
+ </div>
583
+ <div v-else class="text-sm text-gray-500 italic">No members yet</div>
584
+ </div>
585
+ </div>
586
+ </div>
587
+ </div>
588
+
589
+ <div class="schema-modal-footer">
590
+ <div class="text-sm text-gray-500">
591
+ [[ systems.length ]] system(s)
592
+ </div>
593
+ <button @click="closeSystemsModal"
594
+ class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
595
+ Close
596
+ </button>
597
+ </div>
598
+ </div>
599
+ </div>
600
+
601
+ <!-- Delete Confirmation Modal -->
602
+ <div v-if="deleteConfirmOpen" class="schema-modal-overlay" @click.self="cancelDelete">
603
+ <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full">
604
+ <div class="flex items-center space-x-3 mb-4">
605
+ <span class="text-3xl">⚠️</span>
606
+ <div>
607
+ <h3 class="text-lg font-semibold text-gray-800">Confirm Delete</h3>
608
+ <p class="text-sm text-gray-500">[[ deleteConfirmMessage ]]</p>
609
+ </div>
610
+ </div>
611
+ <div class="flex justify-end space-x-3">
612
+ <button @click="cancelDelete"
613
+ class="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded hover:bg-gray-50">
614
+ Cancel
615
+ </button>
616
+ <button @click="confirmDelete"
617
+ class="px-4 py-2 text-sm text-white bg-red-600 hover:bg-red-700 rounded">
618
+ Delete
619
+ </button>
620
+ </div>
621
+ </div>
622
+ </div>
623
+ {% endblock %}
624
+
625
+ {% block extra_js %}
626
+ <script>
627
+ const { createApp } = Vue;
628
+
629
+ // Agent configuration from Django
630
+ const agentConfig = {
631
+ isNew: {{ is_new|yesno:"true,false" }},
632
+ agentId: {% if agent %}"{{ agent.id }}"{% else %}null{% endif %},
633
+ agentSlug: {% if agent %}"{{ agent.slug }}"{% else %}null{% endif %},
634
+ builderAgentKey: "{{ builder_agent_key }}",
635
+ };
636
+
637
+ // Backend URL (same origin)
638
+ const backendUrl = window.location.origin;
639
+
640
+ // Initialize the test agent chat widget
641
+ function initTestChat(agentSlug = null) {
642
+ const container = document.getElementById('test-chat-container');
643
+ if (!container) return; // Container not ready yet
644
+
645
+ // Use passed slug or fall back to agentConfig
646
+ const slugToUse = agentSlug || agentConfig.agentSlug;
647
+
648
+ if (!slugToUse) {
649
+ // Show placeholder for new agents
650
+ container.innerHTML = `
651
+ <div class="flex items-center justify-center h-full text-gray-500">
652
+ <div class="text-center">
653
+ <div class="text-4xl mb-4">🤖</div>
654
+ <p>Select an agent above to test it</p>
655
+ <p class="text-sm mt-2">Or create a new one with the "+ New" button</p>
656
+ </div>
657
+ </div>
658
+ `;
659
+ return;
660
+ }
661
+
662
+ // Create a unique instance for the test chat
663
+ if (window.testChatWidget) {
664
+ window.testChatWidget.destroy();
665
+ }
666
+
667
+ window.testChatWidget = ChatWidget.createInstance({
668
+ containerId: 'test-chat-container',
669
+ backendUrl: backendUrl,
670
+ agentKey: slugToUse,
671
+ title: 'Test Agent',
672
+ primaryColor: '#3b82f6',
673
+ showClearButton: true,
674
+ showDebugButton: true,
675
+ showExpandButton: false,
676
+ showModelSelector: true,
677
+ embedded: true,
678
+ authStrategy: 'session',
679
+ apiPaths: {
680
+ runs: '/api/agent-runtime/runs/',
681
+ runEvents: '/api/agent-runtime/runs/{runId}/events/',
682
+ models: '/api/agent-runtime/models/',
683
+ },
684
+ });
685
+ }
686
+
687
+ // Initialize the builder agent chat widget
688
+ function initBuilderChat(targetAgentId = null, vueApp = null) {
689
+ const container = document.getElementById('builder-chat-container');
690
+ if (!container) return; // Container not ready yet
691
+
692
+ if (window.builderChatWidget) {
693
+ window.builderChatWidget.destroy();
694
+ }
695
+
696
+ // Use passed agent ID or fall back to agentConfig
697
+ const agentIdToUse = targetAgentId || agentConfig.agentId;
698
+
699
+ window.builderChatWidget = ChatWidget.createInstance({
700
+ containerId: 'builder-chat-container',
701
+ backendUrl: backendUrl,
702
+ agentKey: agentConfig.builderAgentKey,
703
+ title: 'Agent Builder',
704
+ primaryColor: '#8b5cf6',
705
+ showClearButton: true,
706
+ showDebugButton: true,
707
+ showExpandButton: false,
708
+ showModelSelector: true,
709
+ embedded: true,
710
+ authStrategy: 'session',
711
+ apiPaths: {
712
+ runs: '/api/agent-runtime/runs/',
713
+ runEvents: '/api/agent-runtime/runs/{runId}/events/',
714
+ models: '/api/agent-runtime/models/',
715
+ },
716
+ // Pass the agent ID to the builder agent
717
+ metadata: {
718
+ agent_id: agentIdToUse,
719
+ },
720
+ // Handle UI control events from the builder agent
721
+ onUIControl: (payload) => {
722
+ console.log('[Builder] UI Control event:', payload);
723
+ if (vueApp && payload.action) {
724
+ switch (payload.action) {
725
+ case 'switch_agent':
726
+ // Switch to a different agent
727
+ if (payload.agent_id) {
728
+ vueApp.selectedAgentId = payload.agent_id;
729
+ vueApp.onAgentChange();
730
+ }
731
+ break;
732
+ case 'switch_system':
733
+ // Switch to a different system
734
+ if (payload.system_id) {
735
+ vueApp.selectedSystemId = payload.system_id;
736
+ vueApp.onSystemChange();
737
+ }
738
+ break;
739
+ }
740
+ }
741
+ },
742
+ // Handle events for debugging/logging
743
+ onEvent: (eventType, payload) => {
744
+ // Log sub-agent events for visibility
745
+ if (eventType === 'sub_agent.start' || eventType === 'sub_agent.end') {
746
+ console.log(`[Builder] ${eventType}:`, payload);
747
+ }
748
+ },
749
+ });
750
+ }
751
+
752
+ createApp({
753
+ delimiters: ['[[', ']]'], // Avoid conflict with Django templates
754
+ data() {
755
+ return {
756
+ agentConfig,
757
+ // Agent/version selection
758
+ agents: [],
759
+ versions: [],
760
+ selectedAgentId: agentConfig.agentId || '',
761
+ selectedVersionId: '',
762
+ // Schema editor state
763
+ schemaEditorOpen: false,
764
+ schemaTab: 'full',
765
+ schemaLoading: false,
766
+ schemaError: null,
767
+ schemaSaved: false,
768
+ schemaModified: false,
769
+ schemaLoadedAt: null,
770
+ // Schema data
771
+ fullSchema: null,
772
+ schemaJson: '',
773
+ versionJson: '',
774
+ toolsJson: '',
775
+ dynamicToolsJson: '',
776
+ subAgentToolsJson: '',
777
+ knowledgeJson: '',
778
+ ragConfigJson: '',
779
+ functionsJson: '',
780
+ // Systems management
781
+ systems: [],
782
+ selectedSystemId: '',
783
+ systemsModalOpen: false,
784
+ systemsLoading: false,
785
+ expandedSystemId: null,
786
+ newSystemName: '',
787
+ newSystemSlug: '',
788
+ newSystemEntryAgent: '',
789
+ // Delete confirmation
790
+ deleteConfirmOpen: false,
791
+ deleteConfirmMessage: '',
792
+ deleteConfirmCallback: null,
793
+ }
794
+ },
795
+ computed: {
796
+ selectedAgent() {
797
+ return this.agents.find(a => a.id === this.selectedAgentId);
798
+ },
799
+ isActiveVersion() {
800
+ const version = this.versions.find(v => v.id === this.selectedVersionId);
801
+ return version && version.is_active;
802
+ },
803
+ },
804
+ methods: {
805
+ async loadAgents() {
806
+ try {
807
+ const response = await fetch('/studio/api/agents/', {
808
+ credentials: 'include',
809
+ headers: { 'Accept': 'application/json' },
810
+ });
811
+ if (response.ok) {
812
+ const data = await response.json();
813
+ // Handle paginated response (results array) or direct array
814
+ const agentList = Array.isArray(data) ? data : (data.results || []);
815
+ // Filter out any agents without valid IDs
816
+ this.agents = agentList.filter(a => a && a.id);
817
+ // If we have a pre-selected agent, load its versions
818
+ if (this.selectedAgentId) {
819
+ await this.loadVersions();
820
+ }
821
+ }
822
+ } catch (error) {
823
+ console.error('Error loading agents:', error);
824
+ }
825
+ },
826
+
827
+ async loadVersions() {
828
+ if (!this.selectedAgentId) {
829
+ this.versions = [];
830
+ return;
831
+ }
832
+ try {
833
+ const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/versions/`, {
834
+ credentials: 'include',
835
+ headers: { 'Accept': 'application/json' },
836
+ });
837
+ if (response.ok) {
838
+ const data = await response.json();
839
+ // Handle paginated response (results array) or direct array
840
+ this.versions = Array.isArray(data) ? data : (data.results || []);
841
+ // Select the active version by default
842
+ const activeVersion = this.versions.find(v => v.is_active);
843
+ if (activeVersion) {
844
+ this.selectedVersionId = activeVersion.id;
845
+ } else if (this.versions.length > 0) {
846
+ this.selectedVersionId = this.versions[0].id;
847
+ }
848
+ }
849
+ } catch (error) {
850
+ console.error('Error loading versions:', error);
851
+ }
852
+ },
853
+
854
+ async onAgentChange() {
855
+ // Update the agentConfig for chat widgets
856
+ const agent = this.selectedAgent;
857
+ if (agent) {
858
+ this.agentConfig.agentId = agent.id;
859
+ this.agentConfig.agentSlug = agent.slug;
860
+ // Update URL without reload
861
+ window.history.replaceState({}, '', `/studio/agents/${agent.id}/`);
862
+ } else {
863
+ this.agentConfig.agentId = null;
864
+ this.agentConfig.agentSlug = null;
865
+ window.history.replaceState({}, '', '/studio/agents/new/');
866
+ }
867
+
868
+ // Load versions for the new agent
869
+ await this.loadVersions();
870
+
871
+ // Reinitialize chat widgets
872
+ this.refreshTestAgent();
873
+ this.refreshBuilderChat();
874
+ },
875
+
876
+ async onVersionChange() {
877
+ // Version change might affect the test agent behavior
878
+ // For now, just refresh the test chat
879
+ this.refreshTestAgent();
880
+ },
881
+
882
+ async activateVersion() {
883
+ if (!this.selectedAgentId || !this.selectedVersionId) return;
884
+
885
+ try {
886
+ const response = await fetch(
887
+ `/studio/api/agents/${this.selectedAgentId}/versions/${this.selectedVersionId}/activate/`,
888
+ {
889
+ method: 'POST',
890
+ credentials: 'include',
891
+ headers: {
892
+ 'Content-Type': 'application/json',
893
+ 'X-CSRFToken': this.getCsrfToken(),
894
+ },
895
+ }
896
+ );
897
+ if (response.ok) {
898
+ await this.loadVersions();
899
+ this.refreshTestAgent();
900
+ }
901
+ } catch (error) {
902
+ console.error('Error activating version:', error);
903
+ }
904
+ },
905
+
906
+ async createNewAgent() {
907
+ const name = prompt('Enter a name for the new agent:');
908
+ if (!name) return;
909
+
910
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
911
+
912
+ try {
913
+ const response = await fetch('/studio/api/agents/', {
914
+ method: 'POST',
915
+ credentials: 'include',
916
+ headers: {
917
+ 'Content-Type': 'application/json',
918
+ 'X-CSRFToken': this.getCsrfToken(),
919
+ },
920
+ body: JSON.stringify({ name, slug }),
921
+ });
922
+
923
+ if (response.ok) {
924
+ const newAgent = await response.json();
925
+ await this.loadAgents();
926
+ this.selectedAgentId = newAgent.id;
927
+ await this.onAgentChange();
928
+ } else {
929
+ const error = await response.json();
930
+ alert(`Failed to create agent: ${error.detail || error.slug || JSON.stringify(error)}`);
931
+ }
932
+ } catch (error) {
933
+ console.error('Error creating agent:', error);
934
+ alert('Failed to create agent');
935
+ }
936
+ },
937
+
938
+ refreshTestAgent() {
939
+ const agent = this.selectedAgent;
940
+ initTestChat(agent ? agent.slug : null);
941
+ },
942
+
943
+ refreshBuilderChat() {
944
+ initBuilderChat(this.selectedAgentId, this);
945
+ },
946
+
947
+ async openSchemaEditor() {
948
+ if (!this.selectedAgentId) return;
949
+ this.schemaEditorOpen = true;
950
+ await this.reloadSchema();
951
+ },
952
+
953
+ closeSchemaEditor() {
954
+ this.schemaEditorOpen = false;
955
+ this.schemaError = null;
956
+ this.schemaSaved = false;
957
+ this.schemaModified = false;
958
+ },
959
+
960
+ async reloadSchema() {
961
+ if (!this.selectedAgentId) return;
962
+
963
+ this.schemaLoading = true;
964
+ this.schemaError = null;
965
+ this.schemaSaved = false;
966
+ this.schemaModified = false;
967
+
968
+ try {
969
+ const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/full-schema/`, {
970
+ credentials: 'include',
971
+ headers: {
972
+ 'Accept': 'application/json',
973
+ },
974
+ });
975
+
976
+ if (!response.ok) {
977
+ throw new Error(`Failed to load schema: ${response.status}`);
978
+ }
979
+
980
+ this.fullSchema = await response.json();
981
+ this.schemaLoadedAt = new Date().toLocaleTimeString();
982
+
983
+ // Update individual JSON views
984
+ this.updateJsonViews();
985
+
986
+ } catch (error) {
987
+ console.error('Error loading schema:', error);
988
+ this.schemaError = error.message;
989
+ } finally {
990
+ this.schemaLoading = false;
991
+ }
992
+ },
993
+
994
+ updateJsonViews() {
995
+ if (!this.fullSchema) return;
996
+
997
+ this.schemaJson = JSON.stringify(this.fullSchema, null, 2);
998
+ this.versionJson = JSON.stringify(this.fullSchema.version || {}, null, 2);
999
+ this.toolsJson = JSON.stringify(this.fullSchema.tools || [], null, 2);
1000
+ this.dynamicToolsJson = JSON.stringify(this.fullSchema.dynamic_tools || [], null, 2);
1001
+ this.subAgentToolsJson = JSON.stringify(this.fullSchema.sub_agent_tools || [], null, 2);
1002
+ this.knowledgeJson = JSON.stringify(this.fullSchema.knowledge || [], null, 2);
1003
+ this.ragConfigJson = JSON.stringify(this.fullSchema.rag_config || {}, null, 2);
1004
+ this.functionsJson = JSON.stringify(this.fullSchema.available_functions || [], null, 2);
1005
+ },
1006
+
1007
+ validateSchema() {
1008
+ this.schemaModified = true;
1009
+ this.schemaSaved = false;
1010
+ this.schemaError = null;
1011
+
1012
+ try {
1013
+ // Validate current tab's JSON
1014
+ switch (this.schemaTab) {
1015
+ case 'full':
1016
+ JSON.parse(this.schemaJson);
1017
+ break;
1018
+ case 'version':
1019
+ JSON.parse(this.versionJson);
1020
+ break;
1021
+ case 'tools':
1022
+ JSON.parse(this.toolsJson);
1023
+ break;
1024
+ case 'dynamic_tools':
1025
+ JSON.parse(this.dynamicToolsJson);
1026
+ break;
1027
+ case 'sub_agents':
1028
+ JSON.parse(this.subAgentToolsJson);
1029
+ break;
1030
+ case 'knowledge':
1031
+ JSON.parse(this.knowledgeJson);
1032
+ break;
1033
+ case 'rag':
1034
+ JSON.parse(this.ragConfigJson);
1035
+ break;
1036
+ }
1037
+ } catch (e) {
1038
+ this.schemaError = `Invalid JSON: ${e.message}`;
1039
+ }
1040
+ },
1041
+
1042
+ async saveSchema() {
1043
+ if (this.schemaError || this.schemaTab === 'functions') return;
1044
+
1045
+ this.schemaLoading = true;
1046
+ this.schemaError = null;
1047
+
1048
+ try {
1049
+ // Build the update payload based on current tab
1050
+ let payload = {};
1051
+
1052
+ switch (this.schemaTab) {
1053
+ case 'full':
1054
+ payload = JSON.parse(this.schemaJson);
1055
+ break;
1056
+ case 'version':
1057
+ payload = { version: JSON.parse(this.versionJson) };
1058
+ break;
1059
+ case 'tools':
1060
+ payload = { tools: JSON.parse(this.toolsJson) };
1061
+ break;
1062
+ case 'dynamic_tools':
1063
+ payload = { dynamic_tools: JSON.parse(this.dynamicToolsJson) };
1064
+ break;
1065
+ case 'sub_agents':
1066
+ payload = { sub_agent_tools: JSON.parse(this.subAgentToolsJson) };
1067
+ break;
1068
+ case 'knowledge':
1069
+ payload = { knowledge: JSON.parse(this.knowledgeJson) };
1070
+ break;
1071
+ case 'rag':
1072
+ payload = { rag_config: JSON.parse(this.ragConfigJson) };
1073
+ break;
1074
+ }
1075
+
1076
+ const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/full-schema/`, {
1077
+ method: 'PUT',
1078
+ credentials: 'include',
1079
+ headers: {
1080
+ 'Content-Type': 'application/json',
1081
+ 'X-CSRFToken': this.getCsrfToken(),
1082
+ },
1083
+ body: JSON.stringify(payload),
1084
+ });
1085
+
1086
+ if (!response.ok) {
1087
+ const error = await response.json();
1088
+ throw new Error(error.detail || error.message || `Failed to save: ${response.status}`);
1089
+ }
1090
+
1091
+ this.schemaSaved = true;
1092
+ this.schemaModified = false;
1093
+
1094
+ // Reload to get the updated schema
1095
+ setTimeout(() => {
1096
+ this.reloadSchema();
1097
+ }, 1000);
1098
+
1099
+ } catch (error) {
1100
+ console.error('Error saving schema:', error);
1101
+ this.schemaError = error.message;
1102
+ } finally {
1103
+ this.schemaLoading = false;
1104
+ }
1105
+ },
1106
+
1107
+ async copySchema() {
1108
+ let textToCopy = '';
1109
+ switch (this.schemaTab) {
1110
+ case 'full':
1111
+ textToCopy = this.schemaJson;
1112
+ break;
1113
+ case 'version':
1114
+ textToCopy = this.versionJson;
1115
+ break;
1116
+ case 'tools':
1117
+ textToCopy = this.toolsJson;
1118
+ break;
1119
+ case 'dynamic_tools':
1120
+ textToCopy = this.dynamicToolsJson;
1121
+ break;
1122
+ case 'sub_agents':
1123
+ textToCopy = this.subAgentToolsJson;
1124
+ break;
1125
+ case 'knowledge':
1126
+ textToCopy = this.knowledgeJson;
1127
+ break;
1128
+ case 'rag':
1129
+ textToCopy = this.ragConfigJson;
1130
+ break;
1131
+ case 'functions':
1132
+ textToCopy = this.functionsJson;
1133
+ break;
1134
+ }
1135
+
1136
+ try {
1137
+ await navigator.clipboard.writeText(textToCopy);
1138
+ // Could show a toast here
1139
+ } catch (e) {
1140
+ console.error('Failed to copy:', e);
1141
+ }
1142
+ },
1143
+
1144
+ // ==========================================================================
1145
+ // Delete Agent Methods
1146
+ // ==========================================================================
1147
+
1148
+ async deleteAgent() {
1149
+ if (!this.selectedAgentId) return;
1150
+ const agent = this.selectedAgent;
1151
+ if (!agent) return;
1152
+
1153
+ this.deleteConfirmMessage = `Are you sure you want to delete "${agent.name}"? This action cannot be undone.`;
1154
+ this.deleteConfirmCallback = async () => {
1155
+ try {
1156
+ const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/`, {
1157
+ method: 'DELETE',
1158
+ credentials: 'include',
1159
+ headers: {
1160
+ 'X-CSRFToken': this.getCsrfToken(),
1161
+ },
1162
+ });
1163
+
1164
+ if (response.ok || response.status === 204) {
1165
+ this.selectedAgentId = '';
1166
+ this.agentConfig.agentId = null;
1167
+ this.agentConfig.agentSlug = null;
1168
+ window.history.replaceState({}, '', '/studio/agents/new/');
1169
+ await this.loadAgents();
1170
+ this.refreshTestAgent();
1171
+ this.refreshBuilderChat();
1172
+ } else {
1173
+ const error = await response.json();
1174
+ alert(`Failed to delete agent: ${error.detail || JSON.stringify(error)}`);
1175
+ }
1176
+ } catch (error) {
1177
+ console.error('Error deleting agent:', error);
1178
+ alert('Failed to delete agent');
1179
+ }
1180
+ };
1181
+ this.deleteConfirmOpen = true;
1182
+ },
1183
+
1184
+ // ==========================================================================
1185
+ // Systems Management Methods
1186
+ // ==========================================================================
1187
+
1188
+ async loadSystems() {
1189
+ this.systemsLoading = true;
1190
+ try {
1191
+ const response = await fetch('/studio/api/systems/', {
1192
+ credentials: 'include',
1193
+ headers: { 'Accept': 'application/json' },
1194
+ });
1195
+ if (response.ok) {
1196
+ const data = await response.json();
1197
+ this.systems = Array.isArray(data) ? data : (data.results || []);
1198
+ }
1199
+ } catch (error) {
1200
+ console.error('Error loading systems:', error);
1201
+ } finally {
1202
+ this.systemsLoading = false;
1203
+ }
1204
+ },
1205
+
1206
+ onSystemChange() {
1207
+ // Could filter agents by system or show system-specific info
1208
+ console.log('Selected system:', this.selectedSystemId);
1209
+ },
1210
+
1211
+ openSystemsModal() {
1212
+ this.systemsModalOpen = true;
1213
+ this.loadSystems();
1214
+ },
1215
+
1216
+ closeSystemsModal() {
1217
+ this.systemsModalOpen = false;
1218
+ this.expandedSystemId = null;
1219
+ },
1220
+
1221
+ async createSystem() {
1222
+ if (!this.newSystemName || !this.newSystemSlug) return;
1223
+
1224
+ try {
1225
+ const payload = {
1226
+ name: this.newSystemName,
1227
+ slug: this.newSystemSlug,
1228
+ };
1229
+ if (this.newSystemEntryAgent) {
1230
+ payload.entry_agent = this.newSystemEntryAgent;
1231
+ }
1232
+
1233
+ const response = await fetch('/studio/api/systems/', {
1234
+ method: 'POST',
1235
+ credentials: 'include',
1236
+ headers: {
1237
+ 'Content-Type': 'application/json',
1238
+ 'X-CSRFToken': this.getCsrfToken(),
1239
+ },
1240
+ body: JSON.stringify(payload),
1241
+ });
1242
+
1243
+ if (response.ok) {
1244
+ this.newSystemName = '';
1245
+ this.newSystemSlug = '';
1246
+ this.newSystemEntryAgent = '';
1247
+ await this.loadSystems();
1248
+ } else {
1249
+ const error = await response.json();
1250
+ alert(`Failed to create system: ${error.detail || error.slug || JSON.stringify(error)}`);
1251
+ }
1252
+ } catch (error) {
1253
+ console.error('Error creating system:', error);
1254
+ alert('Failed to create system');
1255
+ }
1256
+ },
1257
+
1258
+ viewSystemDetails(system) {
1259
+ if (this.expandedSystemId === system.id) {
1260
+ this.expandedSystemId = null;
1261
+ } else {
1262
+ this.expandedSystemId = system.id;
1263
+ this.loadSystemMembers(system);
1264
+ }
1265
+ },
1266
+
1267
+ async loadSystemMembers(system) {
1268
+ try {
1269
+ const response = await fetch(`/studio/api/systems/${system.id}/members/`, {
1270
+ credentials: 'include',
1271
+ headers: { 'Accept': 'application/json' },
1272
+ });
1273
+ if (response.ok) {
1274
+ const data = await response.json();
1275
+ const members = Array.isArray(data) ? data : (data.results || []);
1276
+ // Update the system in our list with members
1277
+ const idx = this.systems.findIndex(s => s.id === system.id);
1278
+ if (idx !== -1) {
1279
+ this.systems[idx] = { ...this.systems[idx], members };
1280
+ }
1281
+ }
1282
+ } catch (error) {
1283
+ console.error('Error loading system members:', error);
1284
+ }
1285
+ },
1286
+
1287
+ async publishSystem(system) {
1288
+ try {
1289
+ const response = await fetch(`/studio/api/systems/${system.id}/publish/`, {
1290
+ method: 'POST',
1291
+ credentials: 'include',
1292
+ headers: {
1293
+ 'Content-Type': 'application/json',
1294
+ 'X-CSRFToken': this.getCsrfToken(),
1295
+ },
1296
+ });
1297
+
1298
+ if (response.ok) {
1299
+ alert('System version published successfully!');
1300
+ await this.loadSystems();
1301
+ } else {
1302
+ const error = await response.json();
1303
+ alert(`Failed to publish: ${error.detail || JSON.stringify(error)}`);
1304
+ }
1305
+ } catch (error) {
1306
+ console.error('Error publishing system:', error);
1307
+ alert('Failed to publish system');
1308
+ }
1309
+ },
1310
+
1311
+ async deleteSystem(system) {
1312
+ this.deleteConfirmMessage = `Are you sure you want to delete system "${system.name}"? This action cannot be undone.`;
1313
+ this.deleteConfirmCallback = async () => {
1314
+ try {
1315
+ const response = await fetch(`/studio/api/systems/${system.id}/`, {
1316
+ method: 'DELETE',
1317
+ credentials: 'include',
1318
+ headers: {
1319
+ 'X-CSRFToken': this.getCsrfToken(),
1320
+ },
1321
+ });
1322
+
1323
+ if (response.ok || response.status === 204) {
1324
+ if (this.selectedSystemId === system.id) {
1325
+ this.selectedSystemId = '';
1326
+ }
1327
+ await this.loadSystems();
1328
+ } else {
1329
+ const error = await response.json();
1330
+ alert(`Failed to delete system: ${error.detail || JSON.stringify(error)}`);
1331
+ }
1332
+ } catch (error) {
1333
+ console.error('Error deleting system:', error);
1334
+ alert('Failed to delete system');
1335
+ }
1336
+ };
1337
+ this.deleteConfirmOpen = true;
1338
+ },
1339
+
1340
+ async addMemberToSystem(system) {
1341
+ // Simple prompt for now - could be a modal
1342
+ const agentId = prompt('Enter the agent ID to add:');
1343
+ if (!agentId) return;
1344
+
1345
+ try {
1346
+ const response = await fetch(`/studio/api/systems/${system.id}/members/`, {
1347
+ method: 'POST',
1348
+ credentials: 'include',
1349
+ headers: {
1350
+ 'Content-Type': 'application/json',
1351
+ 'X-CSRFToken': this.getCsrfToken(),
1352
+ },
1353
+ body: JSON.stringify({ agent: agentId }),
1354
+ });
1355
+
1356
+ if (response.ok) {
1357
+ await this.loadSystemMembers(system);
1358
+ } else {
1359
+ const error = await response.json();
1360
+ alert(`Failed to add member: ${error.detail || JSON.stringify(error)}`);
1361
+ }
1362
+ } catch (error) {
1363
+ console.error('Error adding member:', error);
1364
+ alert('Failed to add member');
1365
+ }
1366
+ },
1367
+
1368
+ async removeMemberFromSystem(system, member) {
1369
+ try {
1370
+ const response = await fetch(`/studio/api/systems/${system.id}/members/${member.id}/`, {
1371
+ method: 'DELETE',
1372
+ credentials: 'include',
1373
+ headers: {
1374
+ 'X-CSRFToken': this.getCsrfToken(),
1375
+ },
1376
+ });
1377
+
1378
+ if (response.ok || response.status === 204) {
1379
+ await this.loadSystemMembers(system);
1380
+ } else {
1381
+ const error = await response.json();
1382
+ alert(`Failed to remove member: ${error.detail || JSON.stringify(error)}`);
1383
+ }
1384
+ } catch (error) {
1385
+ console.error('Error removing member:', error);
1386
+ alert('Failed to remove member');
1387
+ }
1388
+ },
1389
+
1390
+ // ==========================================================================
1391
+ // Delete Confirmation Methods
1392
+ // ==========================================================================
1393
+
1394
+ cancelDelete() {
1395
+ this.deleteConfirmOpen = false;
1396
+ this.deleteConfirmMessage = '';
1397
+ this.deleteConfirmCallback = null;
1398
+ },
1399
+
1400
+ async confirmDelete() {
1401
+ if (this.deleteConfirmCallback) {
1402
+ await this.deleteConfirmCallback();
1403
+ }
1404
+ this.cancelDelete();
1405
+ },
1406
+
1407
+ getCsrfToken() {
1408
+ const name = 'csrftoken';
1409
+ let cookieValue = null;
1410
+ if (document.cookie && document.cookie !== '') {
1411
+ const cookies = document.cookie.split(';');
1412
+ for (let i = 0; i < cookies.length; i++) {
1413
+ const cookie = cookies[i].trim();
1414
+ if (cookie.substring(0, name.length + 1) === (name + '=')) {
1415
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
1416
+ break;
1417
+ }
1418
+ }
1419
+ }
1420
+ return cookieValue;
1421
+ },
1422
+ },
1423
+ async mounted() {
1424
+ // Load agents and systems lists
1425
+ await Promise.all([
1426
+ this.loadAgents(),
1427
+ this.loadSystems(),
1428
+ ]);
1429
+
1430
+ // Auto-select first agent if none selected and agents exist
1431
+ if (!this.selectedAgentId && this.agents.length > 0) {
1432
+ this.selectedAgentId = this.agents[0].id;
1433
+ await this.onAgentChange();
1434
+ }
1435
+
1436
+ // Initialize chat widgets with current selection
1437
+ this.refreshTestAgent();
1438
+ this.refreshBuilderChat();
1439
+ }
1440
+ }).use(primevue.config.default).mount('#app');
1441
+ </script>
1442
+ {% endblock %}
1443
+