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.
@@ -2,7 +2,7 @@
2
2
  .right-panel {
3
3
  flex: 1;
4
4
  height: 100%;
5
- background: #fff;
5
+ background: #f0f2f5;
6
6
  position: relative;
7
7
  display: flex;
8
8
  flex-direction: column;
@@ -18,6 +18,8 @@
18
18
  padding: 0 15px;
19
19
  gap: 10px;
20
20
  justify-content: space-between;
21
+ z-index: 20;
22
+ /* Highest */
21
23
  }
22
24
 
23
25
  .ai-select-group {
@@ -45,40 +47,213 @@
45
47
  flex: 1;
46
48
  position: relative;
47
49
  width: 100%;
48
- background: #fff;
50
+ height: 100%;
51
+ overflow: hidden;
49
52
  }
50
53
 
51
54
  .right-panel iframe {
52
55
  width: 100%;
53
56
  height: 100%;
54
57
  border: none;
55
- position: relative;
56
- z-index: 2;
58
+ position: absolute;
59
+ top: 0;
60
+ left: 0;
61
+ z-index: 1;
62
+ /* Low z-index */
63
+ background: white;
57
64
  }
58
65
 
59
- /* Placeholder underneath iframe */
66
+ /* Placeholder / Guide (Overlay on top of Iframe) */
60
67
  .iframe-placeholder {
61
68
  position: absolute;
62
- top: 50%;
63
- left: 50%;
64
- transform: translate(-50%, -50%);
69
+ top: 0;
70
+ left: 0;
71
+ width: 100%;
72
+ height: 100%;
73
+ z-index: 10;
74
+ /* Higher than iframe to cover error page */
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ padding: 20px;
79
+ overflow-y: auto;
80
+ backdrop-filter: blur(5px);
81
+ transition: opacity 0.3s ease, visibility 0.3s;
82
+ }
83
+
84
+ /* Class to hide guide when user clicks "Done" */
85
+ .iframe-placeholder.hidden {
86
+ opacity: 0;
87
+ visibility: hidden;
88
+ pointer-events: none;
89
+ }
90
+
91
+ /* Center Guide Card */
92
+ .extension-guide-center {
93
+ background: var(--ios-card);
94
+ padding: 30px;
95
+ border-radius: 16px;
96
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
97
+ max-width: 500px;
98
+ width: 100%;
99
+ text-align: left;
100
+ border: 1px solid var(--ios-separator);
101
+ position: relative;
102
+ }
103
+
104
+ .guide-close-btn {
105
+ position: absolute;
106
+ top: 15px;
107
+ right: 15px;
108
+ background: transparent;
109
+ border: none;
110
+ font-size: 20px;
111
+ color: var(--text-secondary);
112
+ cursor: pointer;
113
+ width: 30px;
114
+ height: 30px;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ border-radius: 50%;
119
+ }
120
+
121
+ .guide-close-btn:hover {
122
+ background: var(--ios-input-bg);
123
+ color: var(--text-primary);
124
+ }
125
+
126
+ .guide-icon {
127
+ font-size: 40px;
128
+ margin-bottom: 15px;
65
129
  text-align: center;
130
+ display: block;
131
+ }
132
+
133
+ .guide-title {
134
+ font-size: 18px;
135
+ font-weight: 600;
136
+ margin-bottom: 10px;
137
+ text-align: center;
138
+ color: var(--text-primary);
139
+ }
140
+
141
+ .guide-desc {
142
+ font-size: 13px;
66
143
  color: var(--text-secondary);
67
- z-index: 1;
144
+ text-align: center;
145
+ margin-bottom: 25px;
146
+ line-height: 1.5;
147
+ }
148
+
149
+ .guide-steps {
150
+ list-style: none;
151
+ padding: 0;
152
+ margin: 0;
153
+ }
154
+
155
+ .guide-step {
156
+ margin-bottom: 15px;
157
+ font-size: 13px;
158
+ color: var(--text-primary);
159
+ }
160
+
161
+ .step-number {
162
+ display: inline-block;
163
+ width: 20px;
164
+ height: 20px;
165
+ background: var(--ios-blue);
166
+ color: white;
167
+ border-radius: 50%;
168
+ text-align: center;
169
+ line-height: 20px;
170
+ font-size: 11px;
171
+ font-weight: bold;
172
+ margin-right: 8px;
173
+ }
174
+
175
+ .url-box,
176
+ .path-box {
177
+ display: flex;
178
+ gap: 8px;
179
+ margin-top: 8px;
180
+ background: var(--ios-input-bg);
181
+ padding: 4px;
182
+ border-radius: 8px;
183
+ border: 1px solid var(--ios-separator);
184
+ }
185
+
186
+ .guide-input {
187
+ flex: 1;
188
+ background: transparent;
189
+ border: none;
190
+ font-family: 'SF Mono', 'Menlo', monospace;
191
+ font-size: 12px;
192
+ color: var(--text-primary);
193
+ padding: 6px;
68
194
  width: 100%;
69
195
  }
