vg-coder-cli 2.0.5 → 2.0.7
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 +1 -1
- package/src/server/views/dashboard.html +674 -388
- package/vg-coder-cli-2.0.6.tgz +0 -0
- package/vg-coder-cli-2.0.7.tgz +0 -0
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
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
|
-
<title>VG Coder
|
|
10
|
+
<title>VG Coder API Dashboard</title>
|
|
11
11
|
<style>
|
|
12
12
|
:root {
|
|
13
13
|
--ios-bg: #F2F2F7;
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
--ios-blue: #007AFF;
|
|
16
16
|
--ios-green: #34C759;
|
|
17
17
|
--ios-red: #FF3B30;
|
|
18
|
-
--ios-
|
|
19
|
-
--ios-
|
|
18
|
+
--ios-gray: #8E8E93;
|
|
19
|
+
--ios-gray-light: #E5E5EA;
|
|
20
|
+
--ios-input-bg: #F2F2F7;
|
|
20
21
|
--ios-separator: #C6C6C8;
|
|
21
|
-
--
|
|
22
|
-
--
|
|
22
|
+
--safe-area-top: env(safe-area-inset-top);
|
|
23
|
+
--safe-area-bottom: env(safe-area-inset-bottom);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
* {
|
|
@@ -30,97 +31,163 @@
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
body {
|
|
33
|
-
font-family:
|
|
34
|
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
34
35
|
background-color: var(--ios-bg);
|
|
35
|
-
color:
|
|
36
|
+
color: #000;
|
|
36
37
|
min-height: 100vh;
|
|
37
|
-
padding
|
|
38
|
-
|
|
39
|
-
line-height: 1.5;
|
|
38
|
+
padding: 0;
|
|
39
|
+
padding-bottom: calc(20px + var(--safe-area-bottom));
|
|
40
40
|
-webkit-font-smoothing: antialiased;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
align-items: center;
|
|
56
|
-
padding-top: max(15px, env(safe-area-inset-top));
|
|
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;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
.
|
|
57
|
+
.header h1 {
|
|
58
|
+
color: #000;
|
|
59
|
+
font-size: 34px;
|
|
60
60
|
font-weight: 700;
|
|
61
|
-
font-size: 1.2rem;
|
|
62
61
|
letter-spacing: -0.5px;
|
|
62
|
+
margin-bottom: 8px;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
font-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
background: var(--ios-green);
|
|
71
|
-
color: white;
|
|
72
|
-
box-shadow: 0 2px 4px rgba(52, 199, 89, 0.2);
|
|
73
|
-
transition: all 0.3s ease;
|
|
65
|
+
.header p {
|
|
66
|
+
color: var(--ios-gray);
|
|
67
|
+
font-size: 17px;
|
|
68
|
+
line-height: 1.4;
|
|
69
|
+
margin-bottom: 12px;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
padding:
|
|
80
|
-
|
|
81
|
-
|
|
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
82
|
}
|
|
83
83
|
|
|
84
|
-
/*
|
|
85
|
-
.card
|
|
84
|
+
/* Card Style (Inset Grouped) */
|
|
85
|
+
.system-prompt-card,
|
|
86
|
+
.endpoint-card {
|
|
86
87
|
background: var(--ios-card);
|
|
87
|
-
border-radius:
|
|
88
|
-
padding:
|
|
89
|
-
margin-bottom:
|
|
90
|
-
box-shadow: 0
|
|
91
|
-
position: relative;
|
|
88
|
+
border-radius: 16px;
|
|
89
|
+
padding: 20px;
|
|
90
|
+
margin-bottom: 20px;
|
|
91
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
92
92
|
overflow: hidden;
|
|
93
|
+
position: relative;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
/* System Prompt */
|
|
97
|
+
.system-prompt-header {
|
|
96
98
|
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
97
100
|
align-items: center;
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
padding: 4px 0;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
.
|
|
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;
|
|
105
140
|
border-radius: 12px;
|
|
106
|
-
|
|
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 {
|
|
107
155
|
display: flex;
|
|
108
156
|
align-items: center;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
gap: 10px;
|
|
159
|
+
margin-bottom: 12px;
|
|
160
|
+
border-bottom: 0.5px solid var(--ios-separator);
|
|
161
|
+
padding-bottom: 12px;
|
|
112
162
|
}
|
|
113
163
|
|
|
114
|
-
.
|
|
115
|
-
|
|
164
|
+
.method {
|
|
165
|
+
padding: 4px 10px;
|
|
166
|
+
border-radius: 6px;
|
|
116
167
|
font-weight: 700;
|
|
117
|
-
|
|
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;
|
|
118
184
|
}
|
|
119
185
|
|
|
120
|
-
.
|
|
121
|
-
color: var(--ios-
|
|
122
|
-
font-size: 0.9rem;
|
|
186
|
+
.endpoint-desc {
|
|
187
|
+
color: var(--ios-gray);
|
|
123
188
|
margin-bottom: 20px;
|
|
189
|
+
font-size: 15px;
|
|
190
|
+
line-height: 1.4;
|
|
124
191
|
}
|
|
125
192
|
|
|
126
193
|
/* Forms */
|
|
@@ -128,85 +195,103 @@
|
|
|
128
195
|
margin-bottom: 20px;
|
|
129
196
|
}
|
|
130
197
|
|
|
131
|
-
.form-label {
|
|
198
|
+
.form-group label {
|
|
132
199
|
display: block;
|
|
133
|
-
font-size: 0.85rem;
|
|
134
|
-
font-weight: 600;
|
|
135
|
-
color: var(--ios-text-secondary);
|
|
136
200
|
margin-bottom: 8px;
|
|
201
|
+
color: #000;
|
|
202
|
+
font-weight: 500;
|
|
203
|
+
font-size: 14px;
|
|
137
204
|
text-transform: uppercase;
|
|
138
|
-
letter-spacing: 0.
|
|
205
|
+
letter-spacing: 0.3px;
|
|
206
|
+
opacity: 0.6;
|
|
139
207
|
}
|
|
140
208
|
|
|
141
|
-
input
|
|
142
|
-
textarea {
|
|
209
|
+
.form-group input,
|
|
210
|
+
.form-group textarea {
|
|
143
211
|
width: 100%;
|
|
212
|
+
padding: 14px;
|
|
144
213
|
background: var(--ios-input-bg);
|
|
145
214
|
border: none;
|
|
146
215
|
border-radius: 12px;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
font-family:
|
|
150
|
-
color:
|
|
151
|
-
transition:
|
|
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;
|
|
152
222
|
-webkit-appearance: none;
|
|
153
223
|
}
|
|
154
224
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
225
|
+
.form-group input:focus,
|
|
226
|
+
.form-group textarea:focus {
|
|
227
|
+
outline: none;
|
|
228
|
+
background: #E5E5EA;
|
|
229
|
+
/* Slightly darker on focus */
|
|
159
230
|
}
|
|
160
231
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
background: #D1D1D6;
|
|
232
|
+
.form-group textarea {
|
|
233
|
+
min-height: 120px;
|
|
234
|
+
resize: none;
|
|
165
235
|
}
|
|
166
236
|
|
|
167
237
|
/* Buttons */
|
|
168
238
|
.btn-group {
|
|
169
|
-
display:
|
|
170
|
-
grid-template-columns: 1fr 1fr;
|
|
171
|
-
/* 2 columns for better layout */
|
|
239
|
+
display: flex;
|
|
172
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
|
+
}
|
|
173
250
|
}
|
|
174
251
|
|
|
175
252
|
.btn {
|
|
176
|
-
border: none;
|
|
177
253
|
background: var(--ios-blue);
|
|
178
254
|
color: white;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
font-weight: 600;
|
|
255
|
+
border: none;
|
|
256
|
+
padding: 14px 20px;
|
|
257
|
+
border-radius: 12px;
|
|
183
258
|
cursor: pointer;
|
|
259
|
+
font-size: 17px;
|
|
260
|
+
font-weight: 600;
|
|
184
261
|
transition: transform 0.1s, opacity 0.2s;
|
|
185
262
|
display: flex;
|
|
186
263
|
align-items: center;
|
|
187
264
|
justify-content: center;
|
|
188
265
|
gap: 8px;
|
|
189
|
-
|
|
266
|
+
width: 100%;
|
|
267
|
+
position: relative;
|
|
268
|
+
overflow: hidden;
|
|
190
269
|
}
|
|
191
270
|
|
|
192
271
|
.btn:active {
|
|
193
272
|
transform: scale(0.98);
|
|
194
|
-
opacity: 0.
|
|
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;
|
|
195
281
|
}
|
|
196
282
|
|
|
197
|
-
.btn-
|
|
198
|
-
background: rgba(0, 122, 255, 0.
|
|
283
|
+
.btn-copy {
|
|
284
|
+
background: rgba(0, 122, 255, 0.1);
|
|
199
285
|
color: var(--ios-blue);
|
|
200
286
|
}
|
|
201
287
|
|
|
202
|
-
/*
|
|
203
|
-
.btn-
|
|
204
|
-
|
|
288
|
+
/* Specific override for prompt copy button to look nice */
|
|
289
|
+
.system-prompt-content .btn-copy {
|
|
290
|
+
margin-top: 8px;
|
|
205
291
|
}
|
|
206
292
|
|
|
207
|
-
.btn-copy {
|
|
208
|
-
background:
|
|
209
|
-
color: black;
|
|
293
|
+
.btn-copy:active {
|
|
294
|
+
background: rgba(0, 122, 255, 0.2);
|
|
210
295
|
}
|
|
211
296
|
|
|
212
297
|
.btn-copy.copied {
|
|
@@ -214,376 +299,389 @@
|
|
|
214
299
|
color: white;
|
|
215
300
|
}
|
|
216
301
|
|
|
217
|
-
/* System Prompt Accordion */
|
|
218
|
-
.prompt-accordion {
|
|
219
|
-
cursor: pointer;
|
|
220
|
-
user-select: none;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.prompt-header {
|
|
224
|
-
display: flex;
|
|
225
|
-
justify-content: space-between;
|
|
226
|
-
align-items: center;
|
|
227
|
-
padding: 4px 0;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.chevron {
|
|
231
|
-
color: var(--ios-text-secondary);
|
|
232
|
-
transition: transform 0.3s ease;
|
|
233
|
-
font-size: 0.8rem;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.prompt-content {
|
|
237
|
-
max-height: 0;
|
|
238
|
-
overflow: hidden;
|
|
239
|
-
transition: max-height 0.4s cubic-bezier(0.65, 0, 0.35, 1);
|
|
240
|
-
opacity: 0;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.prompt-content.open {
|
|
244
|
-
max-height: 500px;
|
|
245
|
-
opacity: 1;
|
|
246
|
-
margin-top: 15px;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.code-block {
|
|
250
|
-
background: #2c2c2e;
|
|
251
|
-
color: #fff;
|
|
252
|
-
padding: 15px;
|
|
253
|
-
border-radius: 12px;
|
|
254
|
-
font-family: 'SF Mono', 'Menlo', monospace;
|
|
255
|
-
font-size: 0.85rem;
|
|
256
|
-
overflow-x: auto;
|
|
257
|
-
white-space: pre-wrap;
|
|
258
|
-
margin-bottom: 15px;
|
|
259
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
302
|
/* Response Area */
|
|
263
303
|
.response {
|
|
264
304
|
margin-top: 20px;
|
|
265
|
-
|
|
305
|
+
padding: 16px;
|
|
266
306
|
border-radius: 12px;
|
|
267
|
-
|
|
268
|
-
|
|
307
|
+
background: #1C1C1E;
|
|
308
|
+
/* Dark background for code contrast */
|
|
309
|
+
color: #fff;
|
|
269
310
|
display: none;
|
|
270
|
-
|
|
311
|
+
font-size: 13px;
|
|
312
|
+
overflow-x: auto;
|
|
313
|
+
border-left: none;
|
|
314
|
+
/* Removed the side border style */
|
|
271
315
|
}
|
|
272
316
|
|
|
273
317
|
.response.show {
|
|
274
318
|
display: block;
|
|
319
|
+
animation: fadeIn 0.3s ease;
|
|
275
320
|
}
|
|
276
321
|
|
|
277
322
|
.response.success {
|
|
278
|
-
border
|
|
279
|
-
background: #F0FFF4;
|
|
323
|
+
border: 1px solid rgba(52, 199, 89, 0.3);
|
|
280
324
|
}
|
|
281
325
|
|
|
282
326
|
.response.error {
|
|
283
|
-
border
|
|
284
|
-
background: #
|
|
327
|
+
border: 1px solid rgba(255, 59, 48, 0.3);
|
|
328
|
+
background: #2C1515;
|
|
285
329
|
}
|
|
286
330
|
|
|
287
331
|
.response pre {
|
|
332
|
+
margin: 0;
|
|
288
333
|
font-family: 'SF Mono', 'Menlo', monospace;
|
|
289
|
-
font-size: 0.85rem;
|
|
290
334
|
white-space: pre-wrap;
|
|
291
|
-
word-
|
|
292
|
-
|
|
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
|
+
}
|
|
293
365
|
}
|
|
294
366
|
|
|
295
|
-
/* Toast */
|
|
367
|
+
/* Toast - iOS Notification Style */
|
|
296
368
|
.toast {
|
|
297
369
|
position: fixed;
|
|
298
|
-
top:
|
|
370
|
+
top: 10px;
|
|
299
371
|
left: 50%;
|
|
300
372
|
transform: translateX(-50%) translateY(-100px);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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;
|
|
311
385
|
display: flex;
|
|
312
386
|
align-items: center;
|
|
313
|
-
gap:
|
|
387
|
+
gap: 10px;
|
|
388
|
+
width: auto;
|
|
389
|
+
max-width: 90%;
|
|
314
390
|
white-space: nowrap;
|
|
391
|
+
opacity: 0;
|
|
392
|
+
pointer-events: none;
|
|
315
393
|
}
|
|
316
394
|
|
|
317
395
|
.toast.show {
|
|
318
|
-
transform: translateX(-50%) translateY(
|
|
396
|
+
transform: translateX(-50%) translateY(calc(var(--safe-area-top)));
|
|
397
|
+
opacity: 1;
|
|
319
398
|
}
|
|
320
399
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
400
|
+
/* Icons in toast */
|
|
401
|
+
.toast::before {
|
|
402
|
+
content: '';
|
|
403
|
+
display: block;
|
|
404
|
+
width: 20px;
|
|
405
|
+
height: 20px;
|
|
325
406
|
border-radius: 50%;
|
|
326
|
-
|
|
327
|
-
animation: spin 0.8s linear infinite;
|
|
407
|
+
flex-shrink: 0;
|
|
328
408
|
}
|
|
329
409
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
transform: rotate(360deg);
|
|
333
|
-
}
|
|
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;
|
|
334
412
|
}
|
|
335
413
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
transform: translateY(-10px);
|
|
340
|
-
}
|
|
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
|
+
}
|
|
341
417
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
transform: translateY(0);
|
|
345
|
-
}
|
|
418
|
+
.toast.info::before {
|
|
419
|
+
background: var(--ios-blue);
|
|
346
420
|
}
|
|
347
421
|
</style>
|
|
348
422
|
</head>
|
|
349
423
|
|
|
350
424
|
<body>
|
|
351
|
-
<!-- Glass Header -->
|
|
352
|
-
<nav class="header-nav">
|
|
353
|
-
<div class="app-title">VG Coder</div>
|
|
354
|
-
<div id="status" class="status-badge">● Offline</div>
|
|
355
|
-
</nav>
|
|
356
|
-
|
|
357
425
|
<div class="container">
|
|
426
|
+
<div class="header">
|
|
427
|
+
<span class="status" id="status">● Server Starting...</span>
|
|
428
|
+
<div style="height: 10px;"></div>
|
|
429
|
+
<h1>VG Coder</h1>
|
|
430
|
+
<p>API Dashboard & Automation</p>
|
|
431
|
+
</div>
|
|
358
432
|
|
|
359
|
-
<!-- System Prompt
|
|
360
|
-
<div class="
|
|
361
|
-
<div class="
|
|
362
|
-
<
|
|
363
|
-
<
|
|
364
|
-
<
|
|
365
|
-
</
|
|
366
|
-
<
|
|
433
|
+
<!-- System Prompt Section -->
|
|
434
|
+
<div class="system-prompt-card">
|
|
435
|
+
<div class="system-prompt-header" onclick="toggleSystemPrompt()">
|
|
436
|
+
<h2>
|
|
437
|
+
<span>📝</span>
|
|
438
|
+
<span>System Prompt</span>
|
|
439
|
+
</h2>
|
|
440
|
+
<span class="toggle-icon" id="toggle-icon">▼</span>
|
|
367
441
|
</div>
|
|
368
|
-
<div class="prompt-content" id="system-prompt-content">
|
|
369
|
-
<div class="
|
|
370
|
-
<button class="btn btn-
|
|
442
|
+
<div class="system-prompt-content" id="system-prompt-content">
|
|
443
|
+
<div class="prompt-text" id="prompt-text"></div>
|
|
444
|
+
<button class="btn btn-copy" onclick="copySystemPrompt()">
|
|
371
445
|
<span id="copy-icon">📋</span>
|
|
372
|
-
<span id="copy-text">Copy Prompt</span>
|
|
446
|
+
<span id="copy-text">Copy System Prompt</span>
|
|
373
447
|
</button>
|
|
374
448
|
</div>
|
|
375
449
|
</div>
|
|
376
450
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
<div class="card
|
|
380
|
-
<div class="
|
|
381
|
-
|
|
382
|
-
<
|
|
451
|
+
<div class="endpoints">
|
|
452
|
+
<!-- Analyze -->
|
|
453
|
+
<div class="endpoint-card">
|
|
454
|
+
<div class="endpoint-header">
|
|
455
|
+
<span class="method post">POST</span>
|
|
456
|
+
<span class="endpoint-path">/api/analyze</span>
|
|
383
457
|
</div>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
<label class="form-label">Project Path</label>
|
|
389
|
-
<input type="text" id="analyze-path" value="." placeholder="/path/to/project">
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<div class="btn-group">
|
|
393
|
-
<button class="btn" onclick="testAnalyze()">
|
|
394
|
-
<span>📥</span> Download
|
|
395
|
-
</button>
|
|
396
|
-
<button class="btn btn-secondary" onclick="copyAnalyzeResult()">
|
|
397
|
-
<span id="analyze-copy-icon">📋</span> Copy
|
|
398
|
-
</button>
|
|
399
|
-
</div>
|
|
400
|
-
<div class="response" id="analyze-response"></div>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
<!-- Execute Card -->
|
|
404
|
-
<div class="card">
|
|
405
|
-
<div class="card-header">
|
|
406
|
-
<div class="card-icon" style="background: rgba(52, 199, 89, 0.1); color: var(--ios-green);">🚀</div>
|
|
407
|
-
<div>
|
|
408
|
-
<div class="card-title">Execute Script</div>
|
|
458
|
+
<p class="endpoint-desc">Phân tích dự án và lấy toàn bộ source code.</p>
|
|
459
|
+
<div class="form-group">
|
|
460
|
+
<label>Path</label>
|
|
461
|
+
<input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
|
|
409
462
|
</div>
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
463
|
+
<div class="btn-group">
|
|
464
|
+
<button class="btn" onclick="testAnalyze()">
|
|
465
|
+
<span>📥</span>
|
|
466
|
+
<span>Download</span>
|
|
467
|
+
</button>
|
|
468
|
+
<button class="btn btn-copy" onclick="copyAnalyzeResult()">
|
|
469
|
+
<span id="analyze-copy-icon">📋</span>
|
|
470
|
+
<span id="analyze-copy-text">Copy Text</span>
|
|
471
|
+
</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
|
+
</div>
|
|
477
|
+
<div class="response" id="analyze-response"></div>
|
|
416
478
|
</div>
|
|
417
479
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
480
|
+
<!-- Execute Bash -->
|
|
481
|
+
<div class="endpoint-card">
|
|
482
|
+
<div class="endpoint-header">
|
|
483
|
+
<span class="method post">POST</span>
|
|
484
|
+
<span class="endpoint-path">/api/execute</span>
|
|
485
|
+
</div>
|
|
486
|
+
<p class="endpoint-desc">Thực thi bash script với syntax validation.</p>
|
|
487
|
+
<div class="form-group">
|
|
488
|
+
<label>Bash Script</label>
|
|
489
|
+
<textarea id="execute-bash"
|
|
490
|
+
placeholder="mkdir -p src/test echo 'Hello' > src/test/hello.txt"></textarea>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="btn-group">
|
|
493
|
+
<button class="btn" onclick="testExecute()">
|
|
494
|
+
<span>▶️</span>
|
|
495
|
+
<span>Run Script</span>
|
|
496
|
+
</button>
|
|
497
|
+
<button class="btn" onclick="executeFromClipboard()">
|
|
498
|
+
<span>📋</span>
|
|
499
|
+
<span>Paste & Run</span>
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="response" id="execute-response"></div>
|
|
425
503
|
</div>
|
|
426
|
-
<div class="response" id="execute-response"></div>
|
|
427
504
|
</div>
|
|
428
505
|
</div>
|
|
429
506
|
|
|
430
|
-
<!-- iOS Toast -->
|
|
431
507
|
<div class="toast" id="toast"></div>
|
|
432
508
|
|
|
433
509
|
<script>
|
|
434
510
|
const API_BASE = window.location.origin;
|
|
435
511
|
let lastAnalyzeResult = null;
|
|
436
512
|
|
|
513
|
+
// System Prompt
|
|
437
514
|
const SYSTEM_PROMPT = `# VG Coder AI System Prompt
|
|
438
515
|
|
|
439
516
|
## Command Prefixes
|
|
440
|
-
|
|
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
|
+
|
|
441
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
|
+
|
|
442
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
|
+
|
|
443
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.
|
|
444
550
|
|
|
445
|
-
## ⚠️ QUY TẮC BẮT BUỘC
|
|
551
|
+
## ⚠️ QUY TẮC BẮT BUỘC
|
|
446
552
|
|
|
447
|
-
1.
|
|
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:**
|
|
448
560
|
\`\`\`bash
|
|
449
561
|
mkdir -p $(dirname "path/to/file.ext")
|
|
450
562
|
cat <<'EOF' > path/to/file.ext
|
|
451
|
-
...
|
|
563
|
+
... toàn bộ nội dung file sau khi chỉnh sửa ...
|
|
452
564
|
EOF
|
|
453
565
|
\`\`\`
|
|
454
566
|
|
|
455
|
-
|
|
456
|
-
-
|
|
457
|
-
-
|
|
458
|
-
-
|
|
459
|
-
|
|
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
|
|
460
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
|
|
461
632
|
document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
|
|
462
633
|
|
|
463
634
|
function toggleSystemPrompt() {
|
|
464
635
|
const content = document.getElementById('system-prompt-content');
|
|
465
636
|
const icon = document.getElementById('toggle-icon');
|
|
466
637
|
content.classList.toggle('open');
|
|
467
|
-
icon.
|
|
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
|
+
});
|
|
468
660
|
}
|
|
469
661
|
|
|
470
662
|
function showResponse(elementId, data, isError = false) {
|
|
471
663
|
const el = document.getElementById(elementId);
|
|
472
664
|
el.className = 'response show ' + (isError ? 'error' : 'success');
|
|
473
|
-
|
|
474
|
-
// Format nice JSON
|
|
475
|
-
if (typeof data === 'object') {
|
|
476
|
-
if (data.stdout) data.stdout = data.stdout.trim();
|
|
477
|
-
if (data.stderr) data.stderr = data.stderr.trim();
|
|
478
|
-
}
|
|
479
|
-
|
|
480
665
|
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
481
666
|
}
|
|
482
667
|
|
|
483
668
|
function showLoading(button, originalText) {
|
|
484
669
|
button.disabled = true;
|
|
485
|
-
button.innerHTML = '<
|
|
670
|
+
button.innerHTML = '<span class="loading"></span>';
|
|
486
671
|
button.dataset.originalText = originalText;
|
|
487
672
|
}
|
|
488
673
|
|
|
489
674
|
function resetButton(button) {
|
|
490
675
|
button.disabled = false;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
function showToast(message, type = 'success') {
|
|
495
|
-
const toast = document.getElementById('toast');
|
|
496
|
-
let icon = type === 'success' ? '✅' : (type === 'error' ? '❌' : 'ℹ️');
|
|
497
|
-
toast.innerHTML = `${icon} ${message}`;
|
|
498
|
-
toast.classList.add('show');
|
|
499
|
-
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// --- ROBUST COPY FUNCTION (MODERN + FALLBACK) ---
|
|
503
|
-
function fallbackCopyTextToClipboard(text) {
|
|
504
|
-
var textArea = document.createElement("textarea");
|
|
505
|
-
textArea.value = text;
|
|
506
|
-
|
|
507
|
-
// Ensure textarea is not visible but part of DOM
|
|
508
|
-
textArea.style.position = "fixed";
|
|
509
|
-
textArea.style.left = "-9999px";
|
|
510
|
-
textArea.style.top = "0";
|
|
511
|
-
document.body.appendChild(textArea);
|
|
512
|
-
|
|
513
|
-
textArea.focus();
|
|
514
|
-
textArea.select();
|
|
515
|
-
|
|
516
|
-
try {
|
|
517
|
-
var successful = document.execCommand('copy');
|
|
518
|
-
return successful;
|
|
519
|
-
} catch (err) {
|
|
520
|
-
console.error('Fallback: Oops, unable to copy', err);
|
|
521
|
-
return false;
|
|
522
|
-
} finally {
|
|
523
|
-
document.body.removeChild(textArea);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
async function copyToClipboard(text, btnElement, successIconId, successTextId) {
|
|
528
|
-
if (!text) return showToast('Nothing to copy', 'error');
|
|
529
|
-
|
|
530
|
-
const updateUI = () => {
|
|
531
|
-
if (btnElement) {
|
|
532
|
-
btnElement.classList.add('copied');
|
|
533
|
-
const icon = document.getElementById(successIconId);
|
|
534
|
-
const label = document.getElementById(successTextId);
|
|
535
|
-
|
|
536
|
-
const originalIcon = icon.textContent;
|
|
537
|
-
const originalLabel = label.textContent;
|
|
538
|
-
|
|
539
|
-
icon.textContent = '✓';
|
|
540
|
-
label.textContent = 'Copied';
|
|
541
|
-
|
|
542
|
-
setTimeout(() => {
|
|
543
|
-
btnElement.classList.remove('copied');
|
|
544
|
-
icon.textContent = originalIcon;
|
|
545
|
-
label.textContent = originalLabel;
|
|
546
|
-
}, 2000);
|
|
547
|
-
}
|
|
548
|
-
showToast('Copied to clipboard!');
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
// Try Modern API first
|
|
552
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
553
|
-
try {
|
|
554
|
-
await navigator.clipboard.writeText(text);
|
|
555
|
-
updateUI();
|
|
556
|
-
} catch (err) {
|
|
557
|
-
console.warn('Modern copy failed, trying fallback', err);
|
|
558
|
-
// Try fallback if modern API fails
|
|
559
|
-
if (fallbackCopyTextToClipboard(text)) {
|
|
560
|
-
updateUI();
|
|
561
|
-
} else {
|
|
562
|
-
showToast('Failed to copy', 'error');
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
} else {
|
|
566
|
-
// Use fallback immediately if API doesn't exist
|
|
567
|
-
if (fallbackCopyTextToClipboard(text)) {
|
|
568
|
-
updateUI();
|
|
569
|
-
} else {
|
|
570
|
-
showToast('Failed to copy', 'error');
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// --- Logic Handlers ---
|
|
576
|
-
|
|
577
|
-
function copySystemPrompt() {
|
|
578
|
-
copyToClipboard(SYSTEM_PROMPT, event.target.closest('.btn'), 'copy-icon', 'copy-text');
|
|
676
|
+
const originalText = button.dataset.originalText;
|
|
677
|
+
button.innerHTML = originalText;
|
|
579
678
|
}
|
|
580
679
|
|
|
581
680
|
async function testAnalyze() {
|
|
582
681
|
const btn = event.target.closest('.btn');
|
|
583
682
|
const path = document.getElementById('analyze-path').value;
|
|
584
|
-
const originalHTML = btn.innerHTML;
|
|
585
683
|
|
|
586
|
-
showLoading(btn,
|
|
684
|
+
showLoading(btn, btn.innerHTML);
|
|
587
685
|
try {
|
|
588
686
|
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
589
687
|
method: 'POST',
|
|
@@ -595,7 +693,7 @@ EOF
|
|
|
595
693
|
const text = await res.text();
|
|
596
694
|
lastAnalyzeResult = text;
|
|
597
695
|
|
|
598
|
-
//
|
|
696
|
+
// Download file
|
|
599
697
|
const blob = new Blob([text], { type: 'text/plain' });
|
|
600
698
|
const url = window.URL.createObjectURL(blob);
|
|
601
699
|
const a = document.createElement('a');
|
|
@@ -603,34 +701,167 @@ EOF
|
|
|
603
701
|
a.download = 'project.txt';
|
|
604
702
|
a.click();
|
|
605
703
|
|
|
606
|
-
showResponse('analyze-response', {
|
|
607
|
-
|
|
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');
|
|
608
711
|
} else {
|
|
609
712
|
const data = await res.json();
|
|
610
713
|
showResponse('analyze-response', data, true);
|
|
611
|
-
showToast('
|
|
714
|
+
showToast('Lỗi analyze', 'error');
|
|
612
715
|
}
|
|
613
716
|
} catch (err) {
|
|
614
717
|
showResponse('analyze-response', { error: err.message }, true);
|
|
615
|
-
showToast('
|
|
718
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
616
719
|
}
|
|
617
720
|
resetButton(btn);
|
|
618
721
|
}
|
|
619
722
|
|
|
620
723
|
async function copyAnalyzeResult() {
|
|
621
|
-
const
|
|
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
|
+
|
|
622
728
|
if (!lastAnalyzeResult) {
|
|
623
|
-
//
|
|
624
|
-
|
|
625
|
-
|
|
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');
|
|
626
854
|
}
|
|
627
|
-
copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
|
|
628
855
|
}
|
|
629
856
|
|
|
630
857
|
async function testExecute() {
|
|
631
858
|
const btn = event.target.closest('.btn');
|
|
632
859
|
const bash = document.getElementById('execute-bash').value;
|
|
633
|
-
|
|
860
|
+
|
|
861
|
+
if (!bash.trim()) {
|
|
862
|
+
showToast('Vui lòng nhập bash script', 'error');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
634
865
|
|
|
635
866
|
showLoading(btn, btn.innerHTML);
|
|
636
867
|
try {
|
|
@@ -640,41 +871,96 @@ EOF
|
|
|
640
871
|
body: JSON.stringify({ bash })
|
|
641
872
|
});
|
|
642
873
|
const data = await res.json();
|
|
643
|
-
showResponse('execute-response', data, !data.success);
|
|
644
|
-
|
|
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
|
+
}
|
|
645
881
|
} catch (err) {
|
|
646
882
|
showResponse('execute-response', { error: err.message }, true);
|
|
883
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
647
884
|
}
|
|
648
885
|
resetButton(btn);
|
|
649
886
|
}
|
|
650
887
|
|
|
651
888
|
async function executeFromClipboard() {
|
|
889
|
+
const btn = event.target.closest('.btn');
|
|
890
|
+
|
|
891
|
+
showLoading(btn, btn.innerHTML);
|
|
892
|
+
|
|
652
893
|
try {
|
|
653
|
-
const
|
|
654
|
-
|
|
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
|
+
}
|
|
655
905
|
|
|
656
|
-
document.getElementById('execute-bash').value =
|
|
657
|
-
|
|
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
|
+
}
|
|
658
925
|
} catch (err) {
|
|
659
|
-
|
|
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
|
+
}
|
|
660
932
|
}
|
|
933
|
+
resetButton(btn);
|
|
661
934
|
}
|
|
662
935
|
|
|
663
|
-
|
|
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
|
|
664
950
|
(async () => {
|
|
665
|
-
const statusBadge = document.getElementById('status');
|
|
666
951
|
try {
|
|
667
952
|
const res = await fetch(`${API_BASE}/health`);
|
|
668
953
|
if (res.ok) {
|
|
669
|
-
|
|
670
|
-
|
|
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)';
|
|
671
957
|
}
|
|
672
958
|
} catch {
|
|
673
|
-
|
|
674
|
-
|
|
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)';
|
|
675
962
|
}
|
|
676
963
|
})();
|
|
677
|
-
|
|
678
964
|
</script>
|
|
679
965
|
</body>
|
|
680
966
|
|
|
Binary file
|
|
Binary file
|