vg-coder-cli 2.0.7 → 2.0.9
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 +2 -1
- package/src/scanner/file-scanner.js +3 -104
- package/src/server/api-server.js +62 -18
- package/src/server/views/css/structure.css +122 -0
- package/src/server/views/dashboard.css +488 -0
- package/src/server/views/dashboard.html +83 -892
- package/src/server/views/dashboard.js +457 -0
- package/src/server/views/js/api.js +118 -0
- package/src/server/views/js/config.js +120 -0
- package/src/server/views/js/features/structure.js +221 -0
- package/src/server/views/js/handlers.js +182 -0
- package/src/server/views/js/main.js +72 -0
- package/src/server/views/js/utils.js +82 -0
- package/src/tokenizer/token-manager.js +52 -2
- package/vg-coder-cli-2.0.8.tgz +0 -0
- package/vg-coder-cli-2.0.9.tgz +0 -0
- package/vg-coder-cli-1.0.17.tgz +0 -0
- package/vg-coder-cli-2.0.4.tgz +0 -0
- package/vg-coder-cli-2.0.5.tgz +0 -0
- package/vg-coder-cli-2.0.6.tgz +0 -0
- package/vg-coder-cli-2.0.7.tgz +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { getStructure, analyzeProject, copyToClipboard } from '../api.js';
|
|
2
|
+
import { showToast, showLoading, resetButton, showResponse, formatNumber, showCopiedState } from '../utils.js';
|
|
3
|
+
|
|
4
|
+
// Global variable to store current structure data
|
|
5
|
+
let currentStructureData = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handle Structure button click logic
|
|
9
|
+
*/
|
|
10
|
+
export async function handleStructureView(event) {
|
|
11
|
+
const btn = event.target.closest('.btn');
|
|
12
|
+
const pathInput = document.getElementById('structure-path');
|
|
13
|
+
const treeContainer = document.getElementById('structure-tree');
|
|
14
|
+
const treeContent = document.getElementById('tree-content');
|
|
15
|
+
const errorContainer = document.getElementById('structure-response');
|
|
16
|
+
|
|
17
|
+
const path = pathInput.value;
|
|
18
|
+
|
|
19
|
+
showLoading(btn, btn.innerHTML);
|
|
20
|
+
treeContainer.style.display = 'none';
|
|
21
|
+
errorContainer.style.display = 'none';
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const data = await getStructure(path);
|
|
25
|
+
currentStructureData = data.structure;
|
|
26
|
+
|
|
27
|
+
// Render Tree HTML using recursive function
|
|
28
|
+
treeContent.innerHTML = generateTreeHtml(data.structure);
|
|
29
|
+
|
|
30
|
+
// Initial token update
|
|
31
|
+
updateTotalTokens();
|
|
32
|
+
|
|
33
|
+
treeContainer.style.display = 'block';
|
|
34
|
+
showToast('Tải cấu trúc thành công', 'success');
|
|
35
|
+
|
|
36
|
+
} catch (err) {
|
|
37
|
+
showResponse('structure-response', { error: err.message }, true);
|
|
38
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resetButton(btn);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Toggle folder collapse/expand
|
|
46
|
+
* Only triggers if clicked on row but NOT on checkbox
|
|
47
|
+
*/
|
|
48
|
+
export function handleToggleFolder(event) {
|
|
49
|
+
if (event.target.type === 'checkbox') return;
|
|
50
|
+
|
|
51
|
+
// Find closest parent LI
|
|
52
|
+
const li = event.currentTarget.closest('.tree-li');
|
|
53
|
+
if (li && li.classList.contains('has-children')) {
|
|
54
|
+
li.classList.toggle('collapsed');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle Checkbox Logic (Parent <-> Child sync) & Update Token Total
|
|
60
|
+
*/
|
|
61
|
+
export function handleCheckboxChange(event) {
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
const checkbox = event.target;
|
|
64
|
+
const isChecked = checkbox.checked;
|
|
65
|
+
|
|
66
|
+
// 1. Sync Children: If this is a folder, update all children checkboxes
|
|
67
|
+
const li = checkbox.closest('.tree-li');
|
|
68
|
+
if (li) {
|
|
69
|
+
const childrenCheckboxes = li.querySelectorAll('.tree-checkbox');
|
|
70
|
+
childrenCheckboxes.forEach(child => {
|
|
71
|
+
child.checked = isChecked;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Recalculate Tokens
|
|
76
|
+
updateTotalTokens();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Calculate total tokens of CHECKED files only
|
|
81
|
+
*/
|
|
82
|
+
function updateTotalTokens() {
|
|
83
|
+
// Select all checked checkboxes that are FILES (have data-tokens)
|
|
84
|
+
const checkedFiles = document.querySelectorAll('.tree-checkbox[data-type="file"]:checked');
|
|
85
|
+
|
|
86
|
+
let total = 0;
|
|
87
|
+
checkedFiles.forEach(box => {
|
|
88
|
+
const tokens = parseInt(box.dataset.tokens || '0');
|
|
89
|
+
total += tokens;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Update Badge
|
|
93
|
+
const badge = document.getElementById('total-tokens-badge');
|
|
94
|
+
badge.textContent = `${formatNumber(total)} tokens`;
|
|
95
|
+
|
|
96
|
+
// Optional: Visual styling if 0
|
|
97
|
+
if (total === 0) {
|
|
98
|
+
badge.style.color = 'var(--ios-gray)';
|
|
99
|
+
} else {
|
|
100
|
+
badge.style.color = ''; // reset to default
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Copy Content of Selected Files (via API)
|
|
106
|
+
*/
|
|
107
|
+
export async function handleCopySelected(event) {
|
|
108
|
+
const btn = event.target.closest('.btn-copy') || event.target.closest('.btn-icon-head');
|
|
109
|
+
const icon = document.getElementById('copy-structure-icon') || btn;
|
|
110
|
+
const text = document.getElementById('copy-structure-text') || { textContent: '' };
|
|
111
|
+
|
|
112
|
+
// 1. Get all checked FILE paths
|
|
113
|
+
const checkedBoxes = document.querySelectorAll('.tree-checkbox[data-type="file"]:checked');
|
|
114
|
+
const checkedPaths = [];
|
|
115
|
+
|
|
116
|
+
checkedBoxes.forEach(box => {
|
|
117
|
+
if (box.dataset.path) {
|
|
118
|
+
checkedPaths.push(box.dataset.path);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (checkedPaths.length === 0) {
|
|
123
|
+
showToast('Chưa chọn file nào', 'error');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Save original button state
|
|
128
|
+
const originalText = btn.innerHTML;
|
|
129
|
+
if (btn.classList.contains('btn-copy')) {
|
|
130
|
+
showLoading(btn, btn.innerHTML);
|
|
131
|
+
} else {
|
|
132
|
+
// For header icon, just show visual feedback
|
|
133
|
+
btn.style.opacity = '0.5';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const path = document.getElementById('structure-path').value;
|
|
138
|
+
|
|
139
|
+
// 2. Call Analyze API with specific files
|
|
140
|
+
const content = await analyzeProject(path, checkedPaths);
|
|
141
|
+
|
|
142
|
+
// 3. Copy to clipboard
|
|
143
|
+
await copyToClipboard(content);
|
|
144
|
+
|
|
145
|
+
// UI Feedback
|
|
146
|
+
if (btn.classList.contains('btn-copy')) {
|
|
147
|
+
showCopiedState(btn, icon, text, '📋', 'Copy Selected');
|
|
148
|
+
resetButton(btn);
|
|
149
|
+
} else {
|
|
150
|
+
// Header icon feedback
|
|
151
|
+
btn.style.opacity = '1';
|
|
152
|
+
const originalIcon = btn.textContent;
|
|
153
|
+
btn.textContent = '✓';
|
|
154
|
+
setTimeout(() => btn.textContent = originalIcon, 2000);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
showToast(`Đã copy nội dung ${checkedPaths.length} file!`, 'success');
|
|
158
|
+
|
|
159
|
+
} catch (err) {
|
|
160
|
+
resetButton(btn);
|
|
161
|
+
if (!btn.classList.contains('btn-copy')) btn.style.opacity = '1';
|
|
162
|
+
showToast('Lỗi copy: ' + err.message, 'error');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Recursive function to generate Tree HTML with Checkboxes
|
|
168
|
+
*/
|
|
169
|
+
function generateTreeHtml(node) {
|
|
170
|
+
if (!node) return '';
|
|
171
|
+
|
|
172
|
+
const isDir = node.type === 'directory';
|
|
173
|
+
const hasChildren = isDir && node.children && node.children.length > 0;
|
|
174
|
+
|
|
175
|
+
// Determine Token Color
|
|
176
|
+
const tokens = node.tokens || 0;
|
|
177
|
+
let tokenClass = 'token-low';
|
|
178
|
+
if (tokens > 5000) tokenClass = 'token-high';
|
|
179
|
+
else if (tokens > 2000) tokenClass = 'token-med';
|
|
180
|
+
|
|
181
|
+
// Icon
|
|
182
|
+
const icon = isDir ? (hasChildren ? '📁' : '📂') : '📄';
|
|
183
|
+
const arrow = hasChildren ? '▼' : '';
|
|
184
|
+
const liClass = `tree-li ${hasChildren ? 'has-children' : ''}`;
|
|
185
|
+
|
|
186
|
+
// Build HTML
|
|
187
|
+
let html = `<li class="${liClass}">`;
|
|
188
|
+
|
|
189
|
+
const clickAttr = hasChildren ? 'onclick="toggleFolder(event)"' : '';
|
|
190
|
+
|
|
191
|
+
// Add data-tokens and data-type for client-side calculation
|
|
192
|
+
|
|
193
|
+
html += `
|
|
194
|
+
<div class="tree-item-row" ${clickAttr}>
|
|
195
|
+
<span class="arrow">${arrow}</span>
|
|
196
|
+
<input type="checkbox" class="tree-checkbox"
|
|
197
|
+
data-path="${node.relativePath || node.path}"
|
|
198
|
+
data-tokens="${tokens}"
|
|
199
|
+
data-type="${node.type}"
|
|
200
|
+
checked
|
|
201
|
+
onclick="handleCheckboxChange(event)">
|
|
202
|
+
<span class="tree-icon">${icon}</span>
|
|
203
|
+
<span class="tree-name">${node.name}</span>
|
|
204
|
+
<span class="token-badge ${tokenClass}">${formatNumber(tokens)}</span>
|
|
205
|
+
</div>
|
|
206
|
+
`;
|
|
207
|
+
|
|
208
|
+
// Children recursion
|
|
209
|
+
if (hasChildren) {
|
|
210
|
+
html += '<ul class="tree-ul">';
|
|
211
|
+
// Sort: Folders first, then files
|
|
212
|
+
node.children.forEach(child => {
|
|
213
|
+
html += generateTreeHtml(child);
|
|
214
|
+
});
|
|
215
|
+
html += '</ul>';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
html += '</li>';
|
|
219
|
+
|
|
220
|
+
return isDir ? `<ul class="tree-ul">${html}</ul>` : html;
|
|
221
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Event Handlers & Business Logic
|
|
2
|
+
import { SYSTEM_PROMPT } from './config.js';
|
|
3
|
+
import { analyzeProject, executeScript, copyToClipboard, readFromClipboard } from './api.js';
|
|
4
|
+
import { showToast, showLoading, resetButton, showResponse, showCopiedState } from './utils.js';
|
|
5
|
+
// Import dedicated feature handlers
|
|
6
|
+
import { handleStructureView, handleToggleFolder, handleCheckboxChange, handleCopySelected } from './features/structure.js';
|
|
7
|
+
|
|
8
|
+
let lastAnalyzeResult = null;
|
|
9
|
+
|
|
10
|
+
// ==========================================
|
|
11
|
+
// SYSTEM PROMPT HANDLERS
|
|
12
|
+
// ==========================================
|
|
13
|
+
|
|
14
|
+
export function toggleSystemPrompt() {
|
|
15
|
+
const content = document.getElementById('system-prompt-content');
|
|
16
|
+
const icon = document.getElementById('toggle-icon');
|
|
17
|
+
content.classList.toggle('open');
|
|
18
|
+
icon.classList.toggle('open');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function copySystemPromptFromHeader(event) {
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
const btn = event.currentTarget;
|
|
24
|
+
btn.textContent = '✓';
|
|
25
|
+
navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
|
|
26
|
+
showToast('Đã copy System Prompt', 'success');
|
|
27
|
+
setTimeout(() => btn.textContent = '📋', 2000);
|
|
28
|
+
}).catch(err => {
|
|
29
|
+
showToast('Lỗi copy: ' + err.message, 'error');
|
|
30
|
+
btn.textContent = '📋';
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function copySystemPrompt(event) {
|
|
35
|
+
const copyBtn = event.target.closest('.btn-copy');
|
|
36
|
+
const copyIcon = document.getElementById('copy-icon');
|
|
37
|
+
const copyText = document.getElementById('copy-text');
|
|
38
|
+
|
|
39
|
+
navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
|
|
40
|
+
showCopiedState(copyBtn, copyIcon, copyText, '📋', 'Copy System Prompt');
|
|
41
|
+
showToast('Đã copy System Prompt', 'success');
|
|
42
|
+
}).catch(err => showToast('Lỗi copy: ' + err.message, 'error'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ==========================================
|
|
46
|
+
// ANALYZE HANDLERS
|
|
47
|
+
// ==========================================
|
|
48
|
+
|
|
49
|
+
export async function testAnalyze(event) {
|
|
50
|
+
const btn = event.target.closest('.btn') || event.target.closest('.btn-icon-head');
|
|
51
|
+
const path = document.getElementById('analyze-path').value;
|
|
52
|
+
|
|
53
|
+
showLoading(btn, btn.innerHTML);
|
|
54
|
+
try {
|
|
55
|
+
const text = await analyzeProject(path);
|
|
56
|
+
lastAnalyzeResult = text;
|
|
57
|
+
|
|
58
|
+
const blob = new Blob([text], { type: 'text/plain' });
|
|
59
|
+
const url = window.URL.createObjectURL(blob);
|
|
60
|
+
const a = document.createElement('a');
|
|
61
|
+
a.href = url;
|
|
62
|
+
a.download = 'project.txt';
|
|
63
|
+
a.click();
|
|
64
|
+
|
|
65
|
+
showResponse('analyze-response', {
|
|
66
|
+
success: true,
|
|
67
|
+
message: 'File downloaded!',
|
|
68
|
+
files: text.split('\n').filter(l => l.includes('===== FILE:')).length,
|
|
69
|
+
size: (text.length / 1024).toFixed(2) + ' KB'
|
|
70
|
+
});
|
|
71
|
+
showToast('Đã download file', 'success');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
showResponse('analyze-response', { error: err.message }, true);
|
|
74
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
75
|
+
}
|
|
76
|
+
resetButton(btn);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function copyAnalyzeResult(event) {
|
|
80
|
+
const copyBtn = event.target.closest('.btn-copy');
|
|
81
|
+
const copyIcon = document.getElementById('analyze-copy-icon');
|
|
82
|
+
const copyText = document.getElementById('analyze-copy-text');
|
|
83
|
+
|
|
84
|
+
if (!lastAnalyzeResult) {
|
|
85
|
+
const path = document.getElementById('analyze-path').value;
|
|
86
|
+
showLoading(copyBtn, copyBtn.innerHTML);
|
|
87
|
+
try {
|
|
88
|
+
lastAnalyzeResult = await analyzeProject(path);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
91
|
+
resetButton(copyBtn);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
resetButton(copyBtn);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await copyToClipboard(lastAnalyzeResult);
|
|
99
|
+
showCopiedState(copyBtn, copyIcon, copyText, '📋', 'Copy Text');
|
|
100
|
+
showToast('Đã copy project.txt', 'success');
|
|
101
|
+
} catch (err) {
|
|
102
|
+
showToast('Lỗi copy: ' + err.message, 'error');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ==========================================
|
|
107
|
+
// EXECUTE HANDLERS
|
|
108
|
+
// ==========================================
|
|
109
|
+
|
|
110
|
+
export async function testExecute(event) {
|
|
111
|
+
const btn = event.target.closest('.btn');
|
|
112
|
+
const bashInput = document.getElementById('execute-bash');
|
|
113
|
+
const bash = bashInput.value;
|
|
114
|
+
|
|
115
|
+
if (!bash.trim()) {
|
|
116
|
+
showToast('Vui lòng nhập bash script', 'error');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
showLoading(btn, btn.innerHTML);
|
|
121
|
+
try {
|
|
122
|
+
const data = await executeScript(bash);
|
|
123
|
+
showResponse('execute-response', data, !data.success);
|
|
124
|
+
data.success ? showToast('Thực thi thành công', 'success') : showToast('Thực thi thất bại', 'error');
|
|
125
|
+
if (data.success) bashInput.value = '';
|
|
126
|
+
} catch (err) {
|
|
127
|
+
showResponse('execute-response', { error: err.message }, true);
|
|
128
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
129
|
+
}
|
|
130
|
+
resetButton(btn);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function executeFromClipboard(event) {
|
|
134
|
+
const btn = event.target.closest('.btn');
|
|
135
|
+
const bashInput = document.getElementById('execute-bash');
|
|
136
|
+
|
|
137
|
+
showLoading(btn, btn.innerHTML);
|
|
138
|
+
try {
|
|
139
|
+
const clipboardText = await readFromClipboard();
|
|
140
|
+
if (!clipboardText || !clipboardText.trim()) {
|
|
141
|
+
showToast('Clipboard trống!', 'error');
|
|
142
|
+
resetButton(btn);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
bashInput.value = clipboardText;
|
|
146
|
+
const data = await executeScript(clipboardText);
|
|
147
|
+
showResponse('execute-response', data, !data.success);
|
|
148
|
+
|
|
149
|
+
if (data.success) {
|
|
150
|
+
showToast('Thực thi OK', 'success');
|
|
151
|
+
bashInput.value = '';
|
|
152
|
+
} else {
|
|
153
|
+
data.syntaxError ? showToast('Lỗi syntax script', 'error') : showToast('Thực thi thất bại', 'error');
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err.name === 'NotAllowedError') {
|
|
157
|
+
showToast('Không có quyền clipboard', 'error');
|
|
158
|
+
} else {
|
|
159
|
+
showResponse('execute-response', { error: err.message }, true);
|
|
160
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
resetButton(btn);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ==========================================
|
|
167
|
+
// EXPORT TO WINDOW (GLOBAL)
|
|
168
|
+
// ==========================================
|
|
169
|
+
|
|
170
|
+
window.toggleSystemPrompt = toggleSystemPrompt;
|
|
171
|
+
window.copySystemPrompt = copySystemPrompt;
|
|
172
|
+
window.copySystemPromptFromHeader = copySystemPromptFromHeader;
|
|
173
|
+
window.testAnalyze = testAnalyze;
|
|
174
|
+
window.copyAnalyzeResult = copyAnalyzeResult;
|
|
175
|
+
window.testExecute = testExecute;
|
|
176
|
+
window.executeFromClipboard = executeFromClipboard;
|
|
177
|
+
|
|
178
|
+
// Map Structure handlers from feature module to window
|
|
179
|
+
window.testStructure = handleStructureView;
|
|
180
|
+
window.toggleFolder = handleToggleFolder;
|
|
181
|
+
window.handleCheckboxChange = handleCheckboxChange;
|
|
182
|
+
window.copySelectedStructure = handleCopySelected;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Main entry point - Initialize application
|
|
2
|
+
import { SYSTEM_PROMPT } from './config.js';
|
|
3
|
+
import { checkHealth } from './api.js';
|
|
4
|
+
import './handlers.js'; // Import to register global functions
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Initialize application on DOM ready
|
|
8
|
+
*/
|
|
9
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
10
|
+
// Load system prompt text
|
|
11
|
+
document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
|
|
12
|
+
|
|
13
|
+
// Check server status
|
|
14
|
+
await checkServerStatus();
|
|
15
|
+
|
|
16
|
+
// Initialize Theme
|
|
17
|
+
initTheme();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check and update server status
|
|
22
|
+
*/
|
|
23
|
+
async function checkServerStatus() {
|
|
24
|
+
const statusEl = document.getElementById('status');
|
|
25
|
+
const isHealthy = await checkHealth();
|
|
26
|
+
|
|
27
|
+
if (isHealthy) {
|
|
28
|
+
statusEl.textContent = '● Online';
|
|
29
|
+
statusEl.style.background = 'rgba(52, 199, 89, 0.15)';
|
|
30
|
+
statusEl.style.color = 'var(--ios-green)';
|
|
31
|
+
} else {
|
|
32
|
+
statusEl.textContent = '● Offline';
|
|
33
|
+
statusEl.style.background = 'rgba(255, 59, 48, 0.15)';
|
|
34
|
+
statusEl.style.color = 'var(--ios-red)';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize Theme Logic
|
|
40
|
+
*/
|
|
41
|
+
function initTheme() {
|
|
42
|
+
const themeBtn = document.getElementById('theme-toggle');
|
|
43
|
+
const themeIcon = document.getElementById('theme-icon');
|
|
44
|
+
|
|
45
|
+
// Get current theme from DOM (set by inline script) or localStorage
|
|
46
|
+
let currentTheme = localStorage.getItem('theme') || 'light';
|
|
47
|
+
|
|
48
|
+
// Update icon initially
|
|
49
|
+
updateThemeIcon(currentTheme);
|
|
50
|
+
|
|
51
|
+
themeBtn.addEventListener('click', () => {
|
|
52
|
+
// Toggle theme
|
|
53
|
+
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
54
|
+
|
|
55
|
+
// Update DOM
|
|
56
|
+
document.documentElement.setAttribute('data-theme', newTheme);
|
|
57
|
+
localStorage.setItem('theme', newTheme);
|
|
58
|
+
|
|
59
|
+
// Update local state
|
|
60
|
+
currentTheme = newTheme;
|
|
61
|
+
updateThemeIcon(newTheme);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function updateThemeIcon(theme) {
|
|
66
|
+
const themeIcon = document.getElementById('theme-icon');
|
|
67
|
+
if (theme === 'dark') {
|
|
68
|
+
themeIcon.textContent = '☀️';
|
|
69
|
+
} else {
|
|
70
|
+
themeIcon.textContent = '🌙';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// UI Utility Functions
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Display a toast notification
|
|
5
|
+
* @param {string} message - Message to display
|
|
6
|
+
* @param {string} type - Type of toast: 'success', 'error', 'info'
|
|
7
|
+
*/
|
|
8
|
+
export function showToast(message, type = 'success') {
|
|
9
|
+
const toast = document.getElementById('toast');
|
|
10
|
+
// Reset text content to remove potential icon junk
|
|
11
|
+
toast.textContent = message;
|
|
12
|
+
toast.className = `toast ${type}`;
|
|
13
|
+
toast.classList.add('show');
|
|
14
|
+
|
|
15
|
+
// Clear previous timeout if exists
|
|
16
|
+
if (toast.timeoutId) clearTimeout(toast.timeoutId);
|
|
17
|
+
|
|
18
|
+
toast.timeoutId = setTimeout(() => toast.classList.remove('show'), 3000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Show loading state on button
|
|
23
|
+
* @param {HTMLElement} button - Button element
|
|
24
|
+
* @param {string} originalText - Original button HTML
|
|
25
|
+
*/
|
|
26
|
+
export function showLoading(button, originalText) {
|
|
27
|
+
button.disabled = true;
|
|
28
|
+
button.innerHTML = '<span class="loading"></span>';
|
|
29
|
+
button.dataset.originalText = originalText;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reset button to original state
|
|
34
|
+
* @param {HTMLElement} button - Button element
|
|
35
|
+
*/
|
|
36
|
+
export function resetButton(button) {
|
|
37
|
+
button.disabled = false;
|
|
38
|
+
const originalText = button.dataset.originalText;
|
|
39
|
+
button.innerHTML = originalText;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Display API response in response area
|
|
44
|
+
* @param {string} elementId - ID of response element
|
|
45
|
+
* @param {Object} data - Response data
|
|
46
|
+
* @param {boolean} isError - Whether this is an error response
|
|
47
|
+
*/
|
|
48
|
+
export function showResponse(elementId, data, isError = false) {
|
|
49
|
+
const el = document.getElementById(elementId);
|
|
50
|
+
el.className = 'response show ' + (isError ? 'error' : 'success');
|
|
51
|
+
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Update button state to show copied status
|
|
56
|
+
* @param {HTMLElement} button - Button element
|
|
57
|
+
* @param {HTMLElement} icon - Icon element
|
|
58
|
+
* @param {HTMLElement} text - Text element
|
|
59
|
+
* @param {string} originalIcon - Original icon text
|
|
60
|
+
* @param {string} originalText - Original button text
|
|
61
|
+
*/
|
|
62
|
+
export function showCopiedState(button, icon, text, originalIcon, originalText) {
|
|
63
|
+
button.classList.add('copied');
|
|
64
|
+
icon.textContent = '✓';
|
|
65
|
+
text.textContent = 'Copied';
|
|
66
|
+
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
button.classList.remove('copied');
|
|
69
|
+
icon.textContent = originalIcon;
|
|
70
|
+
text.textContent = originalText;
|
|
71
|
+
}, 2000);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Format number with commas (handles undefined/null)
|
|
76
|
+
* @param {number} num - Number to format
|
|
77
|
+
* @returns {string} Formatted string
|
|
78
|
+
*/
|
|
79
|
+
export function formatNumber(num) {
|
|
80
|
+
if (num === undefined || num === null) return '0';
|
|
81
|
+
return num.toLocaleString('en-US');
|
|
82
|
+
}
|
|
@@ -93,6 +93,58 @@ class TokenManager {
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Phân tích và điền thông tin token vào cấu trúc cây thư mục
|
|
98
|
+
* Tính toán token cho từng file và tổng hợp cho folder
|
|
99
|
+
*/
|
|
100
|
+
analyzeTree(tree, files) {
|
|
101
|
+
// Tạo map để lookup content file nhanh hơn
|
|
102
|
+
const fileMap = new Map();
|
|
103
|
+
files.forEach(f => {
|
|
104
|
+
if (f.content) {
|
|
105
|
+
fileMap.set(f.path, f.content);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const traverse = (node) => {
|
|
110
|
+
if (!node) return 0;
|
|
111
|
+
|
|
112
|
+
// Nếu là file
|
|
113
|
+
if (node.type === 'file') {
|
|
114
|
+
const content = fileMap.get(node.path);
|
|
115
|
+
// Nếu không có content (binary hoặc error), token là 0
|
|
116
|
+
node.tokens = content ? this.countTokens(content) : 0;
|
|
117
|
+
return node.tokens;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Nếu là directory có con
|
|
121
|
+
if (node.children) {
|
|
122
|
+
let sum = 0;
|
|
123
|
+
|
|
124
|
+
// Sắp xếp: Folder trước, File sau
|
|
125
|
+
node.children.sort((a, b) => {
|
|
126
|
+
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
127
|
+
return a.type === 'directory' ? -1 : 1;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Đệ quy tính tổng token của con
|
|
131
|
+
node.children.forEach(child => {
|
|
132
|
+
sum += traverse(child);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
node.tokens = sum;
|
|
136
|
+
return sum;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Nếu là node rỗng hoặc loại khác
|
|
140
|
+
node.tokens = 0;
|
|
141
|
+
return 0;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
traverse(tree);
|
|
145
|
+
return tree;
|
|
146
|
+
}
|
|
147
|
+
|
|
96
148
|
/**
|
|
97
149
|
* Chia nhỏ content thành chunks
|
|
98
150
|
*/
|
|
@@ -247,8 +299,6 @@ class TokenManager {
|
|
|
247
299
|
let currentTokens = 0;
|
|
248
300
|
let chunkIndex = 0;
|
|
249
301
|
|
|
250
|
-
// Không thêm header "Large File" nữa để UI sạch hơn
|
|
251
|
-
|
|
252
302
|
for (const line of lines) {
|
|
253
303
|
const lineTokens = this.countTokens(line + '\n');
|
|
254
304
|
|
|
Binary file
|
|
Binary file
|
package/vg-coder-cli-1.0.17.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.4.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.5.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.6.tgz
DELETED
|
Binary file
|
package/vg-coder-cli-2.0.7.tgz
DELETED
|
Binary file
|