django-agent-studio 0.1.5__py3-none-any.whl → 0.1.9__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 +737 -0
- django_agent_studio/api/urls.py +51 -0
- django_agent_studio/api/views.py +379 -0
- django_agent_studio/templates/django_agent_studio/base.html +1 -1
- django_agent_studio/templates/django_agent_studio/builder.html +630 -2
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.9.dist-info}/METADATA +87 -2
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.9.dist-info}/RECORD +10 -9
- django_agent_studio-0.1.9.dist-info/licenses/LICENSE +83 -0
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.9.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -252,6 +252,12 @@
|
|
|
252
252
|
<div v-if="selectedAgent" class="text-sm text-gray-500">
|
|
253
253
|
<span class="font-mono text-xs bg-gray-100 px-2 py-0.5 rounded">[[ selectedAgent.slug ]]</span>
|
|
254
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>
|
|
255
261
|
<button @click="openSchemaEditor"
|
|
256
262
|
class="flex items-center space-x-1 px-2 py-1 text-sm rounded border"
|
|
257
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'"
|
|
@@ -642,6 +648,230 @@
|
|
|
642
648
|
</div>
|
|
643
649
|
</div>
|
|
644
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>
|
|
645
875
|
{% endblock %}
|
|
646
876
|
|
|
647
877
|
{% block extra_js %}
|
|
@@ -782,7 +1012,58 @@ function initBuilderChat(targetAgentId = null, vueApp = null) {
|
|
|
782
1012
|
});
|
|
783
1013
|
}
|
|
784
1014
|
|
|
785
|
-
|
|
1015
|
+
// Spec Tree Node Component
|
|
1016
|
+
const SpecTreeNode = {
|
|
1017
|
+
name: 'spec-tree-node',
|
|
1018
|
+
props: ['node', 'selectedId', 'depth'],
|
|
1019
|
+
emits: ['select', 'add-child'],
|
|
1020
|
+
template: `{% verbatim %}
|
|
1021
|
+
<div class="spec-tree-node">
|
|
1022
|
+
<div class="flex items-center py-1.5 px-2 rounded cursor-pointer hover:bg-gray-100 group"
|
|
1023
|
+
:class="{'bg-emerald-50 border-l-2 border-emerald-500': node.id === selectedId}"
|
|
1024
|
+
:style="{ paddingLeft: (depth * 16 + 8) + 'px' }"
|
|
1025
|
+
@click="$emit('select', node.id)">
|
|
1026
|
+
<span v-if="node.children && node.children.length > 0"
|
|
1027
|
+
class="mr-1 text-gray-400 text-xs"
|
|
1028
|
+
@click.stop="expanded = !expanded">
|
|
1029
|
+
{{ expanded ? '▼' : '▶' }}
|
|
1030
|
+
</span>
|
|
1031
|
+
<span v-else class="mr-1 text-gray-300 text-xs">•</span>
|
|
1032
|
+
<span class="flex-1 text-sm truncate" :class="node.has_content ? 'text-gray-800' : 'text-gray-400 italic'">
|
|
1033
|
+
{{ node.title }}
|
|
1034
|
+
</span>
|
|
1035
|
+
<span v-if="node.linked_agent"
|
|
1036
|
+
class="text-xs bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded ml-1"
|
|
1037
|
+
:title="'Linked to ' + node.linked_agent.name">
|
|
1038
|
+
🤖
|
|
1039
|
+
</span>
|
|
1040
|
+
<button @click.stop="$emit('add-child', node.id)"
|
|
1041
|
+
class="opacity-0 group-hover:opacity-100 text-gray-400 hover:text-emerald-600 ml-1 text-xs"
|
|
1042
|
+
title="Add child document">
|
|
1043
|
+
+
|
|
1044
|
+
</button>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div v-if="expanded && node.children && node.children.length > 0">
|
|
1047
|
+
<spec-tree-node
|
|
1048
|
+
v-for="child in node.children"
|
|
1049
|
+
:key="child.id"
|
|
1050
|
+
:node="child"
|
|
1051
|
+
:selected-id="selectedId"
|
|
1052
|
+
:depth="depth + 1"
|
|
1053
|
+
@select="$emit('select', $event)"
|
|
1054
|
+
@add-child="$emit('add-child', $event)"
|
|
1055
|
+
></spec-tree-node>
|
|
1056
|
+
</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
{% endverbatim %}`,
|
|
1059
|
+
data() {
|
|
1060
|
+
return {
|
|
1061
|
+
expanded: true
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
const app = createApp({
|
|
786
1067
|
delimiters: ['[[', ']]'], // Avoid conflict with Django templates
|
|
787
1068
|
data() {
|
|
788
1069
|
return {
|
|
@@ -826,6 +1107,23 @@ createApp({
|
|
|
826
1107
|
// Test auth mode
|
|
827
1108
|
testAuthMode: 'authenticated',
|
|
828
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: '',
|
|
829
1127
|
}
|
|
830
1128
|
},
|
|
831
1129
|
computed: {
|
|
@@ -836,6 +1134,14 @@ createApp({
|
|
|
836
1134
|
const version = this.versions.find(v => v.id === this.selectedVersionId);
|
|
837
1135
|
return version && version.is_active;
|
|
838
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
|
+
},
|
|
839
1145
|
},
|
|
840
1146
|
methods: {
|
|
841
1147
|
async loadAgents() {
|
|
@@ -1478,6 +1784,322 @@ createApp({
|
|
|
1478
1784
|
}
|
|
1479
1785
|
return cookieValue;
|
|
1480
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
|
+
},
|
|
1481
2103
|
},
|
|
1482
2104
|
async mounted() {
|
|
1483
2105
|
// Load agents and systems lists
|
|
@@ -1496,7 +2118,13 @@ createApp({
|
|
|
1496
2118
|
this.refreshTestAgent();
|
|
1497
2119
|
this.refreshBuilderChat();
|
|
1498
2120
|
}
|
|
1499
|
-
})
|
|
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');
|
|
1500
2128
|
</script>
|
|
1501
2129
|
{% endblock %}
|
|
1502
2130
|
|