vg-coder-cli 2.0.6 → 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 -386
- 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,84 +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;
|
|
239
|
+
display: flex;
|
|
171
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
|
+
}
|
|
172
250
|
}
|
|
173
251
|
|
|
174
252
|
.btn {
|
|
175
|
-
border: none;
|
|
176
253
|
background: var(--ios-blue);
|
|
177
254
|
color: white;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
font-weight: 600;
|
|
255
|
+
border: none;
|
|
256
|
+
padding: 14px 20px;
|
|
257
|
+
border-radius: 12px;
|
|
182
258
|
cursor: pointer;
|
|
259
|
+
font-size: 17px;
|
|
260
|
+
font-weight: 600;
|
|
183
261
|
transition: transform 0.1s, opacity 0.2s;
|
|
184
262
|
display: flex;
|
|
185
263
|
align-items: center;
|
|
186
264
|
justify-content: center;
|
|
187
265
|
gap: 8px;
|
|
188
|
-
|
|
266
|
+
width: 100%;
|
|
267
|
+
position: relative;
|
|
268
|
+
overflow: hidden;
|
|
189
269
|
}
|
|
190
270
|
|
|
191
271
|
.btn:active {
|
|
192
272
|
transform: scale(0.98);
|
|
193
|
-
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;
|
|
194
281
|
}
|
|
195
282
|
|
|
196
|
-
.btn-
|
|
197
|
-
background: rgba(0, 122, 255, 0.
|
|
283
|
+
.btn-copy {
|
|
284
|
+
background: rgba(0, 122, 255, 0.1);
|
|
198
285
|
color: var(--ios-blue);
|
|
199
286
|
}
|
|
200
287
|
|
|
201
|
-
/*
|
|
202
|
-
.btn-
|
|
203
|
-
|
|
288
|
+
/* Specific override for prompt copy button to look nice */
|
|
289
|
+
.system-prompt-content .btn-copy {
|
|
290
|
+
margin-top: 8px;
|
|
204
291
|
}
|
|
205
292
|
|
|
206
|
-
.btn-copy {
|
|
207
|
-
background:
|
|
208
|
-
color: black;
|
|
293
|
+
.btn-copy:active {
|
|
294
|
+
background: rgba(0, 122, 255, 0.2);
|
|
209
295
|
}
|
|
210
296
|
|
|
211
297
|
.btn-copy.copied {
|
|
@@ -213,376 +299,389 @@
|
|
|
213
299
|
color: white;
|
|
214
300
|
}
|
|
215
301
|
|
|
216
|
-
/* System Prompt Accordion */
|
|
217
|
-
.prompt-accordion {
|
|
218
|
-
cursor: pointer;
|
|
219
|
-
user-select: none;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.prompt-header {
|
|
223
|
-
display: flex;
|
|
224
|
-
justify-content: space-between;
|
|
225
|
-
align-items: center;
|
|
226
|
-
padding: 4px 0;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.chevron {
|
|
230
|
-
color: var(--ios-text-secondary);
|
|
231
|
-
transition: transform 0.3s ease;
|
|
232
|
-
font-size: 0.8rem;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.prompt-content {
|
|
236
|
-
max-height: 0;
|
|
237
|
-
overflow: hidden;
|
|
238
|
-
transition: max-height 0.4s cubic-bezier(0.65, 0, 0.35, 1);
|
|
239
|
-
opacity: 0;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.prompt-content.open {
|
|
243
|
-
max-height: 500px;
|
|
244
|
-
opacity: 1;
|
|
245
|
-
margin-top: 15px;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.code-block {
|
|
249
|
-
background: #2c2c2e;
|
|
250
|
-
color: #fff;
|
|
251
|
-
padding: 15px;
|
|
252
|
-
border-radius: 12px;
|
|
253
|
-
font-family: 'SF Mono', 'Menlo', monospace;
|
|
254
|
-
font-size: 0.85rem;
|
|
255
|
-
overflow-x: auto;
|
|
256
|
-
white-space: pre-wrap;
|
|
257
|
-
margin-bottom: 15px;
|
|
258
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
302
|
/* Response Area */
|
|
262
303
|
.response {
|
|
263
304
|
margin-top: 20px;
|
|
264
|
-
|
|
305
|
+
padding: 16px;
|
|
265
306
|
border-radius: 12px;
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
background: #1C1C1E;
|
|
308
|
+
/* Dark background for code contrast */
|
|
309
|
+
color: #fff;
|
|
268
310
|
display: none;
|
|
269
|
-
|
|
311
|
+
font-size: 13px;
|
|
312
|
+
overflow-x: auto;
|
|
313
|
+
border-left: none;
|
|
314
|
+
/* Removed the side border style */
|
|
270
315
|
}
|
|
271
316
|
|
|
272
317
|
.response.show {
|
|
273
318
|
display: block;
|
|
319
|
+
animation: fadeIn 0.3s ease;
|
|
274
320
|
}
|
|
275
321
|
|
|
276
322
|
.response.success {
|
|
277
|
-
border
|
|
278
|
-
background: #F0FFF4;
|
|
323
|
+
border: 1px solid rgba(52, 199, 89, 0.3);
|
|
279
324
|
}
|
|
280
325
|
|
|
281
326
|
.response.error {
|
|
282
|
-
border
|
|
283
|
-
background: #
|
|
327
|
+
border: 1px solid rgba(255, 59, 48, 0.3);
|
|
328
|
+
background: #2C1515;
|
|
284
329
|
}
|
|
285
330
|
|
|
286
331
|
.response pre {
|
|
332
|
+
margin: 0;
|
|
287
333
|
font-family: 'SF Mono', 'Menlo', monospace;
|
|
288
|
-
font-size: 0.85rem;
|
|
289
334
|
white-space: pre-wrap;
|
|
290
|
-
word-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
365
|
}
|
|
293
366
|
|
|
294
|
-
/* Toast */
|
|
367
|
+
/* Toast - iOS Notification Style */
|
|
295
368
|
.toast {
|
|
296
369
|
position: fixed;
|
|
297
|
-
top:
|
|
370
|
+
top: 10px;
|
|
298
371
|
left: 50%;
|
|
299
372
|
transform: translateX(-50%) translateY(-100px);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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;
|
|
310
385
|
display: flex;
|
|
311
386
|
align-items: center;
|
|
312
|
-
gap:
|
|
387
|
+
gap: 10px;
|
|
388
|
+
width: auto;
|
|
389
|
+
max-width: 90%;
|
|
313
390
|
white-space: nowrap;
|
|
391
|
+
opacity: 0;
|
|
392
|
+
pointer-events: none;
|
|
314
393
|
}
|
|
315
394
|
|
|
316
395
|
.toast.show {
|
|
317
|
-
transform: translateX(-50%) translateY(
|
|
396
|
+
transform: translateX(-50%) translateY(calc(var(--safe-area-top)));
|
|
397
|
+
opacity: 1;
|
|
318
398
|
}
|
|
319
399
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
400
|
+
/* Icons in toast */
|
|
401
|
+
.toast::before {
|
|
402
|
+
content: '';
|
|
403
|
+
display: block;
|
|
404
|
+
width: 20px;
|
|
405
|
+
height: 20px;
|
|
324
406
|
border-radius: 50%;
|
|
325
|
-
|
|
326
|
-
animation: spin 0.8s linear infinite;
|
|
407
|
+
flex-shrink: 0;
|
|
327
408
|
}
|
|
328
409
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
transform: rotate(360deg);
|
|
332
|
-
}
|
|
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;
|
|
333
412
|
}
|
|
334
413
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
transform: translateY(-10px);
|
|
339
|
-
}
|
|
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
|
+
}
|
|
340
417
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
transform: translateY(0);
|
|
344
|
-
}
|
|
418
|
+
.toast.info::before {
|
|
419
|
+
background: var(--ios-blue);
|
|
345
420
|
}
|
|
346
421
|
</style>
|
|
347
422
|
</head>
|
|
348
423
|
|
|
349
424
|
<body>
|
|
350
|
-
<!-- Glass Header -->
|
|
351
|
-
<nav class="header-nav">
|
|
352
|
-
<div class="app-title">VG Coder</div>
|
|
353
|
-
<div id="status" class="status-badge">● Offline</div>
|
|
354
|
-
</nav>
|
|
355
|
-
|
|
356
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>
|
|
357
432
|
|
|
358
|
-
<!-- System Prompt
|
|
359
|
-
<div class="
|
|
360
|
-
<div class="
|
|
361
|
-
<
|
|
362
|
-
<
|
|
363
|
-
<
|
|
364
|
-
</
|
|
365
|
-
<
|
|
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>
|
|
366
441
|
</div>
|
|
367
|
-
<div class="prompt-content" id="system-prompt-content">
|
|
368
|
-
<div class="
|
|
369
|
-
<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()">
|
|
370
445
|
<span id="copy-icon">📋</span>
|
|
371
|
-
<span id="copy-text">Copy Prompt</span>
|
|
446
|
+
<span id="copy-text">Copy System Prompt</span>
|
|
372
447
|
</button>
|
|
373
448
|
</div>
|
|
374
449
|
</div>
|
|
375
450
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
<div class="card
|
|
379
|
-
<div class="
|
|
380
|
-
|
|
381
|
-
<
|
|
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>
|
|
382
457
|
</div>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<label class="form-label">Project Path</label>
|
|
388
|
-
<input type="text" id="analyze-path" value="." placeholder="/path/to/project">
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
<div class="btn-group">
|
|
392
|
-
<button class="btn" onclick="testAnalyze()">
|
|
393
|
-
<span>📥</span> Download
|
|
394
|
-
</button>
|
|
395
|
-
<button class="btn btn-secondary" onclick="copyAnalyzeResult()">
|
|
396
|
-
<span id="analyze-copy-icon">📋</span> Copy
|
|
397
|
-
</button>
|
|
398
|
-
</div>
|
|
399
|
-
<div class="response" id="analyze-response"></div>
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
<!-- Execute Card -->
|
|
403
|
-
<div class="card">
|
|
404
|
-
<div class="card-header">
|
|
405
|
-
<div class="card-icon" style="background: rgba(52, 199, 89, 0.1); color: var(--ios-green);">🚀</div>
|
|
406
|
-
<div>
|
|
407
|
-
<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. .)">
|
|
408
462
|
</div>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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>
|
|
415
478
|
</div>
|
|
416
479
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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>
|
|
424
503
|
</div>
|
|
425
|
-
<div class="response" id="execute-response"></div>
|
|
426
504
|
</div>
|
|
427
505
|
</div>
|
|
428
506
|
|
|
429
|
-
<!-- iOS Toast -->
|
|
430
507
|
<div class="toast" id="toast"></div>
|
|
431
508
|
|
|
432
509
|
<script>
|
|
433
510
|
const API_BASE = window.location.origin;
|
|
434
511
|
let lastAnalyzeResult = null;
|
|
435
512
|
|
|
513
|
+
// System Prompt
|
|
436
514
|
const SYSTEM_PROMPT = `# VG Coder AI System Prompt
|
|
437
515
|
|
|
438
516
|
## Command Prefixes
|
|
439
|
-
|
|
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
|
+
|
|
440
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
|
+
|
|
441
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
|
+
|
|
442
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.
|
|
443
550
|
|
|
444
|
-
## ⚠️ QUY TẮC BẮT BUỘC
|
|
551
|
+
## ⚠️ QUY TẮC BẮT BUỘC
|
|
445
552
|
|
|
446
|
-
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:**
|
|
447
560
|
\`\`\`bash
|
|
448
561
|
mkdir -p $(dirname "path/to/file.ext")
|
|
449
562
|
cat <<'EOF' > path/to/file.ext
|
|
450
|
-
...
|
|
563
|
+
... toàn bộ nội dung file sau khi chỉnh sửa ...
|
|
451
564
|
EOF
|
|
452
565
|
\`\`\`
|
|
453
566
|
|
|
454
|
-
|
|
455
|
-
-
|
|
456
|
-
-
|
|
457
|
-
-
|
|
458
|
-
|
|
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
|
|
459
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
|
|
460
632
|
document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
|
|
461
633
|
|
|
462
634
|
function toggleSystemPrompt() {
|
|
463
635
|
const content = document.getElementById('system-prompt-content');
|
|
464
636
|
const icon = document.getElementById('toggle-icon');
|
|
465
637
|
content.classList.toggle('open');
|
|
466
|
-
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
|
+
});
|
|
467
660
|
}
|
|
468
661
|
|
|
469
662
|
function showResponse(elementId, data, isError = false) {
|
|
470
663
|
const el = document.getElementById(elementId);
|
|
471
664
|
el.className = 'response show ' + (isError ? 'error' : 'success');
|
|
472
|
-
|
|
473
|
-
// Format nice JSON
|
|
474
|
-
if (typeof data === 'object') {
|
|
475
|
-
if (data.stdout) data.stdout = data.stdout.trim();
|
|
476
|
-
if (data.stderr) data.stderr = data.stderr.trim();
|
|
477
|
-
}
|
|
478
|
-
|
|
479
665
|
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
480
666
|
}
|
|
481
667
|
|
|
482
668
|
function showLoading(button, originalText) {
|
|
483
669
|
button.disabled = true;
|
|
484
|
-
button.innerHTML = '<
|
|
670
|
+
button.innerHTML = '<span class="loading"></span>';
|
|
485
671
|
button.dataset.originalText = originalText;
|
|
486
672
|
}
|
|
487
673
|
|
|
488
674
|
function resetButton(button) {
|
|
489
675
|
button.disabled = false;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
function showToast(message, type = 'success') {
|
|
494
|
-
const toast = document.getElementById('toast');
|
|
495
|
-
let icon = type === 'success' ? '✅' : (type === 'error' ? '❌' : 'ℹ️');
|
|
496
|
-
toast.innerHTML = `${icon} ${message}`;
|
|
497
|
-
toast.classList.add('show');
|
|
498
|
-
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// --- ROBUST COPY FUNCTION (MODERN + FALLBACK) ---
|
|
502
|
-
function fallbackCopyTextToClipboard(text) {
|
|
503
|
-
var textArea = document.createElement("textarea");
|
|
504
|
-
textArea.value = text;
|
|
505
|
-
|
|
506
|
-
// Ensure textarea is not visible but part of DOM
|
|
507
|
-
textArea.style.position = "fixed";
|
|
508
|
-
textArea.style.left = "-9999px";
|
|
509
|
-
textArea.style.top = "0";
|
|
510
|
-
document.body.appendChild(textArea);
|
|
511
|
-
|
|
512
|
-
textArea.focus();
|
|
513
|
-
textArea.select();
|
|
514
|
-
|
|
515
|
-
try {
|
|
516
|
-
var successful = document.execCommand('copy');
|
|
517
|
-
return successful;
|
|
518
|
-
} catch (err) {
|
|
519
|
-
console.error('Fallback: Oops, unable to copy', err);
|
|
520
|
-
return false;
|
|
521
|
-
} finally {
|
|
522
|
-
document.body.removeChild(textArea);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async function copyToClipboard(text, btnElement, successIconId, successTextId) {
|
|
527
|
-
if (!text) return showToast('Nothing to copy', 'error');
|
|
528
|
-
|
|
529
|
-
const updateUI = () => {
|
|
530
|
-
if (btnElement) {
|
|
531
|
-
btnElement.classList.add('copied');
|
|
532
|
-
const icon = document.getElementById(successIconId);
|
|
533
|
-
const label = document.getElementById(successTextId);
|
|
534
|
-
|
|
535
|
-
const originalIcon = icon.textContent;
|
|
536
|
-
const originalLabel = label.textContent;
|
|
537
|
-
|
|
538
|
-
icon.textContent = '✓';
|
|
539
|
-
label.textContent = 'Copied';
|
|
540
|
-
|
|
541
|
-
setTimeout(() => {
|
|
542
|
-
btnElement.classList.remove('copied');
|
|
543
|
-
icon.textContent = originalIcon;
|
|
544
|
-
label.textContent = originalLabel;
|
|
545
|
-
}, 2000);
|
|
546
|
-
}
|
|
547
|
-
showToast('Copied to clipboard!');
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
// Try Modern API first
|
|
551
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
552
|
-
try {
|
|
553
|
-
await navigator.clipboard.writeText(text);
|
|
554
|
-
updateUI();
|
|
555
|
-
} catch (err) {
|
|
556
|
-
console.warn('Modern copy failed, trying fallback', err);
|
|
557
|
-
// Try fallback if modern API fails
|
|
558
|
-
if (fallbackCopyTextToClipboard(text)) {
|
|
559
|
-
updateUI();
|
|
560
|
-
} else {
|
|
561
|
-
showToast('Failed to copy', 'error');
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} else {
|
|
565
|
-
// Use fallback immediately if API doesn't exist
|
|
566
|
-
if (fallbackCopyTextToClipboard(text)) {
|
|
567
|
-
updateUI();
|
|
568
|
-
} else {
|
|
569
|
-
showToast('Failed to copy', 'error');
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// --- Logic Handlers ---
|
|
575
|
-
|
|
576
|
-
function copySystemPrompt() {
|
|
577
|
-
copyToClipboard(SYSTEM_PROMPT, event.target.closest('.btn'), 'copy-icon', 'copy-text');
|
|
676
|
+
const originalText = button.dataset.originalText;
|
|
677
|
+
button.innerHTML = originalText;
|
|
578
678
|
}
|
|
579
679
|
|
|
580
680
|
async function testAnalyze() {
|
|
581
681
|
const btn = event.target.closest('.btn');
|
|
582
682
|
const path = document.getElementById('analyze-path').value;
|
|
583
|
-
const originalHTML = btn.innerHTML;
|
|
584
683
|
|
|
585
|
-
showLoading(btn,
|
|
684
|
+
showLoading(btn, btn.innerHTML);
|
|
586
685
|
try {
|
|
587
686
|
const res = await fetch(`${API_BASE}/api/analyze`, {
|
|
588
687
|
method: 'POST',
|
|
@@ -594,7 +693,7 @@ EOF
|
|
|
594
693
|
const text = await res.text();
|
|
595
694
|
lastAnalyzeResult = text;
|
|
596
695
|
|
|
597
|
-
//
|
|
696
|
+
// Download file
|
|
598
697
|
const blob = new Blob([text], { type: 'text/plain' });
|
|
599
698
|
const url = window.URL.createObjectURL(blob);
|
|
600
699
|
const a = document.createElement('a');
|
|
@@ -602,33 +701,167 @@ EOF
|
|
|
602
701
|
a.download = 'project.txt';
|
|
603
702
|
a.click();
|
|
604
703
|
|
|
605
|
-
showResponse('analyze-response', {
|
|
606
|
-
|
|
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');
|
|
607
711
|
} else {
|
|
608
712
|
const data = await res.json();
|
|
609
713
|
showResponse('analyze-response', data, true);
|
|
610
|
-
showToast('
|
|
714
|
+
showToast('Lỗi analyze', 'error');
|
|
611
715
|
}
|
|
612
716
|
} catch (err) {
|
|
613
717
|
showResponse('analyze-response', { error: err.message }, true);
|
|
614
|
-
showToast('
|
|
718
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
615
719
|
}
|
|
616
720
|
resetButton(btn);
|
|
617
721
|
}
|
|
618
722
|
|
|
619
723
|
async function copyAnalyzeResult() {
|
|
620
|
-
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
|
+
|
|
621
728
|
if (!lastAnalyzeResult) {
|
|
622
|
-
|
|
623
|
-
|
|
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');
|
|
624
854
|
}
|
|
625
|
-
copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
|
|
626
855
|
}
|
|
627
856
|
|
|
628
857
|
async function testExecute() {
|
|
629
858
|
const btn = event.target.closest('.btn');
|
|
630
859
|
const bash = document.getElementById('execute-bash').value;
|
|
631
|
-
|
|
860
|
+
|
|
861
|
+
if (!bash.trim()) {
|
|
862
|
+
showToast('Vui lòng nhập bash script', 'error');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
632
865
|
|
|
633
866
|
showLoading(btn, btn.innerHTML);
|
|
634
867
|
try {
|
|
@@ -638,41 +871,96 @@ EOF
|
|
|
638
871
|
body: JSON.stringify({ bash })
|
|
639
872
|
});
|
|
640
873
|
const data = await res.json();
|
|
641
|
-
showResponse('execute-response', data, !data.success);
|
|
642
|
-
|
|
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
|
+
}
|
|
643
881
|
} catch (err) {
|
|
644
882
|
showResponse('execute-response', { error: err.message }, true);
|
|
883
|
+
showToast('Lỗi: ' + err.message, 'error');
|
|
645
884
|
}
|
|
646
885
|
resetButton(btn);
|
|
647
886
|
}
|
|
648
887
|
|
|
649
888
|
async function executeFromClipboard() {
|
|
889
|
+
const btn = event.target.closest('.btn');
|
|
890
|
+
|
|
891
|
+
showLoading(btn, btn.innerHTML);
|
|
892
|
+
|
|
650
893
|
try {
|
|
651
|
-
const
|
|
652
|
-
|
|
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
|
+
}
|
|
653
905
|
|
|
654
|
-
document.getElementById('execute-bash').value =
|
|
655
|
-
|
|
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
|
+
}
|
|
656
925
|
} catch (err) {
|
|
657
|
-
|
|
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
|
+
}
|
|
658
932
|
}
|
|
933
|
+
resetButton(btn);
|
|
659
934
|
}
|
|
660
935
|
|
|
661
|
-
|
|
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
|
|
662
950
|
(async () => {
|
|
663
|
-
const statusBadge = document.getElementById('status');
|
|
664
951
|
try {
|
|
665
952
|
const res = await fetch(`${API_BASE}/health`);
|
|
666
953
|
if (res.ok) {
|
|
667
|
-
|
|
668
|
-
|
|
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)';
|
|
669
957
|
}
|
|
670
958
|
} catch {
|
|
671
|
-
|
|
672
|
-
|
|
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)';
|
|
673
962
|
}
|
|
674
963
|
})();
|
|
675
|
-
|
|
676
964
|
</script>
|
|
677
965
|
</body>
|
|
678
966
|
|
|
Binary file
|