django-agent-studio 0.1.9__py3-none-any.whl → 0.2.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.
@@ -1,2130 +1,37 @@
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="openSpecDocuments"
256
- class="flex items-center space-x-1 px-2 py-1 text-sm rounded border text-emerald-600 border-emerald-300 hover:bg-emerald-50 hover:border-emerald-400"
257
- title="Manage Spec Documents">
258
- <span>📄</span>
259
- <span>Specs</span>
260
- </button>
261
- <button @click="openSchemaEditor"
262
- class="flex items-center space-x-1 px-2 py-1 text-sm rounded border"
263
- :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'"
264
- title="Edit Agent Schema (JSON)"
265
- :disabled="!selectedAgentId">
266
- <span>📋</span>
267
- <span>Schema</span>
268
- </button>
269
- <button @click="loadAgents"
270
- class="text-gray-500 hover:text-gray-700 p-1.5 rounded hover:bg-gray-100"
271
- title="Refresh agent list">
272
- <i class="pi pi-refresh"></i>
273
- </button>
274
- </div>
275
- </div>
276
-
277
- <!-- Main Content -->
278
- <div class="flex-1 flex overflow-hidden">
279
- <!-- Left Pane: Test Your Agent -->
280
- <div class="w-1/2 border-r border-gray-200 flex flex-col">
281
- <div class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
282
- <div class="flex items-center space-x-2">
283
- <span class="text-lg">🧪</span>
284
- <h2 class="font-medium text-gray-700">Test Your Agent</h2>
285
- </div>
286
- <div class="flex items-center space-x-3">
287
- <!-- Auth Mode Toggle -->
288
- <div class="flex items-center space-x-2 text-xs">
289
- <span class="text-gray-500">Test as:</span>
290
- <button @click="setTestAuthMode('authenticated')"
291
- :class="testAuthMode === 'authenticated'
292
- ? 'bg-blue-100 text-blue-700 border-blue-300'
293
- : 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50'"
294
- class="px-2 py-1 border rounded-l text-xs font-medium transition-colors"
295
- title="Test as logged-in user (memories will be saved to your account)">
296
- <i class="pi pi-user text-xs mr-1"></i>Logged In
297
- </button>
298
- <button @click="setTestAuthMode('anonymous')"
299
- :class="testAuthMode === 'anonymous'
300
- ? 'bg-orange-100 text-orange-700 border-orange-300'
301
- : 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50'"
302
- class="px-2 py-1 border border-l-0 rounded-r text-xs font-medium transition-colors"
303
- title="Test as anonymous visitor (no persistent memories)">
304
- <i class="pi pi-eye-slash text-xs mr-1"></i>Anonymous
305
- </button>
306
- </div>
307
- <button @click="refreshTestAgent"
308
- class="text-gray-500 hover:text-gray-700 p-1 rounded hover:bg-gray-200"
309
- title="Refresh agent config">
310
- <i class="pi pi-refresh text-sm"></i>
311
- </button>
312
- </div>
313
- </div>
314
- <div class="flex-1 relative bg-gray-100">
315
- <div id="test-chat-container" class="absolute inset-0"></div>
316
- </div>
317
- </div>
318
-
319
- <!-- Right Pane: Builder Agent -->
320
- <div class="w-1/2 flex flex-col">
321
- <div class="px-4 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
322
- <div class="flex items-center space-x-2">
323
- <span class="text-lg">🛠️</span>
324
- <h2 class="font-medium text-gray-700">Agent Builder</h2>
325
- </div>
326
- <span class="text-xs text-gray-500">Chat with the builder to customize your agent</span>
327
- </div>
328
- <div class="flex-1 relative bg-gray-50">
329
- <div id="builder-chat-container" class="absolute inset-0"></div>
330
- </div>
331
- </div>
332
- </div>
333
- </div>
334
-
335
- <!-- Schema Editor Modal -->
336
- <div v-if="schemaEditorOpen" class="schema-modal-overlay" @click.self="closeSchemaEditor">
337
- <div class="schema-modal">
338
- <div class="schema-modal-header">
339
- <div class="flex items-center space-x-3">
340
- <span class="text-xl">📋</span>
341
- <div>
342
- <h3 class="text-lg font-semibold text-gray-800">Agent Schema Editor</h3>
343
- <p class="text-sm text-gray-500">View and edit the complete agent configuration</p>
344
- </div>
345
- </div>
346
- <div class="flex items-center space-x-2">
347
- <button @click="reloadSchema"
348
- class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded"
349
- :disabled="schemaLoading"
350
- title="Reload schema from server">
351
- <i class="pi pi-refresh" :class="{'pi-spin': schemaLoading}"></i>
352
- <span class="ml-1">Reload</span>
353
- </button>
354
- <button @click="closeSchemaEditor"
355
- class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
356
- <i class="pi pi-times text-lg"></i>
357
- </button>
358
- </div>
359
- </div>
360
-
361
- <div class="schema-tabs">
362
- <div class="schema-tab" :class="{active: schemaTab === 'full'}" @click="schemaTab = 'full'">
363
- Full Schema
364
- </div>
365
- <div class="schema-tab" :class="{active: schemaTab === 'version'}" @click="schemaTab = 'version'">
366
- Version Config
367
- </div>
368
- <div class="schema-tab" :class="{active: schemaTab === 'tools'}" @click="schemaTab = 'tools'">
369
- Tools
370
- </div>
371
- <div class="schema-tab" :class="{active: schemaTab === 'dynamic_tools'}" @click="schemaTab = 'dynamic_tools'">
372
- Dynamic Tools
373
- </div>
374
- <div class="schema-tab" :class="{active: schemaTab === 'sub_agents'}" @click="schemaTab = 'sub_agents'">
375
- 🔗 Sub-Agents
376
- </div>
377
- <div class="schema-tab" :class="{active: schemaTab === 'knowledge'}" @click="schemaTab = 'knowledge'">
378
- Knowledge
379
- </div>
380
- <div class="schema-tab" :class="{active: schemaTab === 'rag'}" @click="schemaTab = 'rag'">
381
- RAG Config
382
- </div>
383
- <div class="schema-tab" :class="{active: schemaTab === 'functions'}" @click="schemaTab = 'functions'">
384
- Available Functions
385
- </div>
386
- </div>
387
-
388
- <div class="schema-modal-body">
389
- <div class="schema-content">
390
- <textarea
391
- v-if="schemaTab === 'full'"
392
- class="schema-editor"
393
- v-model="schemaJson"
394
- @input="validateSchema"
395
- spellcheck="false"
396
- ></textarea>
397
- <textarea
398
- v-else-if="schemaTab === 'version'"
399
- class="schema-editor"
400
- v-model="versionJson"
401
- @input="validateSchema"
402
- spellcheck="false"
403
- ></textarea>
404
- <textarea
405
- v-else-if="schemaTab === 'tools'"
406
- class="schema-editor"
407
- v-model="toolsJson"
408
- @input="validateSchema"
409
- spellcheck="false"
410
- ></textarea>
411
- <textarea
412
- v-else-if="schemaTab === 'dynamic_tools'"
413
- class="schema-editor"
414
- v-model="dynamicToolsJson"
415
- @input="validateSchema"
416
- spellcheck="false"
417
- ></textarea>
418
- <textarea
419
- v-else-if="schemaTab === 'sub_agents'"
420
- class="schema-editor"
421
- v-model="subAgentToolsJson"
422
- @input="validateSchema"
423
- spellcheck="false"
424
- ></textarea>
425
- <textarea
426
- v-else-if="schemaTab === 'knowledge'"
427
- class="schema-editor"
428
- v-model="knowledgeJson"
429
- @input="validateSchema"
430
- spellcheck="false"
431
- ></textarea>
432
- <textarea
433
- v-else-if="schemaTab === 'rag'"
434
- class="schema-editor"
435
- v-model="ragConfigJson"
436
- @input="validateSchema"
437
- spellcheck="false"
438
- ></textarea>
439
- <textarea
440
- v-else-if="schemaTab === 'functions'"
441
- class="schema-editor"
442
- v-model="functionsJson"
443
- readonly
444
- spellcheck="false"
445
- ></textarea>
446
- </div>
447
- </div>
448
-
449
- <div class="schema-modal-footer">
450
- <div class="schema-status" :class="{error: schemaError, success: schemaSaved}">
451
- <span v-if="schemaLoading"><i class="pi pi-spin pi-spinner"></i> Loading...</span>
452
- <span v-else-if="schemaError"><i class="pi pi-exclamation-triangle"></i> [[ schemaError ]]</span>
453
- <span v-else-if="schemaSaved"><i class="pi pi-check"></i> Saved successfully</span>
454
- <span v-else-if="schemaModified"><i class="pi pi-pencil"></i> Modified (unsaved)</span>
455
- <span v-else>Last loaded: [[ schemaLoadedAt ]]</span>
456
- </div>
457
- <div class="btn-group">
458
- <button @click="copySchema"
459
- class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
460
- <i class="pi pi-copy mr-1"></i> Copy
461
- </button>
462
- <button @click="closeSchemaEditor"
463
- class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
464
- Cancel
465
- </button>
466
- <button @click="saveSchema"
467
- class="px-4 py-2 text-sm text-white bg-blue-600 hover:bg-blue-700 rounded"
468
- :disabled="schemaLoading || !!schemaError || schemaTab === 'functions'">
469
- <i class="pi pi-save mr-1"></i> Save Changes
470
- </button>
471
- </div>
472
- </div>
473
- </div>
474
- </div>
475
-
476
- <!-- Systems Management Modal -->
477
- <div v-if="systemsModalOpen" class="schema-modal-overlay" @click.self="closeSystemsModal">
478
- <div class="schema-modal" style="max-width: 900px; height: 80vh;">
479
- <div class="schema-modal-header">
480
- <div class="flex items-center space-x-3">
481
- <span class="text-xl">🔗</span>
482
- <div>
483
- <h3 class="text-lg font-semibold text-gray-800">Multi-Agent Systems</h3>
484
- <p class="text-sm text-gray-500">Manage agent systems and their members</p>
485
- </div>
486
- </div>
487
- <div class="flex items-center space-x-2">
488
- <button @click="loadSystems"
489
- class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded"
490
- :disabled="systemsLoading"
491
- title="Refresh systems">
492
- <i class="pi pi-refresh" :class="{'pi-spin': systemsLoading}"></i>
493
- </button>
494
- <button @click="closeSystemsModal"
495
- class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
496
- <i class="pi pi-times text-lg"></i>
497
- </button>
498
- </div>
499
- </div>
500
-
501
- <div class="schema-modal-body" style="padding: 1rem;">
502
- <!-- Create New System -->
503
- <div class="mb-4 p-4 bg-gray-50 rounded-lg border border-gray-200">
504
- <h4 class="text-sm font-medium text-gray-700 mb-3">Create New System</h4>
505
- <div class="flex items-end space-x-3">
506
- <div class="flex-1">
507
- <label class="block text-xs text-gray-500 mb-1">Name</label>
508
- <input v-model="newSystemName"
509
- type="text"
510
- placeholder="e.g., Customer Support"
511
- class="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
512
- </div>
513
- <div class="flex-1">
514
- <label class="block text-xs text-gray-500 mb-1">Slug</label>
515
- <input v-model="newSystemSlug"
516
- type="text"
517
- placeholder="e.g., customer-support"
518
- class="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
519
- </div>
520
- <div>
521
- <label class="block text-xs text-gray-500 mb-1">Entry Agent</label>
522
- <select v-model="newSystemEntryAgent"
523
- class="px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-purple-500">
524
- <option value="">-- Select --</option>
525
- <option v-for="agent in agents" :key="agent.id" :value="agent.id">
526
- [[ agent.name ]]
527
- </option>
528
- </select>
529
- </div>
530
- <button @click="createSystem"
531
- :disabled="!newSystemName || !newSystemSlug"
532
- class="px-4 py-2 text-sm text-white bg-purple-600 hover:bg-purple-700 rounded disabled:opacity-50 disabled:cursor-not-allowed">
533
- Create
534
- </button>
535
- </div>
536
- </div>
537
-
538
- <!-- Systems List -->
539
- <div class="space-y-3 overflow-y-auto" style="max-height: calc(80vh - 280px);">
540
- <div v-if="systemsLoading" class="text-center py-8 text-gray-500">
541
- <i class="pi pi-spin pi-spinner text-2xl"></i>
542
- <p class="mt-2">Loading systems...</p>
543
- </div>
544
- <div v-else-if="systems.length === 0" class="text-center py-8 text-gray-500">
545
- <span class="text-4xl">🔗</span>
546
- <p class="mt-2">No systems yet. Create one above!</p>
547
- </div>
548
- <div v-else v-for="system in systems" :key="system.id"
549
- class="p-4 bg-white border border-gray-200 rounded-lg hover:border-purple-300 transition-colors">
550
- <div class="flex items-start justify-between">
551
- <div class="flex-1">
552
- <div class="flex items-center space-x-2">
553
- <h4 class="font-medium text-gray-800">[[ system.name ]]</h4>
554
- <span class="text-xs font-mono bg-gray-100 px-2 py-0.5 rounded">[[ system.slug ]]</span>
555
- </div>
556
- <p class="text-sm text-gray-500 mt-1">[[ system.description || 'No description' ]]</p>
557
- <div class="flex items-center space-x-4 mt-2 text-xs text-gray-500">
558
- <span v-if="system.entry_agent_slug">
559
- Entry: <span class="font-mono">[[ system.entry_agent_slug ]]</span>
560
- </span>
561
- <span>[[ system.member_count || 0 ]] members</span>
562
- <span v-if="system.active_version">
563
- Active: v[[ system.active_version ]]
564
- </span>
565
- </div>
566
- </div>
567
- <div class="flex items-center space-x-2">
568
- <button @click="viewSystemDetails(system)"
569
- class="px-3 py-1.5 text-xs text-purple-600 border border-purple-300 rounded hover:bg-purple-50">
570
- Details
571
- </button>
572
- <button @click="publishSystem(system)"
573
- class="px-3 py-1.5 text-xs text-green-600 border border-green-300 rounded hover:bg-green-50">
574
- Publish
575
- </button>
576
- <button @click="deleteSystem(system)"
577
- class="px-3 py-1.5 text-xs text-red-600 border border-red-300 rounded hover:bg-red-50">
578
- Delete
579
- </button>
580
- </div>
581
- </div>
582
-
583
- <!-- System Members (expandable) -->
584
- <div v-if="expandedSystemId === system.id" class="mt-4 pt-4 border-t border-gray-100">
585
- <div class="flex items-center justify-between mb-2">
586
- <h5 class="text-sm font-medium text-gray-700">Members</h5>
587
- <button @click="addMemberToSystem(system)"
588
- class="text-xs text-purple-600 hover:text-purple-800">
589
- + Add Member
590
- </button>
591
- </div>
592
- <div v-if="system.members && system.members.length > 0" class="space-y-2">
593
- <div v-for="member in system.members" :key="member.id"
594
- class="flex items-center justify-between p-2 bg-gray-50 rounded text-sm">
595
- <div class="flex items-center space-x-2">
596
- <span>[[ member.agent_name ]]</span>
597
- <span class="text-xs font-mono text-gray-500">([[ member.agent_slug ]])</span>
598
- <span v-if="member.role" class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">
599
- [[ member.role ]]
600
- </span>
601
- <span v-if="member.is_entry_point" class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded">
602
- Entry Point
603
- </span>
604
- </div>
605
- <button @click="removeMemberFromSystem(system, member)"
606
- class="text-red-500 hover:text-red-700">
607
- <i class="pi pi-times text-xs"></i>
608
- </button>
609
- </div>
610
- </div>
611
- <div v-else class="text-sm text-gray-500 italic">No members yet</div>
612
- </div>
613
- </div>
614
- </div>
615
- </div>
616
-
617
- <div class="schema-modal-footer">
618
- <div class="text-sm text-gray-500">
619
- [[ systems.length ]] system(s)
620
- </div>
621
- <button @click="closeSystemsModal"
622
- class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50">
623
- Close
624
- </button>
625
- </div>
626
- </div>
627
- </div>
628
-
629
- <!-- Delete Confirmation Modal -->
630
- <div v-if="deleteConfirmOpen" class="schema-modal-overlay" @click.self="cancelDelete">
631
- <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full">
632
- <div class="flex items-center space-x-3 mb-4">
633
- <span class="text-3xl">⚠️</span>
634
- <div>
635
- <h3 class="text-lg font-semibold text-gray-800">Confirm Delete</h3>
636
- <p class="text-sm text-gray-500">[[ deleteConfirmMessage ]]</p>
637
- </div>
638
- </div>
639
- <div class="flex justify-end space-x-3">
640
- <button @click="cancelDelete"
641
- class="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded hover:bg-gray-50">
642
- Cancel
643
- </button>
644
- <button @click="confirmDelete"
645
- class="px-4 py-2 text-sm text-white bg-red-600 hover:bg-red-700 rounded">
646
- Delete
647
- </button>
648
- </div>
649
- </div>
650
- </div>
651
-
652
- <!-- Spec Documents Modal -->
653
- <div v-if="specDocsModalOpen" class="schema-modal-overlay" @click.self="closeSpecDocuments">
654
- <div class="schema-modal" style="max-width: 1400px; height: 90vh;">
655
- <div class="schema-modal-header">
656
- <div class="flex items-center space-x-3">
657
- <span class="text-xl">📄</span>
658
- <div>
659
- <h3 class="text-lg font-semibold text-gray-800">Spec Documents</h3>
660
- <p class="text-sm text-gray-500">Manage agent specifications with version history</p>
661
- </div>
662
- </div>
663
- <div class="flex items-center space-x-2">
664
- <button @click="renderFullSpec"
665
- class="px-3 py-1.5 text-sm text-emerald-600 hover:text-emerald-800 hover:bg-emerald-50 rounded border border-emerald-300"
666
- title="Render all specs as markdown">
667
- <i class="pi pi-file-export mr-1"></i>Export
668
- </button>
669
- <button @click="loadSpecTree"
670
- class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded"
671
- :disabled="specDocsLoading"
672
- title="Refresh">
673
- <i class="pi pi-refresh" :class="{'pi-spin': specDocsLoading}"></i>
674
- </button>
675
- <button @click="closeSpecDocuments"
676
- class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
677
- <i class="pi pi-times text-lg"></i>
678
- </button>
679
- </div>
680
- </div>
681
-
682
- <div class="schema-modal-body" style="display: flex; flex-direction: row; overflow: hidden;">
683
- <!-- Left: Document Tree -->
684
- <div class="w-1/3 border-r border-gray-200 flex flex-col" style="min-width: 300px;">
685
- <div class="p-3 border-b border-gray-100 bg-gray-50">
686
- <button @click="createRootDocument"
687
- class="w-full px-3 py-2 text-sm text-emerald-600 border border-emerald-300 rounded hover:bg-emerald-50">
688
- <i class="pi pi-plus mr-1"></i> New Root Document
689
- </button>
690
- </div>
691
- <div class="flex-1 overflow-y-auto p-2">
692
- <div v-if="specDocsLoading" class="text-center py-8 text-gray-500">
693
- <i class="pi pi-spin pi-spinner text-2xl"></i>
694
- <p class="mt-2">Loading...</p>
695
- </div>
696
- <div v-else-if="specTree.length === 0" class="text-center py-8 text-gray-500">
697
- <span class="text-4xl">📄</span>
698
- <p class="mt-2">No spec documents yet</p>
699
- <p class="text-xs mt-1">Create one to get started</p>
700
- </div>
701
- <div v-else>
702
- <spec-tree-node
703
- v-for="node in specTree"
704
- :key="node.id"
705
- :node="node"
706
- :selected-id="selectedSpecId"
707
- :depth="0"
708
- @select="selectSpecDocument"
709
- @add-child="addChildDocument"
710
- ></spec-tree-node>
711
- </div>
712
- </div>
713
- </div>
714
-
715
- <!-- Right: Document Editor -->
716
- <div class="flex-1 flex flex-col overflow-hidden">
717
- <div v-if="!selectedSpecId" class="flex-1 flex items-center justify-center text-gray-500">
718
- <div class="text-center">
719
- <span class="text-5xl">📝</span>
720
- <p class="mt-3">Select a document to edit</p>
721
- <p class="text-sm mt-1">Or create a new one from the tree</p>
722
- </div>
723
- </div>
724
- <template v-else>
725
- <!-- Document Header -->
726
- <div class="p-4 border-b border-gray-200 bg-gray-50">
727
- <div class="flex items-center justify-between">
728
- <div class="flex-1 mr-4">
729
- <input v-model="specDocTitle"
730
- @blur="updateSpecTitle"
731
- class="text-lg font-semibold text-gray-800 bg-transparent border-b border-transparent hover:border-gray-300 focus:border-emerald-500 focus:outline-none w-full"
732
- placeholder="Document Title">
733
- <div class="flex items-center space-x-3 mt-1 text-xs text-gray-500">
734
- <span>v[[ specDocVersion ]]</span>
735
- <span v-if="specDocPath">[[ specDocPath ]]</span>
736
- </div>
737
- </div>
738
- <div class="flex items-center space-x-2">
739
- <!-- Link to Agent -->
740
- <div class="flex items-center space-x-1">
741
- <select v-model="specDocLinkedAgent"
742
- @change="linkSpecToAgent"
743
- class="text-xs px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-emerald-500">
744
- <option value="">Not linked</option>
745
- <option v-for="agent in agents" :key="agent.id" :value="agent.id">
746
- 🤖 [[ agent.name ]]
747
- </option>
748
- </select>
749
- </div>
750
- <button @click="showSpecHistory"
751
- class="px-2 py-1 text-xs text-gray-600 border border-gray-300 rounded hover:bg-gray-100"
752
- title="Version history">
753
- <i class="pi pi-history"></i>
754
- </button>
755
- <button @click="deleteSpecDocument"
756
- class="px-2 py-1 text-xs text-red-600 border border-red-300 rounded hover:bg-red-50"
757
- title="Delete document">
758
- <i class="pi pi-trash"></i>
759
- </button>
760
- </div>
761
- </div>
762
- </div>
763
-
764
- <!-- Content Editor -->
765
- <div class="flex-1 flex overflow-hidden">
766
- <!-- Edit Mode -->
767
- <div class="flex-1 flex flex-col" :class="{'w-1/2': specPreviewMode}">
768
- <div class="px-4 py-2 bg-gray-100 border-b border-gray-200 flex items-center justify-between">
769
- <span class="text-xs font-medium text-gray-600">MARKDOWN</span>
770
- <div class="flex items-center space-x-2">
771
- <button @click="specPreviewMode = !specPreviewMode"
772
- class="text-xs px-2 py-1 rounded"
773
- :class="specPreviewMode ? 'bg-emerald-100 text-emerald-700' : 'text-gray-600 hover:bg-gray-200'">
774
- <i class="pi pi-eye mr-1"></i>Preview
775
- </button>
776
- </div>
777
- </div>
778
- <textarea v-model="specDocContent"
779
- @input="markSpecModified"
780
- class="flex-1 p-4 font-mono text-sm resize-none focus:outline-none"
781
- placeholder="Write your spec in markdown..."
782
- spellcheck="false"></textarea>
783
- </div>
784
-
785
- <!-- Preview Mode -->
786
- <div v-if="specPreviewMode" class="w-1/2 border-l border-gray-200 flex flex-col">
787
- <div class="px-4 py-2 bg-gray-100 border-b border-gray-200">
788
- <span class="text-xs font-medium text-gray-600">PREVIEW</span>
789
- </div>
790
- <div class="flex-1 p-4 overflow-y-auto prose prose-sm max-w-none"
791
- v-html="renderedSpecContent"></div>
792
- </div>
793
- </div>
794
-
795
- <!-- Footer -->
796
- <div class="p-3 border-t border-gray-200 bg-gray-50 flex items-center justify-between">
797
- <div class="text-sm text-gray-500">
798
- <span v-if="specDocModified" class="text-orange-600">
799
- <i class="pi pi-pencil mr-1"></i>Unsaved changes
800
- </span>
801
- <span v-else-if="specDocSaved" class="text-green-600">
802
- <i class="pi pi-check mr-1"></i>Saved
803
- </span>
804
- </div>
805
- <button @click="saveSpecDocument"
806
- :disabled="!specDocModified"
807
- class="px-4 py-2 text-sm text-white bg-emerald-600 hover:bg-emerald-700 rounded disabled:opacity-50 disabled:cursor-not-allowed">
808
- <i class="pi pi-save mr-1"></i>Save
809
- </button>
810
- </div>
811
- </template>
812
- </div>
813
- </div>
814
- </div>
815
- </div>
816
-
817
- <!-- Spec History Modal -->
818
- <div v-if="specHistoryOpen" class="schema-modal-overlay" @click.self="specHistoryOpen = false">
819
- <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] flex flex-col">
820
- <div class="p-4 border-b border-gray-200 flex items-center justify-between">
821
- <h3 class="text-lg font-semibold text-gray-800">Version History</h3>
822
- <button @click="specHistoryOpen = false" class="text-gray-400 hover:text-gray-600">
823
- <i class="pi pi-times"></i>
824
- </button>
825
- </div>
826
- <div class="flex-1 overflow-y-auto p-4">
827
- <div v-if="specHistory.length === 0" class="text-center py-8 text-gray-500">
828
- No version history available
829
- </div>
830
- <div v-else class="space-y-3">
831
- <div v-for="version in specHistory" :key="version.version_number"
832
- class="p-3 border border-gray-200 rounded-lg hover:border-emerald-300 cursor-pointer"
833
- @click="restoreSpecVersion(version.version_number)">
834
- <div class="flex items-center justify-between">
835
- <span class="font-medium text-gray-800">Version [[ version.version_number ]]</span>
836
- <span class="text-xs text-gray-500">[[ formatDate(version.created_at) ]]</span>
837
- </div>
838
- <div class="text-sm text-gray-600 mt-1">[[ version.title ]]</div>
839
- <div v-if="version.content_preview" class="text-xs text-gray-400 mt-1 truncate">
840
- [[ version.content_preview ]]
841
- </div>
842
- </div>
843
- </div>
844
- </div>
845
- </div>
846
- </div>
847
-
848
- <!-- Rendered Spec Modal -->
849
- <div v-if="renderedSpecModalOpen" class="schema-modal-overlay" @click.self="renderedSpecModalOpen = false">
850
- <div class="schema-modal" style="max-width: 900px; height: 85vh;">
851
- <div class="schema-modal-header">
852
- <div class="flex items-center space-x-3">
853
- <span class="text-xl">📋</span>
854
- <div>
855
- <h3 class="text-lg font-semibold text-gray-800">Full Specification</h3>
856
- <p class="text-sm text-gray-500">All spec documents rendered as markdown</p>
857
- </div>
858
- </div>
859
- <div class="flex items-center space-x-2">
860
- <button @click="copyRenderedSpec"
861
- class="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded border border-gray-300">
862
- <i class="pi pi-copy mr-1"></i>Copy
863
- </button>
864
- <button @click="renderedSpecModalOpen = false"
865
- class="p-1.5 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded">
866
- <i class="pi pi-times text-lg"></i>
867
- </button>
868
- </div>
869
- </div>
870
- <div class="flex-1 overflow-y-auto p-6">
871
- <pre class="whitespace-pre-wrap font-mono text-sm text-gray-800 bg-gray-50 p-4 rounded-lg">[[ fullRenderedSpec ]]</pre>
872
- </div>
873
- </div>
874
- </div>
875
- {% endblock %}
876
-
877
- {% block extra_js %}
878
- <script>
879
- const { createApp } = Vue;
880
-
881
- // Agent configuration from Django
882
- const agentConfig = {
883
- isNew: {{ is_new|yesno:"true,false" }},
884
- agentId: {% if agent %}"{{ agent.id }}"{% else %}null{% endif %},
885
- agentSlug: {% if agent %}"{{ agent.slug }}"{% else %}null{% endif %},
886
- builderAgentKey: "{{ builder_agent_key }}",
887
- };
888
-
889
- // Backend URL (same origin)
890
- const backendUrl = window.location.origin;
891
-
892
- // Initialize the test agent chat widget
893
- function initTestChat(agentSlug = null, authMode = 'authenticated', anonymousToken = null) {
894
- const container = document.getElementById('test-chat-container');
895
- if (!container) return; // Container not ready yet
896
-
897
- // Use passed slug or fall back to agentConfig
898
- const slugToUse = agentSlug || agentConfig.agentSlug;
899
-
900
- if (!slugToUse) {
901
- // Show placeholder for new agents
902
- container.innerHTML = `
903
- <div class="flex items-center justify-center h-full text-gray-500">
904
- <div class="text-center">
905
- <div class="text-4xl mb-4">🤖</div>
906
- <p>Select an agent above to test it</p>
907
- <p class="text-sm mt-2">Or create a new one with the "+ New" button</p>
908
- </div>
909
- </div>
910
- `;
911
- return;
912
- }
913
-
914
- // Create a unique instance for the test chat
915
- if (window.testChatWidget) {
916
- window.testChatWidget.destroy();
917
- }
918
-
919
- // Configure auth based on mode
920
- const authConfig = authMode === 'anonymous'
921
- ? {
922
- authStrategy: 'anonymous',
923
- authToken: anonymousToken,
924
- anonymousSessionEndpoint: '/api/accounts/anonymous-session/',
925
- }
926
- : {
927
- authStrategy: 'session',
928
- };
929
-
930
- window.testChatWidget = ChatWidget.createInstance({
931
- containerId: 'test-chat-container',
932
- backendUrl: backendUrl,
933
- agentKey: slugToUse,
934
- title: authMode === 'anonymous' ? 'Test Agent (Anonymous)' : 'Test Agent',
935
- primaryColor: authMode === 'anonymous' ? '#f97316' : '#3b82f6',
936
- showClearButton: true,
937
- showDebugButton: true,
938
- showExpandButton: false,
939
- showModelSelector: true,
940
- embedded: true,
941
- ...authConfig,
942
- apiPaths: {
943
- runs: '/api/agent-runtime/runs/',
944
- runEvents: '/api/agent-runtime/runs/{runId}/events/',
945
- models: '/api/agent-runtime/models/',
946
- },
947
- });
948
- }
949
-
950
- // Initialize the builder agent chat widget
951
- function initBuilderChat(targetAgentId = null, vueApp = null) {
952
- const container = document.getElementById('builder-chat-container');
953
- if (!container) return; // Container not ready yet
954
-
955
- if (window.builderChatWidget) {
956
- window.builderChatWidget.destroy();
957
- }
958
-
959
- // Use passed agent ID or fall back to agentConfig
960
- const agentIdToUse = targetAgentId || agentConfig.agentId;
961
-
962
- window.builderChatWidget = ChatWidget.createInstance({
963
- containerId: 'builder-chat-container',
964
- backendUrl: backendUrl,
965
- agentKey: agentConfig.builderAgentKey,
966
- title: 'Agent Builder',
967
- primaryColor: '#8b5cf6',
968
- showClearButton: true,
969
- showDebugButton: true,
970
- showExpandButton: false,
971
- showModelSelector: true,
972
- embedded: true,
973
- authStrategy: 'session',
974
- apiPaths: {
975
- runs: '/api/agent-runtime/runs/',
976
- runEvents: '/api/agent-runtime/runs/{runId}/events/',
977
- models: '/api/agent-runtime/models/',
978
- },
979
- // Pass the agent ID to the builder agent
980
- metadata: {
981
- agent_id: agentIdToUse,
982
- },
983
- // Handle UI control events from the builder agent
984
- onUIControl: (payload) => {
985
- console.log('[Builder] UI Control event:', payload);
986
- if (vueApp && payload.action) {
987
- switch (payload.action) {
988
- case 'switch_agent':
989
- // Switch to a different agent
990
- if (payload.agent_id) {
991
- vueApp.selectedAgentId = payload.agent_id;
992
- vueApp.onAgentChange();
993
- }
994
- break;
995
- case 'switch_system':
996
- // Switch to a different system
997
- if (payload.system_id) {
998
- vueApp.selectedSystemId = payload.system_id;
999
- vueApp.onSystemChange();
1000
- }
1001
- break;
1002
- }
1003
- }
1004
- },
1005
- // Handle events for debugging/logging
1006
- onEvent: (eventType, payload) => {
1007
- // Log sub-agent events for visibility
1008
- if (eventType === 'sub_agent.start' || eventType === 'sub_agent.end') {
1009
- console.log(`[Builder] ${eventType}:`, payload);
1010
- }
1011
- },
1012
- });
1013
- }
1014
-
1015
- // Spec Tree Node Component
1016
- const SpecTreeNode = {
1017
- name: 'spec-tree-node',
1018
- props: ['node', 'selectedId', 'depth'],
1019
- emits: ['select', 'add-child'],
1020
- template: `{% verbatim %}
1021
- <div class="spec-tree-node">
1022
- <div class="flex items-center py-1.5 px-2 rounded cursor-pointer hover:bg-gray-100 group"
1023
- :class="{'bg-emerald-50 border-l-2 border-emerald-500': node.id === selectedId}"
1024
- :style="{ paddingLeft: (depth * 16 + 8) + 'px' }"
1025
- @click="$emit('select', node.id)">
1026
- <span v-if="node.children && node.children.length > 0"
1027
- class="mr-1 text-gray-400 text-xs"
1028
- @click.stop="expanded = !expanded">
1029
- {{ expanded ? '▼' : '▶' }}
1030
- </span>
1031
- <span v-else class="mr-1 text-gray-300 text-xs">•</span>
1032
- <span class="flex-1 text-sm truncate" :class="node.has_content ? 'text-gray-800' : 'text-gray-400 italic'">
1033
- {{ node.title }}
1034
- </span>
1035
- <span v-if="node.linked_agent"
1036
- class="text-xs bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded ml-1"
1037
- :title="'Linked to ' + node.linked_agent.name">
1038
- 🤖
1039
- </span>
1040
- <button @click.stop="$emit('add-child', node.id)"
1041
- class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-emerald-600 ml-1 text-xs"
1042
- title="Add child document">
1043
- +
1044
- </button>
1045
- </div>
1046
- <div v-if="expanded && node.children && node.children.length > 0">
1047
- <spec-tree-node
1048
- v-for="child in node.children"
1049
- :key="child.id"
1050
- :node="child"
1051
- :selected-id="selectedId"
1052
- :depth="depth + 1"
1053
- @select="$emit('select', $event)"
1054
- @add-child="$emit('add-child', $event)"
1055
- ></spec-tree-node>
1056
- </div>
1057
- </div>
1058
- {% endverbatim %}`,
1059
- data() {
1060
- return {
1061
- expanded: true
1
+ {% load static %}
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>{% if is_new %}Create Agent{% else %}Edit {{ agent.name }}{% endif %} - Agent Studio</title>
8
+
9
+ <!-- Built Vue app styles (PrimeVue + custom) -->
10
+ <link rel="stylesheet" href="{% static 'django_agent_studio/js/style.css' %}">
11
+
12
+ <!-- Agent Frontend Chat Widget (external dependency) -->
13
+ <link rel="stylesheet" href="{% static 'agent-frontend/chat-widget.css' %}">
14
+ <script src="{% static 'agent-frontend/chat-widget.js' %}"></script>
15
+
16
+ <!-- Markdown support for chat widget -->
17
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
18
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
19
+ <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
20
+ <script src="{% static 'agent-frontend/chat-widget-markdown.js' %}"></script>
21
+ </head>
22
+ <body>
23
+ <div id="studio-app"></div>
24
+
25
+ <script>
26
+ // Configuration passed from Django
27
+ window.STUDIO_CONFIG = {
28
+ agentId: {% if agent %}"{{ agent.id }}"{% else %}null{% endif %},
29
+ csrfToken: "{{ csrf_token }}",
30
+ builderAgentKey: "{{ builder_agent_key }}",
1062
31
  };
1063
- }
1064
- };
1065
-
1066
- const app = createApp({
1067
- delimiters: ['[[', ']]'], // Avoid conflict with Django templates
1068
- data() {
1069
- return {
1070
- agentConfig,
1071
- // Agent/version selection
1072
- agents: [],
1073
- versions: [],
1074
- selectedAgentId: agentConfig.agentId || '',
1075
- selectedVersionId: '',
1076
- // Schema editor state
1077
- schemaEditorOpen: false,
1078
- schemaTab: 'full',
1079
- schemaLoading: false,
1080
- schemaError: null,
1081
- schemaSaved: false,
1082
- schemaModified: false,
1083
- schemaLoadedAt: null,
1084
- // Schema data
1085
- fullSchema: null,
1086
- schemaJson: '',
1087
- versionJson: '',
1088
- toolsJson: '',
1089
- dynamicToolsJson: '',
1090
- subAgentToolsJson: '',
1091
- knowledgeJson: '',
1092
- ragConfigJson: '',
1093
- functionsJson: '',
1094
- // Systems management
1095
- systems: [],
1096
- selectedSystemId: '',
1097
- systemsModalOpen: false,
1098
- systemsLoading: false,
1099
- expandedSystemId: null,
1100
- newSystemName: '',
1101
- newSystemSlug: '',
1102
- newSystemEntryAgent: '',
1103
- // Delete confirmation
1104
- deleteConfirmOpen: false,
1105
- deleteConfirmMessage: '',
1106
- deleteConfirmCallback: null,
1107
- // Test auth mode
1108
- testAuthMode: 'authenticated',
1109
- anonymousToken: null,
1110
- // Spec documents
1111
- specDocsModalOpen: false,
1112
- specDocsLoading: false,
1113
- specTree: [],
1114
- selectedSpecId: null,
1115
- specDocTitle: '',
1116
- specDocContent: '',
1117
- specDocVersion: 1,
1118
- specDocPath: '',
1119
- specDocLinkedAgent: '',
1120
- specDocModified: false,
1121
- specDocSaved: false,
1122
- specPreviewMode: false,
1123
- specHistoryOpen: false,
1124
- specHistory: [],
1125
- renderedSpecModalOpen: false,
1126
- fullRenderedSpec: '',
1127
- }
1128
- },
1129
- computed: {
1130
- selectedAgent() {
1131
- return this.agents.find(a => a.id === this.selectedAgentId);
1132
- },
1133
- isActiveVersion() {
1134
- const version = this.versions.find(v => v.id === this.selectedVersionId);
1135
- return version && version.is_active;
1136
- },
1137
- renderedSpecContent() {
1138
- if (!this.specDocContent) return '';
1139
- // Simple markdown rendering (could use marked.js for better rendering)
1140
- if (typeof marked !== 'undefined') {
1141
- return marked.parse(this.specDocContent);
1142
- }
1143
- return this.specDocContent.replace(/\n/g, '<br>');
1144
- },
1145
- },
1146
- methods: {
1147
- async loadAgents() {
1148
- try {
1149
- const response = await fetch('/studio/api/agents/', {
1150
- credentials: 'include',
1151
- headers: { 'Accept': 'application/json' },
1152
- });
1153
- if (response.ok) {
1154
- const data = await response.json();
1155
- // Handle paginated response (results array) or direct array
1156
- const agentList = Array.isArray(data) ? data : (data.results || []);
1157
- // Filter out any agents without valid IDs
1158
- this.agents = agentList.filter(a => a && a.id);
1159
- // If we have a pre-selected agent, load its versions
1160
- if (this.selectedAgentId) {
1161
- await this.loadVersions();
1162
- }
1163
- }
1164
- } catch (error) {
1165
- console.error('Error loading agents:', error);
1166
- }
1167
- },
1168
-
1169
- async loadVersions() {
1170
- if (!this.selectedAgentId) {
1171
- this.versions = [];
1172
- return;
1173
- }
1174
- try {
1175
- const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/versions/`, {
1176
- credentials: 'include',
1177
- headers: { 'Accept': 'application/json' },
1178
- });
1179
- if (response.ok) {
1180
- const data = await response.json();
1181
- // Handle paginated response (results array) or direct array
1182
- this.versions = Array.isArray(data) ? data : (data.results || []);
1183
- // Select the active version by default
1184
- const activeVersion = this.versions.find(v => v.is_active);
1185
- if (activeVersion) {
1186
- this.selectedVersionId = activeVersion.id;
1187
- } else if (this.versions.length > 0) {
1188
- this.selectedVersionId = this.versions[0].id;
1189
- }
1190
- }
1191
- } catch (error) {
1192
- console.error('Error loading versions:', error);
1193
- }
1194
- },
1195
-
1196
- async onAgentChange() {
1197
- // Update the agentConfig for chat widgets
1198
- const agent = this.selectedAgent;
1199
- if (agent) {
1200
- this.agentConfig.agentId = agent.id;
1201
- this.agentConfig.agentSlug = agent.slug;
1202
- // Update URL without reload
1203
- window.history.replaceState({}, '', `/studio/agents/${agent.id}/`);
1204
- } else {
1205
- this.agentConfig.agentId = null;
1206
- this.agentConfig.agentSlug = null;
1207
- window.history.replaceState({}, '', '/studio/agents/new/');
1208
- }
1209
-
1210
- // Load versions for the new agent
1211
- await this.loadVersions();
1212
-
1213
- // Reinitialize chat widgets
1214
- this.refreshTestAgent();
1215
- this.refreshBuilderChat();
1216
- },
1217
-
1218
- async onVersionChange() {
1219
- // Version change might affect the test agent behavior
1220
- // For now, just refresh the test chat
1221
- this.refreshTestAgent();
1222
- },
1223
-
1224
- async activateVersion() {
1225
- if (!this.selectedAgentId || !this.selectedVersionId) return;
1226
-
1227
- try {
1228
- const response = await fetch(
1229
- `/studio/api/agents/${this.selectedAgentId}/versions/${this.selectedVersionId}/activate/`,
1230
- {
1231
- method: 'POST',
1232
- credentials: 'include',
1233
- headers: {
1234
- 'Content-Type': 'application/json',
1235
- 'X-CSRFToken': this.getCsrfToken(),
1236
- },
1237
- }
1238
- );
1239
- if (response.ok) {
1240
- await this.loadVersions();
1241
- this.refreshTestAgent();
1242
- }
1243
- } catch (error) {
1244
- console.error('Error activating version:', error);
1245
- }
1246
- },
1247
-
1248
- async createNewAgent() {
1249
- const name = prompt('Enter a name for the new agent:');
1250
- if (!name) return;
1251
-
1252
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
1253
-
1254
- try {
1255
- const response = await fetch('/studio/api/agents/', {
1256
- method: 'POST',
1257
- credentials: 'include',
1258
- headers: {
1259
- 'Content-Type': 'application/json',
1260
- 'X-CSRFToken': this.getCsrfToken(),
1261
- },
1262
- body: JSON.stringify({ name, slug }),
1263
- });
1264
-
1265
- if (response.ok) {
1266
- const newAgent = await response.json();
1267
- await this.loadAgents();
1268
- this.selectedAgentId = newAgent.id;
1269
- await this.onAgentChange();
1270
- } else {
1271
- const error = await response.json();
1272
- alert(`Failed to create agent: ${error.detail || error.slug || JSON.stringify(error)}`);
1273
- }
1274
- } catch (error) {
1275
- console.error('Error creating agent:', error);
1276
- alert('Failed to create agent');
1277
- }
1278
- },
1279
-
1280
- refreshTestAgent() {
1281
- const agent = this.selectedAgent;
1282
- initTestChat(agent ? agent.slug : null, this.testAuthMode, this.anonymousToken);
1283
- },
1284
-
1285
- async setTestAuthMode(mode) {
1286
- this.testAuthMode = mode;
1287
-
1288
- // If switching to anonymous and we don't have a token, create one
1289
- if (mode === 'anonymous' && !this.anonymousToken) {
1290
- try {
1291
- const response = await fetch('/api/accounts/anonymous-session/', {
1292
- method: 'POST',
1293
- headers: { 'Content-Type': 'application/json' },
1294
- });
1295
- if (response.ok) {
1296
- const data = await response.json();
1297
- this.anonymousToken = data.token;
1298
- }
1299
- } catch (e) {
1300
- console.warn('Failed to create anonymous session:', e);
1301
- }
1302
- }
1303
-
1304
- // Refresh the test chat with new auth mode
1305
- this.refreshTestAgent();
1306
- },
1307
-
1308
- refreshBuilderChat() {
1309
- initBuilderChat(this.selectedAgentId, this);
1310
- },
1311
-
1312
- async openSchemaEditor() {
1313
- if (!this.selectedAgentId) return;
1314
- this.schemaEditorOpen = true;
1315
- await this.reloadSchema();
1316
- },
1317
-
1318
- closeSchemaEditor() {
1319
- this.schemaEditorOpen = false;
1320
- this.schemaError = null;
1321
- this.schemaSaved = false;
1322
- this.schemaModified = false;
1323
- },
1324
-
1325
- async reloadSchema() {
1326
- if (!this.selectedAgentId) return;
1327
-
1328
- this.schemaLoading = true;
1329
- this.schemaError = null;
1330
- this.schemaSaved = false;
1331
- this.schemaModified = false;
1332
-
1333
- try {
1334
- const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/full-schema/`, {
1335
- credentials: 'include',
1336
- headers: {
1337
- 'Accept': 'application/json',
1338
- },
1339
- });
1340
-
1341
- if (!response.ok) {
1342
- throw new Error(`Failed to load schema: ${response.status}`);
1343
- }
1344
-
1345
- this.fullSchema = await response.json();
1346
- this.schemaLoadedAt = new Date().toLocaleTimeString();
1347
-
1348
- // Update individual JSON views
1349
- this.updateJsonViews();
1350
-
1351
- } catch (error) {
1352
- console.error('Error loading schema:', error);
1353
- this.schemaError = error.message;
1354
- } finally {
1355
- this.schemaLoading = false;
1356
- }
1357
- },
1358
-
1359
- updateJsonViews() {
1360
- if (!this.fullSchema) return;
1361
-
1362
- this.schemaJson = JSON.stringify(this.fullSchema, null, 2);
1363
- this.versionJson = JSON.stringify(this.fullSchema.version || {}, null, 2);
1364
- this.toolsJson = JSON.stringify(this.fullSchema.tools || [], null, 2);
1365
- this.dynamicToolsJson = JSON.stringify(this.fullSchema.dynamic_tools || [], null, 2);
1366
- this.subAgentToolsJson = JSON.stringify(this.fullSchema.sub_agent_tools || [], null, 2);
1367
- this.knowledgeJson = JSON.stringify(this.fullSchema.knowledge || [], null, 2);
1368
- this.ragConfigJson = JSON.stringify(this.fullSchema.rag_config || {}, null, 2);
1369
- this.functionsJson = JSON.stringify(this.fullSchema.available_functions || [], null, 2);
1370
- },
1371
-
1372
- validateSchema() {
1373
- this.schemaModified = true;
1374
- this.schemaSaved = false;
1375
- this.schemaError = null;
1376
-
1377
- try {
1378
- // Validate current tab's JSON
1379
- switch (this.schemaTab) {
1380
- case 'full':
1381
- JSON.parse(this.schemaJson);
1382
- break;
1383
- case 'version':
1384
- JSON.parse(this.versionJson);
1385
- break;
1386
- case 'tools':
1387
- JSON.parse(this.toolsJson);
1388
- break;
1389
- case 'dynamic_tools':
1390
- JSON.parse(this.dynamicToolsJson);
1391
- break;
1392
- case 'sub_agents':
1393
- JSON.parse(this.subAgentToolsJson);
1394
- break;
1395
- case 'knowledge':
1396
- JSON.parse(this.knowledgeJson);
1397
- break;
1398
- case 'rag':
1399
- JSON.parse(this.ragConfigJson);
1400
- break;
1401
- }
1402
- } catch (e) {
1403
- this.schemaError = `Invalid JSON: ${e.message}`;
1404
- }
1405
- },
1406
-
1407
- async saveSchema() {
1408
- if (this.schemaError || this.schemaTab === 'functions') return;
1409
-
1410
- this.schemaLoading = true;
1411
- this.schemaError = null;
1412
-
1413
- try {
1414
- // Build the update payload based on current tab
1415
- let payload = {};
1416
-
1417
- switch (this.schemaTab) {
1418
- case 'full':
1419
- payload = JSON.parse(this.schemaJson);
1420
- break;
1421
- case 'version':
1422
- payload = { version: JSON.parse(this.versionJson) };
1423
- break;
1424
- case 'tools':
1425
- payload = { tools: JSON.parse(this.toolsJson) };
1426
- break;
1427
- case 'dynamic_tools':
1428
- payload = { dynamic_tools: JSON.parse(this.dynamicToolsJson) };
1429
- break;
1430
- case 'sub_agents':
1431
- payload = { sub_agent_tools: JSON.parse(this.subAgentToolsJson) };
1432
- break;
1433
- case 'knowledge':
1434
- payload = { knowledge: JSON.parse(this.knowledgeJson) };
1435
- break;
1436
- case 'rag':
1437
- payload = { rag_config: JSON.parse(this.ragConfigJson) };
1438
- break;
1439
- }
1440
-
1441
- const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/full-schema/`, {
1442
- method: 'PUT',
1443
- credentials: 'include',
1444
- headers: {
1445
- 'Content-Type': 'application/json',
1446
- 'X-CSRFToken': this.getCsrfToken(),
1447
- },
1448
- body: JSON.stringify(payload),
1449
- });
1450
-
1451
- if (!response.ok) {
1452
- const error = await response.json();
1453
- throw new Error(error.detail || error.message || `Failed to save: ${response.status}`);
1454
- }
1455
-
1456
- this.schemaSaved = true;
1457
- this.schemaModified = false;
1458
-
1459
- // Reload to get the updated schema
1460
- setTimeout(() => {
1461
- this.reloadSchema();
1462
- }, 1000);
1463
-
1464
- } catch (error) {
1465
- console.error('Error saving schema:', error);
1466
- this.schemaError = error.message;
1467
- } finally {
1468
- this.schemaLoading = false;
1469
- }
1470
- },
1471
-
1472
- async copySchema() {
1473
- let textToCopy = '';
1474
- switch (this.schemaTab) {
1475
- case 'full':
1476
- textToCopy = this.schemaJson;
1477
- break;
1478
- case 'version':
1479
- textToCopy = this.versionJson;
1480
- break;
1481
- case 'tools':
1482
- textToCopy = this.toolsJson;
1483
- break;
1484
- case 'dynamic_tools':
1485
- textToCopy = this.dynamicToolsJson;
1486
- break;
1487
- case 'sub_agents':
1488
- textToCopy = this.subAgentToolsJson;
1489
- break;
1490
- case 'knowledge':
1491
- textToCopy = this.knowledgeJson;
1492
- break;
1493
- case 'rag':
1494
- textToCopy = this.ragConfigJson;
1495
- break;
1496
- case 'functions':
1497
- textToCopy = this.functionsJson;
1498
- break;
1499
- }
1500
-
1501
- try {
1502
- await navigator.clipboard.writeText(textToCopy);
1503
- // Could show a toast here
1504
- } catch (e) {
1505
- console.error('Failed to copy:', e);
1506
- }
1507
- },
1508
-
1509
- // ==========================================================================
1510
- // Delete Agent Methods
1511
- // ==========================================================================
1512
-
1513
- async deleteAgent() {
1514
- if (!this.selectedAgentId) return;
1515
- const agent = this.selectedAgent;
1516
- if (!agent) return;
1517
-
1518
- this.deleteConfirmMessage = `Are you sure you want to delete "${agent.name}"? This action cannot be undone.`;
1519
- this.deleteConfirmCallback = async () => {
1520
- try {
1521
- const response = await fetch(`/studio/api/agents/${this.selectedAgentId}/`, {
1522
- method: 'DELETE',
1523
- credentials: 'include',
1524
- headers: {
1525
- 'X-CSRFToken': this.getCsrfToken(),
1526
- },
1527
- });
1528
-
1529
- if (response.ok || response.status === 204) {
1530
- this.selectedAgentId = '';
1531
- this.agentConfig.agentId = null;
1532
- this.agentConfig.agentSlug = null;
1533
- window.history.replaceState({}, '', '/studio/agents/new/');
1534
- await this.loadAgents();
1535
- this.refreshTestAgent();
1536
- this.refreshBuilderChat();
1537
- } else {
1538
- const error = await response.json();
1539
- alert(`Failed to delete agent: ${error.detail || JSON.stringify(error)}`);
1540
- }
1541
- } catch (error) {
1542
- console.error('Error deleting agent:', error);
1543
- alert('Failed to delete agent');
1544
- }
1545
- };
1546
- this.deleteConfirmOpen = true;
1547
- },
1548
-
1549
- // ==========================================================================
1550
- // Systems Management Methods
1551
- // ==========================================================================
1552
-
1553
- async loadSystems() {
1554
- this.systemsLoading = true;
1555
- try {
1556
- const response = await fetch('/studio/api/systems/', {
1557
- credentials: 'include',
1558
- headers: { 'Accept': 'application/json' },
1559
- });
1560
- if (response.ok) {
1561
- const data = await response.json();
1562
- this.systems = Array.isArray(data) ? data : (data.results || []);
1563
- }
1564
- } catch (error) {
1565
- console.error('Error loading systems:', error);
1566
- } finally {
1567
- this.systemsLoading = false;
1568
- }
1569
- },
1570
-
1571
- onSystemChange() {
1572
- // Could filter agents by system or show system-specific info
1573
- console.log('Selected system:', this.selectedSystemId);
1574
- },
1575
-
1576
- openSystemsModal() {
1577
- this.systemsModalOpen = true;
1578
- this.loadSystems();
1579
- },
1580
-
1581
- closeSystemsModal() {
1582
- this.systemsModalOpen = false;
1583
- this.expandedSystemId = null;
1584
- },
1585
-
1586
- async createSystem() {
1587
- if (!this.newSystemName || !this.newSystemSlug) return;
1588
-
1589
- try {
1590
- const payload = {
1591
- name: this.newSystemName,
1592
- slug: this.newSystemSlug,
1593
- };
1594
- if (this.newSystemEntryAgent) {
1595
- payload.entry_agent = this.newSystemEntryAgent;
1596
- }
1597
-
1598
- const response = await fetch('/studio/api/systems/', {
1599
- method: 'POST',
1600
- credentials: 'include',
1601
- headers: {
1602
- 'Content-Type': 'application/json',
1603
- 'X-CSRFToken': this.getCsrfToken(),
1604
- },
1605
- body: JSON.stringify(payload),
1606
- });
1607
-
1608
- if (response.ok) {
1609
- this.newSystemName = '';
1610
- this.newSystemSlug = '';
1611
- this.newSystemEntryAgent = '';
1612
- await this.loadSystems();
1613
- } else {
1614
- const error = await response.json();
1615
- alert(`Failed to create system: ${error.detail || error.slug || JSON.stringify(error)}`);
1616
- }
1617
- } catch (error) {
1618
- console.error('Error creating system:', error);
1619
- alert('Failed to create system');
1620
- }
1621
- },
1622
-
1623
- viewSystemDetails(system) {
1624
- if (this.expandedSystemId === system.id) {
1625
- this.expandedSystemId = null;
1626
- } else {
1627
- this.expandedSystemId = system.id;
1628
- this.loadSystemMembers(system);
1629
- }
1630
- },
1631
-
1632
- async loadSystemMembers(system) {
1633
- try {
1634
- const response = await fetch(`/studio/api/systems/${system.id}/members/`, {
1635
- credentials: 'include',
1636
- headers: { 'Accept': 'application/json' },
1637
- });
1638
- if (response.ok) {
1639
- const data = await response.json();
1640
- const members = Array.isArray(data) ? data : (data.results || []);
1641
- // Update the system in our list with members
1642
- const idx = this.systems.findIndex(s => s.id === system.id);
1643
- if (idx !== -1) {
1644
- this.systems[idx] = { ...this.systems[idx], members };
1645
- }
1646
- }
1647
- } catch (error) {
1648
- console.error('Error loading system members:', error);
1649
- }
1650
- },
1651
-
1652
- async publishSystem(system) {
1653
- try {
1654
- const response = await fetch(`/studio/api/systems/${system.id}/publish/`, {
1655
- method: 'POST',
1656
- credentials: 'include',
1657
- headers: {
1658
- 'Content-Type': 'application/json',
1659
- 'X-CSRFToken': this.getCsrfToken(),
1660
- },
1661
- });
1662
-
1663
- if (response.ok) {
1664
- alert('System version published successfully!');
1665
- await this.loadSystems();
1666
- } else {
1667
- const error = await response.json();
1668
- alert(`Failed to publish: ${error.detail || JSON.stringify(error)}`);
1669
- }
1670
- } catch (error) {
1671
- console.error('Error publishing system:', error);
1672
- alert('Failed to publish system');
1673
- }
1674
- },
1675
-
1676
- async deleteSystem(system) {
1677
- this.deleteConfirmMessage = `Are you sure you want to delete system "${system.name}"? This action cannot be undone.`;
1678
- this.deleteConfirmCallback = async () => {
1679
- try {
1680
- const response = await fetch(`/studio/api/systems/${system.id}/`, {
1681
- method: 'DELETE',
1682
- credentials: 'include',
1683
- headers: {
1684
- 'X-CSRFToken': this.getCsrfToken(),
1685
- },
1686
- });
1687
-
1688
- if (response.ok || response.status === 204) {
1689
- if (this.selectedSystemId === system.id) {
1690
- this.selectedSystemId = '';
1691
- }
1692
- await this.loadSystems();
1693
- } else {
1694
- const error = await response.json();
1695
- alert(`Failed to delete system: ${error.detail || JSON.stringify(error)}`);
1696
- }
1697
- } catch (error) {
1698
- console.error('Error deleting system:', error);
1699
- alert('Failed to delete system');
1700
- }
1701
- };
1702
- this.deleteConfirmOpen = true;
1703
- },
1704
-
1705
- async addMemberToSystem(system) {
1706
- // Simple prompt for now - could be a modal
1707
- const agentId = prompt('Enter the agent ID to add:');
1708
- if (!agentId) return;
1709
-
1710
- try {
1711
- const response = await fetch(`/studio/api/systems/${system.id}/members/`, {
1712
- method: 'POST',
1713
- credentials: 'include',
1714
- headers: {
1715
- 'Content-Type': 'application/json',
1716
- 'X-CSRFToken': this.getCsrfToken(),
1717
- },
1718
- body: JSON.stringify({ agent: agentId }),
1719
- });
1720
-
1721
- if (response.ok) {
1722
- await this.loadSystemMembers(system);
1723
- } else {
1724
- const error = await response.json();
1725
- alert(`Failed to add member: ${error.detail || JSON.stringify(error)}`);
1726
- }
1727
- } catch (error) {
1728
- console.error('Error adding member:', error);
1729
- alert('Failed to add member');
1730
- }
1731
- },
1732
-
1733
- async removeMemberFromSystem(system, member) {
1734
- try {
1735
- const response = await fetch(`/studio/api/systems/${system.id}/members/${member.id}/`, {
1736
- method: 'DELETE',
1737
- credentials: 'include',
1738
- headers: {
1739
- 'X-CSRFToken': this.getCsrfToken(),
1740
- },
1741
- });
1742
-
1743
- if (response.ok || response.status === 204) {
1744
- await this.loadSystemMembers(system);
1745
- } else {
1746
- const error = await response.json();
1747
- alert(`Failed to remove member: ${error.detail || JSON.stringify(error)}`);
1748
- }
1749
- } catch (error) {
1750
- console.error('Error removing member:', error);
1751
- alert('Failed to remove member');
1752
- }
1753
- },
1754
-
1755
- // ==========================================================================
1756
- // Delete Confirmation Methods
1757
- // ==========================================================================
1758
-
1759
- cancelDelete() {
1760
- this.deleteConfirmOpen = false;
1761
- this.deleteConfirmMessage = '';
1762
- this.deleteConfirmCallback = null;
1763
- },
1764
-
1765
- async confirmDelete() {
1766
- if (this.deleteConfirmCallback) {
1767
- await this.deleteConfirmCallback();
1768
- }
1769
- this.cancelDelete();
1770
- },
1771
-
1772
- getCsrfToken() {
1773
- const name = 'csrftoken';
1774
- let cookieValue = null;
1775
- if (document.cookie && document.cookie !== '') {
1776
- const cookies = document.cookie.split(';');
1777
- for (let i = 0; i < cookies.length; i++) {
1778
- const cookie = cookies[i].trim();
1779
- if (cookie.substring(0, name.length + 1) === (name + '=')) {
1780
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
1781
- break;
1782
- }
1783
- }
1784
- }
1785
- return cookieValue;
1786
- },
1787
-
1788
- // ==========================================================================
1789
- // Spec Document Methods
1790
- // ==========================================================================
1791
-
1792
- openSpecDocuments() {
1793
- this.specDocsModalOpen = true;
1794
- this.loadSpecTree();
1795
- },
1796
-
1797
- closeSpecDocuments() {
1798
- this.specDocsModalOpen = false;
1799
- this.selectedSpecId = null;
1800
- this.specDocModified = false;
1801
- },
1802
-
1803
- async loadSpecTree() {
1804
- this.specDocsLoading = true;
1805
- try {
1806
- const response = await fetch('/studio/api/spec-documents/tree/', {
1807
- credentials: 'include',
1808
- headers: { 'Accept': 'application/json' },
1809
- });
1810
- if (response.ok) {
1811
- const data = await response.json();
1812
- this.specTree = data.tree || [];
1813
- }
1814
- } catch (error) {
1815
- console.error('Error loading spec tree:', error);
1816
- } finally {
1817
- this.specDocsLoading = false;
1818
- }
1819
- },
1820
-
1821
- async selectSpecDocument(docId) {
1822
- if (this.specDocModified) {
1823
- if (!confirm('You have unsaved changes. Discard them?')) {
1824
- return;
1825
- }
1826
- }
1827
-
1828
- this.selectedSpecId = docId;
1829
- this.specDocModified = false;
1830
- this.specDocSaved = false;
1831
-
1832
- try {
1833
- const response = await fetch(`/studio/api/spec-documents/${docId}/`, {
1834
- credentials: 'include',
1835
- headers: { 'Accept': 'application/json' },
1836
- });
1837
- if (response.ok) {
1838
- const doc = await response.json();
1839
- this.specDocTitle = doc.title;
1840
- this.specDocContent = doc.content;
1841
- this.specDocVersion = doc.current_version;
1842
- this.specDocPath = doc.full_path;
1843
- this.specDocLinkedAgent = doc.linked_agent ? doc.linked_agent.id : '';
1844
- }
1845
- } catch (error) {
1846
- console.error('Error loading spec document:', error);
1847
- }
1848
- },
1849
-
1850
- async createRootDocument() {
1851
- const title = prompt('Enter document title:');
1852
- if (!title) return;
1853
-
1854
- try {
1855
- const response = await fetch('/studio/api/spec-documents/', {
1856
- method: 'POST',
1857
- credentials: 'include',
1858
- headers: {
1859
- 'Content-Type': 'application/json',
1860
- 'X-CSRFToken': this.getCsrfToken(),
1861
- },
1862
- body: JSON.stringify({ title, content: '' }),
1863
- });
1864
-
1865
- if (response.ok) {
1866
- const doc = await response.json();
1867
- await this.loadSpecTree();
1868
- this.selectSpecDocument(doc.id);
1869
- } else {
1870
- const error = await response.json();
1871
- alert(`Failed to create document: ${error.error || JSON.stringify(error)}`);
1872
- }
1873
- } catch (error) {
1874
- console.error('Error creating document:', error);
1875
- alert('Failed to create document');
1876
- }
1877
- },
1878
-
1879
- async addChildDocument(parentId) {
1880
- const title = prompt('Enter child document title:');
1881
- if (!title) return;
1882
-
1883
- try {
1884
- const response = await fetch('/studio/api/spec-documents/', {
1885
- method: 'POST',
1886
- credentials: 'include',
1887
- headers: {
1888
- 'Content-Type': 'application/json',
1889
- 'X-CSRFToken': this.getCsrfToken(),
1890
- },
1891
- body: JSON.stringify({ title, content: '', parent_id: parentId }),
1892
- });
1893
-
1894
- if (response.ok) {
1895
- const doc = await response.json();
1896
- await this.loadSpecTree();
1897
- this.selectSpecDocument(doc.id);
1898
- } else {
1899
- const error = await response.json();
1900
- alert(`Failed to create document: ${error.error || JSON.stringify(error)}`);
1901
- }
1902
- } catch (error) {
1903
- console.error('Error creating document:', error);
1904
- alert('Failed to create document');
1905
- }
1906
- },
1907
-
1908
- markSpecModified() {
1909
- this.specDocModified = true;
1910
- this.specDocSaved = false;
1911
- },
1912
-
1913
- async updateSpecTitle() {
1914
- if (!this.selectedSpecId || !this.specDocTitle) return;
1915
-
1916
- try {
1917
- await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/`, {
1918
- method: 'PUT',
1919
- credentials: 'include',
1920
- headers: {
1921
- 'Content-Type': 'application/json',
1922
- 'X-CSRFToken': this.getCsrfToken(),
1923
- },
1924
- body: JSON.stringify({ title: this.specDocTitle }),
1925
- });
1926
- await this.loadSpecTree();
1927
- } catch (error) {
1928
- console.error('Error updating title:', error);
1929
- }
1930
- },
1931
-
1932
- async saveSpecDocument() {
1933
- if (!this.selectedSpecId) return;
1934
-
1935
- try {
1936
- const response = await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/`, {
1937
- method: 'PUT',
1938
- credentials: 'include',
1939
- headers: {
1940
- 'Content-Type': 'application/json',
1941
- 'X-CSRFToken': this.getCsrfToken(),
1942
- },
1943
- body: JSON.stringify({
1944
- title: this.specDocTitle,
1945
- content: this.specDocContent,
1946
- }),
1947
- });
1948
-
1949
- if (response.ok) {
1950
- const data = await response.json();
1951
- this.specDocVersion = data.current_version;
1952
- this.specDocModified = false;
1953
- this.specDocSaved = true;
1954
- setTimeout(() => { this.specDocSaved = false; }, 2000);
1955
- } else {
1956
- const error = await response.json();
1957
- alert(`Failed to save: ${error.error || JSON.stringify(error)}`);
1958
- }
1959
- } catch (error) {
1960
- console.error('Error saving document:', error);
1961
- alert('Failed to save document');
1962
- }
1963
- },
1964
-
1965
- async linkSpecToAgent() {
1966
- if (!this.selectedSpecId) return;
1967
-
1968
- if (this.specDocLinkedAgent) {
1969
- try {
1970
- await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/link/`, {
1971
- method: 'POST',
1972
- credentials: 'include',
1973
- headers: {
1974
- 'Content-Type': 'application/json',
1975
- 'X-CSRFToken': this.getCsrfToken(),
1976
- },
1977
- body: JSON.stringify({ agent_id: this.specDocLinkedAgent }),
1978
- });
1979
- await this.loadSpecTree();
1980
- } catch (error) {
1981
- console.error('Error linking to agent:', error);
1982
- }
1983
- } else {
1984
- try {
1985
- await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/link/`, {
1986
- method: 'DELETE',
1987
- credentials: 'include',
1988
- headers: {
1989
- 'X-CSRFToken': this.getCsrfToken(),
1990
- },
1991
- });
1992
- await this.loadSpecTree();
1993
- } catch (error) {
1994
- console.error('Error unlinking from agent:', error);
1995
- }
1996
- }
1997
- },
1998
-
1999
- async deleteSpecDocument() {
2000
- if (!this.selectedSpecId) return;
2001
- if (!confirm('Are you sure you want to delete this document? This will also delete all child documents.')) {
2002
- return;
2003
- }
2004
-
2005
- try {
2006
- const response = await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/`, {
2007
- method: 'DELETE',
2008
- credentials: 'include',
2009
- headers: {
2010
- 'X-CSRFToken': this.getCsrfToken(),
2011
- },
2012
- });
2013
-
2014
- if (response.ok) {
2015
- this.selectedSpecId = null;
2016
- this.specDocTitle = '';
2017
- this.specDocContent = '';
2018
- await this.loadSpecTree();
2019
- } else {
2020
- const error = await response.json();
2021
- alert(`Failed to delete: ${error.error || JSON.stringify(error)}`);
2022
- }
2023
- } catch (error) {
2024
- console.error('Error deleting document:', error);
2025
- alert('Failed to delete document');
2026
- }
2027
- },
2028
-
2029
- async showSpecHistory() {
2030
- if (!this.selectedSpecId) return;
2031
-
2032
- try {
2033
- const response = await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/history/`, {
2034
- credentials: 'include',
2035
- headers: { 'Accept': 'application/json' },
2036
- });
2037
- if (response.ok) {
2038
- const data = await response.json();
2039
- this.specHistory = data.versions || [];
2040
- this.specHistoryOpen = true;
2041
- }
2042
- } catch (error) {
2043
- console.error('Error loading history:', error);
2044
- }
2045
- },
2046
-
2047
- async restoreSpecVersion(versionNumber) {
2048
- if (!confirm(`Restore to version ${versionNumber}? This will create a new version with the old content.`)) {
2049
- return;
2050
- }
2051
-
2052
- try {
2053
- const response = await fetch(`/studio/api/spec-documents/${this.selectedSpecId}/restore/`, {
2054
- method: 'POST',
2055
- credentials: 'include',
2056
- headers: {
2057
- 'Content-Type': 'application/json',
2058
- 'X-CSRFToken': this.getCsrfToken(),
2059
- },
2060
- body: JSON.stringify({ version_number: versionNumber }),
2061
- });
2062
-
2063
- if (response.ok) {
2064
- this.specHistoryOpen = false;
2065
- await this.selectSpecDocument(this.selectedSpecId);
2066
- } else {
2067
- const error = await response.json();
2068
- alert(`Failed to restore: ${error.error || JSON.stringify(error)}`);
2069
- }
2070
- } catch (error) {
2071
- console.error('Error restoring version:', error);
2072
- alert('Failed to restore version');
2073
- }
2074
- },
2075
-
2076
- async renderFullSpec() {
2077
- try {
2078
- const response = await fetch('/studio/api/spec-documents/render/', {
2079
- credentials: 'include',
2080
- headers: { 'Accept': 'application/json' },
2081
- });
2082
- if (response.ok) {
2083
- const data = await response.json();
2084
- this.fullRenderedSpec = data.markdown;
2085
- this.renderedSpecModalOpen = true;
2086
- }
2087
- } catch (error) {
2088
- console.error('Error rendering spec:', error);
2089
- }
2090
- },
2091
-
2092
- async copyRenderedSpec() {
2093
- try {
2094
- await navigator.clipboard.writeText(this.fullRenderedSpec);
2095
- } catch (e) {
2096
- console.error('Failed to copy:', e);
2097
- }
2098
- },
2099
-
2100
- formatDate(isoString) {
2101
- return new Date(isoString).toLocaleString();
2102
- },
2103
- },
2104
- async mounted() {
2105
- // Load agents and systems lists
2106
- await Promise.all([
2107
- this.loadAgents(),
2108
- this.loadSystems(),
2109
- ]);
2110
-
2111
- // Auto-select first agent if none selected and agents exist
2112
- if (!this.selectedAgentId && this.agents.length > 0) {
2113
- this.selectedAgentId = this.agents[0].id;
2114
- await this.onAgentChange();
2115
- }
2116
-
2117
- // Initialize chat widgets with current selection
2118
- this.refreshTestAgent();
2119
- this.refreshBuilderChat();
2120
- }
2121
- });
2122
-
2123
- // Register the tree node component
2124
- app.component('spec-tree-node', SpecTreeNode);
2125
-
2126
- // Use PrimeVue and mount
2127
- app.use(primevue.config.default).mount('#app');
2128
- </script>
2129
- {% endblock %}
32
+ </script>
2130
33
 
34
+ <!-- Built Vue app -->
35
+ <script src="{% static 'django_agent_studio/js/builder.js' %}"></script>
36
+ </body>
37
+ </html>