main
js 132 lines 3.72 KB
Raw
1 import { createStore } from "/js/AlpineStore.js";
2
3 const model = {
4 // State
5 isLoading: false,
6 contextData: null,
7 tokenCount: 0,
8 error: null,
9 editor: null,
10 closePromise: null,
11
12 // Open Context Window modal
13 async open() {
14 if (this.isLoading) return; // Prevent double-open
15
16 this.isLoading = true;
17 this.error = null;
18 this.contextData = null;
19 this.tokenCount = 0;
20
21 try {
22 // Open modal FIRST (immediate UI feedback, but DON'T await)
23 this.closePromise = window.openModal('modals/context/context.html');
24
25 // Setup cleanup on modal close
26 if (this.closePromise && typeof this.closePromise.then === 'function') {
27 this.closePromise.then(() => {
28 this.destroy();
29 });
30 }
31
32 this.updateModalTitle(); // Set initial "loading" title
33
34 // Fetch data from backend
35 const contextId = window.getContext();
36 const response = await window.sendJsonData('/ctx_window_get', {
37 context: contextId,
38 });
39
40 // Update state with data
41 this.contextData = response.content;
42 this.tokenCount = response.tokens || 0;
43 this.isLoading = false;
44 this.updateModalTitle(); // Update with token count
45
46 // Initialize ACE editor
47 this.scheduleEditorInit();
48
49 } catch (error) {
50 console.error("Context fetch error:", error);
51 this.error = error?.message || "Failed to load context window";
52 this.isLoading = false;
53 this.updateModalTitle(); // Show error in title
54 }
55 },
56
57 scheduleEditorInit() {
58 // Use double requestAnimationFrame to ensure DOM is ready
59 window.requestAnimationFrame(() => {
60 if (this.isLoading || this.error) return;
61 window.requestAnimationFrame(() => this.initEditor());
62 });
63 },
64
65 initEditor() {
66 const container = document.getElementById("context-viewer-container");
67 if (!container) {
68 console.warn("Context container not found, deferring editor init");
69 return;
70 }
71
72 // Destroy old instance if exists
73 if (this.editor?.destroy) {
74 this.editor.destroy();
75 }
76
77 // Check if ACE is available
78 if (!window.ace?.edit) {
79 console.error("ACE editor not available");
80 this.error = "Editor library not loaded";
81 return;
82 }
83
84 const editorInstance = window.ace.edit("context-viewer-container");
85 if (!editorInstance) {
86 console.error("Failed to create ACE editor instance");
87 return;
88 }
89
90 this.editor = editorInstance;
91
92 // Configure theme based on dark mode (legacy parity: != "false")
93 const darkMode = window.localStorage?.getItem("darkMode");
94 const theme = darkMode !== "false" ? "ace/theme/github_dark" : "ace/theme/tomorrow";
95
96 this.editor.setTheme(theme);
97 this.editor.session.setMode("ace/mode/markdown");
98 this.editor.setValue(this.contextData, -1); // -1 moves cursor to start
99 this.editor.setReadOnly(true);
100 this.editor.clearSelection();
101 },
102
103 updateModalTitle() {
104 window.requestAnimationFrame(() => {
105 const modalTitles = document.querySelectorAll(".modal.show .modal-title");
106 if (!modalTitles.length) return;
107
108 // Get the last (topmost) modal title
109 const title = modalTitles[modalTitles.length - 1];
110 if (!title) return;
111
112 if (this.error) {
113 title.textContent = "Context Window – Error";
114 } else if (this.isLoading) {
115 title.textContent = "Context Window (loading…)";
116 } else {
117 title.textContent = `Context Window ~${this.tokenCount} tokens`;
118 }
119 });
120 },
121
122 // Optional: cleanup method for lifecycle management
123 destroy() {
124 if (this.editor?.destroy) {
125 this.editor.destroy();
126 }
127 this.editor = null;
128 },
129 };
130
131 export const store = createStore("context", model);
132