vg-coder-cli 2.0.1 → 2.0.4

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.
Files changed (80) hide show
  1. package/README.md +318 -92
  2. package/{vg/apps/api/src/assets/.gitkeep → change.sh} +0 -0
  3. package/package.json +4 -11
  4. package/src/index.js +0 -56
  5. package/src/server/views/dashboard.html +391 -613
  6. package/vg-coder-cli-1.0.17.tgz +0 -0
  7. package/vg-coder-cli-2.0.4.tgz +0 -0
  8. package/CHANGELOG.md +0 -59
  9. package/PUBLISHING.md +0 -86
  10. package/SYSTEM_PROMPT.md +0 -157
  11. package/init-nx-monorepo.sh +0 -107
  12. package/vg/.vscode/extensions.json +0 -8
  13. package/vg/.vscode/launch.json +0 -23
  14. package/vg/README.md +0 -85
  15. package/vg/apps/api/project.json +0 -83
  16. package/vg/apps/api/src/app/analyze.controller.ts +0 -17
  17. package/vg/apps/api/src/app/analyze.service.ts +0 -57
  18. package/vg/apps/api/src/app/app.controller.ts +0 -12
  19. package/vg/apps/api/src/app/app.module.ts +0 -29
  20. package/vg/apps/api/src/app/app.service.ts +0 -8
  21. package/vg/apps/api/src/app/clean.controller.ts +0 -40
  22. package/vg/apps/api/src/app/execute.controller.ts +0 -19
  23. package/vg/apps/api/src/app/execute.service.ts +0 -46
  24. package/vg/apps/api/src/app/info.controller.ts +0 -12
  25. package/vg/apps/api/src/app/info.service.ts +0 -65
  26. package/vg/apps/api/src/main.ts +0 -28
  27. package/vg/apps/api/webpack.config.js +0 -25
  28. package/vg/apps/api-e2e/jest.config.cts +0 -18
  29. package/vg/apps/api-e2e/project.json +0 -17
  30. package/vg/apps/api-e2e/src/support/global-setup.ts +0 -16
  31. package/vg/apps/api-e2e/src/support/global-teardown.ts +0 -10
  32. package/vg/apps/api-e2e/src/support/test-setup.ts +0 -9
  33. package/vg/apps/ng-app/jest.config.ts +0 -21
  34. package/vg/apps/ng-app/project.json +0 -110
  35. package/vg/apps/ng-app/proxy.conf.json +0 -8
  36. package/vg/apps/ng-app/public/favicon.ico +0 -0
  37. package/vg/apps/ng-app/src/app/app.config.ts +0 -17
  38. package/vg/apps/ng-app/src/app/app.html +0 -1
  39. package/vg/apps/ng-app/src/app/app.routes.ts +0 -7
  40. package/vg/apps/ng-app/src/app/app.scss +0 -0
  41. package/vg/apps/ng-app/src/app/app.ts +0 -12
  42. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.html +0 -87
  43. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.scss +0 -290
  44. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.ts +0 -236
  45. package/vg/apps/ng-app/src/app/nx-welcome.ts +0 -872
  46. package/vg/apps/ng-app/src/app/services/api.service.ts +0 -28
  47. package/vg/apps/ng-app/src/index.html +0 -13
  48. package/vg/apps/ng-app/src/main.ts +0 -5
  49. package/vg/apps/ng-app/src/styles.scss +0 -1
  50. package/vg/apps/ng-app/src/test-setup.ts +0 -6
  51. package/vg/jest.config.ts +0 -6
  52. package/vg/nx.json +0 -85
  53. package/vg/package-lock.json +0 -30707
  54. package/vg/package.json +0 -75
  55. package/vg/packages/client/data-access/README.md +0 -7
  56. package/vg/packages/client/data-access/jest.config.ts +0 -21
  57. package/vg/packages/client/data-access/project.json +0 -21
  58. package/vg/packages/client/data-access/src/index.ts +0 -1
  59. package/vg/packages/client/data-access/src/lib/data-access/data-access.html +0 -1
  60. package/vg/packages/client/data-access/src/lib/data-access/data-access.scss +0 -0
  61. package/vg/packages/client/data-access/src/lib/data-access/data-access.ts +0 -9
  62. package/vg/packages/client/data-access/src/test-setup.ts +0 -6
  63. package/vg/packages/core/README.md +0 -11
  64. package/vg/packages/core/jest.config.ts +0 -10
  65. package/vg/packages/core/package.json +0 -11
  66. package/vg/packages/core/project.json +0 -26
  67. package/vg/packages/core/src/index.ts +0 -6
  68. package/vg/packages/core/src/lib/core.ts +0 -3
  69. package/vg/packages/core/src/lib/detectors/project-detector.ts +0 -343
  70. package/vg/packages/core/src/lib/ignore/ignore-manager.ts +0 -315
  71. package/vg/packages/core/src/lib/scanner/file-scanner.ts +0 -675
  72. package/vg/packages/core/src/lib/tokenizer/token-manager.ts +0 -435
  73. package/vg/packages/core/src/lib/utils/bash-executor.ts +0 -146
  74. package/vg/packages/shared/data-types/README.md +0 -11
  75. package/vg/packages/shared/data-types/jest.config.ts +0 -10
  76. package/vg/packages/shared/data-types/package.json +0 -11
  77. package/vg/packages/shared/data-types/project.json +0 -26
  78. package/vg/packages/shared/data-types/src/index.ts +0 -1
  79. package/vg/packages/shared/data-types/src/lib/data-types.ts +0 -3
  80. package/vg/start-dev.sh +0 -22
@@ -3,245 +3,271 @@
3
3
 
4
4
  <head>
5
5
  <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>VG Coder API Dashboard</title>
6
+ <meta name="viewport"
7
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
8
+ <meta name="apple-mobile-web-app-capable" content="yes">
9
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
+ <title>VG Coder CLI</title>
8
11
  <style>
