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.
@@ -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
- createApp({
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
- }).use(primevue.config.default).mount('#app');
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