django-agent-studio 0.1.8__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.
- django_agent_studio/agents/builder.py +824 -24
- django_agent_studio/agents/dynamic.py +350 -40
- django_agent_studio/api/urls.py +7 -0
- django_agent_studio/api/views.py +133 -0
- django_agent_studio/static/agent-frontend/chat-widget.css +222 -0
- django_agent_studio/static/agent-frontend/chat-widget.js +182 -126
- django_agent_studio/static/django_agent_studio/js/builder.js +247 -0
- django_agent_studio/static/django_agent_studio/js/builder.js.map +1 -0
- django_agent_studio/static/django_agent_studio/js/style.css +1 -0
- django_agent_studio/templates/django_agent_studio/base.html +1 -1
- django_agent_studio/templates/django_agent_studio/builder.html +35 -2128
- {django_agent_studio-0.1.8.dist-info → django_agent_studio-0.2.0.dist-info}/METADATA +95 -2
- {django_agent_studio-0.1.8.dist-info → django_agent_studio-0.2.0.dist-info}/RECORD +16 -12
- django_agent_studio-0.2.0.dist-info/licenses/LICENSE +83 -0
- {django_agent_studio-0.1.8.dist-info → django_agent_studio-0.2.0.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.1.8.dist-info → django_agent_studio-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,2130 +1,37 @@
|
|
|
1
|
-
{%
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{%
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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: `
|
|
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
|
-
`,
|
|
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>
|