12
+ :root {
13
+ --ios-bg: #F2F2F7;
14
+ --ios-card: #FFFFFF;
15
+ --ios-blue: #007AFF;
16
+ --ios-green: #34C759;
17
+ --ios-red: #FF3B30;
18
+ --ios-text-primary: #000000;
19
+ --ios-text-secondary: #8E8E93;
20
+ --ios-separator: #C6C6C8;
21
+ --ios-input-bg: #E5E5EA;
22
+ --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
23
+ }
24
+
9
25
  * {
10
26
  margin: 0;
11
27
  padding: 0;
12
28
  box-sizing: border-box;
29
+ -webkit-tap-highlight-color: transparent;
13
30
  }
14
31
 
15
32
  body {
16
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
+ font-family: var(--font-stack);
34
+ background-color: var(--ios-bg);
35
+ color: var(--ios-text-primary);
18
36
  min-height: 100vh;
19
- padding: 20px;
20
- }
21
-
22
- .container {
23
- max-width: 1000px;
24
- margin: 0 auto;
37
+ padding-bottom: 40px;
38
+ font-size: 17px;
39
+ line-height: 1.5;
40
+ -webkit-font-smoothing: antialiased;
41
+ }
42
+
43
+ /* Glass Header */
44
+ .header-nav {
45
+ position: sticky;
46
+ top: 0;
47
+ z-index: 100;
48
+ background: rgba(255, 255, 255, 0.85);
49
+ backdrop-filter: blur(20px);
50
+ -webkit-backdrop-filter: blur(20px);
51
+ border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
52
+ padding: 15px 20px;
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: center;
56
+ padding-top: max(15px, env(safe-area-inset-top));
25
57
  }
26
58
 