70
196
 
197
+ .guide-btn-copy {
198
+ background: var(--ios-card);
199
+ border: 1px solid var(--ios-separator);
200
+ border-radius: 6px;
201
+ padding: 0 12px;
202
+ font-size: 12px;
203
+ cursor: pointer;
204
+ font-weight: 500;
205
+ color: var(--text-primary);
206
+ transition: all 0.2s;
207
+ }
208
+
209
+ .guide-btn-copy:hover {
210
+ background: var(--ios-blue);
211
+ color: white;
212
+ border-color: var(--ios-blue);
213
+ }
214
+
215
+ .guide-footer {
216
+ margin-top: 25px;
217
+ padding-top: 20px;
218
+ border-top: 1px solid var(--ios-separator);
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: space-between;
222
+ }
223
+
224
+ .btn-primary-guide {
225
+ background: var(--ios-blue);
226
+ color: white;
227
+ border: none;
228
+ padding: 8px 16px;
229
+ border-radius: 8px;
230
+ font-weight: 500;
231
+ cursor: pointer;
232
+ font-size: 13px;
233
+ }
234
+
235
+ .btn-primary-guide:hover {
236
+ background: #0062cc;
237
+ }
238
+
71
239
  .link-fallback {
72
240
  color: var(--ios-blue);
73
241
  text-decoration: none;
74
- margin-top: 10px;
75
- display: inline-block;
242
+ font-weight: 500;
243
+ font-size: 13px;
244
+ }
245
+
246
+ .link-fallback:hover {
247
+ text-decoration: underline;
76
248
  }
77
249
 
78
- /* Mobile Responsive adjustment for right panel */
79
250
  @media (max-width: 768px) {
80
251
  .right-panel {
81
252
  flex: 1;
82
253
  height: 50vh;
83
254
  }
84
- }
255
+
256
+ .extension-guide-center {
257
+ padding: 20px;
258
+ }
259
+ }
@@ -11,19 +11,21 @@
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
-
14
+
15
15
  <!-- Syntax Highlighting -->
16
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" />
17
-
16
+ <link rel="stylesheet"
17
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" />
18
+
18
19
  <!-- Git Diff -->
19
- <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
20
-
20
+ <link rel="stylesheet" type="text/css"
21
+ href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
22
+
21
23
  <!-- Terminal Dependencies -->
22
24
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
23
25
  <script src="/socket.io/socket.io.js"></script>
24
26
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
25
27
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
26
-
28
+
27
29
  <!-- Other Libs -->
28
30
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"></script>
29
31
 
@@ -49,11 +51,13 @@
49
51
  <span id="theme-icon">🌙</span>
50
52
  </button>
51
53
  </div>
52
-
54
+
53
55
  <!-- Quick Actions -->
54
- <div class="endpoint-card" style="display: flex; gap: 10px; align-items: center; justify-content: space-between;">
56
+ <div class="endpoint-card"
57
+ style="display: flex; gap: 10px; align-items: center; justify-content: space-between;">
55
58
  <span style="font-weight: 600;">Tools:</span>
56
- <button class="btn" onclick="createNewTerminal()" style="background: #252526; border: 1px solid #444;">
59
+ <button class="btn" onclick="createNewTerminal()"
60
+ style="background: #252526; border: 1px solid #444;">
57
61
  <span>🖥️</span> New Terminal
58
62
  </button>
59
63
  </div>
@@ -79,50 +83,8 @@
79
83
  </div>
80
84
  </div>
81
85
 
