vg-coder-cli 2.0.17 → 2.0.20
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/package.json +1 -1
- package/src/server/api-server.js +57 -13
- package/src/server/views/css/editor.css +161 -0
- package/src/server/views/css/git-view.css +50 -15
- package/src/server/views/css/monaco.css +15 -0
- package/src/server/views/dashboard.html +96 -60
- package/src/server/views/js/features/editor-tabs.js +123 -0
- package/src/server/views/js/features/git-view.js +10 -1
- package/src/server/views/js/features/iframe-manager.js +8 -6
- package/src/server/views/js/features/monaco-manager.js +160 -0
- package/src/server/views/js/features/structure.js +20 -6
- package/src/server/views/js/main.js +12 -0
- package/vg-coder-cli-2.0.20.tgz +0 -0
- package/vg-coder-cli-2.0.21.tgz +0 -0
- package/vg-coder-cli-2.0.17.tgz +0 -0
package/package.json
CHANGED
package/src/server/api-server.js
CHANGED
|
@@ -94,12 +94,58 @@ class ApiServer {
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// --- FILE OPERATIONS (NEW) ---
|
|
98
|
+
|
|
99
|
+
// Read raw file content
|
|
100
|
+
this.app.get('/api/read-file', async (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const filePath = req.query.path;
|
|
103
|
+
if (!filePath) return res.status(400).json({ error: 'Missing path' });
|
|
104
|
+
|
|
105
|
+
// Prevent directory traversal (basic check)
|
|
106
|
+
const resolvedPath = path.resolve(this.workingDir, filePath);
|
|
107
|
+
if (!resolvedPath.startsWith(this.workingDir)) {
|
|
108
|
+
// Allow reading but log warning - in dev tool we might want flexibility
|
|
109
|
+
// For strict mode: return res.status(403).json({ error: 'Access denied' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!await fs.pathExists(resolvedPath)) {
|
|
113
|
+
return res.status(404).json({ error: 'File not found' });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const content = await fs.readFile(resolvedPath, 'utf8');
|
|
117
|
+
res.json({ content, path: filePath });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
res.status(500).json({ error: error.message });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Save file content
|
|
124
|
+
this.app.post('/api/save-file', async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const { path: filePath, content } = req.body;
|
|
127
|
+
if (!filePath || content === undefined) return res.status(400).json({ error: 'Missing data' });
|
|
128
|
+
|
|
129
|
+
const resolvedPath = path.resolve(this.workingDir, filePath);
|
|
130
|
+
|
|
131
|
+
// Security check
|
|
132
|
+
if (!resolvedPath.startsWith(this.workingDir)) {
|
|
133
|
+
// For strict mode: return res.status(403).json({ error: 'Access denied' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await fs.writeFile(resolvedPath, content, 'utf8');
|
|
137
|
+
res.json({ success: true });
|
|
138
|
+
} catch (error) {
|
|
139
|
+
res.status(500).json({ error: error.message });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
97
143
|
// --- GIT API START ---
|
|
98
144
|
|
|
99
145
|
// Get Git Status
|
|
100
146
|
this.app.get('/api/git/status', async (req, res) => {
|
|
101
147
|
try {
|
|
102
|
-
const { stdout } = await execAsync('git status --porcelain', { cwd: this.workingDir });
|
|
148
|
+
const { stdout } = await execAsync('git status --porcelain -u', { cwd: this.workingDir });
|
|
103
149
|
|
|
104
150
|
const staged = [];
|
|
105
151
|
const unstaged = [];
|
|
@@ -157,23 +203,16 @@ class ApiServer {
|
|
|
157
203
|
}
|
|
158
204
|
});
|
|
159
205
|
|
|
160
|
-
// Git Discard
|
|
206
|
+
// Git Discard
|
|
161
207
|
this.app.post('/api/git/discard', async (req, res) => {
|
|
162
208
|
try {
|
|
163
|
-
const { files } = req.body;
|
|
164
|
-
|
|
165
|
-
// Discard All
|
|
209
|
+
const { files } = req.body;
|
|
166
210
|
if (files.includes('*')) {
|
|
167
|
-
// Restore tracked files
|
|
168
211
|
try { await execAsync('git restore .', { cwd: this.workingDir }); } catch (e) {}
|
|
169
|
-
// Clean untracked files
|
|
170
212
|
try { await execAsync('git clean -fd', { cwd: this.workingDir }); } catch (e) {}
|
|
171
213
|
} else {
|
|
172
|
-
// Discard specific files
|
|
173
214
|
for (const file of files) {
|
|
174
|
-
// Try restore (for tracked modified/deleted)
|
|
175
215
|
try { await execAsync(`git restore "${file}"`, { cwd: this.workingDir }); } catch (e) {}
|
|
176
|
-
// Try clean (for untracked)
|
|
177
216
|
try { await execAsync(`git clean -f "${file}"`, { cwd: this.workingDir }); } catch (e) {}
|
|
178
217
|
}
|
|
179
218
|
}
|
|
@@ -204,11 +243,17 @@ class ApiServer {
|
|
|
204
243
|
const type = req.query.type || 'working';
|
|
205
244
|
|
|
206
245
|
let cmd = '';
|
|
207
|
-
|
|
208
246
|
if (type === 'staged') {
|
|
209
247
|
cmd = file ? `git diff --cached -- "${file}"` : `git diff --cached`;
|
|
210
248
|
} else {
|
|
211
249
|
if (file) {
|
|
250
|
+
try {
|
|
251
|
+
const filePath = path.join(this.workingDir, file);
|
|
252
|
+
if (await fs.pathExists(filePath)) {
|
|
253
|
+
const stat = await fs.stat(filePath);
|
|
254
|
+
if (stat.isDirectory()) return res.json({ diff: '' });
|
|
255
|
+
}
|
|
256
|
+
} catch (e) {}
|
|
212
257
|
const { stdout: isUntracked } = await execAsync(`git ls-files --others --exclude-standard "${file}"`, { cwd: this.workingDir });
|
|
213
258
|
if (isUntracked.trim()) {
|
|
214
259
|
const content = await fs.readFile(path.join(this.workingDir, file), 'utf8');
|
|
@@ -221,7 +266,6 @@ class ApiServer {
|
|
|
221
266
|
cmd = `git diff`;
|
|
222
267
|
}
|
|
223
268
|
}
|
|
224
|
-
|
|
225
269
|
const { stdout } = await execAsync(cmd, { cwd: this.workingDir, maxBuffer: 20 * 1024 * 1024 });
|
|
226
270
|
res.json({ diff: stdout });
|
|
227
271
|
} catch (error) {
|
|
@@ -230,7 +274,7 @@ class ApiServer {
|
|
|
230
274
|
}
|
|
231
275
|
});
|
|
232
276
|
|
|
233
|
-
// ---
|
|
277
|
+
// --- GENERAL API ---
|
|
234
278
|
|
|
235
279
|
this.app.post('/api/analyze', async (req, res) => {
|
|
236
280
|
const { path: projectPath, options = {}, specificFiles } = req.body;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/* Container chính cho khu vực Editor */
|
|
2
|
+
.editor-container {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
height: 100%;
|
|
6
|
+
background: var(--ios-bg);
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Thanh Tabs - Layout Flexbox mới */
|
|
11
|
+
.tabs-header {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: space-between; /* Tabs trái, Actions phải */
|
|
15
|
+
background: #252526;
|
|
16
|
+
height: 35px;
|
|
17
|
+
border-bottom: 1px solid #1e1e1e;
|
|
18
|
+
user-select: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Khu vực chứa các Tabs (Scrollable) */
|
|
22
|
+
.tabs-scroll-area {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
overflow-x: auto;
|
|
26
|
+
flex: 1; /* Chiếm phần lớn không gian */
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
.tabs-scroll-area::-webkit-scrollbar {
|
|
30
|
+
height: 0px; /* Ẩn scrollbar cho gọn */
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Khu vực Actions bên phải */
|
|
34
|
+
.tabs-actions {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
padding: 0 5px;
|
|
38
|
+
background: #252526;
|
|
39
|
+
height: 100%;
|
|
40
|
+
border-left: 1px solid #1e1e1e;
|
|
41
|
+
box-shadow: -5px 0 10px rgba(0,0,0,0.2); /* Tạo bóng đổ nhẹ ngăn cách */
|
|
42
|
+
z-index: 5;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Style cho Tab Item */
|
|
46
|
+
.tab-item {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 0 10px;
|
|
50
|
+
min-width: fit-content;
|
|
51
|
+
max-width: 200px;
|
|
52
|
+
height: 100%;
|
|
53
|
+
background: #2d2d2d;
|
|
54
|
+
color: #969696;
|
|
55
|
+
border-right: 1px solid #252526;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
transition: background 0.2s;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-item:hover {
|
|
62
|
+
background: #383838;
|
|
63
|
+
color: #e0e0e0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.tab-item.active {
|
|
67
|
+
background: #1e1e1e;
|
|
68
|
+
color: #ffffff;
|
|
69
|
+
border-top: 1px solid var(--ios-blue);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tab-icon {
|
|
73
|
+
margin-right: 6px;
|
|
74
|
+
font-size: 14px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tab-name {
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
text-overflow: ellipsis;
|
|
81
|
+
max-width: 150px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tab-close {
|
|
85
|
+
margin-left: 8px;
|
|
86
|
+
border-radius: 3px;
|
|
87
|
+
width: 16px;
|
|
88
|
+
height: 16px;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
opacity: 0;
|
|
93
|
+
font-size: 11px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.tab-item:hover .tab-close,
|
|
97
|
+
.tab-item.active .tab-close {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.tab-close:hover {
|
|
102
|
+
background: rgba(255, 255, 255, 0.2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Style đặc biệt cho Select Box trong Tab */
|
|
106
|
+
.ai-provider-select {
|
|
107
|
+
background: transparent;
|
|
108
|
+
border: none;
|
|
109
|
+
color: inherit;
|
|
110
|
+
font-family: inherit;
|
|
111
|
+
font-size: 13px;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
outline: none;
|
|
115
|
+
padding: 0;
|
|
116
|
+
margin: 0;
|
|
117
|
+
max-width: 120px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ai-provider-select option {
|
|
121
|
+
background: #252526;
|
|
122
|
+
color: white;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Action Buttons ở góc phải */
|
|
126
|
+
.action-btn-mini {
|
|
127
|
+
background: transparent;
|
|
128
|
+
border: none;
|
|
129
|
+
color: #cccccc;
|
|
130
|
+
width: 28px;
|
|
131
|
+
height: 28px;
|
|
132
|
+
border-radius: 4px;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
font-size: 14px;
|
|
138
|
+
margin-left: 2px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.action-btn-mini:hover {
|
|
142
|
+
background: rgba(255,255,255,0.1);
|
|
143
|
+
color: white;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.action-btn-mini.active {
|
|
147
|
+
background: rgba(0, 122, 255, 0.2);
|
|
148
|
+
color: var(--ios-blue);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Khu vực nội dung Code */
|
|
152
|
+
.editor-body {
|
|
153
|
+
flex: 1;
|
|
154
|
+
position: relative;
|
|
155
|
+
background: #1e1e1e;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.view-mode-hidden {
|
|
160
|
+
display: none !important;
|
|
161
|
+
}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
.git-view-container {
|
|
46
46
|
position: absolute;
|
|
47
|
-
top:
|
|
47
|
+
top: 35px;
|
|
48
48
|
left: 0;
|
|
49
49
|
right: 0;
|
|
50
50
|
bottom: 0;
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
|
|
194
194
|
.git-indent-guide {
|
|
195
195
|
display: inline-block;
|
|
196
|
-
width: 16px;
|
|
196
|
+
width: 16px;
|
|
197
197
|
height: 100%;
|
|
198
198
|
flex-shrink: 0;
|
|
199
199
|
}
|
|
@@ -208,11 +208,11 @@
|
|
|
208
208
|
flex-shrink: 0;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
.git-tree-node.collapsed
|
|
211
|
+
.git-tree-node.collapsed>.git-tree-content>.git-arrow {
|
|
212
212
|
transform: rotate(-90deg);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
.git-tree-node.collapsed
|
|
215
|
+
.git-tree-node.collapsed>ul {
|
|
216
216
|
display: none;
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -226,10 +226,23 @@
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/* Status Colors */
|
|
229
|
-
.git-status-M {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
.git-status-M {
|
|
230
|
+
color: #d29922;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.git-status-A,
|
|
234
|
+
.git-status-U {
|
|
235
|
+
color: #3fb950;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.git-status-D {
|
|
239
|
+
color: #f85149;
|
|
240
|
+
text-decoration: line-through;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.git-status-R {
|
|
244
|
+
color: #d29922;
|
|
245
|
+
}
|
|
233
246
|
|
|
234
247
|
.git-label {
|
|
235
248
|
flex: 1;
|
|
@@ -245,6 +258,7 @@
|
|
|
245
258
|
.git-file-label {
|
|
246
259
|
color: #8b949e;
|
|
247
260
|
}
|
|
261
|
+
|
|
248
262
|
.git-tree-content:hover .git-file-label,
|
|
249
263
|
.git-tree-content.selected .git-file-label {
|
|
250
264
|
color: #e6edf3;
|
|
@@ -304,20 +318,41 @@
|
|
|
304
318
|
}
|
|
305
319
|
|
|
306
320
|
/* Diff2Html Overrides */
|
|
307
|
-
.d2h-wrapper * {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
.d2h-
|
|
321
|
+
.d2h-wrapper * {
|
|
322
|
+
box-sizing: border-box;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.d2h-file-list-wrapper {
|
|
326
|
+
display: none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.d2h-wrapper {
|
|
330
|
+
margin: 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.d2h-file-header {
|
|
334
|
+
display: none;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.d2h-code-line-ctn,
|
|
338
|
+
.d2h-code-line,
|
|
339
|
+
.hljs {
|
|
312
340
|
color: #e6edf3 !important;
|
|
313
341
|
background: transparent !important;
|
|
314
342
|
font-family: Menlo, Monaco, Consolas, monospace;
|
|
315
343
|
font-size: 12px;
|
|
316
344
|
}
|
|
345
|
+
|
|
317
346
|
.d2h-code-side-linenumber {
|
|
318
347
|
background: #0d1117 !important;
|
|
319
348
|
border-color: #30363d !important;
|
|
320
349
|
color: #6e7681 !important;
|
|
321
350
|
}
|
|
322
|
-
|
|
323
|
-
.d2h-
|
|
351
|
+
|
|
352
|
+
.d2h-ins {
|
|
353
|
+
background-color: rgba(46, 160, 67, 0.15) !important;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.d2h-del {
|
|
357
|
+
background-color: rgba(248, 81, 73, 0.15) !important;
|
|
358
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.monaco-container {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
background: #1e1e1e; /* Màu nền mặc định của VS Code Dark */
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Ẩn hiện container */
|
|
8
|
+
.view-mode-hidden {
|
|
9
|
+
display: none !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Fix z-index để context menu của Monaco đè lên được các thành phần khác */
|
|
13
|
+
.monaco-editor .context-view.widget {
|
|
14
|
+
z-index: 10000 !important;
|
|
15
|
+
}
|
|
@@ -11,14 +11,15 @@
|
|
|
11
11
|
<link rel="stylesheet" href="/css/iframe.css">
|
|
12
12
|
<link rel="stylesheet" href="/css/git-view.css">
|
|
13
13
|
<link rel="stylesheet" href="/css/terminal.css">
|
|
14
|
+
<link rel="stylesheet" href="/css/editor.css">
|
|
15
|
+
<link rel="stylesheet" href="/css/monaco.css">
|
|
14
16
|
|
|
15
17
|
<!-- Syntax Highlighting -->
|
|
16
18
|
<link rel="stylesheet"
|
|
17
19
|
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" />
|
|
18
20
|
|
|
19
|
-
<!-- Git Diff -->
|
|
20
|
-
<link rel="stylesheet"
|
|
21
|
-
href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
|
|
21
|
+
<!-- Git Diff CSS (JSDelivr Bundle) -->
|
|
22
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/css/diff2html.min.css" />
|
|
22
23
|
|
|
23
24
|
<!-- Terminal Dependencies -->
|
|
24
25
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
|
|
@@ -26,8 +27,25 @@
|
|
|
26
27
|
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
|
27
28
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
|
28
29
|
|
|
29
|
-
<!--
|
|
30
|
-
<script
|
|
30
|
+
<!-- Monaco Editor Loader -->
|
|
31
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"></script>
|
|
32
|
+
|
|
33
|
+
<!-- FIX: AMD Loader Conflict Patch -->
|
|
34
|
+
<script>
|
|
35
|
+
var __amdDefine = window.define;
|
|
36
|
+
var __amdRequire = window.require;
|
|
37
|
+
window.define = undefined;
|
|
38
|
+
window.require = undefined;
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<!-- Git Diff JS (JSDelivr Bundle) -->
|
|
42
|
+
<script src="https://cdn.jsdelivr.net/npm/diff2html@3.4.47/bundles/js/diff2html-ui.min.js"></script>
|
|
43
|
+
|
|
44
|
+
<!-- FIX: Restore AMD Loader -->
|
|
45
|
+
<script>
|
|
46
|
+
window.define = __amdDefine;
|
|
47
|
+
window.require = __amdRequire;
|
|
48
|
+
</script>
|
|
31
49
|
|
|
32
50
|
<script>
|
|
33
51
|
(function () {
|
|
@@ -163,73 +181,91 @@
|
|
|
163
181
|
</div>
|
|
164
182
|
</div>
|
|
165
183
|
|
|
166
|
-
<!-- CỘT PHẢI: AI Iframe -->
|
|
184
|
+
<!-- CỘT PHẢI: AI Iframe & Editor -->
|
|
167
185
|
<div class="right-panel">
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<!-- Options filled by JS -->
|
|
173
|
-
</select>
|
|
174
|
-
</div>
|
|
186
|
+
<!-- HEADER MỚI: Tích hợp Tabs & Actions -->
|
|
187
|
+
<div id="tabs-header" class="tabs-header">
|
|
188
|
+
<!-- Vùng Tabs Scrollable -->
|
|
189
|
+
<div class="tabs-scroll-area">
|
|
175
190
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
<!-- AI Tab (STATIC) -->
|
|
192
|
+
<div class="tab-item active" id="ai-tab" onclick="window.switchTab('ai-assistant')"
|
|
193
|
+
data-path="ai-assistant">
|
|
194
|
+
<span class="tab-icon">🤖</span>
|
|
195
|
+
<!-- Select Box nằm ngay trong tên Tab -->
|
|
196
|
+
<select id="ai-provider-select" class="ai-provider-select" onclick="event.stopPropagation()">
|
|
197
|
+
<!-- Options filled by JS -->
|
|
198
|
+
</select>
|
|
199
|
+
</div>
|
|
179
200
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
<!-- Dynamic File Tabs -->
|
|
202
|
+
<div id="file-tabs-container" style="display:flex; height:100%;"></div>
|
|
203
|
+
</div>
|
|
183
204
|
|
|
184
|
-
|
|
205
|
+
<!-- Global Actions -->
|
|
206
|
+
<div class="tabs-actions">
|
|
207
|
+
<button id="guide-toggle-btn" class="action-btn-mini" title="Show Installation Guide">🧩</button>
|
|
208
|
+
<button id="git-refresh-btn" class="action-btn-mini" style="display:none;"
|
|
209
|
+
title="Refresh Git">↻</button>
|
|
210
|
+
<button id="git-view-toggle" class="action-btn-mini" title="Toggle Git View">
|
|
211
|
+
<span style="font-size:12px">Git</span>
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
185
214
|
</div>
|
|
186
215
|
|
|
187
216
|
<!-- Git View Container -->
|
|
188
217
|
<div id="git-view-container" class="git-view-container"></div>
|
|
189
218
|
|
|
190
|
-
<!--
|
|
191
|
-
<div class="
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<
|
|
219
|
+
<!-- Editor Container -->
|
|
220
|
+
<div class="editor-container" style="flex: 1; position: relative; overflow: hidden;">
|
|
221
|
+
|
|
222
|
+
<!-- AI Iframe -->
|
|
223
|
+
<div class="ai-iframe-container">
|
|
224
|
+
<div id="iframe-placeholder" class="iframe-placeholder hidden">
|
|
225
|
+
<div class="extension-guide-center">
|
|
226
|
+
<button class="guide-close-btn" id="guide-close-btn" title="Close Guide">×</button>
|
|
227
|
+
<span class="guide-icon">🧩</span>
|
|
228
|
+
<h3 class="guide-title">Cài đặt VG Coder Extension</h3>
|
|
229
|
+
<p class="guide-desc">
|
|
230
|
+
Không thấy trang web? AI Provider chặn hiển thị trong Iframe.<br>
|
|
231
|
+
Hãy cài đặt Extension để bỏ qua các giới hạn này.
|
|
232
|
+
</p>
|
|
233
|
+
<ol class="guide-steps">
|
|
234
|
+
<li class="guide-step">
|
|
235
|
+
<span class="step-number">1</span>
|
|
236
|
+
Copy link và dán vào tab mới:
|
|
237
|
+
<div class="url-box">
|
|
238
|
+
<input type="text" id="chrome-url-input-center" class="guide-input" readonly
|
|
239
|
+
value="chrome://extensions" onclick="this.select()">
|
|
240
|
+
<button class="guide-btn-copy" onclick="copyChromeUrl(event)">Copy</button>
|
|
241
|
+
</div>
|
|
242
|
+
</li>
|
|
243
|
+
<li class="guide-step">
|
|
244
|
+
<span class="step-number">2</span>
|
|
245
|
+
Bật <b>Developer mode</b> (Góc phải trên) → <b>Load unpacked</b>
|
|
246
|
+
</li>
|
|
247
|
+
<li class="guide-step">
|
|
248
|
+
<span class="step-number">3</span>
|
|
249
|
+
Chọn thư mục bên dưới:
|
|
250
|
+
<div class="path-box">
|
|
251
|
+
<input type="text" id="extension-path-input-center" class="guide-input" readonly
|
|
252
|
+
value="Loading..." onclick="this.select()">
|
|
253
|
+
<button class="guide-btn-copy" onclick="copyExtensionPath(event)">Copy</button>
|
|
254
|
+
</div>
|
|
255
|
+
</li>
|
|
256
|
+
</ol>
|
|
257
|
+
<div class="guide-footer">
|
|
258
|
+
<a id="ai-placeholder-link" href="#" target="_blank" class="link-fallback">Mở tab mới
|
|
259
|
+
↗</a>
|
|
260
|
+
<button id="guide-done-btn" class="btn-primary-guide">Đã xong / Tải lại</button>
|
|
261
|
+
</div>
|
|
229
262
|
</div>
|
|
230
263
|
</div>
|
|
264
|
+
<iframe id="ai-iframe" src="" title="AI Integration"></iframe>
|
|
231
265
|
</div>
|
|
232
|
-
|
|
266
|
+
|
|
267
|
+
<!-- Monaco Editor Container -->
|
|
268
|
+
<div id="monaco-container" class="monaco-container view-mode-hidden"></div>
|
|
233
269
|
</div>
|
|
234
270
|
</div>
|
|
235
271
|
</div>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { openFileInMonaco, saveViewState, disposeModel } from './monaco-manager.js';
|
|
2
|
+
|
|
3
|
+
let activeTabs = []; // Array of { path, name, icon }
|
|
4
|
+
let currentPath = 'ai-assistant'; // Default
|
|
5
|
+
|
|
6
|
+
export function initEditorTabs() {
|
|
7
|
+
renderTabs();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function openFileTab(path, name, icon = '📄') {
|
|
11
|
+
// 1. Ẩn AI, hiện Monaco
|
|
12
|
+
toggleViewMode('code');
|
|
13
|
+
|
|
14
|
+
// 2. Lưu state tab cũ
|
|
15
|
+
if (currentPath && currentPath !== 'ai-assistant' && currentPath !== path) {
|
|
16
|
+
saveViewState(currentPath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 3. Logic Tabs Array
|
|
20
|
+
const existingTab = activeTabs.find(t => t.path === path);
|
|
21
|
+
if (!existingTab) {
|
|
22
|
+
activeTabs.push({ path, name, icon });
|
|
23
|
+
renderTabs();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 4. Update UI
|
|
27
|
+
currentPath = path;
|
|
28
|
+
updateTabUI();
|
|
29
|
+
|
|
30
|
+
// 5. Open in Monaco
|
|
31
|
+
await openFileInMonaco(path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function switchTab(path) {
|
|
35
|
+
if (currentPath && currentPath !== 'ai-assistant') {
|
|
36
|
+
saveViewState(currentPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
currentPath = path;
|
|
40
|
+
updateTabUI();
|
|
41
|
+
|
|
42
|
+
if (path === 'ai-assistant') {
|
|
43
|
+
toggleViewMode('ai');
|
|
44
|
+
} else {
|
|
45
|
+
toggleViewMode('code');
|
|
46
|
+
openFileInMonaco(path);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function closeTab(event, path) {
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
|
|
53
|
+
const index = activeTabs.findIndex(t => t.path === path);
|
|
54
|
+
if (index === -1) return;
|
|
55
|
+
|
|
56
|
+
activeTabs.splice(index, 1);
|
|
57
|
+
disposeModel(path);
|
|
58
|
+
|
|
59
|
+
if (currentPath === path) {
|
|
60
|
+
if (activeTabs.length > 0) {
|
|
61
|
+
const nextTab = activeTabs[activeTabs.length - 1];
|
|
62
|
+
switchTab(nextTab.path);
|
|
63
|
+
} else {
|
|
64
|
+
switchTab('ai-assistant');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
renderTabs();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderTabs() {
|
|
71
|
+
// Chỉ render các file tabs vào container con
|
|
72
|
+
const container = document.getElementById('file-tabs-container');
|
|
73
|
+
if (!container) return;
|
|
74
|
+
|
|
75
|
+
let html = '';
|
|
76
|
+
activeTabs.forEach(tab => {
|
|
77
|
+
html += `
|
|
78
|
+
<div class="tab-item"
|
|
79
|
+
onclick="window.switchTab('${tab.path}')"
|
|
80
|
+
data-path="${tab.path}"
|
|
81
|
+
title="${tab.path}">
|
|
82
|
+
<span class="tab-icon">${tab.icon}</span>
|
|
83
|
+
<span class="tab-name">${tab.name}</span>
|
|
84
|
+
<span class="tab-close" onclick="window.closeTab(event, '${tab.path}')">×</span>
|
|
85
|
+
</div>
|
|
86
|
+
`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
container.innerHTML = html;
|
|
90
|
+
updateTabUI();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function updateTabUI() {
|
|
94
|
+
// 1. Update Static AI Tab
|
|
95
|
+
const aiTab = document.getElementById('ai-tab');
|
|
96
|
+
if (currentPath === 'ai-assistant') aiTab.classList.add('active');
|
|
97
|
+
else aiTab.classList.remove('active');
|
|
98
|
+
|
|
99
|
+
// 2. Update Dynamic Tabs
|
|
100
|
+
const fileTabs = document.querySelectorAll('#file-tabs-container .tab-item');
|
|
101
|
+
fileTabs.forEach(el => {
|
|
102
|
+
if (el.dataset.path === currentPath) el.classList.add('active');
|
|
103
|
+
else el.classList.remove('active');
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toggleViewMode(mode) {
|
|
108
|
+
const aiContainer = document.querySelector('.ai-iframe-container');
|
|
109
|
+
const monacoContainer = document.getElementById('monaco-container');
|
|
110
|
+
|
|
111
|
+
if (mode === 'code') {
|
|
112
|
+
aiContainer.classList.add('view-mode-hidden');
|
|
113
|
+
monacoContainer.classList.remove('view-mode-hidden');
|
|
114
|
+
} else {
|
|
115
|
+
aiContainer.classList.remove('view-mode-hidden');
|
|
116
|
+
monacoContainer.classList.add('view-mode-hidden');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Export global helpers
|
|
121
|
+
window.switchTab = switchTab;
|
|
122
|
+
window.closeTab = closeTab;
|
|
123
|
+
window.openFileTab = openFileTab;
|
|
@@ -344,6 +344,15 @@ async function loadDiffView(filePath, type) {
|
|
|
344
344
|
const viewer = document.getElementById('git-diff-viewer');
|
|
345
345
|
viewer.innerHTML = '<div class="git-empty-state">Loading diff...</div>';
|
|
346
346
|
|
|
347
|
+
// SAFE CHECK FOR UI LIBRARY
|
|
348
|
+
// We check window.Diff2HtmlUI first (standard for bundles)
|
|
349
|
+
const UIConstructor = window.Diff2HtmlUI;
|
|
350
|
+
|
|
351
|
+
if (!UIConstructor) {
|
|
352
|
+
viewer.innerHTML = '<div class="git-empty-state" style="color:#f85149">Error: Diff2HtmlUI library not loaded correctly.<br>Please check your internet connection or CDN availability.</div>';
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
347
356
|
try {
|
|
348
357
|
const diff = await getGitDiff(filePath, type === 'staged' ? 'staged' : 'working');
|
|
349
358
|
|
|
@@ -353,7 +362,7 @@ async function loadDiffView(filePath, type) {
|
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
viewer.innerHTML = '';
|
|
356
|
-
const ui = new
|
|
365
|
+
const ui = new UIConstructor(viewer, diff, {
|
|
357
366
|
drawFileList: false,
|
|
358
367
|
matching: 'lines',
|
|
359
368
|
outputFormat: 'side-by-side',
|
|
@@ -9,7 +9,7 @@ const AI_PROVIDERS = [
|
|
|
9
9
|
export function initIframeManager() {
|
|
10
10
|
const select = document.getElementById('ai-provider-select');
|
|
11
11
|
const iframe = document.getElementById('ai-iframe');
|
|
12
|
-
|
|
12
|
+
// Removed externalLink reference
|
|
13
13
|
const placeholderLink = document.getElementById('ai-placeholder-link');
|
|
14
14
|
|
|
15
15
|
// Guide elements
|
|
@@ -42,9 +42,11 @@ export function initIframeManager() {
|
|
|
42
42
|
iframe.src = provider.url;
|
|
43
43
|
}, 50);
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
placeholderLink
|
|
47
|
-
|
|
45
|
+
// Update placeholder link only (external link removed from UI)
|
|
46
|
+
if (placeholderLink) {
|
|
47
|
+
placeholderLink.href = provider.url;
|
|
48
|
+
placeholderLink.textContent = `Mở ${provider.name} tab mới ↗`;
|
|
49
|
+
}
|
|
48
50
|
|
|
49
51
|
localStorage.setItem('ai_provider', providerId);
|
|
50
52
|
};
|
|
@@ -60,11 +62,11 @@ export function initIframeManager() {
|
|
|
60
62
|
// --- GUIDE TOGGLE LOGIC ---
|
|
61
63
|
|
|
62
64
|
const showGuide = () => {
|
|
63
|
-
guideContainer.classList.remove('hidden');
|
|
65
|
+
if (guideContainer) guideContainer.classList.remove('hidden');
|
|
64
66
|
};
|
|
65
67
|
|
|
66
68
|
const hideGuide = () => {
|
|
67
|
-
guideContainer.classList.add('hidden');
|
|
69
|
+
if (guideContainer) guideContainer.classList.add('hidden');
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
const reloadIframe = () => {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { API_BASE } from '../config.js';
|
|
2
|
+
import { showToast } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
let editor = null;
|
|
5
|
+
let models = new Map(); // Map<filePath, { model: ITextModel, viewState: object }>
|
|
6
|
+
let isMonacoLoaded = false;
|
|
7
|
+
|
|
8
|
+
// Cấu hình đường dẫn cho AMD Loader của Monaco
|
|
9
|
+
export function initMonaco() {
|
|
10
|
+
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Đảm bảo Editor đã được khởi tạo
|
|
15
|
+
*/
|
|
16
|
+
async function ensureEditor() {
|
|
17
|
+
if (editor) return editor;
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
require(['vs/editor/editor.main'], function () {
|
|
21
|
+
const container = document.getElementById('monaco-container');
|
|
22
|
+
|
|
23
|
+
// Xác định theme dựa trên theme hiện tại của web
|
|
24
|
+
const currentTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'vs-dark' : 'vs';
|
|
25
|
+
|
|
26
|
+
editor = monaco.editor.create(container, {
|
|
27
|
+
value: '',
|
|
28
|
+
language: 'plaintext',
|
|
29
|
+
theme: currentTheme,
|
|
30
|
+
automaticLayout: true, // Tự động resize
|
|
31
|
+
minimap: { enabled: true },
|
|
32
|
+
fontSize: 13,
|
|
33
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
34
|
+
scrollBeyondLastLine: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Lắng nghe phím tắt Ctrl+S / Cmd+S để lưu
|
|
38
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
39
|
+
saveCurrentFile();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
isMonacoLoaded = true;
|
|
43
|
+
resolve(editor);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mở file vào Editor
|
|
50
|
+
*/
|
|
51
|
+
export async function openFileInMonaco(path) {
|
|
52
|
+
const editorInstance = await ensureEditor();
|
|
53
|
+
|
|
54
|
+
// 1. Nếu Model đã tồn tại trong bộ nhớ -> Dùng lại
|
|
55
|
+
if (models.has(path)) {
|
|
56
|
+
const stored = models.get(path);
|
|
57
|
+
editorInstance.setModel(stored.model);
|
|
58
|
+
if (stored.viewState) {
|
|
59
|
+
editorInstance.restoreViewState(stored.viewState);
|
|
60
|
+
}
|
|
61
|
+
editorInstance.focus();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Nếu chưa -> Fetch nội dung từ Server
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${API_BASE}/api/read-file?path=${encodeURIComponent(path)}`);
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
|
|
70
|
+
if (res.ok) {
|
|
71
|
+
// Xác định ngôn ngữ từ extension
|
|
72
|
+
const language = getLanguageFromPath(path);
|
|
73
|
+
|
|
74
|
+
// Tạo Model mới
|
|
75
|
+
const newModel = monaco.editor.createModel(data.content, language, monaco.Uri.file(path));
|
|
76
|
+
|
|
77
|
+
// Lưu vào cache
|
|
78
|
+
models.set(path, { model: newModel, viewState: null });
|
|
79
|
+
|
|
80
|
+
// Gán vào Editor
|
|
81
|
+
editorInstance.setModel(newModel);
|
|
82
|
+
} else {
|
|
83
|
+
showToast(`Error opening file: ${data.error}`, 'error');
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
showToast(`Failed to load file: ${err.message}`, 'error');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Lưu trạng thái View (Scroll, Cursor) trước khi chuyển tab
|
|
92
|
+
*/
|
|
93
|
+
export function saveViewState(path) {
|
|
94
|
+
if (editor && models.has(path)) {
|
|
95
|
+
const viewState = editor.saveViewState();
|
|
96
|
+
const stored = models.get(path);
|
|
97
|
+
stored.viewState = viewState;
|
|
98
|
+
models.set(path, stored);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Giải phóng Model khi đóng Tab
|
|
104
|
+
*/
|
|
105
|
+
export function disposeModel(path) {
|
|
106
|
+
if (models.has(path)) {
|
|
107
|
+
const stored = models.get(path);
|
|
108
|
+
stored.model.dispose(); // Quan trọng: Tránh memory leak
|
|
109
|
+
models.delete(path);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Cập nhật Theme cho Monaco khi web đổi theme
|
|
115
|
+
*/
|
|
116
|
+
export function updateMonacoTheme(theme) {
|
|
117
|
+
if (editor) {
|
|
118
|
+
monaco.editor.setTheme(theme === 'dark' ? 'vs-dark' : 'vs');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lưu file hiện tại
|
|
124
|
+
*/
|
|
125
|
+
async function saveCurrentFile() {
|
|
126
|
+
const model = editor.getModel();
|
|
127
|
+
if (!model) return;
|
|
128
|
+
|
|
129
|
+
const content = model.getValue();
|
|
130
|
+
const filePath = model.uri.fsPath; // Lấy path từ URI
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(`${API_BASE}/api/save-file`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json' },
|
|
136
|
+
body: JSON.stringify({ path: filePath, content })
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (res.ok) {
|
|
140
|
+
showToast('File saved successfully!', 'success');
|
|
141
|
+
} else {
|
|
142
|
+
showToast('Failed to save file', 'error');
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
showToast('Error saving file: ' + err.message, 'error');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Helper: Map extension to Monaco Language
|
|
150
|
+
function getLanguageFromPath(path) {
|
|
151
|
+
const ext = path.split('.').pop().toLowerCase();
|
|
152
|
+
const map = {
|
|
153
|
+
js: 'javascript', ts: 'typescript', html: 'html', css: 'css',
|
|
154
|
+
json: 'json', md: 'markdown', py: 'python', java: 'java',
|
|
155
|
+
sh: 'shell', xml: 'xml', sql: 'sql'
|
|
156
|
+
};
|
|
157
|
+
return map[ext] || 'plaintext';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { editor };
|
|
@@ -183,15 +183,29 @@ function generateTreeHtml(node) {
|
|
|
183
183
|
const arrow = hasChildren ? '▼' : '';
|
|
184
184
|
const liClass = `tree-li ${hasChildren ? 'has-children' : ''}`;
|
|
185
185
|
|
|
186
|
+
// Actions
|
|
187
|
+
// On click: Toggle if folder, Open Tab if file
|
|
188
|
+
let clickAction = '';
|
|
189
|
+
let cursorStyle = '';
|
|
190
|
+
|
|
191
|
+
if (isDir) {
|
|
192
|
+
if (hasChildren) {
|
|
193
|
+
clickAction = 'onclick="toggleFolder(event)"';
|
|
194
|
+
cursorStyle = 'cursor: pointer;';
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
// File click -> Open in Editor
|
|
198
|
+
// Escape backslashes for Windows paths
|
|
199
|
+
const safePath = (node.relativePath || node.path).replace(/\\/g, '\\\\');
|
|
200
|
+
clickAction = `onclick="window.openFileTab('${safePath}', '${node.name}')"`;
|
|
201
|
+
cursorStyle = 'cursor: pointer; color: var(--text-primary);';
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
// Build HTML
|
|
187
205
|
let html = `<li class="${liClass}">`;
|
|
188
206
|
|
|
189
|
-
const clickAttr = hasChildren ? 'onclick="toggleFolder(event)"' : '';
|
|
190
|
-
|
|
191
|
-
// Add data-tokens and data-type for client-side calculation
|
|
192
|
-
|
|
193
207
|
html += `
|
|
194
|
-
<div class="tree-item-row" ${
|
|
208
|
+
<div class="tree-item-row" ${isDir ? clickAction : ''}>
|
|
195
209
|
<span class="arrow">${arrow}</span>
|
|
196
210
|
<input type="checkbox" class="tree-checkbox"
|
|
197
211
|
data-path="${node.relativePath || node.path}"
|
|
@@ -200,7 +214,7 @@ function generateTreeHtml(node) {
|
|
|
200
214
|
checked
|
|
201
215
|
onclick="handleCheckboxChange(event)">
|
|
202
216
|
<span class="tree-icon">${icon}</span>
|
|
203
|
-
<span class="tree-name">${node.name}</span>
|
|
217
|
+
<span class="tree-name" style="${cursorStyle}" ${!isDir ? clickAction : ''}>${node.name}</span>
|
|
204
218
|
<span class="token-badge ${tokenClass}">${formatNumber(tokens)}</span>
|
|
205
219
|
</div>
|
|
206
220
|
`;
|
|
@@ -6,6 +6,8 @@ import { showToast, showCopiedState } from './utils.js';
|
|
|
6
6
|
import { initIframeManager } from './features/iframe-manager.js';
|
|
7
7
|
import { initGitView } from './features/git-view.js';
|
|
8
8
|
import { initTerminal, createNewTerminal } from './features/terminal.js';
|
|
9
|
+
import { initEditorTabs } from './features/editor-tabs.js';
|
|
10
|
+
import { initMonaco, updateMonacoTheme } from './features/monaco-manager.js';
|
|
9
11
|
|
|
10
12
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
11
13
|
// Load system prompt text
|
|
@@ -28,6 +30,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
28
30
|
|
|
29
31
|
// Initialize Terminal System
|
|
30
32
|
initTerminal();
|
|
33
|
+
|
|
34
|
+
// Initialize Monaco & Tabs
|
|
35
|
+
initMonaco();
|
|
36
|
+
initEditorTabs();
|
|
37
|
+
|
|
38
|
+
// Set default tab to AI Assistant
|
|
39
|
+
if (window.switchTab) {
|
|
40
|
+
window.switchTab('ai-assistant');
|
|
41
|
+
}
|
|
31
42
|
});
|
|
32
43
|
|
|
33
44
|
async function checkServerStatus() {
|
|
@@ -56,6 +67,7 @@ function initTheme() {
|
|
|
56
67
|
localStorage.setItem('theme', newTheme);
|
|
57
68
|
currentTheme = newTheme;
|
|
58
69
|
updateThemeIcon(newTheme);
|
|
70
|
+
updateMonacoTheme(newTheme);
|
|
59
71
|
});
|
|
60
72
|
}
|
|
61
73
|
|
|
Binary file
|
|
Binary file
|
package/vg-coder-cli-2.0.17.tgz
DELETED
|
Binary file
|