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
|
@@ -8,440 +8,45 @@
|
|
|
8
8
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
9
9
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
10
10
|
<title>VG Coder API Dashboard</title>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
--ios-input-bg: #F2F2F7;
|
|
21
|
-
--ios-separator: #C6C6C8;
|
|
22
|
-
--safe-area-top: env(safe-area-inset-top);
|
|
23
|
-
--safe-area-bottom: env(safe-area-inset-bottom);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
* {
|
|
27
|
-
margin: 0;
|
|
28
|
-
padding: 0;
|
|
29
|
-
box-sizing: border-box;
|
|
30
|
-
-webkit-tap-highlight-color: transparent;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
body {
|
|
34
|
-
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
35
|
-
background-color: var(--ios-bg);
|
|
36
|
-
color: #000;
|
|
37
|
-
min-height: 100vh;
|
|
38
|
-
padding: 0;
|
|
39
|
-
padding-bottom: calc(20px + var(--safe-area-bottom));
|
|
40
|
-
-webkit-font-smoothing: antialiased;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.container {
|
|
44
|
-
max-width: 800px;
|
|
45
|
-
margin: 0 auto;
|
|
46
|
-
padding: 20px;
|
|
47
|
-
padding-top: calc(20px + var(--safe-area-top));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* Header iOS Style */
|
|
51
|
-
.header {
|
|
52
|
-
text-align: left;
|
|
53
|
-
margin-bottom: 24px;
|
|
54
|
-
padding: 0 4px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.header h1 {
|
|
58
|
-
color: #000;
|
|
59
|
-
font-size: 34px;
|
|
60
|
-
font-weight: 700;
|
|
61
|
-
letter-spacing: -0.5px;
|
|
62
|
-
margin-bottom: 8px;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.header p {
|
|
66
|
-
color: var(--ios-gray);
|
|
67
|
-
font-size: 17px;
|
|
68
|
-
line-height: 1.4;
|
|
69
|
-
margin-bottom: 12px;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.status {
|
|
73
|
-
display: inline-flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
padding: 6px 12px;
|
|
76
|
-
background: rgba(52, 199, 89, 0.15);
|
|
77
|
-
color: var(--ios-green);
|
|
78
|
-
border-radius: 20px;
|
|
79
|
-
font-size: 13px;
|
|
80
|
-
font-weight: 600;
|
|
81
|
-
margin-top: 5px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Card Style (Inset Grouped) */
|
|
85
|
-
.system-prompt-card,
|
|
86
|
-
.endpoint-card {
|
|
87
|
-
background: var(--ios-card);
|
|
88
|
-
border-radius: 16px;
|
|
89
|
-
padding: 20px;
|
|
90
|
-
margin-bottom: 20px;
|
|
91
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
92
|
-
overflow: hidden;
|
|
93
|
-
position: relative;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* System Prompt */
|
|
97
|
-
.system-prompt-header {
|
|
98
|
-
display: flex;
|
|
99
|
-
justify-content: space-between;
|
|
100
|
-
align-items: center;
|
|
101
|
-
cursor: pointer;
|
|
102
|
-
padding: 4px 0;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.system-prompt-header h2 {
|
|
106
|
-
color: #000;
|
|
107
|
-
font-size: 20px;
|
|
108
|
-
font-weight: 600;
|
|
109
|
-
display: flex;
|
|
110
|
-
align-items: center;
|
|
111
|
-
gap: 10px;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.toggle-icon {
|
|
115
|
-
color: var(--ios-gray);
|
|
116
|
-
font-size: 14px;
|
|
117
|
-
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.toggle-icon.open {
|
|
121
|
-
transform: rotate(180deg);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.system-prompt-content {
|
|
125
|
-
max-height: 0;
|
|
126
|
-
overflow: hidden;
|
|
127
|
-
transition: max-height 0.4s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.3s;
|
|
128
|
-
opacity: 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.system-prompt-content.open {
|
|
132
|
-
max-height: 2000px;
|
|
133
|
-
opacity: 1;
|
|
134
|
-
margin-top: 16px;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.prompt-text {
|
|
138
|
-
background: var(--ios-input-bg);
|
|
139
|
-
border: none;
|
|
140
|
-
border-radius: 12px;
|
|
141
|
-
padding: 16px;
|
|
142
|
-
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
|
|
143
|
-
font-size: 13px;
|
|
144
|
-
line-height: 1.5;
|
|
145
|
-
white-space: pre-wrap;
|
|
146
|
-
max-height: 300px;
|
|
147
|
-
overflow-y: auto;
|
|
148
|
-
margin-bottom: 16px;
|
|
149
|
-
color: #333;
|
|
150
|
-
-webkit-overflow-scrolling: touch;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/* Endpoints */
|
|
154
|
-
.endpoint-header {
|
|
155
|
-
display: flex;
|
|
156
|
-
align-items: center;
|
|
157
|
-
flex-wrap: wrap;
|
|
158
|
-
gap: 10px;
|
|
159
|
-
margin-bottom: 12px;
|
|
160
|
-
border-bottom: 0.5px solid var(--ios-separator);
|
|
161
|
-
padding-bottom: 12px;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.method {
|
|
165
|
-
padding: 4px 10px;
|
|
166
|
-
border-radius: 6px;
|
|
167
|
-
font-weight: 700;
|
|
168
|
-
font-size: 12px;
|
|
169
|
-
text-transform: uppercase;
|
|
170
|
-
letter-spacing: 0.5px;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.method.post {
|
|
174
|
-
background: var(--ios-green);
|
|
175
|
-
color: white;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.endpoint-path {
|
|
179
|
-
font-family: 'SF Mono', 'Menlo', monospace;
|
|
180
|
-
color: #000;
|
|
181
|
-
font-size: 16px;
|
|
182
|
-
font-weight: 600;
|
|
183
|
-
word-break: break-all;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.endpoint-desc {
|
|
187
|
-
color: var(--ios-gray);
|
|
188
|
-
margin-bottom: 20px;
|
|
189
|
-
font-size: 15px;
|
|
190
|
-
line-height: 1.4;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/* Forms */
|
|
194
|
-
.form-group {
|
|
195
|
-
margin-bottom: 20px;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.form-group label {
|
|
199
|
-
display: block;
|
|
200
|
-
margin-bottom: 8px;
|
|
201
|
-
color: #000;
|
|
202
|
-
font-weight: 500;
|
|
203
|
-
font-size: 14px;
|
|
204
|
-
text-transform: uppercase;
|
|
205
|
-
letter-spacing: 0.3px;
|
|
206
|
-
opacity: 0.6;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.form-group input,
|
|
210
|
-
.form-group textarea {
|
|
211
|
-
width: 100%;
|
|
212
|
-
padding: 14px;
|
|
213
|
-
background: var(--ios-input-bg);
|
|
214
|
-
border: none;
|
|
215
|
-
border-radius: 12px;
|
|
216
|
-
font-size: 17px;
|
|
217
|
-
/* Prevent zoom on iOS */
|
|
218
|
-
font-family: 'SF Mono', 'Menlo', monospace;
|
|
219
|
-
color: #000;
|
|
220
|
-
transition: background 0.2s;
|
|
221
|
-
appearance: none;
|
|
222
|
-
-webkit-appearance: none;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.form-group input:focus,
|
|
226
|
-
.form-group textarea:focus {
|
|
227
|
-
outline: none;
|
|
228
|
-
background: #E5E5EA;
|
|
229
|
-
/* Slightly darker on focus */
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.form-group textarea {
|
|
233
|
-
min-height: 120px;
|
|
234
|
-
resize: none;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/* Buttons */
|
|
238
|
-
.btn-group {
|
|
239
|
-
display: flex;
|
|
240
|
-
gap: 12px;
|
|
241
|
-
margin-top: 24px;
|
|
242
|
-
flex-direction: column;
|
|
243
|
-
/* Stack on mobile by default */
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
@media (min-width: 640px) {
|
|
247
|
-
.btn-group {
|
|
248
|
-
flex-direction: row;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.btn {
|
|
253
|
-
background: var(--ios-blue);
|
|
254
|
-
color: white;
|
|
255
|
-
border: none;
|
|
256
|
-
padding: 14px 20px;
|
|
257
|
-
border-radius: 12px;
|
|
258
|
-
cursor: pointer;
|
|
259
|
-
font-size: 17px;
|
|
260
|
-
font-weight: 600;
|
|
261
|
-
transition: transform 0.1s, opacity 0.2s;
|
|
262
|
-
display: flex;
|
|
263
|
-
align-items: center;
|
|
264
|
-
justify-content: center;
|
|
265
|
-
gap: 8px;
|
|
266
|
-
width: 100%;
|
|
267
|
-
position: relative;
|
|
268
|
-
overflow: hidden;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.btn:active {
|
|
272
|
-
transform: scale(0.98);
|
|
273
|
-
opacity: 0.9;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.btn:disabled {
|
|
277
|
-
background: var(--ios-gray-light);
|
|
278
|
-
color: var(--ios-gray);
|
|
279
|
-
cursor: not-allowed;
|
|
280
|
-
transform: none;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.btn-copy {
|
|
284
|
-
background: rgba(0, 122, 255, 0.1);
|
|
285
|
-
color: var(--ios-blue);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/* Specific override for prompt copy button to look nice */
|
|
289
|
-
.system-prompt-content .btn-copy {
|
|
290
|
-
margin-top: 8px;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
.btn-copy:active {
|
|
294
|
-
background: rgba(0, 122, 255, 0.2);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
.btn-copy.copied {
|
|
298
|
-
background: var(--ios-green);
|
|
299
|
-
color: white;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/* Response Area */
|
|
303
|
-
.response {
|
|
304
|
-
margin-top: 20px;
|
|
305
|
-
padding: 16px;
|
|
306
|
-
border-radius: 12px;
|
|
307
|
-
background: #1C1C1E;
|
|
308
|
-
/* Dark background for code contrast */
|
|
309
|
-
color: #fff;
|
|
310
|
-
display: none;
|
|
311
|
-
font-size: 13px;
|
|
312
|
-
overflow-x: auto;
|
|
313
|
-
border-left: none;
|
|
314
|
-
/* Removed the side border style */
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.response.show {
|
|
318
|
-
display: block;
|
|
319
|
-
animation: fadeIn 0.3s ease;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.response.success {
|
|
323
|
-
border: 1px solid rgba(52, 199, 89, 0.3);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.response.error {
|
|
327
|
-
border: 1px solid rgba(255, 59, 48, 0.3);
|
|
328
|
-
background: #2C1515;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.response pre {
|
|
332
|
-
margin: 0;
|
|
333
|
-
font-family: 'SF Mono', 'Menlo', monospace;
|
|
334
|
-
white-space: pre-wrap;
|
|
335
|
-
word-wrap: break-word;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
@keyframes fadeIn {
|
|
339
|
-
from {
|
|
340
|
-
opacity: 0;
|
|
341
|
-
transform: translateY(5px);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
to {
|
|
345
|
-
opacity: 1;
|
|
346
|
-
transform: translateY(0);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/* Loading Spinner */
|
|
351
|
-
.loading {
|
|
352
|
-
display: inline-block;
|
|
353
|
-
width: 18px;
|
|
354
|
-
height: 18px;
|
|
355
|
-
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
356
|
-
border-radius: 50%;
|
|
357
|
-
border-top-color: currentColor;
|
|
358
|
-
animation: spin 0.8s linear infinite;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
@keyframes spin {
|
|
362
|
-
to {
|
|
363
|
-
transform: rotate(360deg);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/* Toast - iOS Notification Style */
|
|
368
|
-
.toast {
|
|
369
|
-
position: fixed;
|
|
370
|
-
top: 10px;
|
|
371
|
-
left: 50%;
|
|
372
|
-
transform: translateX(-50%) translateY(-100px);
|
|
373
|
-
padding: 12px 20px;
|
|
374
|
-
border-radius: 25px;
|
|
375
|
-
/* Pill shape */
|
|
376
|
-
color: #000;
|
|
377
|
-
background: rgba(255, 255, 255, 0.85);
|
|
378
|
-
backdrop-filter: blur(20px);
|
|
379
|
-
-webkit-backdrop-filter: blur(20px);
|
|
380
|
-
font-weight: 500;
|
|
381
|
-
font-size: 15px;
|
|
382
|
-
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
|
|
383
|
-
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s;
|
|
384
|
-
z-index: 2000;
|
|
385
|
-
display: flex;
|
|
386
|
-
align-items: center;
|
|
387
|
-
gap: 10px;
|
|
388
|
-
width: auto;
|
|
389
|
-
max-width: 90%;
|
|
390
|
-
white-space: nowrap;
|
|
391
|
-
opacity: 0;
|
|
392
|
-
pointer-events: none;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.toast.show {
|
|
396
|
-
transform: translateX(-50%) translateY(calc(var(--safe-area-top)));
|
|
397
|
-
opacity: 1;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/* Icons in toast */
|
|
401
|
-
.toast::before {
|
|
402
|
-
content: '';
|
|
403
|
-
display: block;
|
|
404
|
-
width: 20px;
|
|
405
|
-
height: 20px;
|
|
406
|
-
border-radius: 50%;
|
|
407
|
-
flex-shrink: 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.toast.success::before {
|
|
411
|
-
background: var(--ios-green) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M5 13l4 4L19 7'/%3E%3C/svg%3E") center/12px no-repeat;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
.toast.error::before {
|
|
415
|
-
background: var(--ios-red) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 18L18 6M6 6l12 12'/%3E%3C/svg%3E") center/12px no-repeat;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
.toast.info::before {
|
|
419
|
-
background: var(--ios-blue);
|
|
420
|
-
}
|
|
421
|
-
</style>
|
|
11
|
+
<link rel="stylesheet" href="/dashboard.css">
|
|
12
|
+
<link rel="stylesheet" href="/css/structure.css">
|
|
13
|
+
<script>
|
|
14
|
+
// Pre-load theme to prevent flash
|
|
15
|
+
(function() {
|
|
16
|
+
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
17
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
18
|
+
})();
|
|
19
|
+
</script>
|
|
422
20
|
</head>
|
|
423
21
|
|
|
424
22
|
<body>
|
|
425
23
|
<div class="container">
|
|
426
24
|
<div class="header">
|
|
427
|
-
<
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
25
|
+
<div class="header-content">
|
|
26
|
+
<span class="status" id="status">● Server Starting...</span>
|
|
27
|
+
<div style="height: 10px;"></div>
|
|
28
|
+
<h1>VG Coder</h1>
|
|
29
|
+
<p>API Dashboard & Automation</p>
|
|
30
|
+
</div>
|
|
31
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle Dark Mode">
|
|
32
|
+
<span id="theme-icon">🌙</span>
|
|
33
|
+
</button>
|
|
431
34
|
</div>
|
|
432
35
|
|
|
433
36
|
<!-- System Prompt Section -->
|
|
434
37
|
<div class="system-prompt-card">
|
|
435
38
|
<div class="system-prompt-header" onclick="toggleSystemPrompt()">
|
|
436
|
-
<
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
39
|
+
<div class="header-title-group">
|
|
40
|
+
<button class="btn-icon-head" onclick="copySystemPromptFromHeader(event)" title="Copy System Prompt">
|
|
41
|
+
📋
|
|
42
|
+
</button>
|
|
43
|
+
<h2>System Prompt</h2>
|
|
44
|
+
</div>
|
|
440
45
|
<span class="toggle-icon" id="toggle-icon">▼</span>
|
|
441
46
|
</div>
|
|
442
47
|
<div class="system-prompt-content" id="system-prompt-content">
|
|
443
48
|
<div class="prompt-text" id="prompt-text"></div>
|
|
444
|
-
<button class="btn btn-copy" onclick="copySystemPrompt()">
|
|
49
|
+
<button class="btn btn-copy" onclick="copySystemPrompt(event)">
|
|
445
50
|
<span id="copy-icon">📋</span>
|
|
446
51
|
<span id="copy-text">Copy System Prompt</span>
|
|
447
52
|
</button>
|
|
@@ -452,8 +57,15 @@
|
|
|
452
57
|
<!-- Analyze -->
|
|
453
58
|
<div class="endpoint-card">
|
|
454
59
|
<div class="endpoint-header">
|
|
455
|
-
|
|
456
|
-
<
|
|
60
|
+
<!-- Left side: Method + Path -->
|
|
61
|
+
<div class="endpoint-title-group">
|
|
62
|
+
<span class="method post">POST</span>
|
|
63
|
+
<span class="endpoint-path">/api/analyze</span>
|
|
64
|
+
</div>
|
|
65
|
+
<!-- Right side: Download Icon -->
|
|
66
|
+
<button class="btn-icon-head" onclick="testAnalyze(event)" title="Download Project Source">
|
|
67
|
+
📥
|
|
68
|
+
</button>
|
|
457
69
|
</div>
|
|
458
70
|
<p class="endpoint-desc">Phân tích dự án và lấy toàn bộ source code.</p>
|
|
459
71
|
<div class="form-group">
|
|
@@ -461,18 +73,11 @@
|
|
|
461
73
|
<input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
|
|
462
74
|
</div>
|
|
463
75
|
<div class="btn-group">
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
<span>Download</span>
|
|
467
|
-
</button>
|
|
468
|
-
<button class="btn btn-copy" onclick="copyAnalyzeResult()">
|
|
76
|
+
<!-- Big Download button removed as it's now in the header -->
|
|
77
|
+
<button class="btn btn-copy" onclick="copyAnalyzeResult(event)">
|
|
469
78
|
<span id="analyze-copy-icon">📋</span>
|
|
470
79
|
<span id="analyze-copy-text">Copy Text</span>
|
|
471
80
|
</button>
|
|
472
|
-
<button class="btn btn-copy" onclick="copyAnalyzeAsFile()">
|
|
473
|
-
<span id="analyze-file-icon">📄</span>
|
|
474
|
-
<span id="analyze-file-text">Copy as File</span>
|
|
475
|
-
</button>
|
|
476
81
|
</div>
|
|
477
82
|
<div class="response" id="analyze-response"></div>
|
|
478
83
|
</div>
|
|
@@ -480,8 +85,11 @@
|
|
|
480
85
|
<!-- Execute Bash -->
|
|
481
86
|
<div class="endpoint-card">
|
|
482
87
|
<div class="endpoint-header">
|
|
483
|
-
<
|
|
484
|
-
|
|
88
|
+
<div class="endpoint-title-group">
|
|
89
|
+
<span class="method post">POST</span>
|
|
90
|
+
<span class="endpoint-path">/api/execute</span>
|
|
91
|
+
</div>
|
|
92
|
+
<!-- Could add an execute icon here later if needed -->
|
|
485
93
|
</div>
|
|
486
94
|
<p class="endpoint-desc">Thực thi bash script với syntax validation.</p>
|
|
487
95
|
<div class="form-group">
|
|
@@ -490,478 +98,61 @@
|
|
|
490
98
|
placeholder="mkdir -p src/test echo 'Hello' > src/test/hello.txt"></textarea>
|
|
491
99
|
</div>
|
|
492
100
|
<div class="btn-group">
|
|
493
|
-
<button class="btn" onclick="testExecute()">
|
|
101
|
+
<button class="btn" onclick="testExecute(event)">
|
|
494
102
|
<span>▶️</span>
|
|
495
103
|
<span>Run Script</span>
|
|
496
104
|
</button>
|
|
497
|
-
<button class="btn" onclick="executeFromClipboard()">
|
|
105
|
+
<button class="btn" onclick="executeFromClipboard(event)">
|
|
498
106
|
<span>📋</span>
|
|
499
107
|
<span>Paste & Run</span>
|
|
500
108
|
</button>
|
|
501
109
|
</div>
|
|
502
110
|
<div class="response" id="execute-response"></div>
|
|
503
111
|
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Structure & Tokens (NEW) -->
|
|
114
|
+
<div class="endpoint-card">
|
|
115
|
+
<div class="endpoint-header">
|
|
116
|
+
<div class="endpoint-title-group">
|
|
117
|
+
<span class="method get" style="background: var(--ios-blue); color: white;">GET</span>
|
|
118
|
+
<span class="endpoint-path">/api/structure</span>
|
|
119
|
+
</div>
|
|
120
|
+
<!-- Button Copy Selected -->
|
|
121
|
+
<button class="btn-icon-head" onclick="copySelectedStructure(event)" title="Copy Selected Tree">
|
|
122
|
+
📋
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
<p class="endpoint-desc">Xem cây thư mục và phân tích số lượng Token.</p>
|
|
126
|
+
<div class="form-group">
|
|
127
|
+
<label>Path</label>
|
|
128
|
+
<input type="text" id="structure-path" value="." placeholder="Project path">
|
|
129
|
+
</div>
|
|
130
|
+
<div class="btn-group">
|
|
131
|
+
<button class="btn" onclick="testStructure(event)">
|
|
132
|
+
<span>🌳</span>
|
|
133
|
+
<span>View Structure</span>
|
|
134
|
+
</button>
|
|
135
|
+
<button class="btn btn-copy" onclick="copySelectedStructure(event)">
|
|
136
|
+
<span id="copy-structure-icon">📋</span>
|
|
137
|
+
<span id="copy-structure-text">Copy Selected</span>
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
<!-- Tree Container -->
|
|
141
|
+
<div class="tree-container" id="structure-tree" style="display: none;">
|
|
142
|
+
<div class="tree-header">
|
|
143
|
+
<span>Project Tree</span>
|
|
144
|
+
<span class="tree-total-tokens" id="total-tokens-badge">0 tokens</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="tree-content" id="tree-content"></div>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="response" id="structure-response" style="display: none;"></div>
|
|
149
|
+
</div>
|
|
504
150
|
</div>
|
|
505
151
|
</div>
|
|
506
152
|
|
|
507
153
|
<div class="toast" id="toast"></div>
|
|
508
154
|
|
|
509
|
-
<script>
|
|
510
|
-
const API_BASE = window.location.origin;
|
|
511
|
-
let lastAnalyzeResult = null;
|
|
512
|
-
|
|
513
|
-
// System Prompt
|
|
514
|
-
const SYSTEM_PROMPT = `# VG Coder AI System Prompt
|
|
515
|
-
|
|
516
|
-
## Command Prefixes
|
|
517
|
-
|
|
518
|
-
### /ask - Question & Answer Mode
|
|
519
|
-
Khi người dùng hỏi với prefix /ask, họ đang muốn tìm hiểu hoặc được giải thích về một vấn đề.
|
|
520
|
-
|
|
521
|
-
**Response Format:** Markdown
|
|
522
|
-
- Trả lời chi tiết, rõ ràng
|
|
523
|
-
- Sử dụng code blocks, lists, tables khi cần
|
|
524
|
-
- Cung cấp ví dụ minh họa
|
|
525
|
-
|
|
526
|
-
---
|
|
527
|
-
|
|
528
|
-
### /plan - Planning Mode
|
|
529
|
-
Khi người dùng muốn lên kế hoạch với prefix /plan, tạo một implementation plan chi tiết.
|
|
530
|
-
|
|
531
|
-
**Response Format:** Markdown checklist với bash commands
|
|
532
|
-
- Chia nhỏ thành các bước cụ thể
|
|
533
|
-
- Mỗi bước có bash command tương ứng
|
|
534
|
-
- Sắp xếp theo thứ tự logic
|
|
535
|
-
|
|
536
|
-
---
|
|
537
|
-
|
|
538
|
-
### /fix - Bug Fix Mode
|
|
539
|
-
Khi người dùng cần fix bug với prefix /fix, phân tích lỗi và đưa ra giải pháp.
|
|
540
|
-
|
|
541
|
-
**Response Format:** Markdown + Bash script
|
|
542
|
-
1. **Phân tích lỗi:** Giải thích nguyên nhân
|
|
543
|
-
2. **Giải pháp:** Mô tả cách fix
|
|
544
|
-
3. **Bash script:** Code để fix (nếu cần)
|
|
545
|
-
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
### /code - Code Generation Mode
|
|
549
|
-
Khi người dùng hỏi với prefix /code, trả về **BASH SCRIPT DUY NHẤT** để tạo/cập nhật files.
|
|
550
|
-
|
|
551
|
-
## ⚠️ QUY TẮC BẮT BUỘC
|
|
552
|
-
|
|
553
|
-
### 1. Chỉ bao gồm files có thay đổi
|
|
554
|
-
- ❌ **KHÔNG** bao gồm files không có sự thay đổi nội dung
|
|
555
|
-
- ✅ Nếu nội dung file sau chỉnh sửa giống 100% bản cũ → **BỎ QUA**
|
|
556
|
-
|
|
557
|
-
### 2. Format Script Chuẩn
|
|
558
|
-
|
|
559
|
-
**Mỗi file PHẢI theo cú pháp:**
|
|
560
|
-
\`\`\`bash
|
|
561
|
-
mkdir -p $(dirname "path/to/file.ext")
|
|
562
|
-
cat <<'EOF' > path/to/file.ext
|
|
563
|
-
... toàn bộ nội dung file sau khi chỉnh sửa ...
|
|
564
|
-
EOF
|
|
565
|
-
\`\`\`
|
|
566
|
-
|
|
567
|
-
### 3. Chi tiết quan trọng
|
|
568
|
-
- ✅ **LUÔN** có \`mkdir -p $(dirname "...")\` trước mỗi file
|
|
569
|
-
- ✅ Sử dụng \`<<'EOF'\` (có dấu nháy đơn) để tránh bash expansion
|
|
570
|
-
- ✅ Ghi đè hoàn toàn file bằng nội dung mới
|
|
571
|
-
- ✅ Tự động tạo file và thư mục cha nếu chưa tồn tại
|
|
572
|
-
- ✅ Đường dẫn giống với file mẫu đính kèm
|
|
573
|
-
|
|
574
|
-
### 4. Example Output
|
|
575
|
-
|
|
576
|
-
\`\`\`bash
|
|
577
|
-
# Create/Update component file
|
|
578
|
-
mkdir -p $(dirname "src/components/Button/index.tsx")
|
|
579
|
-
cat <<'EOF' > src/components/Button/index.tsx
|
|
580
|
-
import React from 'react';
|
|
581
|
-
|
|
582
|
-
export const Button = () => {
|
|
583
|
-
return <button>Click me</button>;
|
|
584
|
-
};
|
|
585
|
-
EOF
|
|
586
|
-
|
|
587
|
-
# Create/Update styles
|
|
588
|
-
mkdir -p $(dirname "src/components/Button/styles.css")
|
|
589
|
-
cat <<'EOF' > src/components/Button/styles.css
|
|
590
|
-
.button {
|
|
591
|
-
padding: 10px 20px;
|
|
592
|
-
background: blue;
|
|
593
|
-
}
|
|
594
|
-
EOF
|
|
595
|
-
\`\`\`
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
|
-
## Integration với VG Coder CLI
|
|
600
|
-
|
|
601
|
-
Bash scripts được generate sẽ được thực thi qua:
|
|
602
|
-
\`\`\`bash
|
|
603
|
-
POST http://localhost:6868/api/execute
|
|
604
|
-
{
|
|
605
|
-
"bash": "mkdir -p $(dirname \\"src/...\\")\\\\ncat <<'EOF' > ..."
|
|
606
|
-
}
|
|
607
|
-
\`\`\`
|
|
608
|
-
|
|
609
|
-
API sẽ:
|
|
610
|
-
1. ✅ Validate bash syntax trong \`.vg/temp-execute\`
|
|
611
|
-
2. ✅ Execute tại working directory nếu syntax OK
|
|
612
|
-
3. ✅ Trả về stdout/stderr/exitCode
|
|
613
|
-
4. ✅ Auto cleanup temp directory
|
|
614
|
-
|
|
615
|
-
---
|
|
616
|
-
|
|
617
|
-
## Best Practices
|
|
618
|
-
|
|
619
|
-
### DO ✅
|
|
620
|
-
- Luôn dùng \`mkdir -p $(dirname "...")\` trước mỗi file
|
|
621
|
-
- Sử dụng \`<<'EOF'\` để tránh variable expansion
|
|
622
|
-
- Ghi đè toàn bộ nội dung file
|
|
623
|
-
- Chỉ include files có thay đổi thực sự
|
|
624
|
-
|
|
625
|
-
### DON'T ❌
|
|
626
|
-
- Không tạo file mà không tạo thư mục cha
|
|
627
|
-
- Không dùng \`<<EOF\` (thiếu quotes) nếu có \`$\` trong content
|
|
628
|
-
- Không include files không thay đổi
|
|
629
|
-
- Không dùng relative paths phức tạp`;
|
|
630
|
-
|
|
631
|
-
// Load system prompt on page load
|
|
632
|
-
document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
|
|
633
|
-
|
|
634
|
-
function toggleSystemPrompt() {
|
|
635
|
-
const content = document.getElementById('system-prompt-content');
|
|
636
|
-
const icon = document.getElementById('toggle-icon');
|
|
637
|
-
content.classList.toggle('open');
|
|
638
|
-
icon.classList.toggle('open');
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function copySystemPrompt() {
|
|
642
|
-
const copyBtn = event.target.closest('.btn-copy');
|
|
643
|
-
const copyIcon = document.getElementById('copy-icon');
|
|
644
|
-
const copyText = document.getElementById('copy-text');
|
|
645
|
-
|
|
646
|
-
navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
|
|
647
|
-
copyBtn.classList.add('copied');
|
|
648
|
-
copyIcon.textContent = '✓';
|
|
649
|
-
copyText.textContent = 'Copied';
|
|
650
|
-
showToast('Đã copy System Prompt', 'success');
|
|
651
|
-
|
|
652
|
-
setTimeout(() => {
|
|
653
|
-
copyBtn.classList.remove('copied');
|
|
654
|
-
copyIcon.textContent = '📋';
|
|
655
|
-
copyText.textContent = 'Copy System Prompt';
|
|
656
|
-
}, 2000);
|
|
657
|
-
}).catch(err => {
|
|
658
|
-
showToast('Lỗi copy: ' + err.message, 'error');
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function showResponse(elementId, data, isError = false) {
|
|
663
|
-
const el = document.getElementById(elementId);
|
|
664
|
-
el.className = 'response show ' + (isError ? 'error' : 'success');
|
|
665
|
-
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function showLoading(button, originalText) {
|
|
669
|
-
button.disabled = true;
|
|
670
|
-
button.innerHTML = '<span class="loading"></span>';
|
|
671
|
-
button.dataset.originalText = originalText;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function resetButton(button) {
|
|
675
|
-
button.disabled = false;
|
|
676
|
-
const originalText = button.dataset.originalText;
|
|
677
|
-
button.innerHTML = originalText;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
async function testAnalyze() {
|
|
681
|
-
const btn = event.target.closest('.btn');
|
|
682
|
-
const path = document.getElementById('analyze-path').value;
|
|
683
|
-
|
|
684
|
-
showLoading(btn, btn.innerHTML);
|
|
685
|
-
try {
|
|
686
|
-
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
687
|
-
method: 'POST',
|
|
688
|
-
headers: { 'Content-Type': 'application/json' },
|
|
689
|
-
body: JSON.stringify({ path })
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
if (res.ok) {
|
|
693
|
-
const text = await res.text();
|
|
694
|
-
lastAnalyzeResult = text;
|
|
695
|
-
|
|
696
|
-
// Download file
|
|
697
|
-
const blob = new Blob([text], { type: 'text/plain' });
|
|
698
|
-
const url = window.URL.createObjectURL(blob);
|
|
699
|
-
const a = document.createElement('a');
|
|
700
|
-
a.href = url;
|
|
701
|
-
a.download = 'project.txt';
|
|
702
|
-
a.click();
|
|
703
|
-
|
|
704
|
-
showResponse('analyze-response', {
|
|
705
|
-
success: true,
|
|
706
|
-
message: 'File downloaded!',
|
|
707
|
-
files: text.split('\n').filter(l => l.includes('===== FILE:')).length,
|
|
708
|
-
size: (text.length / 1024).toFixed(2) + ' KB'
|
|
709
|
-
});
|
|
710
|
-
showToast('Đã download file', 'success');
|
|
711
|
-
} else {
|
|
712
|
-
const data = await res.json();
|
|
713
|
-
showResponse('analyze-response', data, true);
|
|
714
|
-
showToast('Lỗi analyze', 'error');
|
|
715
|
-
}
|
|
716
|
-
} catch (err) {
|
|
717
|
-
showResponse('analyze-response', { error: err.message }, true);
|
|
718
|
-
showToast('Lỗi: ' + err.message, 'error');
|
|
719
|
-
}
|
|
720
|
-
resetButton(btn);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
async function copyAnalyzeResult() {
|
|
724
|
-
const copyBtn = event.target.closest('.btn-copy');
|
|
725
|
-
const copyIcon = document.getElementById('analyze-copy-icon');
|
|
726
|
-
const copyText = document.getElementById('analyze-copy-text');
|
|
727
|
-
|
|
728
|
-
if (!lastAnalyzeResult) {
|
|
729
|
-
// Fetch if not already analyzed
|
|
730
|
-
const path = document.getElementById('analyze-path').value;
|
|
731
|
-
showLoading(copyBtn, copyBtn.innerHTML);
|
|
732
|
-
|
|
733
|
-
try {
|
|
734
|
-
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
735
|
-
method: 'POST',
|
|
736
|
-
headers: { 'Content-Type': 'application/json' },
|
|
737
|
-
body: JSON.stringify({ path })
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
if (res.ok) {
|
|
741
|
-
lastAnalyzeResult = await res.text();
|
|
742
|
-
} else {
|
|
743
|
-
showToast('Lỗi analyze', 'error');
|
|
744
|
-
resetButton(copyBtn);
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
} catch (err) {
|
|
748
|
-
showToast('Lỗi: ' + err.message, 'error');
|
|
749
|
-
resetButton(copyBtn);
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
752
|
-
resetButton(copyBtn);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Copy to clipboard using ClipboardItem
|
|
756
|
-
try {
|
|
757
|
-
const blob = new Blob([lastAnalyzeResult], { type: 'text/plain' });
|
|
758
|
-
const item = new ClipboardItem({ 'text/plain': blob });
|
|
759
|
-
await navigator.clipboard.write([item]);
|
|
760
|
-
|
|
761
|
-
copyBtn.classList.add('copied');
|
|
762
|
-
copyIcon.textContent = '✓';
|
|
763
|
-
copyText.textContent = 'Copied';
|
|
764
|
-
showToast('Đã copy project.txt', 'success');
|
|
765
|
-
|
|
766
|
-
setTimeout(() => {
|
|
767
|
-
copyBtn.classList.remove('copied');
|
|
768
|
-
copyIcon.textContent = '📋';
|
|
769
|
-
copyText.textContent = 'Copy Text';
|
|
770
|
-
}, 2000);
|
|
771
|
-
} catch (err) {
|
|
772
|
-
try {
|
|
773
|
-
await navigator.clipboard.writeText(lastAnalyzeResult);
|
|
774
|
-
copyBtn.classList.add('copied');
|
|
775
|
-
copyIcon.textContent = '✓';
|
|
776
|
-
copyText.textContent = 'Copied';
|
|
777
|
-
showToast('Đã copy project.txt', 'success');
|
|
778
|
-
|
|
779
|
-
setTimeout(() => {
|
|
780
|
-
copyBtn.classList.remove('copied');
|
|
781
|
-
copyIcon.textContent = '📋';
|
|
782
|
-
copyText.textContent = 'Copy Text';
|
|
783
|
-
}, 2000);
|
|
784
|
-
} catch (fallbackErr) {
|
|
785
|
-
showToast('Lỗi copy: ' + fallbackErr.message, 'error');
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
async function copyAsFile(filename, content) {
|
|
791
|
-
const blob = new Blob([content], {
|
|
792
|
-
type: "application/octet-stream"
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
const item = new ClipboardItem(
|
|
796
|
-
{ [blob.type]: blob },
|
|
797
|
-
{
|
|
798
|
-
type: "application/octet-stream",
|
|
799
|
-
presentationStyle: "attachment",
|
|
800
|
-
name: filename
|
|
801
|
-
}
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
await navigator.clipboard.write([item]);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async function copyAnalyzeAsFile() {
|
|
808
|
-
const copyBtn = event.target.closest('.btn-copy');
|
|
809
|
-
const copyIcon = document.getElementById('analyze-file-icon');
|
|
810
|
-
const copyText = document.getElementById('analyze-file-text');
|
|
811
|
-
|
|
812
|
-
if (!lastAnalyzeResult) {
|
|
813
|
-
// Fetch if not already analyzed
|
|
814
|
-
const path = document.getElementById('analyze-path').value;
|
|
815
|
-
showLoading(copyBtn, copyBtn.innerHTML);
|
|
816
|
-
|
|
817
|
-
try {
|
|
818
|
-
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
819
|
-
method: 'POST',
|
|
820
|
-
headers: { 'Content-Type': 'application/json' },
|
|
821
|
-
body: JSON.stringify({ path })
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
if (res.ok) {
|
|
825
|
-
lastAnalyzeResult = await res.text();
|
|
826
|
-
} else {
|
|
827
|
-
showToast('Lỗi analyze', 'error');
|
|
828
|
-
resetButton(copyBtn);
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
} catch (err) {
|
|
832
|
-
showToast('Lỗi: ' + err.message, 'error');
|
|
833
|
-
resetButton(copyBtn);
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
resetButton(copyBtn);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
try {
|
|
840
|
-
await copyAsFile("project.txt", lastAnalyzeResult);
|
|
841
|
-
|
|
842
|
-
copyBtn.classList.add('copied');
|
|
843
|
-
copyIcon.textContent = '✓';
|
|
844
|
-
copyText.textContent = 'Copied';
|
|
845
|
-
showToast('Đã copy file', 'success');
|
|
846
|
-
|
|
847
|
-
setTimeout(() => {
|
|
848
|
-
copyBtn.classList.remove('copied');
|
|
849
|
-
copyIcon.textContent = '📄';
|
|
850
|
-
copyText.textContent = 'Copy as File';
|
|
851
|
-
}, 2000);
|
|
852
|
-
} catch (err) {
|
|
853
|
-
showToast('Lỗi copy: ' + err.message, 'error');
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
async function testExecute() {
|
|
858
|
-
const btn = event.target.closest('.btn');
|
|
859
|
-
const bash = document.getElementById('execute-bash').value;
|
|
860
|
-
|
|
861
|
-
if (!bash.trim()) {
|
|
862
|
-
showToast('Vui lòng nhập bash script', 'error');
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
showLoading(btn, btn.innerHTML);
|
|
867
|
-
try {
|
|
868
|
-
const res = await fetch(`${API_BASE}/api/execute`, {
|
|
869
|
-
method: 'POST',
|
|
870
|
-
headers: { 'Content-Type': 'application/json' },
|
|
871
|
-
body: JSON.stringify({ bash })
|
|
872
|
-
});
|
|
873
|
-
const data = await res.json();
|
|
874
|
-
showResponse('execute-response', data, !res.ok || !data.success);
|
|
875
|
-
|
|
876
|
-
if (data.success) {
|
|
877
|
-
showToast('Thực thi thành công', 'success');
|
|
878
|
-
} else {
|
|
879
|
-
showToast('Thực thi thất bại', 'error');
|
|
880
|
-
}
|
|
881
|
-
} catch (err) {
|
|
882
|
-
showResponse('execute-response', { error: err.message }, true);
|
|
883
|
-
showToast('Lỗi: ' + err.message, 'error');
|
|
884
|
-
}
|
|
885
|
-
resetButton(btn);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
async function executeFromClipboard() {
|
|
889
|
-
const btn = event.target.closest('.btn');
|
|
890
|
-
|
|
891
|
-
showLoading(btn, btn.innerHTML);
|
|
892
|
-
|
|
893
|
-
try {
|
|
894
|
-
const clipboardText = await navigator.clipboard.readText();
|
|
895
|
-
|
|
896
|
-
if (!clipboardText || !clipboardText.trim()) {
|
|
897
|
-
showToast('Clipboard trống!', 'error');
|
|
898
|
-
showResponse('execute-response', {
|
|
899
|
-
error: 'Clipboard is empty',
|
|
900
|
-
message: 'Please copy a bash script to clipboard first'
|
|
901
|
-
}, true);
|
|
902
|
-
resetButton(btn);
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
document.getElementById('execute-bash').value = clipboardText;
|
|
907
|
-
|
|
908
|
-
const res = await fetch(`${API_BASE}/api/execute`, {
|
|
909
|
-
method: 'POST',
|
|
910
|
-
headers: { 'Content-Type': 'application/json' },
|
|
911
|
-
body: JSON.stringify({ bash: clipboardText })
|
|
912
|
-
});
|
|
913
|
-
const data = await res.json();
|
|
914
|
-
showResponse('execute-response', data, !res.ok || !data.success);
|
|
915
|
-
|
|
916
|
-
if (data.success) {
|
|
917
|
-
showToast('Thực thi từ clipboard OK', 'success');
|
|
918
|
-
} else {
|
|
919
|
-
if (data.syntaxError) {
|
|
920
|
-
showToast('Lỗi syntax script', 'error');
|
|
921
|
-
} else {
|
|
922
|
-
showToast('Thực thi thất bại', 'error');
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
} catch (err) {
|
|
926
|
-
if (err.name === 'NotAllowedError') {
|
|
927
|
-
showToast('Không có quyền clipboard', 'error');
|
|
928
|
-
} else {
|
|
929
|
-
showResponse('execute-response', { error: err.message }, true);
|
|
930
|
-
showToast('Lỗi: ' + err.message, 'error');
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
resetButton(btn);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
function showToast(message, type = 'success') {
|
|
937
|
-
const toast = document.getElementById('toast');
|
|
938
|
-
// Reset text content to remove potential icon junk
|
|
939
|
-
toast.textContent = message;
|
|
940
|
-
toast.className = `toast ${type}`;
|
|
941
|
-
toast.classList.add('show');
|
|
942
|
-
|
|
943
|
-
// Clear previous timeout if exists
|
|
944
|
-
if (toast.timeoutId) clearTimeout(toast.timeoutId);
|
|
945
|
-
|
|
946
|
-
toast.timeoutId = setTimeout(() => toast.classList.remove('show'), 3000);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// Check server status
|
|
950
|
-
(async () => {
|
|
951
|
-
try {
|
|
952
|
-
const res = await fetch(`${API_BASE}/health`);
|
|
953
|
-
if (res.ok) {
|
|
954
|
-
document.getElementById('status').textContent = '● Online';
|
|
955
|
-
document.getElementById('status').style.background = 'rgba(52, 199, 89, 0.15)';
|
|
956
|
-
document.getElementById('status').style.color = 'var(--ios-green)';
|
|
957
|
-
}
|
|
958
|
-
} catch {
|
|
959
|
-
document.getElementById('status').textContent = '● Offline';
|
|
960
|
-
document.getElementById('status').style.background = 'rgba(255, 59, 48, 0.15)';
|
|
961
|
-
document.getElementById('status').style.color = 'var(--ios-red)';
|
|
962
|
-
}
|
|
963
|
-
})();
|
|
964
|
-
</script>
|
|
155
|
+
<script type="module" src="/js/main.js"></script>
|
|
965
156
|
</body>
|
|
966
157
|
|
|
967
|
-
</html>
|
|
158
|
+
</html>
|