82
- <!-- Extension Installation Guide -->
83
- <div class="system-prompt-card" id="extension-card">
84
- <div class="system-prompt-header" onclick="toggleExtensionGuide()">
85
- <div class="header-title-group">
86
- <span style="font-size: 16px;">🧩</span>
87
- <h2>Cài đặt Chrome Extension</h2>
88
- </div>
89
- <span class="toggle-icon" id="ext-toggle-icon">▼</span>
90
- </div>
91
- <div class="system-prompt-content" id="extension-content">
92
- <div class="extension-steps">
93
- <ol>
94
- <li style="margin-bottom: 8px;">
95
- Copy link này và dán vào tab mới:
96
- <div class="form-group" style="margin-top: 5px; margin-bottom: 0;">
97
- <div style="display: flex; gap: 5px;">
98
- <input type="text" id="chrome-url-input" readonly
99
- value="chrome://extensions" onclick="this.select()"
100
- style="font-family: monospace;">
101
- <button class="btn btn-copy" style="flex: 0 0 40px; padding: 0;"
102
- onclick="copyChromeUrl(event)" title="Copy URL">
103
- <span>📋</span>
104
- </button>
105
- </div>
106
- </div>
107
- </li>
108
- <li>Bật <b>Developer mode</b> (Góc phải trên cùng)</li>
109
- <li>Chọn <b>Load unpacked</b></li>
110
- <li>Dán đường dẫn bên dưới vào:</li>
111
- </ol>
112
- </div>
113
- <div class="form-group">
114
- <input type="text" id="extension-path-input" readonly value="Loading path..."
115
- onclick="this.select()">
116
- </div>
117
- <button class="btn btn-copy" onclick="copyExtensionPath(event)">
118
- <span id="ext-copy-icon">📋</span>
119
- <span id="ext-copy-text">Copy Path</span>
120
- </button>
121
- </div>
122
- </div>
123
-
124
86
  <div class="endpoints">
125
- <!-- Execute Bash (RESTORED) -->
87
+ <!-- Execute Bash -->
126
88
  <div class="endpoint-card">
127
89
  <div class="endpoint-header">
128
90
  <div class="endpoint-title-group">
@@ -197,11 +159,11 @@
197
159
  <div class="response" id="analyze-response"></div>
198
160
  </div>
199
161
  </div>
200
- <div style="height: 50px;"></div> <!-- Spacer for scrolling -->
162
+ <div style="height: 50px;"></div>
201
163
  </div>
202
164
  </div>
203
165
 
204
- <!-- CỘT PHẢI: AI Iframe with Selector & Git View -->
166
+ <!-- CỘT PHẢI: AI Iframe -->
205
167
  <div class="right-panel">
206
168
  <div class="ai-header">
207
169
  <div class="ai-select-group">
@@ -210,31 +172,71 @@
210
172
  <!-- Options filled by JS -->
211
173
  </select>
212
174
  </div>
213
-
175
+
176
+ <!-- NEW: Toggle Guide Button -->
177
+ <button id="guide-toggle-btn" class="btn-icon-head" style="margin-left:5px;"
178
+ title="Show Installation Guide">🧩</button>
179
+
214
180
  <button id="git-view-toggle" class="git-toggle-btn">View Changes</button>
215
- <button id="git-refresh-btn" class="btn-icon-head" style="display:none; margin-left:5px;" title="Refresh">↻</button>
216
-
181
+ <button id="git-refresh-btn" class="btn-icon-head" style="display:none; margin-left:5px;"
182
+ title="Refresh">↻</button>
183
+
217
184
  <a id="ai-external-link" href="#" target="_blank" class="btn-icon-head" title="Open in new tab">↗</a>
218
185
  </div>
219
-
220
- <!-- Git View Container (Hidden by default) -->
186
+
187
+ <!-- Git View Container -->
221
188
  <div id="git-view-container" class="git-view-container"></div>
222
189
 
223
190
  <!-- AI Iframe Container -->
224
191
  <div class="ai-iframe-container">
