vg-coder-cli 2.0.8 → 2.0.10

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.
Files changed (75) hide show
  1. package/README.md +19 -0
  2. package/SYSTEM_PROMPT.md +157 -0
  3. package/coverage/base.css +224 -0
  4. package/coverage/block-navigation.js +87 -0
  5. package/coverage/favicon.png +0 -0
  6. package/coverage/index.html +206 -0
  7. package/coverage/lcov-report/base.css +224 -0
  8. package/coverage/lcov-report/block-navigation.js +87 -0
  9. package/coverage/lcov-report/favicon.png +0 -0
  10. package/coverage/lcov-report/index.html +206 -0
  11. package/coverage/lcov-report/prettify.css +1 -0
  12. package/coverage/lcov-report/prettify.js +2 -0
  13. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  14. package/coverage/lcov-report/sorter.js +210 -0
  15. package/coverage/lcov-report/src/detectors/index.html +116 -0
  16. package/coverage/lcov-report/src/detectors/project-detector.js.html +1084 -0
  17. package/coverage/lcov-report/src/exporter/html-exporter.js.html +2839 -0
  18. package/coverage/lcov-report/src/exporter/index.html +116 -0
  19. package/coverage/lcov-report/src/ignore/ignore-manager.js.html +979 -0
  20. package/coverage/lcov-report/src/ignore/index.html +116 -0
  21. package/coverage/lcov-report/src/index.html +116 -0
  22. package/coverage/lcov-report/src/index.js.html +928 -0
  23. package/coverage/lcov-report/src/scanner/file-scanner.js.html +1903 -0
  24. package/coverage/lcov-report/src/scanner/index.html +116 -0
  25. package/coverage/lcov-report/src/tokenizer/index.html +116 -0
  26. package/coverage/lcov-report/src/tokenizer/token-manager.js.html +1252 -0
  27. package/coverage/lcov-report/src/utils/helpers.js.html +469 -0
  28. package/coverage/lcov-report/src/utils/index.html +116 -0
  29. package/coverage/lcov.info +1396 -0
  30. package/coverage/prettify.css +1 -0
  31. package/coverage/prettify.js +2 -0
  32. package/coverage/sort-arrow-sprite.png +0 -0
  33. package/coverage/sorter.js +210 -0
  34. package/coverage/src/detectors/index.html +116 -0
  35. package/coverage/src/detectors/project-detector.js.html +1084 -0
  36. package/coverage/src/exporter/html-exporter.js.html +2839 -0
  37. package/coverage/src/exporter/index.html +116 -0
  38. package/coverage/src/ignore/ignore-manager.js.html +979 -0
  39. package/coverage/src/ignore/index.html +116 -0
  40. package/coverage/src/index.html +116 -0
  41. package/coverage/src/index.js.html +928 -0
  42. package/coverage/src/scanner/file-scanner.js.html +1903 -0
  43. package/coverage/src/scanner/index.html +116 -0
  44. package/coverage/src/tokenizer/index.html +116 -0
  45. package/coverage/src/tokenizer/token-manager.js.html +1252 -0
  46. package/coverage/src/utils/helpers.js.html +469 -0
  47. package/coverage/src/utils/index.html +116 -0
  48. package/jest.config.js +16 -0
  49. package/package.json +5 -3
  50. package/scripts/build.js +40 -0
  51. package/src/scanner/file-scanner.js +3 -104
  52. package/src/server/api-server.js +74 -18
  53. package/src/server/views/css/structure.css +148 -0
  54. package/src/server/views/dashboard.css +176 -312
  55. package/src/server/views/dashboard.html +153 -85
  56. package/src/server/views/js/api.js +19 -2
  57. package/src/server/views/js/features/structure.js +221 -0
  58. package/src/server/views/js/handlers.js +38 -70
  59. package/src/server/views/js/main.js +60 -0
  60. package/src/server/views/js/utils.js +10 -0
  61. package/src/server/views/vg-coder/assets/icon128.png +0 -0
  62. package/src/server/views/vg-coder/assets/icon16.png +0 -0
  63. package/src/server/views/vg-coder/assets/icon48.png +0 -0
  64. package/src/server/views/vg-coder/background.js +2 -0
  65. package/src/server/views/vg-coder/background.js.LICENSE.txt +118 -0
  66. package/src/server/views/vg-coder/controller.js +1 -0
  67. package/src/server/views/vg-coder/manifest.json +58 -0
  68. package/src/server/views/vg-coder/options.css +164 -0
  69. package/src/server/views/vg-coder/options.html +48 -0
  70. package/src/server/views/vg-coder/options.js +1 -0
  71. package/src/server/views/vg-coder/rules.json +23 -0
  72. package/src/tokenizer/token-manager.js +52 -2
  73. package/vg-coder-cli-2.0.10.tgz +0 -0
  74. package/vg-coder.zip +0 -0
  75. package/vg-coder-cli-2.0.8.tgz +0 -0
