vg-coder-cli 2.0.15 → 2.0.17
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 +14 -13
- package/src/server/api-server.js +131 -11
- package/src/server/views/css/git-view.css +241 -73
- package/src/server/views/css/iframe.css +188 -13
- package/src/server/views/dashboard.html +68 -66
- package/src/server/views/js/api.js +62 -66
- package/src/server/views/js/features/git-view.js +324 -70
- package/src/server/views/js/features/iframe-manager.js +48 -12
- package/src/server/views/js/main.js +36 -23
- package/vg-coder-cli-2.0.17.tgz +0 -0
- package/change.sh +0 -0
- package/vg-coder-cli-2.0.12.tgz +0 -0
- package/vg-coder-cli-2.0.14.tgz +0 -0
- package/vg-coder-cli-2.0.15.tgz +0 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { getGitDiff } from '../api.js';
|
|
2
|
-
import { showToast
|
|
1
|
+
import { getGitStatus, getGitDiff, stageFile, unstageFile, commitChanges, discardChange } from '../api.js';
|
|
2
|
+
import { showToast } from '../utils.js';
|
|
3
3
|
|
|
4
4
|
export function initGitView() {
|
|
5
|
-
console.log('[GitView] Initializing...');
|
|
6
5
|
const toggleBtn = document.getElementById('git-view-toggle');
|
|
7
6
|
const refreshBtn = document.getElementById('git-refresh-btn');
|
|
8
7
|
|
|
9
8
|
if (toggleBtn) toggleBtn.addEventListener('click', toggleGitMode);
|
|
10
|
-
if (refreshBtn) refreshBtn.addEventListener('click',
|
|
9
|
+
if (refreshBtn) refreshBtn.addEventListener('click', loadGitData);
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
let isGitMode = false;
|
|
13
|
+
let currentStaged = [];
|
|
14
|
+
let currentChanges = [];
|
|
14
15
|
|
|
15
16
|
async function toggleGitMode() {
|
|
16
17
|
const gitContainer = document.getElementById('git-view-container');
|
|
@@ -19,20 +20,13 @@ async function toggleGitMode() {
|
|
|
19
20
|
const toggleText = document.getElementById('git-toggle-text');
|
|
20
21
|
|
|
21
22
|
isGitMode = !isGitMode;
|
|
22
|
-
console.log('[GitView] Toggle Mode:', isGitMode);
|
|
23
23
|
|
|
24
24
|
if (isGitMode) {
|
|
25
25
|
gitContainer.classList.add('active');
|
|
26
26
|
toggleBtn.classList.add('active');
|
|
27
27
|
if (toggleText) toggleText.textContent = 'Close Changes';
|
|
28
28
|
if (refreshBtn) refreshBtn.style.display = 'flex';
|
|
29
|
-
|
|
30
|
-
// Debug kích thước container
|
|
31
|
-
const rect = gitContainer.getBoundingClientRect();
|
|
32
|
-
console.log('[GitView] Container Size:', rect.width, 'x', rect.height);
|
|
33
|
-
|
|
34
|
-
// FIX: Always load changes when opening, removing the empty check
|
|
35
|
-
await loadGitChanges();
|
|
29
|
+
await loadGitData();
|
|
36
30
|
} else {
|
|
37
31
|
gitContainer.classList.remove('active');
|
|
38
32
|
toggleBtn.classList.remove('active');
|
|
@@ -41,77 +35,337 @@ async function toggleGitMode() {
|
|
|
41
35
|
}
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
async function
|
|
45
|
-
const
|
|
46
|
-
const refreshBtn = document.getElementById('git-refresh-btn');
|
|
38
|
+
async function loadGitData() {
|
|
39
|
+
const container = document.getElementById('git-view-container');
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
// Initial Structure if empty
|
|
42
|
+
if (!container.querySelector('.git-sidebar')) {
|
|
43
|
+
container.innerHTML = `
|
|
44
|
+
<div class="git-sidebar">
|
|
45
|
+
<div class="git-commit-section">
|
|
46
|
+
<textarea id="git-commit-message" class="git-commit-input" placeholder="Message (⌘Enter to commit)" rows="1"></textarea>
|
|
47
|
+
<button id="git-commit-btn" class="git-commit-btn">
|
|
48
|
+
<span>✓</span> Commit
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="git-section">
|
|
52
|
+
<div class="git-section-header" id="header-staged">
|
|
53
|
+
<div style="display:flex;align-items:center;gap:5px;">
|
|
54
|
+
<span>STAGED CHANGES</span>
|
|
55
|
+
<span class="git-badge" id="badge-staged">0</span>
|
|
56
|
+
</div>
|
|
57
|
+
<span class="git-btn-action" id="unstage-all" title="Unstage All" style="display:none;">-</span>
|
|
58
|
+
</div>
|
|
59
|
+
<ul class="git-tree-root" id="tree-staged"></ul>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="git-section">
|
|
62
|
+
<div class="git-section-header" id="header-changes">
|
|
63
|
+
<div style="display:flex;align-items:center;gap:5px;">
|
|
64
|
+
<span>CHANGES</span>
|
|
65
|
+
<span class="git-badge" id="badge-changes">0</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div style="display:flex;gap:5px;">
|
|
68
|
+
<span class="git-btn-action destructive" id="discard-all" title="Discard All Changes">↺</span>
|
|
69
|
+
<span class="git-btn-action" id="stage-all" title="Stage All">+</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<ul class="git-tree-root" id="tree-changes"></ul>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="git-diff-area" id="git-diff-viewer">
|
|
76
|
+
<div class="git-empty-state">Select a file to view changes</div>
|
|
77
|
+
</div>
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
// Bind Actions
|
|
81
|
+
document.getElementById('stage-all').addEventListener('click', async (e) => {
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
await handleStage('*');
|
|
84
|
+
});
|
|
85
|
+
document.getElementById('unstage-all').addEventListener('click', async (e) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
await handleUnstage('*');
|
|
88
|
+
});
|
|
89
|
+
document.getElementById('discard-all').addEventListener('click', async (e) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
await handleDiscard('*');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Commit Actions
|
|
95
|
+
const commitBtn = document.getElementById('git-commit-btn');
|
|
96
|
+
const commitInput = document.getElementById('git-commit-message');
|
|
97
|
+
|
|
98
|
+
commitBtn.addEventListener('click', handleCommit);
|
|
99
|
+
commitInput.addEventListener('keydown', (e) => {
|
|
100
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
handleCommit();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const refreshBtn = document.getElementById('git-refresh-btn');
|
|
108
|
+
if(refreshBtn) refreshBtn.disabled = true;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const { staged, changes } = await getGitStatus();
|
|
112
|
+
currentStaged = staged;
|
|
113
|
+
currentChanges = changes;
|
|
114
|
+
renderTrees();
|
|
115
|
+
} catch (err) {
|
|
116
|
+
showToast('Error loading git status: ' + err.message, 'error');
|
|
117
|
+
} finally {
|
|
118
|
+
if(refreshBtn) refreshBtn.disabled = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function handleCommit() {
|
|
123
|
+
const input = document.getElementById('git-commit-message');
|
|
124
|
+
const message = input.value.trim();
|
|
125
|
+
const btn = document.getElementById('git-commit-btn');
|
|
126
|
+
|
|
127
|
+
if (!message) {
|
|
128
|
+
showToast('Please enter a commit message', 'error');
|
|
129
|
+
input.focus();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (currentStaged.length === 0) {
|
|
134
|
+
showToast('Nothing to commit. Stage changes first.', 'error');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
btn.disabled = true;
|
|
139
|
+
btn.innerHTML = '<span>...</span> Committing...';
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await commitChanges(message);
|
|
143
|
+
showToast('Commit successful', 'success');
|
|
144
|
+
input.value = '';
|
|
145
|
+
await loadGitData();
|
|
146
|
+
} catch (err) {
|
|
147
|
+
showToast('Commit failed: ' + err.message, 'error');
|
|
148
|
+
} finally {
|
|
149
|
+
btn.disabled = false;
|
|
150
|
+
btn.innerHTML = '<span>✓</span> Commit';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderTrees() {
|
|
155
|
+
// 1. Render Staged
|
|
156
|
+
const treeStaged = document.getElementById('tree-staged');
|
|
157
|
+
document.getElementById('badge-staged').textContent = currentStaged.length;
|
|
158
|
+
document.getElementById('unstage-all').style.display = currentStaged.length > 0 ? 'block' : 'none';
|
|
159
|
+
treeStaged.innerHTML = '';
|
|
49
160
|
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
161
|
+
if (currentStaged.length > 0) {
|
|
162
|
+
const rootStaged = buildFileTree(currentStaged);
|
|
163
|
+
renderTreeNodes(rootStaged, treeStaged, 'staged', 0);
|
|
53
164
|
}
|
|
165
|
+
|
|
166
|
+
// 2. Render Changes
|
|
167
|
+
const treeChanges = document.getElementById('tree-changes');
|
|
168
|
+
document.getElementById('badge-changes').textContent = currentChanges.length;
|
|
169
|
+
document.getElementById('discard-all').style.display = currentChanges.length > 0 ? 'block' : 'none';
|
|
170
|
+
treeChanges.innerHTML = '';
|
|
54
171
|
|
|
55
|
-
|
|
56
|
-
|
|
172
|
+
if (currentChanges.length > 0) {
|
|
173
|
+
const rootChanges = buildFileTree(currentChanges);
|
|
174
|
+
renderTreeNodes(rootChanges, treeChanges, 'changes', 0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
57
177
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
178
|
+
// --- TREE LOGIC ---
|
|
179
|
+
|
|
180
|
+
function buildFileTree(files) {
|
|
181
|
+
const root = {};
|
|
182
|
+
files.forEach(file => {
|
|
183
|
+
const parts = file.path.split('/');
|
|
184
|
+
let current = root;
|
|
185
|
+
parts.forEach((part, index) => {
|
|
186
|
+
if (!current[part]) {
|
|
187
|
+
current[part] = { name: part, children: {}, fileData: null };
|
|
188
|
+
}
|
|
189
|
+
if (index === parts.length - 1) {
|
|
190
|
+
current[part].fileData = file;
|
|
191
|
+
}
|
|
192
|
+
current = current[part].children;
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
return root;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function renderTreeNodes(nodes, container, type, depth) {
|
|
199
|
+
const items = Object.values(nodes);
|
|
200
|
+
items.sort((a, b) => {
|
|
201
|
+
const aIsFolder = Object.keys(a.children).length > 0;
|
|
202
|
+
const bIsFolder = Object.keys(b.children).length > 0;
|
|
203
|
+
if (aIsFolder && !bIsFolder) return -1;
|
|
204
|
+
if (!aIsFolder && bIsFolder) return 1;
|
|
205
|
+
return a.name.localeCompare(b.name);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
items.forEach(node => {
|
|
209
|
+
const isFolder = Object.keys(node.children).length > 0;
|
|
210
|
+
const li = document.createElement('li');
|
|
211
|
+
li.className = 'git-tree-node';
|
|
66
212
|
|
|
67
|
-
|
|
68
|
-
|
|
213
|
+
// --- CONTENT ROW ---
|
|
214
|
+
const content = document.createElement('div');
|
|
215
|
+
content.className = 'git-tree-content';
|
|
216
|
+
content.style.paddingLeft = (depth * 16 + 8) + 'px';
|
|
69
217
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
218
|
+
// Arrow
|
|
219
|
+
const arrow = document.createElement('span');
|
|
220
|
+
arrow.className = 'git-arrow';
|
|
221
|
+
arrow.textContent = isFolder ? '▼' : '';
|
|
222
|
+
content.appendChild(arrow);
|
|
223
|
+
|
|
224
|
+
// Icon
|
|
225
|
+
const iconSpan = document.createElement('span');
|
|
226
|
+
iconSpan.className = 'git-icon';
|
|
227
|
+
if (isFolder) {
|
|
228
|
+
iconSpan.textContent = '📂';
|
|
76
229
|
} else {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
gitContainer.innerHTML = ''; // Clear loading
|
|
94
|
-
|
|
95
|
-
const diff2htmlUi = new Diff2HtmlUI(gitContainer, diffString, configuration);
|
|
96
|
-
diff2htmlUi.draw();
|
|
97
|
-
diff2htmlUi.highlightCode();
|
|
230
|
+
const status = node.fileData.status;
|
|
231
|
+
iconSpan.textContent = getStatusIcon(status);
|
|
232
|
+
iconSpan.className += ' git-status-' + status;
|
|
233
|
+
}
|
|
234
|
+
content.appendChild(iconSpan);
|
|
235
|
+
|
|
236
|
+
// Label
|
|
237
|
+
const label = document.createElement('span');
|
|
238
|
+
label.className = isFolder ? 'git-label git-dir-label' : 'git-label git-file-label';
|
|
239
|
+
label.textContent = node.name;
|
|
240
|
+
content.appendChild(label);
|
|
241
|
+
|
|
242
|
+
// Actions
|
|
243
|
+
if (!isFolder && node.fileData) {
|
|
244
|
+
const actions = document.createElement('div');
|
|
245
|
+
actions.className = 'git-actions';
|
|
98
246
|
|
|
99
|
-
|
|
247
|
+
// Discard Button (Only for Changes)
|
|
248
|
+
if (type === 'changes') {
|
|
249
|
+
const discardBtn = document.createElement('button');
|
|
250
|
+
discardBtn.className = 'git-btn-action destructive';
|
|
251
|
+
discardBtn.textContent = '↺';
|
|
252
|
+
discardBtn.title = 'Discard Changes';
|
|
253
|
+
discardBtn.onclick = (e) => { e.stopPropagation(); handleDiscard(node.fileData.path); };
|
|
254
|
+
actions.appendChild(discardBtn);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Stage/Unstage Button
|
|
258
|
+
const btn = document.createElement('button');
|
|
259
|
+
btn.className = 'git-btn-action';
|
|
260
|
+
if (type === 'staged') {
|
|
261
|
+
btn.textContent = '-';
|
|
262
|
+
btn.title = 'Unstage';
|
|
263
|
+
btn.onclick = (e) => { e.stopPropagation(); handleUnstage(node.fileData.path); };
|
|
264
|
+
} else {
|
|
265
|
+
btn.textContent = '+';
|
|
266
|
+
btn.title = 'Stage';
|
|
267
|
+
btn.onclick = (e) => { e.stopPropagation(); handleStage(node.fileData.path); };
|
|
268
|
+
}
|
|
269
|
+
actions.appendChild(btn);
|
|
270
|
+
content.appendChild(actions);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Click Handler
|
|
274
|
+
content.addEventListener('click', (e) => {
|
|
275
|
+
e.stopPropagation();
|
|
276
|
+
if (isFolder) {
|
|
277
|
+
li.classList.toggle('collapsed');
|
|
278
|
+
} else {
|
|
279
|
+
document.querySelectorAll('.git-tree-content').forEach(el => el.classList.remove('selected'));
|
|
280
|
+
content.classList.add('selected');
|
|
281
|
+
loadDiffView(node.fileData.path, type);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
li.appendChild(content);
|
|
286
|
+
|
|
287
|
+
// --- CHILDREN ---
|
|
288
|
+
if (isFolder) {
|
|
289
|
+
const ul = document.createElement('ul');
|
|
290
|
+
renderTreeNodes(node.children, ul, type, depth + 1);
|
|
291
|
+
li.appendChild(ul);
|
|
100
292
|
}
|
|
101
|
-
showToast('Git changes loaded', 'success');
|
|
102
293
|
|
|
294
|
+
container.appendChild(li);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function getStatusIcon(status) {
|
|
299
|
+
if(status === 'M') return 'M';
|
|
300
|
+
if(status === 'A' || status === 'U') return 'U';
|
|
301
|
+
if(status === 'D') return 'D';
|
|
302
|
+
if(status === 'R') return 'R';
|
|
303
|
+
return '?';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// --- ACTIONS ---
|
|
307
|
+
|
|
308
|
+
async function handleStage(path) {
|
|
309
|
+
try {
|
|
310
|
+
await stageFile(path);
|
|
311
|
+
await loadGitData();
|
|
312
|
+
} catch (err) {
|
|
313
|
+
showToast('Stage failed: ' + err.message, 'error');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function handleUnstage(path) {
|
|
318
|
+
try {
|
|
319
|
+
await unstageFile(path);
|
|
320
|
+
await loadGitData();
|
|
103
321
|
} catch (err) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
322
|
+
showToast('Unstage failed: ' + err.message, 'error');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function handleDiscard(path) {
|
|
327
|
+
const isAll = path === '*';
|
|
328
|
+
const msg = isAll
|
|
329
|
+
? 'Are you sure you want to discard ALL changes? This is irreversible!'
|
|
330
|
+
: `Discard changes to ${path}? This is irreversible!`;
|
|
331
|
+
|
|
332
|
+
if (confirm(msg)) {
|
|
333
|
+
try {
|
|
334
|
+
await discardChange(path);
|
|
335
|
+
await loadGitData();
|
|
336
|
+
showToast('Changes discarded', 'success');
|
|
337
|
+
} catch (err) {
|
|
338
|
+
showToast('Discard failed: ' + err.message, 'error');
|
|
339
|
+
}
|
|
111
340
|
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function loadDiffView(filePath, type) {
|
|
344
|
+
const viewer = document.getElementById('git-diff-viewer');
|
|
345
|
+
viewer.innerHTML = '<div class="git-empty-state">Loading diff...</div>';
|
|
112
346
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
347
|
+
try {
|
|
348
|
+
const diff = await getGitDiff(filePath, type === 'staged' ? 'staged' : 'working');
|
|
349
|
+
|
|
350
|
+
if (!diff || !diff.trim()) {
|
|
351
|
+
viewer.innerHTML = '<div class="git-empty-state">No text changes detected (binary file?)</div>';
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
viewer.innerHTML = '';
|
|
356
|
+
const ui = new Diff2HtmlUI(viewer, diff, {
|
|
357
|
+
drawFileList: false,
|
|
358
|
+
matching: 'lines',
|
|
359
|
+
outputFormat: 'side-by-side',
|
|
360
|
+
synchronisedScroll: true,
|
|
361
|
+
highlight: true,
|
|
362
|
+
renderNothingWhenEmpty: false,
|
|
363
|
+
colorScheme: 'dark'
|
|
364
|
+
});
|
|
365
|
+
ui.draw();
|
|
366
|
+
ui.highlightCode();
|
|
367
|
+
|
|
368
|
+
} catch (err) {
|
|
369
|
+
viewer.innerHTML = `<div class="git-empty-state" style="color:#f85149">Error loading diff: ${err.message}</div>`;
|
|
116
370
|
}
|
|
117
371
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quản lý tính năng Iframe AI Provider
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Danh sách các AI Providers
|
|
6
1
|
const AI_PROVIDERS = [
|
|
7
2
|
{ id: 'chatgpt', name: 'ChatGPT', url: 'https://chat.openai.com' },
|
|
8
3
|
{ id: 'kimi', name: 'Kimi AI', url: 'https://www.kimi.com' },
|
|
@@ -16,6 +11,12 @@ export function initIframeManager() {
|
|
|
16
11
|
const iframe = document.getElementById('ai-iframe');
|
|
17
12
|
const externalLink = document.getElementById('ai-external-link');
|
|
18
13
|
const placeholderLink = document.getElementById('ai-placeholder-link');
|
|
14
|
+
|
|
15
|
+
// Guide elements
|
|
16
|
+
const guideContainer = document.getElementById('iframe-placeholder');
|
|
17
|
+
const guideToggleBtn = document.getElementById('guide-toggle-btn');
|
|
18
|
+
const guideCloseBtn = document.getElementById('guide-close-btn');
|
|
19
|
+
const guideDoneBtn = document.getElementById('guide-done-btn');
|
|
19
20
|
|
|
20
21
|
if (!select || !iframe) return;
|
|
21
22
|
|
|
@@ -27,18 +28,20 @@ export function initIframeManager() {
|
|
|
27
28
|
select.appendChild(option);
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
// 2. Load saved selection
|
|
31
|
+
// 2. Load saved selection
|
|
31
32
|
const savedProviderId = localStorage.getItem('ai_provider') || 'chatgpt';
|
|
32
|
-
|
|
33
|
-
// Validate if saved provider still exists
|
|
34
33
|
const isValid = AI_PROVIDERS.some(p => p.id === savedProviderId);
|
|
35
34
|
select.value = isValid ? savedProviderId : AI_PROVIDERS[0].id;
|
|
36
35
|
|
|
37
|
-
// 3. Function to update UI
|
|
38
36
|
const updateProvider = (providerId) => {
|
|
39
37
|
const provider = AI_PROVIDERS.find(p => p.id === providerId) || AI_PROVIDERS[0];
|
|
40
38
|
|
|
41
|
-
iframe
|
|
39
|
+
// Reset iframe source to trigger reload
|
|
40
|
+
iframe.src = 'about:blank';
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
iframe.src = provider.url;
|
|
43
|
+
}, 50);
|
|
44
|
+
|
|
42
45
|
externalLink.href = provider.url;
|
|
43
46
|
placeholderLink.href = provider.url;
|
|
44
47
|
placeholderLink.textContent = `Mở ${provider.name} tab mới ↗`;
|
|
@@ -46,11 +49,44 @@ export function initIframeManager() {
|
|
|
46
49
|
localStorage.setItem('ai_provider', providerId);
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
//
|
|
52
|
+
// Initial load
|
|
50
53
|
updateProvider(select.value);
|
|
51
54
|
|
|
52
|
-
//
|
|
55
|
+
// Event listeners
|
|
53
56
|
select.addEventListener('change', (e) => {
|
|
54
57
|
updateProvider(e.target.value);
|
|
55
58
|
});
|
|
59
|
+
|
|
60
|
+
// --- GUIDE TOGGLE LOGIC ---
|
|
61
|
+
|
|
62
|
+
const showGuide = () => {
|
|
63
|
+
guideContainer.classList.remove('hidden');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const hideGuide = () => {
|
|
67
|
+
guideContainer.classList.add('hidden');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const reloadIframe = () => {
|
|
71
|
+
const currentSrc = iframe.src;
|
|
72
|
+
iframe.src = 'about:blank';
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
iframe.src = currentSrc;
|
|
75
|
+
}, 100);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (guideToggleBtn) {
|
|
79
|
+
guideToggleBtn.addEventListener('click', showGuide);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (guideCloseBtn) {
|
|
83
|
+
guideCloseBtn.addEventListener('click', hideGuide);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (guideDoneBtn) {
|
|
87
|
+
guideDoneBtn.addEventListener('click', () => {
|
|
88
|
+
hideGuide();
|
|
89
|
+
reloadIframe();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
56
92
|
}
|
|
@@ -28,9 +28,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
28
28
|
|
|
29
29
|
// Initialize Terminal System
|
|
30
30
|
initTerminal();
|
|
31
|
-
|
|
32
|
-
// Auto open one terminal on start (Optional)
|
|
33
|
-
// setTimeout(() => createNewTerminal(), 500);
|
|
34
31
|
});
|
|
35
32
|
|
|
36
33
|
async function checkServerStatus() {
|
|
@@ -75,40 +72,56 @@ async function loadExtensionPath() {
|
|
|
75
72
|
try {
|
|
76
73
|
const res = await fetch('/api/extension-path');
|
|
77
74
|
const data = await res.json();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
input.value =
|
|
82
|
-
|
|
75
|
+
// Updated ID for the input in center guide
|
|
76
|
+
const input = document.getElementById('extension-path-input-center');
|
|
77
|
+
if (input) {
|
|
78
|
+
if (data.exists) input.value = data.path;
|
|
79
|
+
else {
|
|
80
|
+
input.value = "Error: Extension folder not found.";
|
|
81
|
+
input.style.color = "var(--ios-red)";
|
|
82
|
+
}
|
|
83
83
|
}
|
|
84
84
|
} catch (err) {}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
window.toggleExtensionGuide = function() {
|
|
88
|
-
const content = document.getElementById('extension-content');
|
|
89
|
-
const icon = document.getElementById('ext-toggle-icon');
|
|
90
|
-
content.classList.toggle('open');
|
|
91
|
-
icon.classList.toggle('open');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
87
|
window.copyExtensionPath = function(event) {
|
|
95
|
-
const input = document.getElementById('extension-path-input');
|
|
88
|
+
const input = document.getElementById('extension-path-input-center');
|
|
96
89
|
const btn = event.currentTarget;
|
|
97
|
-
const
|
|
98
|
-
|
|
90
|
+
const originalText = btn.textContent;
|
|
91
|
+
|
|
99
92
|
navigator.clipboard.writeText(input.value).then(() => {
|
|
100
|
-
|
|
93
|
+
btn.textContent = '✓';
|
|
94
|
+
btn.style.background = 'var(--ios-green)';
|
|
95
|
+
btn.style.color = 'white';
|
|
96
|
+
btn.style.borderColor = 'var(--ios-green)';
|
|
101
97
|
showToast('Đã copy đường dẫn extension', 'success');
|
|
98
|
+
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
btn.textContent = originalText;
|
|
101
|
+
btn.style.background = '';
|
|
102
|
+
btn.style.color = '';
|
|
103
|
+
btn.style.borderColor = '';
|
|
104
|
+
}, 2000);
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
window.copyChromeUrl = function(event) {
|
|
106
|
-
const input = document.getElementById('chrome-url-input');
|
|
109
|
+
const input = document.getElementById('chrome-url-input-center');
|
|
107
110
|
const btn = event.currentTarget;
|
|
108
|
-
const originalText = btn.
|
|
111
|
+
const originalText = btn.textContent;
|
|
112
|
+
|
|
109
113
|
navigator.clipboard.writeText(input.value).then(() => {
|
|
110
|
-
btn.
|
|
114
|
+
btn.textContent = '✓';
|
|
115
|
+
btn.style.background = 'var(--ios-green)';
|
|
116
|
+
btn.style.color = 'white';
|
|
117
|
+
btn.style.borderColor = 'var(--ios-green)';
|
|
111
118
|
showToast('Đã copy URL', 'success');
|
|
112
|
-
|
|
119
|
+
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
btn.textContent = originalText;
|
|
122
|
+
btn.style.background = '';
|
|
123
|
+
btn.style.color = '';
|
|
124
|
+
btn.style.borderColor = '';
|
|
125
|
+
}, 2000);
|
|
113
126
|
});
|
|
114
127
|
}
|
|
Binary file
|
package/change.sh
DELETED
|
File without changes
|
package/vg-coder-cli-2.0.12.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.14.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.15.tgz
DELETED
|
Binary file
|