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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
.right-panel {
|
|
3
3
|
flex: 1;
|
|
4
4
|
height: 100%;
|
|
5
|
-
background: #
|
|
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
|
-
|
|
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:
|
|
56
|
-
|
|
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
|
|
66
|
+
/* Placeholder / Guide (Overlay on top of Iframe) */
|
|
60
67
|
.iframe-placeholder {
|
|
61
68
|
position: absolute;
|
|
62
|
-
top:
|
|
63
|
-
left:
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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()"
|
|
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
|
|
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>
|
|
162
|
+
<div style="height: 50px;"></div>
|
|
201
163
|
</div>
|
|
202
164
|
</div>
|
|
203
165
|
|
|
204
|
-
<!-- CỘT PHẢI: AI Iframe
|
|
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;"
|
|
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
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
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) → <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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
}
|