27
- .header {
28
- background: white;
29
- border-radius: 12px;
30
- padding: 30px;
31
- margin-bottom: 30px;
32
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
59
+ .app-title {
60
+ font-weight: 700;
61
+ font-size: 1.2rem;
62
+ letter-spacing: -0.5px;
33
63
  }
34
64
 
35
- .header h1 {
36
- color: #667eea;
37
- font-size: 2.5em;
38
- margin-bottom: 10px;
65
+ .status-badge {
66
+ font-size: 0.75rem;
67
+ font-weight: 600;
68
+ padding: 4px 10px;
69
+ border-radius: 20px;
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;
39
74
  }
40
75
 
41
- .header p {
42
- color: #666;
43
- font-size: 1.1em;
76
+ .container {
77
+ max-width: 800px;
78
+ margin: 0 auto;
79
+ padding: 20px;
80
+ padding-left: max(20px, env(safe-area-inset-left));
81
+ padding-right: max(20px, env(safe-area-inset-right));
44
82
  }
45
83
 
46
- .status {
47
- display: inline-block;
48
- padding: 8px 16px;
49
- background: #10b981;
50
- color: white;
84
+ /* iOS Cards */
85
+ .card {
86
+ background: var(--ios-card);
51
87
  border-radius: 20px;
52
- font-size: 0.9em;
53
- margin-top: 10px;
54
- }
55
-
56
- .system-prompt-card {
57
- background: white;
58
- border-radius: 12px;
59
- padding: 25px;
60
- margin-bottom: 30px;
61
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
88
+ padding: 24px;
89
+ margin-bottom: 24px;
90
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
91
+ position: relative;
92
+ overflow: hidden;
62
93
  }
63
94
 
64
- .system-prompt-header {
95
+ .card-header {
65
96
  display: flex;
66
- justify-content: space-between;
67
97
  align-items: center;
68
- margin-bottom: 15px;
69
- cursor: pointer;
70
- user-select: none;
98
+ margin-bottom: 16px;
99
+ gap: 12px;
71
100
  }
72
101
 
73
- .system-prompt-header h2 {
74
- color: #667eea;
75
- font-size: 1.5em;
102
+ .card-icon {
103
+ width: 40px;
104
+ height: 40px;
105
+ border-radius: 12px;
106
+ background: var(--ios-bg);
76
107
  display: flex;
77
108
  align-items: center;
78
- gap: 10px;
109
+ justify-content: center;
110
+ font-size: 1.2rem;
111
+ color: var(--ios-blue);
79
112
  }
80
113
 
81
- .toggle-icon {
82
- transition: transform 0.3s ease;
114
+ .card-title {
115
+ font-size: 1.1rem;
116
+ font-weight: 700;
117
+ letter-spacing: -0.3px;
83
118
  }
84
119
 
85
- .toggle-icon.open {
86
- transform: rotate(180deg);
120
+ .card-desc {
121
+ color: var(--ios-text-secondary);
122
+ font-size: 0.9rem;
123
+ margin-bottom: 20px;
87
124
  }
88
125
 
89
- .system-prompt-content {
90
- max-height: 0;
91
- overflow: hidden;
92
- transition: max-height 0.3s ease;
126
+ /* Forms */
127
+ .form-group {
128
+ margin-bottom: 20px;
93
129
  }
94
130
 
95
- .system-prompt-content.open {
96
- max-height: 2000px;
131
+ .form-label {
132
+ display: block;
133
+ font-size: 0.85rem;
134
+ font-weight: 600;
135
+ color: var(--ios-text-secondary);
136
+ margin-bottom: 8px;
137
+ text-transform: uppercase;
138
+ letter-spacing: 0.5px;
97
139
  }
98
140
 
99
- .prompt-text {
100
- background: #f9fafb;
101
- border: 2px solid #e5e7eb;
102
- border-radius: 8px;
103
- padding: 20px;
104
- font-family: 'Courier New', monospace;
105
- font-size: 0.9em;
106
- line-height: 1.6;
107
- white-space: pre-wrap;
108
- max-height: 400px;
109
- overflow-y: auto;
110
- margin-bottom: 15px;
141
+ input[type="text"],
142
+ textarea {
143
+ width: 100%;
144
+ background: var(--ios-input-bg);
145
+ border: none;
146
+ border-radius: 12px;
147
+ padding: 14px 16px;
148
+ font-size: 1rem;
149
+ font-family: inherit;
150
+ color: var(--ios-text-primary);
151
+ transition: all 0.2s;
152
+ -webkit-appearance: none;
111
153
  }
112
154
 
113
- .endpoints {
114
- display: grid;
115
- gap: 20px;
155
+ textarea {
156
+ min-height: 120px;
157
+ resize: vertical;
158
+ line-height: 1.4;
116
159
  }
117
160
 
118
- .endpoint-card {
119
- background: white;
120
- border-radius: 12px;
121
- padding: 30px;
122
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
161
+ input:focus,
162
+ textarea:focus {
163
+ outline: none;
164
+ background: #D1D1D6;
123
165
  }
124
166
 
125
- .endpoint-header {
126
- display: flex;
127
- align-items: center;
128
- gap: 15px;
129
- margin-bottom: 20px;
167
+ /* Buttons */
168
+ .btn-group {
169
+ display: grid;
170
+ grid-template-columns: 1fr;
171
+ gap: 12px;
130
172
  }
131
173
 
132
- .method {
133
- padding: 8px 16px;
134
- border-radius: 6px;
135
- font-weight: bold;
136
- font-size: 0.9em;
174
+ @media (min-width: 600px) {
175
+ .btn-group {
176
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
177
+ }
137
178
  }
138
179
 
139
- .method.post {
140
- background: #10b981;
180
+ .btn {
181
+ border: none;
182
+ background: var(--ios-blue);
141
183
  color: white;
142
- }
143
-
144
- .endpoint-path {
145
- font-family: 'Courier New', monospace;
146
- color: #333;
147
- font-size: 1.2em;
184
+ padding: 14px;
185
+ border-radius: 14px;
186
+ font-size: 1rem;
148
187
  font-weight: 600;
188
+ cursor: pointer;
189
+ transition: transform 0.1s, opacity 0.2s;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ gap: 8px;
194
+ -webkit-appearance: none;
149
195
  }
150
196
 
151
- .endpoint-desc {
152
- color: #666;
153
- margin-bottom: 20px;
154
- line-height: 1.6;
155
- font-size: 1.05em;
156
- }
157
-
158
- .form-group {
159
- margin-bottom: 20px;
197
+ .btn:active {
198
+ transform: scale(0.98);
199
+ opacity: 0.8;
160
200
  }
161
201
 
162
- .form-group label {
163
- display: block;
164
- margin-bottom: 8px;
165
- color: #333;
166
- font-weight: 600;
167
- font-size: 1em;
202
+ .btn-secondary {
203
+ background: rgba(0, 122, 255, 0.15);
204
+ color: var(--ios-blue);
168
205
  }
169
206
 
170
- .form-group input,
171
- .form-group textarea {
172
- width: 100%;
173
- padding: 12px;
174
- border: 2px solid #e5e7eb;
175
- border-radius: 8px;
176
- font-size: 1em;
177
- font-family: 'Courier New', monospace;
178
- transition: border-color 0.3s ease;
207
+ .btn-copy {
208
+ background: #E5E5EA;
209
+ color: black;
179
210
  }
180
211
 
181
- .form-group input:focus,
182
- .form-group textarea:focus {
183
- outline: none;
184
- border-color: #667eea;
212
+ .btn-copy.copied {
213
+ background: var(--ios-green);
214
+ color: white;
185
215
  }
186
216
 
187
- .form-group textarea {
188
- min-height: 150px;
189
- resize: vertical;
217
+ /* System Prompt Accordion */
218
+ .prompt-accordion {
219
+ cursor: pointer;
220
+ user-select: none;
190
221
  }
191
222
 
192
- .btn-group {
223
+ .prompt-header {
193
224
  display: flex;
194
- gap: 10px;
195
- margin-top: 20px;
196
- }
197
-
198
- .btn {
199
- background: #667eea;
200
- color: white;
201
- border: none;
202
- padding: 14px 28px;
203
- border-radius: 8px;
204
- cursor: pointer;
205
- font-size: 1em;
206
- font-weight: 600;
207
- transition: all 0.3s ease;
208
- display: inline-flex;
225
+ justify-content: space-between;
209
226
  align-items: center;
210
- gap: 8px;
211
- }
212
-
213
- .btn:hover {
214
- background: #5568d3;
215
- transform: translateY(-2px);
216
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
227
+ padding: 4px 0;
217
228
  }
218
229
 
219
- .btn:disabled {
220
- background: #9ca3af;
221
- cursor: not-allowed;
222
- transform: none;
230
+ .chevron {
231
+ color: var(--ios-text-secondary);
232
+ transition: transform 0.3s ease;
233
+ font-size: 0.8rem;
223
234
  }
224
235
 
225
- .btn-copy {
226
- background: #10b981;
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;
227
241
  }
228
242
 
229
- .btn-copy:hover {
230
- background: #059669;
231
- box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
243
+ .prompt-content.open {
244
+ max-height: 500px;
245
+ opacity: 1;
246
+ margin-top: 15px;
232
247
  }
233
248
 
234
- .btn-copy.copied {
235
- background: #6366f1;
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);
236
260
  }
237
261
 
262
+ /* Response Area */
238
263
  .response {
239
264
  margin-top: 20px;
240
- padding: 20px;
241
- border-radius: 8px;
242
- background: #f9fafb;
243
- border-left: 4px solid #667eea;
265
+ background: #F9F9F9;
266
+ border-radius: 12px;
267
+ padding: 15px;
268
+ border-left: 5px solid var(--ios-text-secondary);
244
269
  display: none;
270
+ animation: slideDown 0.3s ease;
245
271
  }
246
272
 
247
273
  .response.show {
@@ -249,333 +275,271 @@
249
275
  }
250
276
 
251
277
  .response.success {
252
- border-left-color: #10b981;
253
- background: #f0fdf4;
278
+ border-left-color: var(--ios-green);
279
+ background: #F0FFF4;
254
280
  }
255
281
 
256
282
  .response.error {
257
- border-left-color: #ef4444;
258
- background: #fef2f2;
283
+ border-left-color: var(--ios-red);
284
+ background: #FFF5F5;
259
285
  }
260
286
 
261
287
  .response pre {
262
- margin: 0;
263
- font-family: 'Courier New', monospace;
264
- font-size: 0.95em;
288
+ font-family: 'SF Mono', 'Menlo', monospace;
289
+ font-size: 0.85rem;
265
290
  white-space: pre-wrap;
266
- word-wrap: break-word;
267
- }
268
-
269
- .loading {
270
- display: inline-block;
271
- width: 16px;
272
- height: 16px;
273
- border: 3px solid rgba(255, 255, 255, .3);
274
- border-radius: 50%;
275
- border-top-color: white;
276
- animation: spin 1s ease-in-out infinite;
277
- }
278
-
279
- @keyframes spin {
280
- to {
281
- transform: rotate(360deg);
282
- }
291
+ word-break: break-all;
292
+ color: #333;
283
293
  }
284
294
 
295
+ /* Toast */
285
296
  .toast {
286
297
  position: fixed;
287
298
  top: 20px;
288
- right: 20px;
289
- padding: 16px 24px;
290
- border-radius: 8px;
299
+ left: 50%;
300
+ transform: translateX(-50%) translateY(-100px);
301
+ background: rgba(0, 0, 0, 0.8);
302
+ backdrop-filter: blur(10px);
291
303
  color: white;
304
+ padding: 12px 24px;
305
+ border-radius: 50px;
292
306
  font-weight: 600;
293
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
294
- opacity: 0;
295
- transform: translateY(-20px);
296
- transition: all 0.3s ease;
307
+ font-size: 0.95rem;
297
308
  z-index: 1000;
309
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
310
+ transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 8px;
314
+ white-space: nowrap;
298
315
  }
299
316
 
300
317
  .toast.show {
301
- opacity: 1;
302
- transform: translateY(0);
318
+ transform: translateX(-50%) translateY(0);
303
319
  }
304
320
 
305
- .toast.success {
306
- background: #10b981;
321
+ .loading-spinner {
322
+ width: 18px;
323
+ height: 18px;
324
+ border: 2px solid rgba(255, 255, 255, 0.3);
325
+ border-radius: 50%;
326
+ border-top-color: white;
327
+ animation: spin 0.8s linear infinite;
307
328
  }
308
329
 
309
- .toast.error {
310
- background: #ef4444;
330
+ @keyframes spin {
331
+ to {
332
+ transform: rotate(360deg);
333
+ }
311
334
  }
312
335
 
313
- .toast.info {
314
- background: #3b82f6;
336
+ @keyframes slideDown {
337
+ from {
338
+ opacity: 0;
339
+ transform: translateY(-10px);
340
+ }
341
+
342
+ to {
343
+ opacity: 1;
344
+ transform: translateY(0);
345
+ }
315
346
  }
316
347
  </style>
317
348
  </head>
318
349
 
319
350
  <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
+
320
357
  <div class="container">
321
- <div class="header">
322
- <h1>🚀 VG Coder API Dashboard</h1>
323
- <p>Phân tích dự án và thực thi bash scripts</p>
324
- <span class="status" id="status">● Server Running</span>
325
- </div>
326
358
 
327
- <!-- System Prompt Section -->
328
- <div class="system-prompt-card">
329
- <div class="system-prompt-header" onclick="toggleSystemPrompt()">
330
- <h2>
331
- <span>📝</span>
332
- <span>AI System Prompt</span>
333
- </h2>
334
- <span class="toggle-icon" id="toggle-icon">▼</span>
359
+ <!-- System Prompt Card -->
360
+ <div class="card prompt-accordion">
361
+ <div class="card-header prompt-header" onclick="toggleSystemPrompt()">
362
+ <div style="display: flex; align-items: center; gap: 12px;">
363
+ <div class="card-icon" style="background: rgba(255, 59, 48, 0.1); color: var(--ios-red);">⚡</div>
364
+ <div class="card-title">System Prompt</div>
365
+ </div>
366
+ <div class="chevron" id="toggle-icon">▼</div>
335
367
  </div>
336
- <div class="system-prompt-content" id="system-prompt-content">
337
- <div class="prompt-text" id="prompt-text"></div>
338
- <button class="btn btn-copy" onclick="copySystemPrompt()">
368
+ <div class="prompt-content" id="system-prompt-content">
369
+ <div class="code-block" id="prompt-text"></div>
370
+ <button class="btn btn-secondary" onclick="copySystemPrompt()" style="width: 100%">
339
371
  <span id="copy-icon">📋</span>
340
- <span id="copy-text">Copy System Prompt</span>
372
+ <span id="copy-text">Copy Prompt</span>
341
373
  </button>
342
374
  </div>
343
375
  </div>
344
376
 
345
- <div class="endpoints">
346
- <!-- Analyze -->
347
- <div class="endpoint-card">
348
- <div class="endpoint-header">
349
- <span class="method post">POST</span>
350
- <span class="endpoint-path">/api/analyze</span>
351
- </div>
352
- <p class="endpoint-desc">📊 Phân tích dự án và lấy toàn bộ source code</p>
353
- <div class="form-group">
354
- <label>📁 Project Path:</label>
355
- <input type="text" id="analyze-path" value="." placeholder=".">
377
+ <!-- Analyze Card -->
378
+ <div class="card">
379
+ <div class="card-header">
380
+ <div class="card-icon">📊</div>
381
+ <div>
382
+ <div class="card-title">Project Analyze</div>
356
383
  </div>
357
- <div class="btn-group">
358
- <button class="btn" onclick="testAnalyze()">
359
- <span>📥</span>
360
- <span>Download File</span>
361
- </button>
362
- <button class="btn btn-copy" onclick="copyAnalyzeResult()">
363
- <span id="analyze-copy-icon">📋</span>
364
- <span id="analyze-copy-text">Copy to Clipboard</span>
365
- </button>
366
- <button class="btn btn-copy" onclick="copyAnalyzeAsFile()">
367
- <span id="analyze-file-icon">📄</span>
368
- <span id="analyze-file-text">Copy as File</span>
369
- </button>
370
- </div>
371
- <div class="response" id="analyze-response"></div>
372
384
  </div>
385
+ <div class="card-desc">Phân tích dự án và lấy source code</div>
373
386
 
374
- <!-- Execute Bash -->
375
- <div class="endpoint-card">
376
- <div class="endpoint-header">
377
- <span class="method post">POST</span>
378
- <span class="endpoint-path">/api/execute</span>
379
- </div>
380
- <p class="endpoint-desc">⚡ Thực thi bash script với syntax validation</p>
381
- <div class="form-group">
382
- <label>💻 Bash Script:</label>
383
- <textarea id="execute-bash"
384
- placeholder="mkdir -p $(dirname &quot;src/test.js&quot;)&#10;cat <<'EOF' > src/test.js&#10;console.log('Hello World');&#10;EOF"></textarea>
385
- </div>
386
- <div class="btn-group">
387
- <button class="btn" onclick="testExecute()">
388
- <span>▶️</span>
389
- <span>Execute Script</span>
390
- </button>
391
- <button class="btn" onclick="executeFromClipboard()">
392
- <span>📋</span>
393
- <span>Execute from Clipboard</span>
394
- </button>
387
+ <div class="form-group">
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
+ <button class="btn btn-copy" onclick="copyAnalyzeAsFile()">
400
+ <span id="analyze-file-icon">📄</span> File
401
+ </button>
402
+ </div>
403
+ <div class="response" id="analyze-response"></div>
404
+ </div>
405
+
406
+ <!-- Execute Card -->
407
+ <div class="card">
408
+ <div class="card-header">
409
+ <div class="card-icon" style="background: rgba(52, 199, 89, 0.1); color: var(--ios-green);">🚀</div>
410
+ <div>
411
+ <div class="card-title">Execute Script</div>
395
412
  </div>
396
- <div class="response" id="execute-response"></div>
397
413
  </div>
414
+ <div class="card-desc">Thực thi bash script an toàn</div>
415
+
416
+ <div class="form-group">
417
+ <label class="form-label">Bash Script</label>
418
+ <textarea id="execute-bash" placeholder="mkdir -p src..."></textarea>
419
+ </div>
420
+
421
+ <div class="btn-group">
422
+ <button class="btn" style="background: var(--ios-green);" onclick="testExecute()">
423
+ <span>▶️</span> Execute
424
+ </button>
425
+ <button class="btn btn-secondary" onclick="executeFromClipboard()">
426
+ <span>📋</span> Paste & Run
427
+ </button>
428
+ </div>
429
+ <div class="response" id="execute-response"></div>
398
430
  </div>
399
431
  </div>
400
432
 
433
+ <!-- iOS Toast -->
401
434
  <div class="toast" id="toast"></div>
402
435
 
403
436
  <script>
404
437
  const API_BASE = window.location.origin;
405
438
  let lastAnalyzeResult = null;
406
439
 
407
- // System Prompt
408
440
  const SYSTEM_PROMPT = `# VG Coder AI System Prompt
409
441
 
410
442
  ## Command Prefixes
411
-
412
- ### /ask - Question & Answer Mode
413
- 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 đề.
414
-
415
- **Response Format:** Markdown
416
- - Trả lời chi tiết, rõ ràng
417
- - Sử dụng code blocks, lists, tables khi cần
418
- - Cung cấp ví dụ minh họa
419
-
420
- ---
421
-
443
+ ### /ask - Q&A Mode
422
444
  ### /plan - Planning Mode
423
- 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.
424
-
425
- **Response Format:** Markdown checklist với bash commands
426
- - Chia nhỏ thành các bước cụ thể
427
- - Mỗi bước có bash command tương ứng
428
- - Sắp xếp theo thứ tự logic
429
-
430
- ---
431
-
432
445
  ### /fix - Bug Fix Mode
433
- 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.
434
-
435
- **Response Format:** Markdown + Bash script
436
- 1. **Phân tích lỗi:** Giải thích nguyên nhân
437
- 2. **Giải pháp:** Mô tả cách fix
438
- 3. **Bash script:** Code để fix (nếu cần)
439
-
440
- ---
441
-
442
446
  ### /code - Code Generation Mode
443
- 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
447
 
445
- ## ⚠️ QUY TẮC BẮT BUỘC
448
+ ## ⚠️ QUY TẮC BẮT BUỘC /code
446
449
 
447
- ### 1. Chỉ bao gồm files có thay đổi
448
- - ❌ **KHÔNG** bao gồm files không có sự thay đổi nội dung
449
- - ✅ Nếu nội dung file sau chỉnh sửa giống 100% bản cũ → **BỎ QUA**
450
-
451
- ### 2. Format Script Chuẩn
452
-
453
- **Mỗi file PHẢI theo cú pháp:**
450
+ 1. **Format Script Chuẩn:**
454
451
  \`\`\`bash
455
452
  mkdir -p $(dirname "path/to/file.ext")
456
453
  cat <<'EOF' > path/to/file.ext
457
- ... toàn bộ nội dung file sau khi chỉnh sửa ...
454
+ ... content ...
458
455
  EOF
459
456
  \`\`\`
460
457
 
461
- ### 3. Chi tiết quan trọng
462
- - **LUÔN** \`mkdir -p $(dirname "...")\` trước mỗi file
463
- - Sử dụng \`<<'EOF'\` (có dấu nháy đơn) để tránh bash expansion
464
- - Ghi đè hoàn toàn file bằng nội dung mới
465
- - ✅ Tự động tạo file và thư mục cha nếu chưa tồn tại
466
- - ✅ Đường dẫn giống với file mẫu đính kèm
467
-
468
- ### 4. Example Output
469
-
470
- \`\`\`bash
471
- # Create/Update component file
472
- mkdir -p $(dirname "src/components/Button/index.tsx")
473
- cat <<'EOF' > src/components/Button/index.tsx
474
- import React from 'react';
475
-
476
- export const Button = () => {
477
- return <button>Click me</button>;
478
- };
479
- EOF
480
-
481
- # Create/Update styles
482
- mkdir -p $(dirname "src/components/Button/styles.css")
483
- cat <<'EOF' > src/components/Button/styles.css
484
- .button {
485
- padding: 10px 20px;
486
- background: blue;
487
- }
488
- EOF
489
- \`\`\`
490
-
491
- ---
492
-
493
- ## Integration với VG Coder CLI
494
-
495
- Bash scripts được generate sẽ được thực thi qua:
496
- \`\`\`bash
497
- POST http://localhost:6868/api/execute
498
- {
499
- "bash": "mkdir -p $(dirname \\"src/...\\")\\\\ncat <<'EOF' > ..."
500
- }
501
- \`\`\`
502
-
503
- API sẽ:
504
- 1. ✅ Validate bash syntax trong \`.vg/temp-execute\`
505
- 2. ✅ Execute tại working directory nếu syntax OK
506
- 3. ✅ Trả về stdout/stderr/exitCode
507
- 4. ✅ Auto cleanup temp directory
508
-
509
- ---
510
-
511
- ## Best Practices
458
+ 2. **Quy tắc:**
459
+ - Luôn có mkdir -p
460
+ - Dùng <<'EOF' (có quotes)
461
+ - Chỉ include files thay đổi
462
+ `;
512
463
 
513
- ### DO ✅
514
- - Luôn dùng \`mkdir -p $(dirname "...")\` trước mỗi file
515
- - Sử dụng \`<<'EOF'\` để tránh variable expansion
516
- - Ghi đè toàn bộ nội dung file
517
- - Chỉ include files có thay đổi thực sự
518
-
519
- ### DON'T ❌
520
- - Không tạo file mà không tạo thư mục cha
521
- - Không dùng \`<<EOF\` (thiếu quotes) nếu có \`$\` trong content
522
- - Không include files không thay đổi
523
- - Không dùng relative paths phức tạp`;
524
-
525
- // Load system prompt on page load
526
464
  document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
527
465
 
528
466
  function toggleSystemPrompt() {
529
467
  const content = document.getElementById('system-prompt-content');
530
468
  const icon = document.getElementById('toggle-icon');
531
469
  content.classList.toggle('open');
532
- icon.classList.toggle('open');
533
- }
534
-
535
- function copySystemPrompt() {
536
- const copyBtn = event.target.closest('.btn-copy');
537
- const copyIcon = document.getElementById('copy-icon');
538
- const copyText = document.getElementById('copy-text');
539
-
540
- navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
541
- copyBtn.classList.add('copied');
542
- copyIcon.textContent = '✓';
543
- copyText.textContent = 'Copied!';
544
- showToast('✅ Đã copy System Prompt!', 'success');
545
-
546
- setTimeout(() => {
547
- copyBtn.classList.remove('copied');
548
- copyIcon.textContent = '📋';
549
- copyText.textContent = 'Copy System Prompt';
550
- }, 2000);
551
- }).catch(err => {
552
- showToast('❌ Lỗi copy: ' + err.message, 'error');
553
- });
470
+ icon.style.transform = content.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
554
471
  }
555
472
 
556
473
  function showResponse(elementId, data, isError = false) {
557
474
  const el = document.getElementById(elementId);
558
475
  el.className = 'response show ' + (isError ? 'error' : 'success');
476
+
477
+ // Format nice JSON
478
+ if (typeof data === 'object') {
479
+ if (data.stdout) data.stdout = data.stdout.trim();
480
+ if (data.stderr) data.stderr = data.stderr.trim();
481
+ }
482
+
559
483
  el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
560
484
  }
561
485
 
562
486
  function showLoading(button, originalText) {
563
487
  button.disabled = true;
564
- button.innerHTML = '<span class="loading"></span> Loading...';
488
+ button.innerHTML = '<div class="loading-spinner"></div>';
565
489
  button.dataset.originalText = originalText;
566
490
  }
567
491
 
568
492
  function resetButton(button) {
569
493
  button.disabled = false;
570
- const originalText = button.dataset.originalText;
571
- button.innerHTML = originalText;
494
+ button.innerHTML = button.dataset.originalText;
495
+ }
496
+
497
+ function showToast(message, type = 'success') {
498
+ const toast = document.getElementById('toast');
499
+ let icon = type === 'success' ? '✅' : (type === 'error' ? '❌' : 'ℹ️');
500
+ toast.innerHTML = `${icon} ${message}`;
501
+ toast.classList.add('show');
502
+ setTimeout(() => toast.classList.remove('show'), 3000);
503
+ }
504
+
505
+ async function copyToClipboard(text, btnElement, successIconId, successTextId) {
506
+ try {
507
+ await navigator.clipboard.writeText(text);
508
+ if (btnElement) {
509
+ btnElement.classList.add('copied');
510
+ const icon = document.getElementById(successIconId);
511
+ const label = document.getElementById(successTextId);
512
+
513
+ const originalIcon = icon.textContent;
514
+ const originalLabel = label.textContent;
515
+
516
+ icon.textContent = '✓';
517
+ label.textContent = 'Copied';
518
+
519
+ setTimeout(() => {
520
+ btnElement.classList.remove('copied');
521
+ icon.textContent = originalIcon;
522
+ label.textContent = originalLabel;
523
+ }, 2000);
524
+ }
525
+ showToast('Copied to clipboard!');
526
+ } catch (err) {
527
+ showToast('Failed to copy', 'error');
528
+ }
529
+ }
530
+
531
+ // --- Logic Handlers ---
532
+
533
+ function copySystemPrompt() {
534
+ copyToClipboard(SYSTEM_PROMPT, event.target.closest('.btn'), 'copy-icon', 'copy-text');
572
535
  }
573
536
 
574
537
  async function testAnalyze() {
575
538
  const btn = event.target.closest('.btn');
576
539
  const path = document.getElementById('analyze-path').value;
540
+ const originalHTML = btn.innerHTML;
577
541
 
578
- showLoading(btn, '<span>📥</span><span>Download File</span>');
542
+ showLoading(btn, originalHTML);
579
543
  try {
580
544
  const res = await fetch(`${API_BASE}/api/analyze`, {
581
545
  method: 'POST',
@@ -587,7 +551,7 @@ API sẽ:
587
551
  const text = await res.text();
588
552
  lastAnalyzeResult = text;
589
553
 
590
- // Download file
554
+ // Trigger download
591
555
  const blob = new Blob([text], { type: 'text/plain' });
592
556
  const url = window.URL.createObjectURL(blob);
593
557
  const a = document.createElement('a');
@@ -595,172 +559,44 @@ API sẽ:
595
559
  a.download = 'project.txt';
596
560
  a.click();
597
561
 
598
- showResponse('analyze-response', {
599
- success: true,
600
- message: 'File downloaded!',
601
- files: text.split('\n').filter(l => l.includes('===== FILE:')).length,
602
- size: (text.length / 1024).toFixed(2) + ' KB'
603
- });
604
- showToast('✅ Đã download file!', 'success');
562
+ showResponse('analyze-response', { success: true, files: text.split('===== FILE:').length - 1 });
563
+ showToast('Analysis completed');
605
564
  } else {
606
565
  const data = await res.json();
607
566
  showResponse('analyze-response', data, true);
608
- showToast(' Lỗi analyze!', 'error');
567
+ showToast('Analysis failed', 'error');
609
568
  }
610
569
  } catch (err) {
611
570
  showResponse('analyze-response', { error: err.message }, true);
612
- showToast(' Lỗi: ' + err.message, 'error');
571
+ showToast('Connection error', 'error');
613
572
  }
614
573
  resetButton(btn);
615
574
  }
616
575
 
617
576
  async function copyAnalyzeResult() {
618
- const copyBtn = event.target.closest('.btn-copy');
619
- const copyIcon = document.getElementById('analyze-copy-icon');
620
- const copyText = document.getElementById('analyze-copy-text');
621
-
577
+ const btn = event.target.closest('.btn');
622
578
  if (!lastAnalyzeResult) {
623
- // Fetch if not already analyzed
624
- const path = document.getElementById('analyze-path').value;
625
- showLoading(copyBtn, '<span id="analyze-copy-icon">📋</span><span id="analyze-copy-text">Copy to Clipboard</span>');
626
-
627
- try {
628
- const res = await fetch(`${API_BASE}/api/analyze`, {
629
- method: 'POST',
630
- headers: { 'Content-Type': 'application/json' },
631
- body: JSON.stringify({ path })
632
- });
633
-
634
- if (res.ok) {
635
- lastAnalyzeResult = await res.text();
636
- } else {
637
- showToast('❌ Lỗi analyze!', 'error');
638
- resetButton(copyBtn);
639
- return;
640
- }
641
- } catch (err) {
642
- showToast('❌ Lỗi: ' + err.message, 'error');
643
- resetButton(copyBtn);
644
- return;
645
- }
646
- resetButton(copyBtn);
647
- }
648
-
649
- // Copy to clipboard using ClipboardItem (like reference code)
650
- try {
651
- const blob = new Blob([lastAnalyzeResult], { type: 'text/plain' });
652
- const item = new ClipboardItem({ 'text/plain': blob });
653
- await navigator.clipboard.write([item]);
654
-
655
- copyBtn.classList.add('copied');
656
- copyIcon.textContent = '✓';
657
- copyText.textContent = 'Copied!';
658
- showToast('✅ Đã copy project.txt vào clipboard!', 'success');
659
-
660
- setTimeout(() => {
661
- copyBtn.classList.remove('copied');
662
- copyIcon.textContent = '📋';
663
- copyText.textContent = 'Copy to Clipboard';
664
- }, 2000);
665
- } catch (err) {
666
- // Fallback to writeText if ClipboardItem fails
667
- try {
668
- await navigator.clipboard.writeText(lastAnalyzeResult);
669
- copyBtn.classList.add('copied');
670
- copyIcon.textContent = '✓';
671
- copyText.textContent = 'Copied!';
672
- showToast('✅ Đã copy project.txt vào clipboard!', 'success');
673
-
674
- setTimeout(() => {
675
- copyBtn.classList.remove('copied');
676
- copyIcon.textContent = '📋';
677
- copyText.textContent = 'Copy to Clipboard';
678
- }, 2000);
679
- } catch (fallbackErr) {
680
- showToast('❌ Lỗi copy: ' + fallbackErr.message, 'error');
681
- }
579
+ showToast('Please analyze first', 'error');
580
+ return;
682
581
  }
683
- }
684
-
685
- async function copyAsFile(filename, content) {
686
- const blob = new Blob([content], {
687
- type: "application/octet-stream"
688
- });
689
-
690
- // ClipboardItem cần type key phải trùng blob.type
691
- const item = new ClipboardItem(
692
- { [blob.type]: blob },
693
- {
694
- // Không phải chuẩn, nhưng Chrome hỗ trợ unofficial metadata
695
- type: "application/octet-stream",
696
- presentationStyle: "attachment",
697
- name: filename
698
- }
699
- );
700
-
701
- await navigator.clipboard.write([item]);
582
+ copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
702
583
  }
703
584
 
704
585
  async function copyAnalyzeAsFile() {
705
- const copyBtn = event.target.closest('.btn-copy');
706
- const copyIcon = document.getElementById('analyze-file-icon');
707
- const copyText = document.getElementById('analyze-file-text');
708
-
586
+ const btn = event.target.closest('.btn');
709
587
  if (!lastAnalyzeResult) {
710
- // Fetch if not already analyzed
711
- const path = document.getElementById('analyze-path').value;
712
- showLoading(copyBtn, '<span id="analyze-file-icon">📄</span><span id="analyze-file-text">Copy as File</span>');
713
-
714
- try {
715
- const res = await fetch(`${API_BASE}/api/analyze`, {
716
- method: 'POST',
717
- headers: { 'Content-Type': 'application/json' },
718
- body: JSON.stringify({ path })
719
- });
720
-
721
- if (res.ok) {
722
- lastAnalyzeResult = await res.text();
723
- } else {
724
- showToast('❌ Lỗi analyze!', 'error');
725
- resetButton(copyBtn);
726
- return;
727
- }
728
- } catch (err) {
729
- showToast('❌ Lỗi: ' + err.message, 'error');
730
- resetButton(copyBtn);
731
- return;
732
- }
733
- resetButton(copyBtn);
734
- }
735
-
736
- try {
737
- await copyAsFile("project.txt", lastAnalyzeResult);
738
-
739
- copyBtn.classList.add('copied');
740
- copyIcon.textContent = '✓';
741
- copyText.textContent = 'Copied!';
742
- showToast('✅ Đã copy project.txt như file!', 'success');
743
-
744
- setTimeout(() => {
745
- copyBtn.classList.remove('copied');
746
- copyIcon.textContent = '📄';
747
- copyText.textContent = 'Copy as File';
748
- }, 2000);
749
- } catch (err) {
750
- showToast('❌ Lỗi copy: ' + err.message, 'error');
588
+ showToast('Please analyze first', 'error');
589
+ return;
751
590
  }
591
+ copyToClipboard(lastAnalyzeResult, btn, 'analyze-file-icon', 'analyze-file-text');
752
592
  }
753
593
 
754
594
  async function testExecute() {
755
595
  const btn = event.target.closest('.btn');
756
596
  const bash = document.getElementById('execute-bash').value;
597
+ if (!bash.trim()) return showToast('Script is empty', 'error');
757
598
 
758
- if (!bash.trim()) {
759
- showToast('⚠️ Vui lòng nhập bash script!', 'error');
760
- return;
761
- }
762
-
763
- showLoading(btn, '<span>▶️</span><span>Execute Script</span>');
599
+ showLoading(btn, btn.innerHTML);
764
600
  try {
765
601
  const res = await fetch(`${API_BASE}/api/execute`, {
766
602
  method: 'POST',
@@ -768,100 +604,42 @@ API sẽ:
768
604
  body: JSON.stringify({ bash })
769
605
  });
770
606
  const data = await res.json();
771
- showResponse('execute-response', data, !res.ok || !data.success);
772
-
773
- if (data.success) {
774
- showToast('✅ Thực thi thành công!', 'success');
775
- } else {
776
- showToast('❌ Thực thi thất bại!', 'error');
777
- }
607
+ showResponse('execute-response', data, !data.success);
608
+ showToast(data.success ? 'Executed successfully' : 'Execution failed', data.success ? 'success' : 'error');
778
609
  } catch (err) {
779
610
  showResponse('execute-response', { error: err.message }, true);
780
- showToast('❌ Lỗi: ' + err.message, 'error');
781
611
  }
782
612
  resetButton(btn);
783
613
  }
784
614
 
785
615
  async function executeFromClipboard() {
786
616
  const btn = event.target.closest('.btn');
787
-
788
- showLoading(btn, '<span>📋</span><span>Execute from Clipboard</span>');
789
-
790
617
  try {
791
- // Read from clipboard
792
- const clipboardText = await navigator.clipboard.readText();
793
-
794
- // Check if clipboard is empty
795
- if (!clipboardText || !clipboardText.trim()) {
796
- showToast('⚠️ Clipboard trống! Vui lòng copy bash script trước.', 'error');
797
- showResponse('execute-response', {
798
- error: 'Clipboard is empty',
799
- message: 'Please copy a bash script to clipboard first'
800
- }, true);
801
- resetButton(btn);
802
- return;
803
- }
618
+ const text = await navigator.clipboard.readText();
619
+ if (!text.trim()) return showToast('Clipboard is empty', 'error');
804
620
 
805
- // Display clipboard content in textarea for reference
806
- document.getElementById('execute-bash').value = clipboardText;
807
-
808
- // Execute the script (API will validate syntax first)
809
- const res = await fetch(`${API_BASE}/api/execute`, {
810
- method: 'POST',
811
- headers: { 'Content-Type': 'application/json' },
812
- body: JSON.stringify({ bash: clipboardText })
813
- });
814
- const data = await res.json();
815
- showResponse('execute-response', data, !res.ok || !data.success);
816
-
817
- if (data.success) {
818
- showToast('✅ Thực thi từ clipboard thành công!', 'success');
819
- } else {
820
- // Check if it's a syntax error
821
- if (data.syntaxError) {
822
- showToast('❌ Lỗi syntax! Kiểm tra bash script.', 'error');
823
- } else {
824
- showToast('❌ Thực thi thất bại!', 'error');
825
- }
826
- }
621
+ document.getElementById('execute-bash').value = text;
622
+ showToast('Pasted from clipboard');
827
623
  } catch (err) {
828
- // Handle clipboard permission errors
829
- if (err.name === 'NotAllowedError') {
830
- showToast('❌ Không có quyền truy cập clipboard!', 'error');
831
- showResponse('execute-response', {
832
- error: 'Clipboard permission denied',
833
- message: 'Please allow clipboard access in your browser settings'
834
- }, true);
835
- } else {
836
- showResponse('execute-response', { error: err.message }, true);
837
- showToast('❌ Lỗi: ' + err.message, 'error');
838
- }
624
+ showToast('Clipboard permission denied', 'error');
839
625
  }
840
- resetButton(btn);
841
626
  }
842
627
 
843
-
844
- function showToast(message, type = 'success') {
845
- const toast = document.getElementById('toast');
846
- toast.textContent = message;
847
- toast.className = `toast ${type}`;
848
- toast.classList.add('show');
849
- setTimeout(() => toast.classList.remove('show'), 3000);
850
- }
851
-
852
- // Check server status ONCE on page load
628
+ // Init Check
853
629
  (async () => {
630
+ const statusBadge = document.getElementById('status');
854
631
  try {
855
632
  const res = await fetch(`${API_BASE}/health`);
856
633
  if (res.ok) {
857
- document.getElementById('status').textContent = '● Server Running';
858
- document.getElementById('status').style.background = '#10b981';
634
+ statusBadge.textContent = '● Online';
635
+ statusBadge.style.backgroundColor = 'var(--ios-green)';
859
636
  }
860
637
  } catch {
861
- document.getElementById('status').textContent = '● Server Offline';
862
- document.getElementById('status').style.background = '#ef4444';
638
+ statusBadge.textContent = '● Offline';
639
+ statusBadge.style.backgroundColor = 'var(--ios-text-secondary)';
863
640
  }
864
641
  })();
642
+
865
643
  </script>
866
644
  </body>
867
645