@@ -5,13 +5,11 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport"
7
7
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
8
- <meta name="apple-mobile-web-app-capable" content="yes">
9
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
- <title>VG Coder API Dashboard</title>
8
+ <title>VG Coder - Split View</title>
11
9
  <link rel="stylesheet" href="/dashboard.css">
10
+ <link rel="stylesheet" href="/css/structure.css">
12
11
  <script>
13
- // Pre-load theme to prevent flash
14
- (function() {
12
+ (function () {
15
13
  const savedTheme = localStorage.getItem('theme') || 'light';
16
14
  document.documentElement.setAttribute('data-theme', savedTheme);
17
15
  })();
@@ -19,100 +17,170 @@
19
17
  </head>
20
18
 
21
19
  <body>
22
- <div class="container">
23
- <div class="header">
24
- <div class="header-content">
25
- <span class="status" id="status">● Server Starting...</span>
26
- <div style="height: 10px;"></div>
27
- <h1>VG Coder</h1>
28
- <p>API Dashboard & Automation</p>
29
- </div>
30
- <button class="theme-toggle" id="theme-toggle" title="Toggle Dark Mode">
31
- <span id="theme-icon">🌙</span>
32
- </button>
33
- </div>
34
-
35
- <!-- System Prompt Section -->
36
- <div class="system-prompt-card">
37
- <div class="system-prompt-header" onclick="toggleSystemPrompt()">
38
- <div class="header-title-group">
39
- <button class="btn-icon-head" onclick="copySystemPromptFromHeader(event)" title="Copy System Prompt">
40
- 📋
20
+ <div class="split-layout">
21
+ <!-- CỘT TRÁI: Giao diện VG Coder cũ -->
22
+ <div class="left-panel">
23
+ <div class="container">
24
+ <div class="header">
25
+ <div class="header-content">
26
+ <span class="status" id="status">● Server Starting...</span>
27
+ <div style="height: 5px;"></div>
28
+ </div>
29
+ <button class="theme-toggle" id="theme-toggle" title="Toggle Dark Mode">
30
+ <span id="theme-icon">🌙</span>
41
31
  </button>
42
- <h2>System Prompt</h2>
43
32
  </div>
44
- <span class="toggle-icon" id="toggle-icon">▼</span>
45
- </div>
46
- <div class="system-prompt-content" id="system-prompt-content">
47
- <div class="prompt-text" id="prompt-text"></div>
48
- <button class="btn btn-copy" onclick="copySystemPrompt()">
49
- <span id="copy-icon">📋</span>
50
- <span id="copy-text">Copy System Prompt</span>
51
- </button>
52
- </div>
53
- </div>
54
33
 
55
- <div class="endpoints">
56
- <!-- Analyze -->
57
- <div class="endpoint-card">
58
- <div class="endpoint-header">
59
- <!-- Left side: Method + Path -->
60
- <div class="endpoint-title-group">
61
- <span class="method post">POST</span>
62
- <span class="endpoint-path">/api/analyze</span>
34
+ <!-- System Prompt Section -->
35
+ <div class="system-prompt-card">
36
+ <div class="system-prompt-header" onclick="toggleSystemPrompt()">
37
+ <div class="header-title-group">
38
+ <button class="btn-icon-head" onclick="copySystemPromptFromHeader(event)"
39
+ title="Copy System Prompt">
40
+ 📋
41
+ </button>
42
+ <h2>System Prompt</h2>
43
+ </div>
44
+ <span class="toggle-icon" id="toggle-icon">▼</span>
45
+ </div>
46
+ <div class="system-prompt-content" id="system-prompt-content">
47
+ <div class="prompt-text" id="prompt-text"></div>
48
+ <button class="btn btn-copy" onclick="copySystemPrompt(event)">
49
+ <span id="copy-icon">📋</span>
50
+ <span id="copy-text">Copy System Prompt</span>
51
+ </button>
63
52
  </div>
64
- <!-- Right side: Download Icon -->
65
- <button class="btn-icon-head" onclick="testAnalyze()" title="Download Project Source">
66
- 📥
67
- </button>
68
- </div>
69
- <p class="endpoint-desc">Phân tích dự án và lấy toàn bộ source code.</p>
70
- <div class="form-group">
71
- <label>Path</label>
72
- <input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
73
- </div>
74
- <div class="btn-group">
75
- <!-- Big Download button removed as it's now in the header -->
76
- <button class="btn btn-copy" onclick="copyAnalyzeResult()">
77
- <span id="analyze-copy-icon">📋</span>
78
- <span id="analyze-copy-text">Copy Text</span>
79
- </button>
80
53
  </div>
81
- <div class="response" id="analyze-response"></div>
82
- </div>
83
54
 
84
- <!-- Execute Bash -->
85
- <div class="endpoint-card">
86
- <div class="endpoint-header">
87
- <div class="endpoint-title-group">
88
- <span class="method post">POST</span>
89
- <span class="endpoint-path">/api/execute</span>
55
+ <!-- Extension Installation Guide -->
56
+ <div class="system-prompt-card" id="extension-card">
57
+ <div class="system-prompt-header" onclick="toggleExtensionGuide()">
58
+ <div class="header-title-group">
59
+ <span style="font-size: 16px;">🧩</span>
60
+ <h2>Cài đặt Chrome Extension</h2>
61
+ </div>
62
+ <span class="toggle-icon" id="ext-toggle-icon">▼</span>
63
+ </div>
64
+ <div class="system-prompt-content" id="extension-content">
65
+ <div class="extension-steps">
66
+ <ol>
67
+ <li style="margin-bottom: 8px;">
68
+ Copy link này và dán vào tab mới:
69
+ <div class="form-group" style="margin-top: 5px; margin-bottom: 0;">
70
+ <div style="display: flex; gap: 5px;">
71
+ <input type="text" id="chrome-url-input" readonly value="chrome://extensions" onclick="this.select()" style="font-family: monospace;">
72
+ <button class="btn btn-copy" style="flex: 0 0 40px; padding: 0;" onclick="copyChromeUrl(event)" title="Copy URL">
73
+ <span>📋</span>
74
+ </button>
75
+ </div>
76
+ </div>
77
+ </li>
78
+ <li>Bật <b>Developer mode</b> (Góc phải trên cùng)</li>
79
+ <li>Chọn <b>Load unpacked</b></li>
80
+ <li>Dán đường dẫn bên dưới vào:</li>
81
+ </ol>
82
+ </div>
83
+ <div class="form-group">
84
+ <input type="text" id="extension-path-input" readonly value="Loading path..." onclick="this.select()">
85
+ </div>
86
+ <button class="btn btn-copy" onclick="copyExtensionPath(event)">
87
+ <span id="ext-copy-icon">📋</span>
88
+ <span id="ext-copy-text">Copy Path</span>
89
+ </button>
90
90
  </div>
91
- <!-- Could add an execute icon here later if needed -->
92
- </div>
93
- <p class="endpoint-desc">Thực thi bash script với syntax validation.</p>
94
- <div class="form-group">
95
- <label>Bash Script</label>
96
- <textarea id="execute-bash"
97
- placeholder="mkdir -p src/test&#10;echo 'Hello' > src/test/hello.txt"></textarea>
98
91
  </div>
99
- <div class="btn-group">
100
- <button class="btn" onclick="testExecute()">
101
- <span>▶️</span>
102
- <span>Run Script</span>
103
- </button>
104
- <button class="btn" onclick="executeFromClipboard()">
105
- <span>📋</span>
106
- <span>Paste & Run</span>
107
- </button>
92
+
93
+ <div class="endpoints">
94
+ <!-- Analyze -->
95
+ <div class="endpoint-card">
96
+ <div class="endpoint-header">
97
+ <div class="endpoint-title-group">
98
+ <span class="method post">POST</span>
99
+ <span class="endpoint-path">/analyze</span>
100
+ </div>
101
+ <button class="btn-icon-head" onclick="testAnalyze(event)" title="Download Project Source">
102
+ 📥
103
+ </button>
104
+ </div>
105
+ <div class="form-group">
106
+ <input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
107
+ </div>
108
+ <div class="btn-group">
109
+ <button class="btn btn-copy" onclick="copyAnalyzeResult(event)">
110
+ <span id="analyze-copy-icon">📋</span>
111
+ <span id="analyze-copy-text">Copy Text</span>
112
+ </button>
113
+ </div>
114
+ <div class="response" id="analyze-response"></div>
115
+ </div>
116
+
117
+ <!-- Execute Bash -->
118
+ <div class="endpoint-card">
119
+ <div class="endpoint-header">
120
+ <div class="endpoint-title-group">
121
+ <span class="method post">POST</span>
122
+ <span class="endpoint-path">/execute</span>
123
+ </div>
124
+ </div>
125
+ <div class="form-group">
126
+ <textarea id="execute-bash"
127
+ placeholder="mkdir -p src/test&#10;echo 'Hello' > src/test/hello.txt"></textarea>
128
+ </div>
129
+ <div class="btn-group">
130
+ <button class="btn" onclick="testExecute(event)">
131
+ <span>▶️</span> Run
132
+ </button>
133
+ <button class="btn" onclick="executeFromClipboard(event)">
134
+ <span>📋</span> Paste & Run
135
+ </button>
136
+ </div>
137
+ <div class="response" id="execute-response"></div>
138
+ </div>
139
+
140
+ <!-- Structure -->
141
+ <div class="endpoint-card">
142
+ <div class="endpoint-header">
143
+ <div class="endpoint-title-group">
144
+ <span class="method get" style="background: var(--ios-blue); color: white;">GET</span>
145
+ <span class="endpoint-path">/structure</span>
146
+ </div>
147
+ <button class="btn-icon-head" onclick="copySelectedStructure(event)" title="Copy Selected">
148
+ 📋
149
+ </button>
150
+ </div>
151
+ <div class="form-group">
152
+ <input type="text" id="structure-path" value="." placeholder="Project path">
153
+ </div>
154
+ <div class="btn-group">
155
+ <button class="btn" onclick="testStructure(event)">
156
+ <span>🌳</span> View
157
+ </button>
158
+ </div>
159
+ <div class="tree-container" id="structure-tree" style="display: none;">
160
+ <div class="tree-header">
161
+ <span>Tree</span>
162
+ <span class="tree-total-tokens" id="total-tokens-badge">0 tokens</span>
163
+ </div>
164
+ <div class="tree-content" id="tree-content"></div>
165
+ </div>
166
+ <div class="response" id="structure-response" style="display: none;"></div>
167
+ </div>
108
168
  </div>
109
- <div class="response" id="execute-response"></div>
169
+ <div style="height: 50px;"></div> <!-- Spacer for scrolling -->
170
+ </div>
171
+ </div>
172
+
173
+ <!-- CỘT PHẢI: Iframe ChatGPT -->
174
+ <div class="right-panel">
175
+ <div class="iframe-placeholder">
176
+ <p>⚠️ Nếu trang trắng, hãy cài Extension <b>"Ignore X-Frame-Options"</b></p>
177
+ <a href="https://chatgpt.com" target="_blank" class="link-fallback">Mở ChatGPT tab mới ↗</a>
110
178
  </div>
179
+ <iframe src="https://chatgpt.com" title="ChatGPT Integration"></iframe>
111
180
  </div>
112
181
  </div>
113
182
 
114
183
  <div class="toast" id="toast"></div>
115
-
116
184
  <script type="module" src="/js/main.js"></script>
117
185
  </body>
118
186
 
@@ -4,13 +4,14 @@ import { API_BASE } from './config.js';
4
4
  /**
5
5
  * Analyze project and get source code
6
6
  * @param {string} path - Project path to analyze
7
+ * @param {Array<string>} specificFiles - Optional list of relative paths to filter
7
8
  * @returns {Promise<string>} - Project content as text
8
9
  */
9
- export async function analyzeProject(path) {
10
+ export async function analyzeProject(path, specificFiles = null) {
10
11
  const res = await fetch(`${API_BASE}/api/analyze`, {
11
12
  method: 'POST',
12
13
  headers: { 'Content-Type': 'application/json' },
13
- body: JSON.stringify({ path })
14
+ body: JSON.stringify({ path, specificFiles })
14
15
  });
15
16
 
16
17
  if (!res.ok) {
@@ -55,6 +56,22 @@ export async function checkHealth() {
55
56
  }
56
57
  }
57
58
 
59
+ /**
60
+ * Get project structure with tokens
61
+ * @param {string} path - Project path
62
+ * @returns {Promise<Object>} - Structure data
63
+ */
64
+ export async function getStructure(path) {
65
+ const res = await fetch(`${API_BASE}/api/structure?path=${encodeURIComponent(path)}`);
66
+
67
+ if (!res.ok) {
68
+ const data = await res.json();
69
+ throw new Error(data.error || 'Failed to get structure');
70
+ }
71
+
72
+ return await res.json();
73
+ }
74
+
58
75
  /**
59
76
  * Copy content as file to clipboard
60
77
  * @param {string} filename - Filename for the clipboard item
@@ -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
+ }