agentopia 1.0.0
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.
- package/.claude/settings.local.json +28 -0
- package/dist/app.d.ts +10 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +121 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/db/database.d.ts +5 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +39 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +621 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +9 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/auth.d.ts +13 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +733 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/agents.d.ts +3 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +1058 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/issues.d.ts +4 -0
- package/dist/routes/issues.d.ts.map +1 -0
- package/dist/routes/issues.js +946 -0
- package/dist/routes/issues.js.map +1 -0
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/knowledge.d.ts.map +1 -0
- package/dist/routes/knowledge.js +117 -0
- package/dist/routes/knowledge.js.map +1 -0
- package/dist/routes/memories.d.ts +3 -0
- package/dist/routes/memories.d.ts.map +1 -0
- package/dist/routes/memories.js +115 -0
- package/dist/routes/memories.js.map +1 -0
- package/dist/routes/messages.d.ts +3 -0
- package/dist/routes/messages.d.ts.map +1 -0
- package/dist/routes/messages.js +130 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/projects.d.ts +3 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +754 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/templates.d.ts +3 -0
- package/dist/routes/templates.d.ts.map +1 -0
- package/dist/routes/templates.js +117 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/ui.d.ts +3 -0
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +38 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/services/agent-hierarchy.d.ts +14 -0
- package/dist/services/agent-hierarchy.d.ts.map +1 -0
- package/dist/services/agent-hierarchy.js +58 -0
- package/dist/services/agent-hierarchy.js.map +1 -0
- package/dist/services/agent-issue-batch.d.ts +17 -0
- package/dist/services/agent-issue-batch.d.ts.map +1 -0
- package/dist/services/agent-issue-batch.js +57 -0
- package/dist/services/agent-issue-batch.js.map +1 -0
- package/dist/services/controller.d.ts +4 -0
- package/dist/services/controller.d.ts.map +1 -0
- package/dist/services/controller.js +237 -0
- package/dist/services/controller.js.map +1 -0
- package/dist/services/langgraph-runner.d.ts +33 -0
- package/dist/services/langgraph-runner.d.ts.map +1 -0
- package/dist/services/langgraph-runner.js +478 -0
- package/dist/services/langgraph-runner.js.map +1 -0
- package/dist/services/orchestrator.d.ts +9 -0
- package/dist/services/orchestrator.d.ts.map +1 -0
- package/dist/services/orchestrator.js +116 -0
- package/dist/services/orchestrator.js.map +1 -0
- package/dist/services/pre-controller.d.ts +7 -0
- package/dist/services/pre-controller.d.ts.map +1 -0
- package/dist/services/pre-controller.js +101 -0
- package/dist/services/pre-controller.js.map +1 -0
- package/dist/services/process-manager.d.ts +67 -0
- package/dist/services/process-manager.d.ts.map +1 -0
- package/dist/services/process-manager.js +938 -0
- package/dist/services/process-manager.js.map +1 -0
- package/dist/services/project-permissions.d.ts +84 -0
- package/dist/services/project-permissions.d.ts.map +1 -0
- package/dist/services/project-permissions.js +129 -0
- package/dist/services/project-permissions.js.map +1 -0
- package/dist/services/scheduler.d.ts +6 -0
- package/dist/services/scheduler.d.ts.map +1 -0
- package/dist/services/scheduler.js +300 -0
- package/dist/services/scheduler.js.map +1 -0
- package/dist/services/system-prompt.d.ts +3 -0
- package/dist/services/system-prompt.d.ts.map +1 -0
- package/dist/services/system-prompt.js +285 -0
- package/dist/services/system-prompt.js.map +1 -0
- package/dist/services/terminal.d.ts +18 -0
- package/dist/services/terminal.d.ts.map +1 -0
- package/dist/services/terminal.js +222 -0
- package/dist/services/terminal.js.map +1 -0
- package/dist/services/websocket.d.ts +15 -0
- package/dist/services/websocket.d.ts.map +1 -0
- package/dist/services/websocket.js +204 -0
- package/dist/services/websocket.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/env.ini +18 -0
- package/package.json +38 -0
- package/project_id +0 -0
- package/public/admin-users.html +188 -0
- package/public/agent.html +199 -0
- package/public/css/issues.css +275 -0
- package/public/css/style.css +1299 -0
- package/public/index.html +166 -0
- package/public/issue.html +76 -0
- package/public/js/agent.js +19 -0
- package/public/js/common.js +735 -0
- package/public/js/dashboard.js +772 -0
- package/public/js/files-panel.js +703 -0
- package/public/js/interactive-terminal.js +201 -0
- package/public/js/issue-renderer.js +559 -0
- package/public/js/issue.js +57 -0
- package/public/js/project.js +2425 -0
- package/public/js/terminal.js +564 -0
- package/public/project.html +430 -0
- package/public/terminal.html +67 -0
- package/public/vendor/marked.js +74 -0
- package/public/vendor/xterm-addon-fit.js +2 -0
- package/public/vendor/xterm.css +209 -0
- package/public/vendor/xterm.js +2 -0
- package/send_message_and_update_issue.js +65 -0
- package/tsconfig.json +19 -0
- package/update_round2_and_create_round3.js +284 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
const MONACO_LOADER_URL = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js';
|
|
3
|
+
const MONACO_VS_PATH = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs';
|
|
4
|
+
|
|
5
|
+
class FilesPanel {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.state = {
|
|
9
|
+
agent: null,
|
|
10
|
+
expandedDirs: new Set(['']),
|
|
11
|
+
treeCache: new Map(),
|
|
12
|
+
treeIndex: new Map(),
|
|
13
|
+
loadingDirs: new Set(),
|
|
14
|
+
selectedFilePath: '',
|
|
15
|
+
originalContent: '',
|
|
16
|
+
dirty: false,
|
|
17
|
+
saving: false,
|
|
18
|
+
monacoPromise: null,
|
|
19
|
+
editor: null,
|
|
20
|
+
bannerTimer: null,
|
|
21
|
+
canWrite: options.canWrite !== false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.handleWindowResize = this.handleWindowResize.bind(this);
|
|
25
|
+
this.handleSaveShortcut = this.handleSaveShortcut.bind(this);
|
|
26
|
+
window.addEventListener('resize', this.handleWindowResize);
|
|
27
|
+
window.addEventListener('keydown', this.handleSaveShortcut);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get apiName() {
|
|
31
|
+
return this.options.publicApiName || 'ProjectFiles';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getAgentId() {
|
|
35
|
+
return this.state.agent?.id || '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isVisible() {
|
|
39
|
+
return typeof this.options.isVisible === 'function' ? !!this.options.isVisible() : true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isNarrowViewport() {
|
|
43
|
+
return window.matchMedia('(max-width: 800px)').matches;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
el(name) {
|
|
47
|
+
return document.getElementById(this.options[name]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
formatBytes(bytes) {
|
|
51
|
+
if (!Number.isFinite(bytes) || bytes < 1024) return `${bytes || 0} B`;
|
|
52
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
53
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
detectLanguage(filePath) {
|
|
57
|
+
const ext = (filePath.split('.').pop() || '').toLowerCase();
|
|
58
|
+
const map = {
|
|
59
|
+
c: 'c',
|
|
60
|
+
cpp: 'cpp',
|
|
61
|
+
css: 'css',
|
|
62
|
+
go: 'go',
|
|
63
|
+
h: 'c',
|
|
64
|
+
html: 'html',
|
|
65
|
+
java: 'java',
|
|
66
|
+
js: 'javascript',
|
|
67
|
+
json: 'json',
|
|
68
|
+
jsx: 'javascript',
|
|
69
|
+
md: 'markdown',
|
|
70
|
+
mjs: 'javascript',
|
|
71
|
+
py: 'python',
|
|
72
|
+
rs: 'rust',
|
|
73
|
+
sh: 'shell',
|
|
74
|
+
sql: 'sql',
|
|
75
|
+
svg: 'xml',
|
|
76
|
+
ts: 'typescript',
|
|
77
|
+
tsx: 'typescript',
|
|
78
|
+
txt: 'plaintext',
|
|
79
|
+
xml: 'xml',
|
|
80
|
+
yml: 'yaml',
|
|
81
|
+
yaml: 'yaml',
|
|
82
|
+
};
|
|
83
|
+
return map[ext] || 'plaintext';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setWriteEnabled(enabled) {
|
|
87
|
+
this.state.canWrite = !!enabled;
|
|
88
|
+
if (this.state.editor) {
|
|
89
|
+
this.state.editor.updateOptions({ readOnly: !this.state.canWrite || !this.state.selectedFilePath });
|
|
90
|
+
}
|
|
91
|
+
this.updateSaveButton();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setAgent(agent) {
|
|
95
|
+
const previousId = this.state.agent?.id || '';
|
|
96
|
+
const previousWorkdir = this.state.agent?.working_directory || '';
|
|
97
|
+
this.state.agent = agent || null;
|
|
98
|
+
this.updateWorkingDirectoryState();
|
|
99
|
+
this.setWriteEnabled(this.state.canWrite);
|
|
100
|
+
|
|
101
|
+
if ((agent?.id || '') !== previousId || (agent?.working_directory || '') !== previousWorkdir) {
|
|
102
|
+
this.resetTreeState();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (this.isVisible()) {
|
|
106
|
+
this.activate();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
resetTreeState() {
|
|
111
|
+
this.state.expandedDirs = new Set(['']);
|
|
112
|
+
this.state.treeCache = new Map();
|
|
113
|
+
this.state.treeIndex = new Map();
|
|
114
|
+
this.state.loadingDirs = new Set();
|
|
115
|
+
this.state.selectedFilePath = '';
|
|
116
|
+
this.state.originalContent = '';
|
|
117
|
+
this.state.dirty = false;
|
|
118
|
+
this.removePreviewIframe();
|
|
119
|
+
this.updateCurrentFileLabel();
|
|
120
|
+
this.updateSaveButton();
|
|
121
|
+
this.setStatus(this.state.agent ? 'Select a file to preview and edit it.' : 'Select an agent to browse files.');
|
|
122
|
+
this.hideBanner();
|
|
123
|
+
this.renderTree();
|
|
124
|
+
if (this.state.editor) {
|
|
125
|
+
this.state.editor.setValue('');
|
|
126
|
+
this.state.editor.updateOptions({ readOnly: true });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setStatus(message) {
|
|
131
|
+
const status = this.el('statusId');
|
|
132
|
+
if (status) status.textContent = message;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
showBanner(message, tone) {
|
|
136
|
+
const banner = this.el('bannerId');
|
|
137
|
+
if (!banner) return;
|
|
138
|
+
banner.hidden = false;
|
|
139
|
+
banner.className = `file-editor-banner${tone ? ` ${tone}` : ''}`;
|
|
140
|
+
banner.textContent = message;
|
|
141
|
+
if (this.state.bannerTimer) window.clearTimeout(this.state.bannerTimer);
|
|
142
|
+
if (tone === 'success') {
|
|
143
|
+
this.state.bannerTimer = window.setTimeout(() => this.hideBanner(), 2200);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
hideBanner() {
|
|
148
|
+
const banner = this.el('bannerId');
|
|
149
|
+
if (!banner) return;
|
|
150
|
+
banner.hidden = true;
|
|
151
|
+
banner.className = 'file-editor-banner';
|
|
152
|
+
banner.textContent = '';
|
|
153
|
+
if (this.state.bannerTimer) {
|
|
154
|
+
window.clearTimeout(this.state.bannerTimer);
|
|
155
|
+
this.state.bannerTimer = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
updateCurrentFileLabel() {
|
|
160
|
+
const label = this.el('currentPathId');
|
|
161
|
+
if (!label) return;
|
|
162
|
+
label.textContent = this.state.selectedFilePath ? `${this.state.selectedFilePath}${this.state.dirty ? ' *' : ''}` : 'No file selected';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getPreviewMode(filePath) {
|
|
166
|
+
const ext = (filePath.split('.').pop() || '').toLowerCase();
|
|
167
|
+
if (ext === 'pdf') return 'pdf';
|
|
168
|
+
if (ext === 'html' || ext === 'htm') return 'html';
|
|
169
|
+
return 'text';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
updateSaveButton() {
|
|
173
|
+
const button = this.el('saveButtonId');
|
|
174
|
+
if (!button) return;
|
|
175
|
+
button.disabled = this.state.saving || !this.state.canWrite || !this.state.selectedFilePath || !this.state.agent?.working_directory || !this.state.editor || this.state.previewMode;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ensureEditorContainerReady() {
|
|
179
|
+
const editorEl = this.el('editorId');
|
|
180
|
+
if (!editorEl) return false;
|
|
181
|
+
if (this.state.editor) {
|
|
182
|
+
this.state.editor.layout();
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
createEditor(monaco) {
|
|
188
|
+
if (this.state.editor || !this.ensureEditorContainerReady()) return this.state.editor;
|
|
189
|
+
const editorEl = this.el('editorId');
|
|
190
|
+
this.state.editor = monaco.editor.create(editorEl, {
|
|
191
|
+
value: '',
|
|
192
|
+
language: 'plaintext',
|
|
193
|
+
automaticLayout: true,
|
|
194
|
+
minimap: { enabled: false },
|
|
195
|
+
readOnly: true,
|
|
196
|
+
scrollBeyondLastLine: false,
|
|
197
|
+
wordWrap: 'off',
|
|
198
|
+
theme: 'vs',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
this.state.editor.onDidChangeModelContent(() => {
|
|
202
|
+
if (!this.state.selectedFilePath) return;
|
|
203
|
+
this.state.dirty = this.state.editor.getValue() !== this.state.originalContent;
|
|
204
|
+
this.updateCurrentFileLabel();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
this.state.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
208
|
+
this.saveCurrentFile();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
this.updateSaveButton();
|
|
212
|
+
return this.state.editor;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ensureMonaco() {
|
|
216
|
+
if (window.monaco && window.monaco.editor) {
|
|
217
|
+
return Promise.resolve(window.monaco);
|
|
218
|
+
}
|
|
219
|
+
if (this.state.monacoPromise) return this.state.monacoPromise;
|
|
220
|
+
|
|
221
|
+
this.state.monacoPromise = new Promise((resolve, reject) => {
|
|
222
|
+
const finishLoad = () => {
|
|
223
|
+
if (!window.require) {
|
|
224
|
+
reject(new Error('Monaco loader is unavailable'));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
window.require.config({ paths: { vs: MONACO_VS_PATH } });
|
|
228
|
+
window.require(['vs/editor/editor.main'], () => resolve(window.monaco), reject);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const existing = document.querySelector('script[data-monaco-loader="true"]');
|
|
232
|
+
if (existing) {
|
|
233
|
+
if (window.require) finishLoad();
|
|
234
|
+
else existing.addEventListener('load', finishLoad, { once: true });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const script = document.createElement('script');
|
|
239
|
+
script.src = MONACO_LOADER_URL;
|
|
240
|
+
script.async = true;
|
|
241
|
+
script.dataset.monacoLoader = 'true';
|
|
242
|
+
script.onload = finishLoad;
|
|
243
|
+
script.onerror = () => reject(new Error('Failed to load Monaco Editor'));
|
|
244
|
+
document.head.appendChild(script);
|
|
245
|
+
}).then((monaco) => {
|
|
246
|
+
this.createEditor(monaco);
|
|
247
|
+
return monaco;
|
|
248
|
+
}).catch((error) => {
|
|
249
|
+
this.state.monacoPromise = null;
|
|
250
|
+
throw error;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return this.state.monacoPromise;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
updateWorkingDirectoryState() {
|
|
257
|
+
const rootLabel = this.el('rootLabelId');
|
|
258
|
+
const note = this.el('noteId');
|
|
259
|
+
const hasAgent = !!this.state.agent;
|
|
260
|
+
const hasWorkdir = !!this.state.agent?.working_directory;
|
|
261
|
+
|
|
262
|
+
if (rootLabel) {
|
|
263
|
+
if (!hasAgent) rootLabel.textContent = 'Select an agent';
|
|
264
|
+
else rootLabel.textContent = hasWorkdir ? this.state.agent.working_directory : 'Not configured';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!note) return;
|
|
268
|
+
note.style.display = (!hasAgent || !hasWorkdir) ? '' : 'none';
|
|
269
|
+
if (!hasAgent) {
|
|
270
|
+
note.textContent = 'Select an agent to browse files.';
|
|
271
|
+
} else if (!hasWorkdir) {
|
|
272
|
+
note.textContent = 'Working Directory is required for the selected agent.';
|
|
273
|
+
} else {
|
|
274
|
+
note.textContent = '';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
renderTree() {
|
|
279
|
+
const tree = this.el('treeId');
|
|
280
|
+
if (!tree) return;
|
|
281
|
+
|
|
282
|
+
this.updateWorkingDirectoryState();
|
|
283
|
+
|
|
284
|
+
if (!this.state.agent) {
|
|
285
|
+
tree.innerHTML = '<div class="empty-state" style="padding:24px 12px">Select an agent to browse files.</div>';
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!this.state.agent.working_directory) {
|
|
290
|
+
tree.innerHTML = '<div class="empty-state" style="padding:24px 12px">Working Directory is required for the selected agent.</div>';
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const rootEntries = this.state.treeCache.get('');
|
|
295
|
+
if (!rootEntries) {
|
|
296
|
+
tree.innerHTML = '<div class="empty-state" style="padding:24px 12px">Open the Files tab to load the directory tree.</div>';
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (rootEntries.length === 0) {
|
|
301
|
+
tree.innerHTML = '<div class="empty-state" style="padding:24px 12px">This directory has no visible files.</div>';
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const rows = [];
|
|
306
|
+
const pushEntries = (dirPath, depth) => {
|
|
307
|
+
const entries = this.state.treeCache.get(dirPath) || [];
|
|
308
|
+
entries.forEach((entry) => {
|
|
309
|
+
this.state.treeIndex.set(entry.path, entry);
|
|
310
|
+
const isDir = entry.type === 'dir';
|
|
311
|
+
const isExpanded = isDir && this.state.expandedDirs.has(entry.path);
|
|
312
|
+
const isLoading = isDir && this.state.loadingDirs.has(entry.path);
|
|
313
|
+
const isSelected = this.state.selectedFilePath === entry.path;
|
|
314
|
+
const indent = depth * 18;
|
|
315
|
+
const encodedPath = encodeURIComponent(entry.path);
|
|
316
|
+
const caret = isDir ? (isExpanded ? 'v' : '>') : '-';
|
|
317
|
+
const downloadBtn = isDir ? '' : `<button class="file-tree-action-btn" title="Download" onclick="event.stopPropagation();${this.apiName}.downloadFile('${encodedPath}')">↧</button>`;
|
|
318
|
+
const meta = isDir ? '' : `<span class="file-tree-meta">${this.formatBytes(entry.size)}</span>`;
|
|
319
|
+
rows.push(`
|
|
320
|
+
<div class="file-tree-item${isSelected ? ' active' : ''}${isLoading ? ' loading' : ''}" onclick="${this.apiName}.handleTreeClick('${encodedPath}')">
|
|
321
|
+
<span class="file-tree-spacer" style="width:${indent}px"></span>
|
|
322
|
+
<span class="file-tree-caret">${caret}</span>
|
|
323
|
+
<span class="file-tree-name">${esc(entry.name)}</span>
|
|
324
|
+
${downloadBtn}
|
|
325
|
+
${meta}
|
|
326
|
+
</div>
|
|
327
|
+
`);
|
|
328
|
+
|
|
329
|
+
if (isDir && isExpanded) {
|
|
330
|
+
if (this.state.treeCache.has(entry.path)) {
|
|
331
|
+
pushEntries(entry.path, depth + 1);
|
|
332
|
+
} else if (isLoading) {
|
|
333
|
+
rows.push(`
|
|
334
|
+
<div class="file-tree-item loading">
|
|
335
|
+
<span class="file-tree-spacer" style="width:${(depth + 1) * 18}px"></span>
|
|
336
|
+
<span class="file-tree-caret">.</span>
|
|
337
|
+
<span class="file-tree-name">Loading...</span>
|
|
338
|
+
</div>
|
|
339
|
+
`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
pushEntries('', 0);
|
|
346
|
+
tree.innerHTML = rows.join('');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async loadDirectory(dirPath) {
|
|
350
|
+
if (!this.state.agent?.working_directory || !this.getAgentId()) return;
|
|
351
|
+
if (this.state.loadingDirs.has(dirPath)) return;
|
|
352
|
+
|
|
353
|
+
this.state.loadingDirs.add(dirPath);
|
|
354
|
+
this.renderTree();
|
|
355
|
+
|
|
356
|
+
const params = new URLSearchParams();
|
|
357
|
+
if (dirPath) params.set('path', dirPath);
|
|
358
|
+
if (this.el('showHiddenId')?.checked) {
|
|
359
|
+
params.set('showHidden', '1');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const res = await fetch(`/api/agents/${this.getAgentId()}/files?${params.toString()}`, { headers: apiHeaders() });
|
|
364
|
+
if (!res.ok) {
|
|
365
|
+
const err = await res.json().catch(() => ({}));
|
|
366
|
+
throw new Error(err.error || 'Failed to load directory');
|
|
367
|
+
}
|
|
368
|
+
const data = await res.json();
|
|
369
|
+
this.state.treeCache.set(dirPath, Array.isArray(data.entries) ? data.entries : []);
|
|
370
|
+
this.renderTree();
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.showBanner(error.message || 'Failed to load directory', 'error');
|
|
373
|
+
this.renderTree();
|
|
374
|
+
} finally {
|
|
375
|
+
this.state.loadingDirs.delete(dirPath);
|
|
376
|
+
this.renderTree();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
applyMobileEditorFocus(shouldFocus) {
|
|
381
|
+
const shell = this.el('shellId');
|
|
382
|
+
if (!shell) return;
|
|
383
|
+
shell.classList.toggle('mobile-editor-focus', shouldFocus && this.isNarrowViewport());
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
removePreviewIframe() {
|
|
387
|
+
const editorEl = this.el('editorId');
|
|
388
|
+
if (!editorEl) return;
|
|
389
|
+
const existing = editorEl.querySelector('.files-preview-iframe');
|
|
390
|
+
if (existing) existing.remove();
|
|
391
|
+
// Restore Monaco editor visibility if hidden
|
|
392
|
+
if (this.state.editor) {
|
|
393
|
+
this.state.editor.getDomNode().style.display = '';
|
|
394
|
+
}
|
|
395
|
+
this.state.previewMode = null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
showPreviewIframe(filePath, mode) {
|
|
399
|
+
const editorEl = this.el('editorId');
|
|
400
|
+
if (!editorEl) return;
|
|
401
|
+
|
|
402
|
+
// Hide Monaco editor if it exists
|
|
403
|
+
if (this.state.editor) {
|
|
404
|
+
this.state.editor.getDomNode().style.display = 'none';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Remove any existing iframe
|
|
408
|
+
const existing = editorEl.querySelector('.files-preview-iframe');
|
|
409
|
+
if (existing) existing.remove();
|
|
410
|
+
|
|
411
|
+
const src = `/api/agents/${this.getAgentId()}/files/serve?path=${encodeURIComponent(filePath)}`;
|
|
412
|
+
const iframe = document.createElement('iframe');
|
|
413
|
+
iframe.className = 'files-preview-iframe';
|
|
414
|
+
iframe.style.cssText = 'width:100%;height:100%;border:none;background:#fff;';
|
|
415
|
+
iframe.src = src;
|
|
416
|
+
if (mode === 'html') {
|
|
417
|
+
iframe.sandbox = 'allow-same-origin';
|
|
418
|
+
}
|
|
419
|
+
editorEl.appendChild(iframe);
|
|
420
|
+
this.state.previewMode = mode;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async openFile(filePath) {
|
|
424
|
+
if (!this.state.agent?.working_directory || !this.getAgentId()) return;
|
|
425
|
+
|
|
426
|
+
this.state.selectedFilePath = filePath;
|
|
427
|
+
this.state.dirty = false;
|
|
428
|
+
this.state.previewMode = null;
|
|
429
|
+
this.updateCurrentFileLabel();
|
|
430
|
+
this.updateSaveButton();
|
|
431
|
+
this.setStatus(`Loading ${filePath}...`);
|
|
432
|
+
this.hideBanner();
|
|
433
|
+
this.renderTree();
|
|
434
|
+
|
|
435
|
+
const mode = this.getPreviewMode(filePath);
|
|
436
|
+
|
|
437
|
+
if (mode === 'pdf' || mode === 'html') {
|
|
438
|
+
// Preview mode: use iframe instead of Monaco
|
|
439
|
+
this.removePreviewIframe();
|
|
440
|
+
this.showPreviewIframe(filePath, mode);
|
|
441
|
+
this.updateSaveButton();
|
|
442
|
+
const label = mode === 'pdf' ? 'PDF' : 'HTML';
|
|
443
|
+
this.setStatus(`Preview: ${filePath}`);
|
|
444
|
+
this.showBanner(`${label} preview: ${filePath}`, '');
|
|
445
|
+
this.renderTree();
|
|
446
|
+
this.applyMobileEditorFocus(true);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
this.removePreviewIframe();
|
|
452
|
+
const monaco = await this.ensureMonaco();
|
|
453
|
+
this.createEditor(monaco);
|
|
454
|
+
|
|
455
|
+
const res = await fetch(`/api/agents/${this.getAgentId()}/files/content?path=${encodeURIComponent(filePath)}`, { headers: apiHeaders() });
|
|
456
|
+
if (!res.ok) {
|
|
457
|
+
const err = await res.json().catch(() => ({}));
|
|
458
|
+
throw new Error(err.error || 'Failed to load file');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const content = await res.text();
|
|
462
|
+
const language = this.detectLanguage(filePath);
|
|
463
|
+
const model = this.state.editor.getModel();
|
|
464
|
+
window.monaco.editor.setModelLanguage(model, language);
|
|
465
|
+
this.state.editor.updateOptions({ readOnly: !this.state.canWrite });
|
|
466
|
+
this.state.editor.setValue(content);
|
|
467
|
+
this.state.originalContent = content;
|
|
468
|
+
this.state.dirty = false;
|
|
469
|
+
this.updateCurrentFileLabel();
|
|
470
|
+
this.updateSaveButton();
|
|
471
|
+
this.setStatus(this.state.canWrite ? `Loaded ${filePath}` : `Loaded ${filePath} (read-only)`);
|
|
472
|
+
this.showBanner(`Loaded ${filePath}`, '');
|
|
473
|
+
this.renderTree();
|
|
474
|
+
this.applyMobileEditorFocus(true);
|
|
475
|
+
this.state.editor.focus();
|
|
476
|
+
} catch (error) {
|
|
477
|
+
this.state.originalContent = '';
|
|
478
|
+
this.state.dirty = false;
|
|
479
|
+
this.updateCurrentFileLabel();
|
|
480
|
+
this.updateSaveButton();
|
|
481
|
+
this.setStatus(error.message || 'Failed to load file');
|
|
482
|
+
this.showBanner(error.message || 'Failed to load file', 'error');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async activate() {
|
|
487
|
+
this.updateWorkingDirectoryState();
|
|
488
|
+
|
|
489
|
+
if (!this.state.agent) {
|
|
490
|
+
this.renderTree();
|
|
491
|
+
this.setStatus('Select an agent to browse files.');
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!this.state.agent.working_directory) {
|
|
496
|
+
this.renderTree();
|
|
497
|
+
this.setStatus('Working Directory is required for the selected agent.');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
this.setStatus('Loading file browser...');
|
|
502
|
+
this.renderTree();
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
await this.ensureMonaco();
|
|
506
|
+
} catch (error) {
|
|
507
|
+
this.setStatus('Monaco Editor failed to load');
|
|
508
|
+
this.showBanner(error.message || 'Monaco Editor failed to load', 'error');
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!this.state.treeCache.has('')) {
|
|
513
|
+
await this.loadDirectory('');
|
|
514
|
+
} else {
|
|
515
|
+
this.renderTree();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!this.state.selectedFilePath) {
|
|
519
|
+
this.setStatus(this.state.agent ? 'Select a file to preview and edit it.' : 'Select an agent to browse files.');
|
|
520
|
+
} else if (this.state.editor) {
|
|
521
|
+
this.state.editor.layout();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async saveCurrentFile() {
|
|
526
|
+
if (!this.state.canWrite) {
|
|
527
|
+
showToast('Insufficient permission to save file', 'error');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (!this.state.editor || !this.state.selectedFilePath || this.state.saving || !this.getAgentId()) return;
|
|
531
|
+
this.state.saving = true;
|
|
532
|
+
this.updateSaveButton();
|
|
533
|
+
this.hideBanner();
|
|
534
|
+
this.setStatus(`Saving ${this.state.selectedFilePath}...`);
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
const res = await fetch(`/api/agents/${this.getAgentId()}/files/content`, {
|
|
538
|
+
method: 'PUT',
|
|
539
|
+
headers: apiHeaders(),
|
|
540
|
+
body: JSON.stringify({
|
|
541
|
+
path: this.state.selectedFilePath,
|
|
542
|
+
content: this.state.editor.getValue(),
|
|
543
|
+
}),
|
|
544
|
+
});
|
|
545
|
+
if (!res.ok) {
|
|
546
|
+
const err = await res.json().catch(() => ({}));
|
|
547
|
+
throw new Error(err.error || 'Failed to save file');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
this.state.originalContent = this.state.editor.getValue();
|
|
551
|
+
this.state.dirty = false;
|
|
552
|
+
this.updateCurrentFileLabel();
|
|
553
|
+
this.updateSaveButton();
|
|
554
|
+
this.setStatus(`Saved ${this.state.selectedFilePath}`);
|
|
555
|
+
this.showBanner(`Saved ${this.state.selectedFilePath}`, 'success');
|
|
556
|
+
showToast('File saved', 'success');
|
|
557
|
+
const parentPath = this.state.selectedFilePath.includes('/')
|
|
558
|
+
? this.state.selectedFilePath.slice(0, this.state.selectedFilePath.lastIndexOf('/'))
|
|
559
|
+
: '';
|
|
560
|
+
await this.loadDirectory(parentPath);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
this.updateSaveButton();
|
|
563
|
+
this.setStatus(error.message || 'Failed to save file');
|
|
564
|
+
this.showBanner(error.message || 'Failed to save file', 'error');
|
|
565
|
+
showToast(error.message || 'Failed to save file', 'error');
|
|
566
|
+
} finally {
|
|
567
|
+
this.state.saving = false;
|
|
568
|
+
this.updateSaveButton();
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
toggleDirectory(entry) {
|
|
573
|
+
if (this.state.expandedDirs.has(entry.path)) {
|
|
574
|
+
this.state.expandedDirs.delete(entry.path);
|
|
575
|
+
this.renderTree();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this.state.expandedDirs.add(entry.path);
|
|
580
|
+
this.renderTree();
|
|
581
|
+
if (!this.state.treeCache.has(entry.path)) {
|
|
582
|
+
this.loadDirectory(entry.path);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
handleTreeClick(encodedPath) {
|
|
587
|
+
const filePath = decodeURIComponent(encodedPath);
|
|
588
|
+
const entry = this.state.treeIndex.get(filePath);
|
|
589
|
+
if (!entry) return;
|
|
590
|
+
if (entry.type === 'dir') {
|
|
591
|
+
this.toggleDirectory(entry);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this.openFile(entry.path);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
toggleHiddenFiles(checked) {
|
|
598
|
+
const input = this.el('showHiddenId');
|
|
599
|
+
if (input) input.checked = !!checked;
|
|
600
|
+
this.resetTreeState();
|
|
601
|
+
if (this.isVisible()) {
|
|
602
|
+
this.activate();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
showBrowser() {
|
|
607
|
+
this.applyMobileEditorFocus(false);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
handleWindowResize() {
|
|
611
|
+
if (this.state.editor && this.isVisible()) {
|
|
612
|
+
this.state.editor.layout();
|
|
613
|
+
}
|
|
614
|
+
if (!this.isNarrowViewport()) {
|
|
615
|
+
this.applyMobileEditorFocus(false);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
downloadFile(encodedPath) {
|
|
620
|
+
const filePath = decodeURIComponent(encodedPath);
|
|
621
|
+
if (!this.getAgentId()) return;
|
|
622
|
+
const a = document.createElement('a');
|
|
623
|
+
a.href = `/api/agents/${this.getAgentId()}/files/download?path=${encodeURIComponent(filePath)}`;
|
|
624
|
+
a.download = filePath.split('/').pop() || filePath;
|
|
625
|
+
document.body.appendChild(a);
|
|
626
|
+
a.click();
|
|
627
|
+
document.body.removeChild(a);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
triggerUpload() {
|
|
631
|
+
if (!this.getAgentId() || !this.state.agent?.working_directory) {
|
|
632
|
+
this.showBanner('Select an agent with a working directory first', 'error');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const input = document.createElement('input');
|
|
636
|
+
input.type = 'file';
|
|
637
|
+
input.multiple = true;
|
|
638
|
+
input.style.display = 'none';
|
|
639
|
+
input.addEventListener('change', () => {
|
|
640
|
+
if (input.files && input.files.length > 0) {
|
|
641
|
+
this.uploadFiles(input.files);
|
|
642
|
+
}
|
|
643
|
+
document.body.removeChild(input);
|
|
644
|
+
});
|
|
645
|
+
document.body.appendChild(input);
|
|
646
|
+
input.click();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async uploadFiles(files) {
|
|
650
|
+
if (!this.getAgentId()) return;
|
|
651
|
+
const currentDir = this.getCurrentBrowseDir();
|
|
652
|
+
|
|
653
|
+
for (const file of files) {
|
|
654
|
+
const formData = new FormData();
|
|
655
|
+
formData.append('path', currentDir);
|
|
656
|
+
formData.append('file', file);
|
|
657
|
+
|
|
658
|
+
try {
|
|
659
|
+
const res = await fetch(`/api/agents/${this.getAgentId()}/files/upload`, {
|
|
660
|
+
method: 'POST',
|
|
661
|
+
// No Content-Type header — let browser set multipart boundary
|
|
662
|
+
body: formData,
|
|
663
|
+
});
|
|
664
|
+
if (!res.ok) {
|
|
665
|
+
const err = await res.json().catch(() => ({}));
|
|
666
|
+
throw new Error(err.error || 'Upload failed');
|
|
667
|
+
}
|
|
668
|
+
} catch (error) {
|
|
669
|
+
this.showBanner(`Failed to upload ${file.name}: ${error.message}`, 'error');
|
|
670
|
+
if (typeof showToast === 'function') showToast(`Upload failed: ${file.name}`, 'error');
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
this.showBanner(`Uploaded ${files.length} file(s)`, 'success');
|
|
676
|
+
if (typeof showToast === 'function') showToast(`Uploaded ${files.length} file(s)`, 'success');
|
|
677
|
+
await this.loadDirectory(currentDir);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
getCurrentBrowseDir() {
|
|
681
|
+
// Return the deepest expanded directory, or '' for root
|
|
682
|
+
const expanded = Array.from(this.state.expandedDirs).filter(d => d !== '');
|
|
683
|
+
if (expanded.length === 0) return '';
|
|
684
|
+
// Return the longest path (deepest dir)
|
|
685
|
+
expanded.sort((a, b) => b.length - a.length);
|
|
686
|
+
return expanded[0];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
handleSaveShortcut(event) {
|
|
690
|
+
if (!this.isVisible()) return;
|
|
691
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
|
|
692
|
+
event.preventDefault();
|
|
693
|
+
this.saveCurrentFile();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
window.ArgusFilesPanel = {
|
|
699
|
+
create(options) {
|
|
700
|
+
return new FilesPanel(options);
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
})();
|