225
- <div class="iframe-placeholder">
226
- <p>⚠️ Nếu trang trắng, hãy cài Extension <b>"Ignore X-Frame-Options"</b></p>
227
- <a id="ai-placeholder-link" href="#" target="_blank" class="link-fallback">Mở tab mới ↗</a>
192
+ <!-- Center Extension Guide (Z-Index 10, covers iframe) -->
193
+ <div id="iframe-placeholder" class="iframe-placeholder hidden">
194
+ <div class="extension-guide-center">
195
+ <button class="guide-close-btn" id="guide-close-btn" title="Close Guide">×</button>
196
+ <span class="guide-icon">🧩</span>
197
+ <h3 class="guide-title">Cài đặt VG Coder Extension</h3>
198
+ <p class="guide-desc">
199
+ Không thấy trang web? AI Provider chặn hiển thị trong Iframe.<br>
200
+ Hãy cài đặt Extension để bỏ qua các giới hạn này.
201
+ </p>
202
+ <ol class="guide-steps">
203
+ <li class="guide-step">
204
+ <span class="step-number">1</span>
205
+ Copy link và dán vào tab mới:
206
+ <div class="url-box">
207
+ <input type="text" id="chrome-url-input-center" class="guide-input" readonly
208
+ value="chrome://extensions" onclick="this.select()">
209
+ <button class="guide-btn-copy" onclick="copyChromeUrl(event)">Copy</button>
210
+ </div>
211
+ </li>
212
+ <li class="guide-step">
213
+ <span class="step-number">2</span>
214
+ Bật <b>Developer mode</b> (Góc phải trên) &rarr; <b>Load unpacked</b>
215
+ </li>
216
+ <li class="guide-step">
217
+ <span class="step-number">3</span>
218
+ Chọn thư mục bên dưới:
219
+ <div class="path-box">
220
+ <input type="text" id="extension-path-input-center" class="guide-input" readonly
221
+ value="Loading..." onclick="this.select()">
222
+ <button class="guide-btn-copy" onclick="copyExtensionPath(event)">Copy</button>
223
+ </div>
224
+ </li>
225
+ </ol>
226
+ <div class="guide-footer">
227
+ <a id="ai-placeholder-link" href="#" target="_blank" class="link-fallback">Mở tab mới ↗</a>
228
+ <button id="guide-done-btn" class="btn-primary-guide">Đã xong / Tải lại</button>
229
+ </div>
230
+ </div>
228
231
  </div>
229
232
  <iframe id="ai-iframe" src="" title="AI Integration"></iframe>
230
233
  </div>
231
234
  </div>
232
235
  </div>
233
-
234
- <!-- Floating Terminals Container (Fixed on top of everything) -->
235
- <div id="floating-terminals-layer"></div>
236
236
 
237
+ <div id="floating-terminals-layer"></div>
237
238
  <div class="toast" id="toast"></div>
238
239
  <script type="module" src="/js/main.js"></script>
239
240
  </body>
240
- </html>
241
+
242
+ </html>
@@ -1,52 +1,29 @@
1
- // API Layer - All API calls centralized here
2
1
  import { API_BASE } from './config.js';
3
2
 
4
- /**
5
- * Analyze project and get source code
6
- * @param {string} path - Project path to analyze
7
- * @param {Array<string>} specificFiles - Optional list of relative paths to filter
8
- * @returns {Promise<string>} - Project content as text
9
- */
10
3
  export async function analyzeProject(path, specificFiles = null) {
11
4
  const res = await fetch(`${API_BASE}/api/analyze`, {
12
5
  method: 'POST',
13
6
  headers: { 'Content-Type': 'application/json' },
14
7
  body: JSON.stringify({ path, specificFiles })
15
8
  });
16
-
17
9
  if (!res.ok) {
18
10
  const data = await res.json();
19
11
  throw new Error(data.error || 'Analyze failed');
20
12
  }
21
-
22
13
  return await res.text();
23
14
  }
24
15
 
25
- /**
26
- * Execute bash script
27
- * @param {string} bash - Bash script to execute
28
- * @returns {Promise<Object>} - Execution result
29
- */
30
16
  export async function executeScript(bash) {
31
17
  const res = await fetch(`${API_BASE}/api/execute`, {
32
18
  method: 'POST',
33
19
  headers: { 'Content-Type': 'application/json' },
34
20
  body: JSON.stringify({ bash })
35
21
  });
36
-
37
22
  const data = await res.json();
38
-
39
- if (!res.ok) {
40
- throw new Error(data.error || 'Execute failed');
41
- }
42
-
23
+ if (!res.ok) throw new Error(data.error || 'Execute failed');
43
24
  return data;
44
25
  }
45
26
 
