django-agent-studio 0.3.0__py3-none-any.whl → 0.3.2__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/dynamic.py +2 -3
- django_agent_studio/api/serializers.py +139 -0
- django_agent_studio/api/urls.py +35 -0
- django_agent_studio/api/views.py +470 -8
- django_agent_studio/templates/django_agent_studio/agent_list.html +12 -5
- django_agent_studio/templates/django_agent_studio/builder.html +2 -2
- django_agent_studio/templates/django_agent_studio/collaborators.html +418 -0
- django_agent_studio/templates/django_agent_studio/home.html +21 -12
- django_agent_studio/templates/django_agent_studio/system_create.html +148 -0
- django_agent_studio/templates/django_agent_studio/system_list.html +15 -3
- django_agent_studio/templates/django_agent_studio/system_test.html +19 -17
- django_agent_studio/templates/django_agent_studio/test.html +9 -4
- django_agent_studio/urls.py +3 -0
- django_agent_studio/views.py +306 -15
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/METADATA +3 -1
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/RECORD +19 -17
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {django_agent_studio-0.3.0.dist-info → django_agent_studio-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
{% extends "django_agent_studio/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Collaborators - {{ object_name }} - 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
|
+
{% if object_type == 'agent' %}
|
|
9
|
+
<a href="{% url 'agent_studio:agent_list' %}" class="text-gray-500 hover:text-gray-700">My Agents</a>
|
|
10
|
+
<span class="text-gray-400">/</span>
|
|
11
|
+
<a href="{% url 'agent_studio:agent_edit' object.id %}" class="text-gray-500 hover:text-gray-700">{{ object_name }}</a>
|
|
12
|
+
{% else %}
|
|
13
|
+
<a href="{% url 'agent_studio:system_list' %}" class="text-gray-500 hover:text-gray-700">My Systems</a>
|
|
14
|
+
<span class="text-gray-400">/</span>
|
|
15
|
+
<a href="{% url 'agent_studio:system_test' object.id %}" class="text-gray-500 hover:text-gray-700">{{ object_name }}</a>
|
|
16
|
+
{% endif %}
|
|
17
|
+
<span class="text-gray-400">/</span>
|
|
18
|
+
<span class="text-gray-600">Collaborators</span>
|
|
19
|
+
</nav>
|
|
20
|
+
{% endblock %}
|
|
21
|
+
|
|
22
|
+
{% block content %}
|
|
23
|
+
<div class="h-full p-6 overflow-auto">
|
|
24
|
+
<div class="max-w-4xl mx-auto">
|
|
25
|
+
<!-- Header -->
|
|
26
|
+
<div class="flex items-center justify-between mb-6">
|
|
27
|
+
<div>
|
|
28
|
+
<h1 class="text-2xl font-bold text-gray-900">👥 Collaborators</h1>
|
|
29
|
+
<p class="text-gray-500 mt-1">Manage who can access {{ object_name }}</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{% if object_type == 'system' and member_agents %}
|
|
34
|
+
<!-- System: Show included agents -->
|
|
35
|
+
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
36
|
+
<h3 class="font-semibold text-blue-800 mb-2">
|
|
37
|
+
<i class="pi pi-info-circle mr-2"></i>Included Agents
|
|
38
|
+
</h3>
|
|
39
|
+
<p class="text-sm text-blue-700 mb-3">
|
|
40
|
+
Collaborators on this system automatically get access to all agents below:
|
|
41
|
+
</p>
|
|
42
|
+
<div class="flex flex-wrap gap-2">
|
|
43
|
+
{% for agent in member_agents %}
|
|
44
|
+
<a href="{% url 'agent_studio:agent_collaborators' agent.id %}"
|
|
45
|
+
class="inline-flex items-center px-3 py-1.5 bg-white border border-blue-200 rounded-lg text-sm text-blue-700 hover:bg-blue-100 transition-colors">
|
|
46
|
+
<i class="pi pi-android mr-1.5"></i>
|
|
47
|
+
{{ agent.name }}
|
|
48
|
+
<span class="ml-1.5 text-xs text-blue-500">({{ agent.role }})</span>
|
|
49
|
+
</a>
|
|
50
|
+
{% endfor %}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
{% endif %}
|
|
54
|
+
|
|
55
|
+
{% if object_type == 'agent' and parent_systems %}
|
|
56
|
+
<!-- Agent: Show parent systems -->
|
|
57
|
+
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
|
|
58
|
+
<h3 class="font-semibold text-amber-800 mb-2">
|
|
59
|
+
<i class="pi pi-info-circle mr-2"></i>Inherited Access
|
|
60
|
+
</h3>
|
|
61
|
+
<p class="text-sm text-amber-700 mb-3">
|
|
62
|
+
This agent is part of the following system(s). Users with system access can also access this agent:
|
|
63
|
+
</p>
|
|
64
|
+
<div class="flex flex-wrap gap-2">
|
|
65
|
+
{% for system in parent_systems %}
|
|
66
|
+
<a href="{% url 'agent_studio:system_collaborators' system.id %}"
|
|
67
|
+
class="inline-flex items-center px-3 py-1.5 bg-white border border-amber-200 rounded-lg text-sm text-amber-700 hover:bg-amber-100 transition-colors">
|
|
68
|
+
<i class="pi pi-sitemap mr-1.5"></i>
|
|
69
|
+
{{ system.name }}
|
|
70
|
+
<i class="pi pi-external-link ml-1.5 text-xs"></i>
|
|
71
|
+
</a>
|
|
72
|
+
{% endfor %}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
{% endif %}
|
|
76
|
+
|
|
77
|
+
<!-- Owner Info -->
|
|
78
|
+
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-6">
|
|
79
|
+
<div class="flex items-center justify-between">
|
|
80
|
+
<div class="flex items-center space-x-3">
|
|
81
|
+
<div class="w-10 h-10 bg-primary-100 rounded-full flex items-center justify-center">
|
|
82
|
+
<span class="text-primary-600 font-semibold">{{ owner_initial }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<p class="font-medium text-gray-800">{{ owner_email }}</p>
|
|
86
|
+
<p class="text-sm text-gray-500">Owner</p>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<span class="bg-primary-100 text-primary-700 px-3 py-1 rounded-full text-sm font-medium">Owner</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Add Collaborator Form -->
|
|
94
|
+
{% if can_admin %}
|
|
95
|
+
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-6">
|
|
96
|
+
<h3 class="font-semibold text-gray-800 mb-4">Add Collaborator</h3>
|
|
97
|
+
<form @submit.prevent="addCollaborator" class="flex items-end space-x-4">
|
|
98
|
+
<div class="flex-1 relative">
|
|
99
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Search User</label>
|
|
100
|
+
<input type="text" v-model="searchQuery" @input="searchUsers" @focus="showResults = true"
|
|
101
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
|
102
|
+
placeholder="Type to search by name or email..."
|
|
103
|
+
autocomplete="off">
|
|
104
|
+
<!-- Search Results Dropdown -->
|
|
105
|
+
<div v-if="showResults && (searchResults.length > 0 || searchQuery.length >= 2)"
|
|
106
|
+
class="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg max-h-60 overflow-auto">
|
|
107
|
+
<div v-if="searching" class="p-3 text-center text-gray-500">
|
|
108
|
+
<i class="pi pi-spin pi-spinner mr-2"></i>Searching...
|
|
109
|
+
</div>
|
|
110
|
+
<div v-else-if="searchResults.length === 0 && searchQuery.length >= 2" class="p-3 text-center text-gray-500">
|
|
111
|
+
No users found
|
|
112
|
+
</div>
|
|
113
|
+
<div v-else>
|
|
114
|
+
<button v-for="user in searchResults" :key="user.id" type="button"
|
|
115
|
+
@click="selectUser(user)"
|
|
116
|
+
class="w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center space-x-3 border-b border-gray-100 last:border-0">
|
|
117
|
+
<div class="w-8 h-8 bg-primary-100 rounded-full flex items-center justify-center flex-shrink-0">
|
|
118
|
+
<span class="text-primary-600 font-semibold text-sm">[[ user.name.charAt(0).toUpperCase() ]]</span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="min-w-0 flex-1">
|
|
121
|
+
<p class="font-medium text-gray-800 truncate">[[ user.name ]]</p>
|
|
122
|
+
<p class="text-sm text-gray-500 truncate">[[ user.email ]]</p>
|
|
123
|
+
</div>
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<!-- Selected User Display -->
|
|
128
|
+
<div v-if="selectedUser" class="mt-2 flex items-center space-x-2 bg-primary-50 border border-primary-200 rounded-lg px-3 py-2">
|
|
129
|
+
<div class="w-6 h-6 bg-primary-100 rounded-full flex items-center justify-center">
|
|
130
|
+
<span class="text-primary-600 font-semibold text-xs">[[ selectedUser.name.charAt(0).toUpperCase() ]]</span>
|
|
131
|
+
</div>
|
|
132
|
+
<span class="text-sm text-primary-800 flex-1">[[ selectedUser.display ]]</span>
|
|
133
|
+
<button type="button" @click="clearSelection" class="text-primary-600 hover:text-primary-800">
|
|
134
|
+
<i class="pi pi-times text-sm"></i>
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="w-40">
|
|
139
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Role</label>
|
|
140
|
+
<select v-model="newRole"
|
|
141
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500">
|
|
142
|
+
<option value="viewer">Viewer</option>
|
|
143
|
+
<option value="editor">Editor</option>
|
|
144
|
+
<option value="admin">Admin</option>
|
|
145
|
+
</select>
|
|
146
|
+
</div>
|
|
147
|
+
<button type="submit" :disabled="adding || !selectedUser"
|
|
148
|
+
class="bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors disabled:opacity-50">
|
|
149
|
+
<i class="pi pi-plus"></i>
|
|
150
|
+
<span>[[ adding ? 'Adding...' : 'Add' ]]</span>
|
|
151
|
+
</button>
|
|
152
|
+
</form>
|
|
153
|
+
<p v-if="addError" class="mt-2 text-sm text-red-600">[[ addError ]]</p>
|
|
154
|
+
</div>
|
|
155
|
+
{% endif %}
|
|
156
|
+
|
|
157
|
+
<!-- Collaborators List -->
|
|
158
|
+
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
|
|
159
|
+
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50">
|
|
160
|
+
<h3 class="font-semibold text-gray-800">Collaborators ([[ collaborators.length ]])</h3>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div v-if="loading" class="p-8 text-center text-gray-500">
|
|
164
|
+
<i class="pi pi-spin pi-spinner text-2xl"></i>
|
|
165
|
+
<p class="mt-2">Loading collaborators...</p>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div v-else-if="collaborators.length === 0" class="p-8 text-center text-gray-500">
|
|
169
|
+
<i class="pi pi-users text-4xl mb-2"></i>
|
|
170
|
+
<p>No collaborators yet</p>
|
|
171
|
+
<p class="text-sm">Add collaborators to share access to this {{ object_type }}</p>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div v-else class="divide-y divide-gray-100">
|
|
175
|
+
<div v-for="collab in collaborators" :key="collab.id"
|
|
176
|
+
class="p-4 flex items-center justify-between hover:bg-gray-50">
|
|
177
|
+
<div class="flex items-center space-x-3">
|
|
178
|
+
<div class="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
|
|
179
|
+
<span class="text-gray-600 font-semibold">[[ getInitial(collab.user_email) ]]</span>
|
|
180
|
+
</div>
|
|
181
|
+
<div>
|
|
182
|
+
<p class="font-medium text-gray-800">[[ collab.user_name || collab.user_email ]]</p>
|
|
183
|
+
<p class="text-sm text-gray-500">[[ collab.user_email ]]</p>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="flex items-center space-x-3">
|
|
187
|
+
{% if can_admin %}
|
|
188
|
+
<select v-model="collab.role" @change="updateRole(collab)"
|
|
189
|
+
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary-500">
|
|
190
|
+
<option value="viewer">Viewer</option>
|
|
191
|
+
<option value="editor">Editor</option>
|
|
192
|
+
<option value="admin">Admin</option>
|
|
193
|
+
</select>
|
|
194
|
+
<button @click="removeCollaborator(collab)"
|
|
195
|
+
class="text-red-600 hover:text-red-700 p-1.5 hover:bg-red-50 rounded"
|
|
196
|
+
title="Remove collaborator">
|
|
197
|
+
<i class="pi pi-trash"></i>
|
|
198
|
+
</button>
|
|
199
|
+
{% else %}
|
|
200
|
+
<span class="px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-sm">[[ collab.role_display ]]</span>
|
|
201
|
+
{% endif %}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Role Descriptions -->
|
|
208
|
+
<div class="mt-6 bg-gray-50 border border-gray-200 rounded-lg p-4">
|
|
209
|
+
<h4 class="font-medium text-gray-800 mb-3">Role Permissions</h4>
|
|
210
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
|
211
|
+
<div>
|
|
212
|
+
<p class="font-medium text-gray-700">Viewer</p>
|
|
213
|
+
<p class="text-gray-500">Can view and test the {{ object_type }}</p>
|
|
214
|
+
</div>
|
|
215
|
+
<div>
|
|
216
|
+
<p class="font-medium text-gray-700">Editor</p>
|
|
217
|
+
<p class="text-gray-500">Can view, test, and edit the {{ object_type }}</p>
|
|
218
|
+
</div>
|
|
219
|
+
<div>
|
|
220
|
+
<p class="font-medium text-gray-700">Admin</p>
|
|
221
|
+
<p class="text-gray-500">Can view, test, edit, and manage collaborators</p>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
{% if object_type == 'system' %}
|
|
225
|
+
<div class="mt-4 pt-4 border-t border-gray-200">
|
|
226
|
+
<p class="text-sm text-gray-600">
|
|
227
|
+
<i class="pi pi-info-circle mr-1"></i>
|
|
228
|
+
<strong>Note:</strong> System collaborators automatically get the same access level to all agents in this system.
|
|
229
|
+
</p>
|
|
230
|
+
</div>
|
|
231
|
+
{% endif %}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
{% endblock %}
|
|
236
|
+
|
|
237
|
+
{% block extra_js %}
|
|
238
|
+
<script>
|
|
239
|
+
const { createApp } = Vue;
|
|
240
|
+
|
|
241
|
+
const objectType = "{{ object_type }}";
|
|
242
|
+
const objectId = "{{ object.id }}";
|
|
243
|
+
const apiUrl = objectType === 'agent'
|
|
244
|
+
? `/studio/api/agents/${objectId}/collaborators/`
|
|
245
|
+
: `/studio/api/systems/${objectId}/collaborators/`;
|
|
246
|
+
|
|
247
|
+
createApp({
|
|
248
|
+
delimiters: ['[[', ']]'],
|
|
249
|
+
data() {
|
|
250
|
+
return {
|
|
251
|
+
collaborators: [],
|
|
252
|
+
loading: true,
|
|
253
|
+
// Search state
|
|
254
|
+
searchQuery: '',
|
|
255
|
+
searchResults: [],
|
|
256
|
+
selectedUser: null,
|
|
257
|
+
showResults: false,
|
|
258
|
+
searching: false,
|
|
259
|
+
searchTimeout: null,
|
|
260
|
+
// Form state
|
|
261
|
+
newRole: 'viewer',
|
|
262
|
+
adding: false,
|
|
263
|
+
addError: null,
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
methods: {
|
|
267
|
+
async loadCollaborators() {
|
|
268
|
+
this.loading = true;
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(apiUrl, {
|
|
271
|
+
headers: {
|
|
272
|
+
'Content-Type': 'application/json',
|
|
273
|
+
},
|
|
274
|
+
credentials: 'same-origin',
|
|
275
|
+
});
|
|
276
|
+
if (response.ok) {
|
|
277
|
+
const data = await response.json();
|
|
278
|
+
// Handle both paginated and non-paginated responses
|
|
279
|
+
this.collaborators = data.results !== undefined ? data.results : data;
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Failed to load collaborators:', error);
|
|
283
|
+
} finally {
|
|
284
|
+
this.loading = false;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
searchUsers() {
|
|
288
|
+
// Debounce search
|
|
289
|
+
clearTimeout(this.searchTimeout);
|
|
290
|
+
this.showResults = true;
|
|
291
|
+
|
|
292
|
+
if (this.searchQuery.length < 2) {
|
|
293
|
+
this.searchResults = [];
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.searchTimeout = setTimeout(async () => {
|
|
298
|
+
this.searching = true;
|
|
299
|
+
try {
|
|
300
|
+
const response = await fetch(`/studio/api/users/search/?q=${encodeURIComponent(this.searchQuery)}`, {
|
|
301
|
+
credentials: 'same-origin',
|
|
302
|
+
});
|
|
303
|
+
if (response.ok) {
|
|
304
|
+
this.searchResults = await response.json();
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('Search failed:', error);
|
|
308
|
+
} finally {
|
|
309
|
+
this.searching = false;
|
|
310
|
+
}
|
|
311
|
+
}, 300);
|
|
312
|
+
},
|
|
313
|
+
selectUser(user) {
|
|
314
|
+
this.selectedUser = user;
|
|
315
|
+
this.searchQuery = '';
|
|
316
|
+
this.searchResults = [];
|
|
317
|
+
this.showResults = false;
|
|
318
|
+
},
|
|
319
|
+
clearSelection() {
|
|
320
|
+
this.selectedUser = null;
|
|
321
|
+
this.searchQuery = '';
|
|
322
|
+
},
|
|
323
|
+
async addCollaborator() {
|
|
324
|
+
if (!this.selectedUser) return;
|
|
325
|
+
|
|
326
|
+
this.adding = true;
|
|
327
|
+
this.addError = null;
|
|
328
|
+
try {
|
|
329
|
+
const response = await fetch(apiUrl, {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: {
|
|
332
|
+
'Content-Type': 'application/json',
|
|
333
|
+
'X-CSRFToken': this.getCsrfToken(),
|
|
334
|
+
},
|
|
335
|
+
credentials: 'same-origin',
|
|
336
|
+
body: JSON.stringify({
|
|
337
|
+
email: this.selectedUser.email,
|
|
338
|
+
role: this.newRole,
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
const data = await response.json();
|
|
342
|
+
if (response.ok) {
|
|
343
|
+
this.collaborators.push(data);
|
|
344
|
+
this.selectedUser = null;
|
|
345
|
+
this.searchQuery = '';
|
|
346
|
+
this.newRole = 'viewer';
|
|
347
|
+
} else {
|
|
348
|
+
this.addError = data.error || 'Failed to add collaborator';
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
this.addError = 'Network error. Please try again.';
|
|
352
|
+
} finally {
|
|
353
|
+
this.adding = false;
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
async updateRole(collab) {
|
|
357
|
+
try {
|
|
358
|
+
const response = await fetch(`${apiUrl}${collab.id}/`, {
|
|
359
|
+
method: 'PUT',
|
|
360
|
+
headers: {
|
|
361
|
+
'Content-Type': 'application/json',
|
|
362
|
+
'X-CSRFToken': this.getCsrfToken(),
|
|
363
|
+
},
|
|
364
|
+
credentials: 'same-origin',
|
|
365
|
+
body: JSON.stringify({ role: collab.role }),
|
|
366
|
+
});
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
// Revert on error
|
|
369
|
+
await this.loadCollaborators();
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('Failed to update role:', error);
|
|
373
|
+
await this.loadCollaborators();
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
async removeCollaborator(collab) {
|
|
377
|
+
if (!confirm(`Remove ${collab.user_email} as a collaborator?`)) return;
|
|
378
|
+
try {
|
|
379
|
+
const response = await fetch(`${apiUrl}${collab.id}/`, {
|
|
380
|
+
method: 'DELETE',
|
|
381
|
+
headers: {
|
|
382
|
+
'X-CSRFToken': this.getCsrfToken(),
|
|
383
|
+
},
|
|
384
|
+
credentials: 'same-origin',
|
|
385
|
+
});
|
|
386
|
+
if (response.ok) {
|
|
387
|
+
this.collaborators = this.collaborators.filter(c => c.id !== collab.id);
|
|
388
|
+
}
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error('Failed to remove collaborator:', error);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
getInitial(email) {
|
|
394
|
+
return email ? email.charAt(0).toUpperCase() : '?';
|
|
395
|
+
},
|
|
396
|
+
getCsrfToken() {
|
|
397
|
+
const name = 'csrftoken';
|
|
398
|
+
const cookies = document.cookie.split(';');
|
|
399
|
+
for (let cookie of cookies) {
|
|
400
|
+
const [key, value] = cookie.trim().split('=');
|
|
401
|
+
if (key === name) return value;
|
|
402
|
+
}
|
|
403
|
+
return '';
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
mounted() {
|
|
407
|
+
this.loadCollaborators();
|
|
408
|
+
// Close search results when clicking outside
|
|
409
|
+
document.addEventListener('click', (e) => {
|
|
410
|
+
if (!e.target.closest('.relative')) {
|
|
411
|
+
this.showResults = false;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}).use(primevue.config.default).mount('#app');
|
|
416
|
+
</script>
|
|
417
|
+
{% endblock %}
|
|
418
|
+
|
|
@@ -12,40 +12,49 @@
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<!-- Quick Actions -->
|
|
15
|
-
<div class="grid grid-cols-1 md:grid-cols-
|
|
16
|
-
<a href="{% url 'agent_studio:
|
|
17
|
-
class="bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg p-
|
|
15
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
|
|
16
|
+
<a href="{% url 'agent_studio:system_create' %}"
|
|
17
|
+
class="bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg p-5 transition-colors">
|
|
18
18
|
<div class="flex items-center space-x-3 mb-2">
|
|
19
19
|
<span class="text-2xl">🔗</span>
|
|
20
|
-
<span class="text-lg font-semibold">
|
|
20
|
+
<span class="text-lg font-semibold">New System</span>
|
|
21
21
|
</div>
|
|
22
|
-
<p class="text-indigo-100 text-sm">
|
|
22
|
+
<p class="text-indigo-100 text-sm">Create a multi-agent system</p>
|
|
23
23
|
</a>
|
|
24
24
|
|
|
25
25
|
<a href="{% url 'agent_studio:agent_create' %}"
|
|
26
|
-
class="bg-primary-600 hover:bg-primary-700 text-white rounded-lg p-
|
|
26
|
+
class="bg-primary-600 hover:bg-primary-700 text-white rounded-lg p-5 transition-colors">
|
|
27
27
|
<div class="flex items-center space-x-3 mb-2">
|
|
28
28
|
<span class="text-2xl">✨</span>
|
|
29
|
-
<span class="text-lg font-semibold">
|
|
29
|
+
<span class="text-lg font-semibold">New Agent</span>
|
|
30
|
+
</div>
|
|
31
|
+
<p class="text-primary-100 text-sm">Build a custom AI agent</p>
|
|
32
|
+
</a>
|
|
33
|
+
|
|
34
|
+
<a href="{% url 'agent_studio:system_list' %}"
|
|
35
|
+
class="bg-white hover:bg-gray-50 border border-gray-200 rounded-lg p-5 transition-colors">
|
|
36
|
+
<div class="flex items-center space-x-3 mb-2">
|
|
37
|
+
<span class="text-2xl">🔗</span>
|
|
38
|
+
<span class="text-lg font-semibold text-gray-800">My Systems</span>
|
|
30
39
|
</div>
|
|
31
|
-
<p class="text-
|
|
40
|
+
<p class="text-gray-600 text-sm">View multi-agent systems</p>
|
|
32
41
|
</a>
|
|
33
42
|
|
|
34
43
|
<a href="{% url 'agent_studio:agent_list' %}"
|
|
35
|
-
class="bg-white hover:bg-gray-50 border border-gray-200 rounded-lg p-
|
|
44
|
+
class="bg-white hover:bg-gray-50 border border-gray-200 rounded-lg p-5 transition-colors">
|
|
36
45
|
<div class="flex items-center space-x-3 mb-2">
|
|
37
46
|
<span class="text-2xl">📋</span>
|
|
38
47
|
<span class="text-lg font-semibold text-gray-800">My Agents</span>
|
|
39
48
|
</div>
|
|
40
|
-
<p class="text-gray-600 text-sm">View and manage
|
|
49
|
+
<p class="text-gray-600 text-sm">View and manage agents</p>
|
|
41
50
|
</a>
|
|
42
51
|
|
|
43
|
-
<div class="bg-white hover:bg-gray-50 border border-gray-200 rounded-lg p-
|
|
52
|
+
<div class="bg-white hover:bg-gray-50 border border-gray-200 rounded-lg p-5 transition-colors cursor-pointer opacity-60">
|
|
44
53
|
<div class="flex items-center space-x-3 mb-2">
|
|
45
54
|
<span class="text-2xl">📚</span>
|
|
46
55
|
<span class="text-lg font-semibold text-gray-800">Templates</span>
|
|
47
56
|
</div>
|
|
48
|
-
<p class="text-gray-600 text-sm">
|
|
57
|
+
<p class="text-gray-600 text-sm">Coming soon</p>
|
|
49
58
|
</div>
|
|
50
59
|
</div>
|
|
51
60
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{% extends "django_agent_studio/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Create System - 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:system_list' %}" class="text-gray-500 hover:text-gray-700">My Systems</a>
|
|
9
|
+
<span class="text-gray-400">/</span>
|
|
10
|
+
<span class="text-gray-600">Create</span>
|
|
11
|
+
</nav>
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
{% block content %}
|
|
15
|
+
<div class="h-full p-6 overflow-auto">
|
|
16
|
+
<div class="max-w-2xl mx-auto">
|
|
17
|
+
<!-- Header -->
|
|
18
|
+
<div class="mb-6">
|
|
19
|
+
<h1 class="text-2xl font-bold text-gray-900">🔗 Create New System</h1>
|
|
20
|
+
<p class="text-gray-500 mt-1">A system combines multiple agents that work together.</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Form -->
|
|
24
|
+
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
|
25
|
+
<form @submit.prevent="createSystem" class="space-y-6">
|
|
26
|
+
<!-- Name -->
|
|
27
|
+
<div>
|
|
28
|
+
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
|
29
|
+
<input type="text" id="name" v-model="form.name" @input="generateSlug"
|
|
30
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
31
|
+
placeholder="My Agent System" required>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Slug -->
|
|
35
|
+
<div>
|
|
36
|
+
<label for="slug" class="block text-sm font-medium text-gray-700 mb-1">Slug *</label>
|
|
37
|
+
<input type="text" id="slug" v-model="form.slug"
|
|
38
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 font-mono text-sm"
|
|
39
|
+
placeholder="my-agent-system" required>
|
|
40
|
+
<p class="text-xs text-gray-500 mt-1">URL-friendly identifier (auto-generated from name)</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- Description -->
|
|
44
|
+
<div>
|
|
45
|
+
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
|
46
|
+
<textarea id="description" v-model="form.description" rows="3"
|
|
47
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
|
48
|
+
placeholder="Describe what this system does..."></textarea>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Entry Agent -->
|
|
52
|
+
<div>
|
|
53
|
+
<label for="entry_agent" class="block text-sm font-medium text-gray-700 mb-1">Entry Agent</label>
|
|
54
|
+
<select id="entry_agent" v-model="form.entry_agent_id"
|
|
55
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
|
|
56
|
+
<option value="">-- Select an agent --</option>
|
|
57
|
+
{% for agent in agents %}
|
|
58
|
+
<option value="{{ agent.id }}">{{ agent.icon|default:"🤖" }} {{ agent.name }}</option>
|
|
59
|
+
{% endfor %}
|
|
60
|
+
</select>
|
|
61
|
+
<p class="text-xs text-gray-500 mt-1">The agent that handles incoming conversations. Sub-agents will be auto-discovered.</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Error Message -->
|
|
65
|
+
<div v-if="error" class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
|
|
66
|
+
[[ error ]]
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Actions -->
|
|
70
|
+
<div class="flex items-center justify-end space-x-3 pt-4 border-t border-gray-200">
|
|
71
|
+
<a href="{% url 'agent_studio:system_list' %}"
|
|
72
|
+
class="px-4 py-2 text-gray-700 hover:text-gray-900">Cancel</a>
|
|
73
|
+
<button type="submit" :disabled="loading"
|
|
74
|
+
class="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium disabled:opacity-50">
|
|
75
|
+
<span v-if="loading">Creating...</span>
|
|
76
|
+
<span v-else>Create System</span>
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</form>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
{% endblock %}
|
|
84
|
+
|
|
85
|
+
{% block extra_js %}
|
|
86
|
+
<script>
|
|
87
|
+
const { createApp } = Vue;
|
|
88
|
+
|
|
89
|
+
createApp({
|
|
90
|
+
delimiters: ['[[', ']]'],
|
|
91
|
+
data() {
|
|
92
|
+
return {
|
|
93
|
+
form: {
|
|
94
|
+
name: '',
|
|
95
|
+
slug: '',
|
|
96
|
+
description: '',
|
|
97
|
+
entry_agent_id: '',
|
|
98
|
+
},
|
|
99
|
+
loading: false,
|
|
100
|
+
error: null,
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
methods: {
|
|
104
|
+
generateSlug() {
|
|
105
|
+
// Auto-generate slug from name
|
|
106
|
+
this.form.slug = this.form.name
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
109
|
+
.replace(/^-|-$/g, '');
|
|
110
|
+
},
|
|
111
|
+
async createSystem() {
|
|
112
|
+
this.loading = true;
|
|
113
|
+
this.error = null;
|
|
114
|
+
try {
|
|
115
|
+
const payload = {
|
|
116
|
+
name: this.form.name,
|
|
117
|
+
slug: this.form.slug,
|
|
118
|
+
description: this.form.description,
|
|
119
|
+
auto_discover: true,
|
|
120
|
+
};
|
|
121
|
+
if (this.form.entry_agent_id) {
|
|
122
|
+
payload.entry_agent_id = this.form.entry_agent_id;
|
|
123
|
+
}
|
|
124
|
+
const response = await fetch('{% url "agent_studio:api_system_list" %}', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
'Content-Type': 'application/json',
|
|
128
|
+
'X-CSRFToken': '{{ csrf_token }}',
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(payload),
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const data = await response.json();
|
|
134
|
+
throw new Error(data.detail || data.error || JSON.stringify(data));
|
|
135
|
+
}
|
|
136
|
+
const system = await response.json();
|
|
137
|
+
window.location.href = `/studio/systems/${system.id}/test/`;
|
|
138
|
+
} catch (err) {
|
|
139
|
+
this.error = err.message;
|
|
140
|
+
} finally {
|
|
141
|
+
this.loading = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}).use(primevue.config.default).mount('#app');
|
|
146
|
+
</script>
|
|
147
|
+
{% endblock %}
|
|
148
|
+
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
<!-- Header -->
|
|
16
16
|
<div class="flex items-center justify-between mb-6">
|
|
17
17
|
<h1 class="text-2xl font-bold text-gray-900">My Systems</h1>
|
|
18
|
+
<a href="{% url 'agent_studio:system_create' %}"
|
|
19
|
+
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium text-sm">
|
|
20
|
+
+ New System
|
|
21
|
+
</a>
|
|
18
22
|
</div>
|
|
19
23
|
|
|
20
24
|
{% if systems %}
|
|
@@ -56,8 +60,13 @@
|
|
|
56
60
|
<span>Updated {{ system.updated_at|timesince }} ago</span>
|
|
57
61
|
</div>
|
|
58
62
|
</div>
|
|
59
|
-
<div class="border-t border-gray-100 px-4 py-3 bg-gray-50 flex items-center justify-
|
|
60
|
-
<a href="{% url 'agent_studio:
|
|
63
|
+
<div class="border-t border-gray-100 px-4 py-3 bg-gray-50 flex items-center justify-between">
|
|
64
|
+
<a href="{% url 'agent_studio:system_collaborators' system.id %}"
|
|
65
|
+
class="text-gray-500 hover:text-gray-700 text-sm"
|
|
66
|
+
title="Manage collaborators">
|
|
67
|
+
<i class="pi pi-users"></i>
|
|
68
|
+
</a>
|
|
69
|
+
<a href="{% url 'agent_studio:system_test' system.id %}"
|
|
61
70
|
class="text-primary-600 hover:text-primary-700 text-sm font-medium">
|
|
62
71
|
Test System →
|
|
63
72
|
</a>
|
|
@@ -71,7 +80,10 @@
|
|
|
71
80
|
<div class="text-6xl mb-4">🔗</div>
|
|
72
81
|
<h3 class="text-xl font-semibold text-gray-800 mb-2">No systems yet</h3>
|
|
73
82
|
<p class="text-gray-500 mb-6">Systems are multi-agent configurations that work together.</p>
|
|
74
|
-
<
|
|
83
|
+
<a href="{% url 'agent_studio:system_create' %}"
|
|
84
|
+
class="inline-block px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium">
|
|
85
|
+
Create Your First System
|
|
86
|
+
</a>
|
|
75
87
|
</div>
|
|
76
88
|
{% endif %}
|
|
77
89
|
</div>
|