46
- /**
47
- * Check server health status
48
- * @returns {Promise<boolean>} - True if server is healthy
49
- */
50
27
  export async function checkHealth() {
51
28
  try {
52
29
  const res = await fetch(`${API_BASE}/health`);
@@ -56,78 +33,97 @@ export async function checkHealth() {
56
33
  }
57
34
  }
58
35
 
59
- /**
60
- * Get project structure with tokens
61
- * @param {string} path - Project path
62
- * @returns {Promise<Object>} - Structure data
63
- */
64
36
  export async function getStructure(path) {
65
37
  const res = await fetch(`${API_BASE}/api/structure?path=${encodeURIComponent(path)}`);
66
-
67
38
  if (!res.ok) {
68
39
  const data = await res.json();
69
40
  throw new Error(data.error || 'Failed to get structure');
70
41
  }
42
+ return await res.json();
43
+ }
44
+
45
+ // --- Git API Wrappers ---
71
46
 
47
+ export async function getGitStatus() {
48
+ const res = await fetch(`${API_BASE}/api/git/status`);
49
+ if (!res.ok) throw new Error('Failed to fetch status');
72
50
  return await res.json();
73
51
  }
74
52
 
75
- /**
76
- * Get Git Diff
77
- * @returns {Promise<string>} - Unified diff string
78
- */
79
- export async function getGitDiff() {
80
- const res = await fetch(`${API_BASE}/api/git/diff`);
53
+ export async function getGitDiff(file = null, type = 'working') {
54
+ let url = `${API_BASE}/api/git/diff?type=${type}`;
55
+ if (file) url += `&file=${encodeURIComponent(file)}`;
56
+ const res = await fetch(url);
81
57
  const data = await res.json();
82
-
83
- if (!res.ok) {
84
- throw new Error(data.error || 'Failed to get git diff');
85
- }
86
-
58
+ if (!res.ok) throw new Error(data.error || 'Failed to get git diff');
87
59
  return data.diff;
88
60
  }
89
61
 
90
- /**
91
- * Copy content as file to clipboard
92
- * @param {string} filename - Filename for the clipboard item
93
- * @param {string} content - Content to copy
94
- */
95
- export async function copyAsFile(filename, content) {
96
- const blob = new Blob([content], {
97
- type: "application/octet-stream"
62
+ export async function stageFile(files) {
63
+ const res = await fetch(`${API_BASE}/api/git/stage`, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ files: Array.isArray(files) ? files : [files] })
67
+ });
68
+ return res.ok;
69
+ }
70
+
71
+ export async function unstageFile(files) {
72
+ const res = await fetch(`${API_BASE}/api/git/unstage`, {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify({ files: Array.isArray(files) ? files : [files] })
76
+ });
77
+ return res.ok;
78
+ }
79
+
80
+ export async function discardChange(files) {
81
+ const res = await fetch(`${API_BASE}/api/git/discard`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ files: Array.isArray(files) ? files : [files] })
85
+ });
86
+ if (!res.ok) throw new Error('Discard failed');
87
+ return res.ok;
88
+ }
89
+
90
+ export async function commitChanges(message) {
91
+ const res = await fetch(`${API_BASE}/api/git/commit`, {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ message })
98
95
  });
96
+ if (!res.ok) {
97
+ const data = await res.json();
98
+ throw new Error(data.error || 'Commit failed');
99
+ }
100
+ return true;
101
+ }
99
102
 
100
- const item = new ClipboardItem(
101
- { [blob.type]: blob },
102
- {
103
- type: "application/octet-stream",
104
- presentationStyle: "attachment",
105
- name: filename
106
- }
107
- );
103
+ // ------------------------
108
104
 
105
+ export async function copyAsFile(filename, content) {
106
+ const blob = new Blob([content], { type: "application/octet-stream" });
107
+ const item = new ClipboardItem({
108
+ [blob.type]: blob
109
+ }, {
110
+ type: "application/octet-stream",
111
+ presentationStyle: "attachment",
112
+ name: filename
113
+ });
109
114
  await navigator.clipboard.write([item]);
110
115
  }
111
116
 
112
- /**
113
- * Copy text to clipboard with fallback
114
- * @param {string} text - Text to copy
115
- */
116
117
  export async function copyToClipboard(text) {
117
118
  try {
118
119
  const blob = new Blob([text], { type: 'text/plain' });
119
120
  const item = new ClipboardItem({ 'text/plain': blob });
120
121
  await navigator.clipboard.write([item]);
121
122
  } catch (err) {
122
- // Fallback to simple writeText
123
123
  await navigator.clipboard.writeText(text);
124
124
  }
125
125
  }
126
126
 
127
- /**
128
- * Read text from clipboard
129
- * @returns {Promise<string>} - Clipboard text content
130
- */
131
127
  export async function readFromClipboard() {
132
128
  return await navigator.clipboard.readText();
133
129
  }