vg-coder-cli 2.0.3 → 2.0.5

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 (84) 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 -60
  5. package/src/server/views/dashboard.html +432 -619
  6. package/vg-coder-cli-1.0.17.tgz +0 -0
  7. package/vg-coder-cli-2.0.4.tgz +0 -0
  8. package/vg-coder-cli-2.0.5.tgz +0 -0
  9. package/CHANGELOG.md +0 -59
  10. package/PUBLISHING.md +0 -86
  11. package/SYSTEM_PROMPT.md +0 -157
  12. package/init-nx-monorepo.sh +0 -107
  13. package/vg/.vscode/extensions.json +0 -8
  14. package/vg/.vscode/launch.json +0 -23
  15. package/vg/README-WORKSPACE.md +0 -82
  16. package/vg/README.md +0 -85
  17. package/vg/apps/api/project.json +0 -83
  18. package/vg/apps/api/src/app/analyze.controller.ts +0 -17
  19. package/vg/apps/api/src/app/analyze.service.ts +0 -57
  20. package/vg/apps/api/src/app/app.controller.ts +0 -12
  21. package/vg/apps/api/src/app/app.module.ts +0 -29
  22. package/vg/apps/api/src/app/app.service.ts +0 -8
  23. package/vg/apps/api/src/app/clean.controller.ts +0 -40
  24. package/vg/apps/api/src/app/execute.controller.ts +0 -19
  25. package/vg/apps/api/src/app/execute.service.ts +0 -46
  26. package/vg/apps/api/src/app/info.controller.ts +0 -12
  27. package/vg/apps/api/src/app/info.service.ts +0 -65
  28. package/vg/apps/api/src/main.ts +0 -28
  29. package/vg/apps/api/webpack.config.js +0 -25
  30. package/vg/apps/api-e2e/jest.config.cts +0 -18
  31. package/vg/apps/api-e2e/project.json +0 -17
  32. package/vg/apps/api-e2e/src/support/global-setup.ts +0 -16
  33. package/vg/apps/api-e2e/src/support/global-teardown.ts +0 -10
  34. package/vg/apps/api-e2e/src/support/test-setup.ts +0 -9
  35. package/vg/apps/ng-app/jest.config.ts +0 -21
  36. package/vg/apps/ng-app/project.json +0 -110
  37. package/vg/apps/ng-app/proxy.conf.json +0 -8
  38. package/vg/apps/ng-app/public/favicon.ico +0 -0
  39. package/vg/apps/ng-app/src/app/app.config.ts +0 -17
  40. package/vg/apps/ng-app/src/app/app.html +0 -1
  41. package/vg/apps/ng-app/src/app/app.routes.ts +0 -7
  42. package/vg/apps/ng-app/src/app/app.scss +0 -0
  43. package/vg/apps/ng-app/src/app/app.ts +0 -12
  44. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.html +0 -87
  45. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.scss +0 -290
  46. package/vg/apps/ng-app/src/app/dashboard/dashboard.component.ts +0 -236
  47. package/vg/apps/ng-app/src/app/nx-welcome.ts +0 -872
  48. package/vg/apps/ng-app/src/app/services/api.service.ts +0 -28
  49. package/vg/apps/ng-app/src/index.html +0 -13
  50. package/vg/apps/ng-app/src/main.ts +0 -5
  51. package/vg/apps/ng-app/src/styles.scss +0 -1
  52. package/vg/apps/ng-app/src/test-setup.ts +0 -6
  53. package/vg/bin/vg-workspace.js +0 -43
  54. package/vg/jest.config.ts +0 -6
  55. package/vg/nx.json +0 -85
  56. package/vg/package-lock.json +0 -30707
  57. package/vg/package-workspace.json +0 -38
  58. package/vg/package.json +0 -38
  59. package/vg/packages/client/data-access/README.md +0 -7
  60. package/vg/packages/client/data-access/jest.config.ts +0 -21
  61. package/vg/packages/client/data-access/project.json +0 -21
  62. package/vg/packages/client/data-access/src/index.ts +0 -1
  63. package/vg/packages/client/data-access/src/lib/data-access/data-access.html +0 -1
  64. package/vg/packages/client/data-access/src/lib/data-access/data-access.scss +0 -0
  65. package/vg/packages/client/data-access/src/lib/data-access/data-access.ts +0 -9
  66. package/vg/packages/client/data-access/src/test-setup.ts +0 -6
  67. package/vg/packages/core/README.md +0 -11
  68. package/vg/packages/core/jest.config.ts +0 -10
  69. package/vg/packages/core/package.json +0 -11
  70. package/vg/packages/core/project.json +0 -26
  71. package/vg/packages/core/src/index.ts +0 -6
  72. package/vg/packages/core/src/lib/core.ts +0 -3
  73. package/vg/packages/core/src/lib/detectors/project-detector.ts +0 -343
  74. package/vg/packages/core/src/lib/ignore/ignore-manager.ts +0 -315
  75. package/vg/packages/core/src/lib/scanner/file-scanner.ts +0 -675
  76. package/vg/packages/core/src/lib/tokenizer/token-manager.ts +0 -435
  77. package/vg/packages/core/src/lib/utils/bash-executor.ts +0 -146
  78. package/vg/packages/shared/data-types/README.md +0 -11
  79. package/vg/packages/shared/data-types/jest.config.ts +0 -10
  80. package/vg/packages/shared/data-types/package.json +0 -11
  81. package/vg/packages/shared/data-types/project.json +0 -26
  82. package/vg/packages/shared/data-types/src/index.ts +0 -1
  83. package/vg/packages/shared/data-types/src/lib/data-types.ts +0 -3
  84. 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;
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));
20
57
  }
21
58
 
22
- .container {
23
- max-width: 1000px;
24
- margin: 0 auto;
59
+ .app-title {
60
+ font-weight: 700;
61
+ font-size: 1.2rem;
62
+ letter-spacing: -0.5px;
25
63
  }
26
64
 
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);
33
- }
34
-
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);
87
- }
88
-
89
- .system-prompt-content {
90
- max-height: 0;
91
- overflow: hidden;
92
- transition: max-height 0.3s ease;
93
- }
94
-
95
- .system-prompt-content.open {
96
- max-height: 2000px;
120
+ .card-desc {
121
+ color: var(--ios-text-secondary);
122
+ font-size: 0.9rem;
123
+ margin-bottom: 20px;
97
124
  }
98
125
 
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;
126
+ /* Forms */
127
+ .form-group {
128
+ margin-bottom: 20px;
111
129
  }
112
130
 
113
- .endpoints {
114
- display: grid;
115
- gap: 20px;
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;
116
139
  }
117
140
 
118
- .endpoint-card {
119
- background: white;
141
+ input[type="text"],
142
+ textarea {
143
+ width: 100%;
144
+ background: var(--ios-input-bg);
145
+ border: none;
120
146
  border-radius: 12px;
121
- padding: 30px;
122
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
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;
123
153
  }
124
154
 
125
- .endpoint-header {
126
- display: flex;
127
- align-items: center;
128
- gap: 15px;
129
- margin-bottom: 20px;
155
+ textarea {
156
+ min-height: 120px;
157
+ resize: vertical;
158
+ line-height: 1.4;
130
159
  }
131
160
 
132
- .method {
133
- padding: 8px 16px;
134
- border-radius: 6px;
135
- font-weight: bold;
136
- font-size: 0.9em;
161
+ input:focus,
162
+ textarea:focus {
163
+ outline: none;
164
+ background: #D1D1D6;
137
165
  }
138
166
 
139
- .method.post {
140
- background: #10b981;
141
- color: white;
167
+ /* Buttons */
168
+ .btn-group {
169
+ display: grid;
170
+ grid-template-columns: 1fr 1fr;
171
+ /* 2 columns for better layout */
172
+ gap: 12px;
142
173
  }
143
174
 
144
- .endpoint-path {
145
- font-family: 'Courier New', monospace;
146
- color: #333;
147
- font-size: 1.2em;
175
+ .btn {
176
+ border: none;
177
+ background: var(--ios-blue);
178
+ color: white;
179
+ padding: 14px;
180
+ border-radius: 14px;
181
+ font-size: 1rem;
148
182
  font-weight: 600;
183
+ cursor: pointer;
184
+ transition: transform 0.1s, opacity 0.2s;
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ gap: 8px;
189
+ -webkit-appearance: none;
149
190
  }
150
191
 
151
- .endpoint-desc {
152
- color: #666;
153
- margin-bottom: 20px;
154
- line-height: 1.6;
155
- font-size: 1.05em;
192
+ .btn:active {
193
+ transform: scale(0.98);
194
+ opacity: 0.8;
156
195
  }
157
196
 
158
- .form-group {
159
- margin-bottom: 20px;
197
+ .btn-secondary {
198
+ background: rgba(0, 122, 255, 0.15);
199
+ color: var(--ios-blue);
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
+ /* Full width button in single column if needed */
203
+ .btn-full {
204
+ grid-column: 1 / -1;
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;
227
+ padding: 4px 0;
211
228
  }
212
229
 
213
- .btn:hover {
214
- background: #5568d3;
215
- transform: translateY(-2px);
216
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
217
- }
218
-
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,315 @@
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 btn-full" onclick="copySystemPrompt()">
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
+ </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>
395
409
  </div>
396
- <div class="response" id="execute-response"></div>
397
410
  </div>
411
+ <div class="card-desc">Thực thi bash script an toàn</div>
412
+
413
+ <div class="form-group">
414
+ <label class="form-label">Bash Script</label>
415
+ <textarea id="execute-bash" placeholder="mkdir -p src..."></textarea>
416
+ </div>
417
+
418
+ <div class="btn-group">
419
+ <button class="btn" style="background: var(--ios-green);" onclick="testExecute()">
420
+ <span>▶️</span> Execute
421
+ </button>
422
+ <button class="btn btn-secondary" onclick="executeFromClipboard()">
423
+ <span>📋</span> Paste & Run
424
+ </button>
425
+ </div>
426
+ <div class="response" id="execute-response"></div>
398
427
  </div>
399
428
  </div>
400
429
 
430
+ <!-- iOS Toast -->
401
431
  <div class="toast" id="toast"></div>
402
432
 
403
433
  <script>
404
434
  const API_BASE = window.location.origin;
405
435
  let lastAnalyzeResult = null;
406
436
 
407
- // System Prompt
408
437
  const SYSTEM_PROMPT = `# VG Coder AI System Prompt
409
438
 
410
439
  ## 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
-
440
+ ### /ask - Q&A Mode
422
441
  ### /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
442
  ### /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
443
  ### /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
-
445
- ## ⚠️ QUY TẮC BẮT BUỘC
446
-
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
444
 
451
- ### 2. Format Script Chuẩn
445
+ ## ⚠️ QUY TẮC BẮT BUỘC /code
452
446
 
453
- **Mỗi file PHẢI theo cú pháp:**
447
+ 1. **Format Script Chuẩn:**
454
448
  \`\`\`bash
455
449
  mkdir -p $(dirname "path/to/file.ext")
456
450
  cat <<'EOF' > path/to/file.ext
457
- ... toàn bộ nội dung file sau khi chỉnh sửa ...
451
+ ... content ...
458
452
  EOF
459
453
  \`\`\`
460
454
 
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
455
+ 2. **Quy tắc:**
456
+ - Luôn có mkdir -p
457
+ - Dùng <<'EOF' (có quotes)
458
+ - Chỉ include files thay đổi
459
+ `;
467
460
 
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
512
-
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
461
  document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
527
462
 
528
463
  function toggleSystemPrompt() {
529
464
  const content = document.getElementById('system-prompt-content');
530
465
  const icon = document.getElementById('toggle-icon');
531
466
  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
- });
467
+ icon.style.transform = content.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
554
468
  }
555
469
 
556
470
  function showResponse(elementId, data, isError = false) {
557
471
  const el = document.getElementById(elementId);
558
472
  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
+
559
480
  el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
560
481
  }
561
482
 
562
483
  function showLoading(button, originalText) {
563
484
  button.disabled = true;
564
- button.innerHTML = '<span class="loading"></span> Loading...';
485
+ button.innerHTML = '<div class="loading-spinner"></div>';
565
486
  button.dataset.originalText = originalText;
566
487
  }
567
488
 
568
489
  function resetButton(button) {
569
490
  button.disabled = false;
570
- const originalText = button.dataset.originalText;
571
- button.innerHTML = originalText;
491
+ button.innerHTML = button.dataset.originalText;
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');
572
579
  }
573
580
 
574
581
  async function testAnalyze() {
575
582
  const btn = event.target.closest('.btn');
576
583
  const path = document.getElementById('analyze-path').value;
584
+ const originalHTML = btn.innerHTML;
577
585
 
578
- showLoading(btn, '<span>📥</span><span>Download File</span>');
586
+ showLoading(btn, originalHTML);
579
587
  try {
580
588
  const res = await fetch(`${API_BASE}/api/analyze`, {
581
589
  method: 'POST',
@@ -587,7 +595,7 @@ API sẽ:
587
595
  const text = await res.text();
588
596
  lastAnalyzeResult = text;
589
597
 
590
- // Download file
598
+ // Trigger download
591
599
  const blob = new Blob([text], { type: 'text/plain' });
592
600
  const url = window.URL.createObjectURL(blob);
593
601
  const a = document.createElement('a');
@@ -595,172 +603,36 @@ API sẽ:
595
603
  a.download = 'project.txt';
596
604
  a.click();
597
605
 
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');
606
+ showResponse('analyze-response', { success: true, files: text.split('===== FILE:').length - 1 });
607
+ showToast('Analysis completed');
605
608
  } else {
606
609
  const data = await res.json();
607
610
  showResponse('analyze-response', data, true);
608
- showToast(' Lỗi analyze!', 'error');
611
+ showToast('Analysis failed', 'error');
609
612
  }
610
613
  } catch (err) {
611
614
  showResponse('analyze-response', { error: err.message }, true);
612
- showToast(' Lỗi: ' + err.message, 'error');
615
+ showToast('Connection error', 'error');
613
616
  }
614
617
  resetButton(btn);
615
618
  }
616
619
 
617
620
  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
-
622
- 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
- }
682
- }
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]);
702
- }
703
-
704
- 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
-
621
+ const btn = event.target.closest('.btn');
709
622
  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');
623
+ // If not analyzed yet, try to analyze silently or warn
624
+ showToast('Please analyze/download first', 'error');
625
+ return;
751
626
  }
627
+ copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
752
628
  }
753
629
 
754
630
  async function testExecute() {
755
631
  const btn = event.target.closest('.btn');
756
632
  const bash = document.getElementById('execute-bash').value;
633
+ if (!bash.trim()) return showToast('Script is empty', 'error');
757
634
 
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>');
635
+ showLoading(btn, btn.innerHTML);
764
636
  try {
765
637
  const res = await fetch(`${API_BASE}/api/execute`, {
766
638
  method: 'POST',
@@ -768,100 +640,41 @@ API sẽ:
768
640
  body: JSON.stringify({ bash })
769
641
  });
770
642
  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
- }
643
+ showResponse('execute-response', data, !data.success);
644
+ showToast(data.success ? 'Executed successfully' : 'Execution failed', data.success ? 'success' : 'error');
778
645
  } catch (err) {
779
646
  showResponse('execute-response', { error: err.message }, true);
780
- showToast('❌ Lỗi: ' + err.message, 'error');
781
647
  }
782
648
  resetButton(btn);
783
649
  }
784
650
 
785
651
  async function executeFromClipboard() {
786
- const btn = event.target.closest('.btn');
787
-
788
- showLoading(btn, '<span>📋</span><span>Execute from Clipboard</span>');
789
-
790
652
  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
- }
653
+ const text = await navigator.clipboard.readText();
654
+ if (!text.trim()) return showToast('Clipboard is empty', 'error');
804
655
 
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
- }
656
+ document.getElementById('execute-bash').value = text;
657
+ showToast('Pasted from clipboard');
827
658
  } 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
- }
659
+ showToast('Clipboard permission denied', 'error');
839
660
  }
840
- resetButton(btn);
841
661
  }
842
662
 
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
663
+ // Init Check
853
664
  (async () => {
665
+ const statusBadge = document.getElementById('status');
854
666
  try {
855
667
  const res = await fetch(`${API_BASE}/health`);
856
668
  if (res.ok) {
857
- document.getElementById('status').textContent = '● Server Running';
858
- document.getElementById('status').style.background = '#10b981';
669
+ statusBadge.textContent = '● Online';
670
+ statusBadge.style.backgroundColor = 'var(--ios-green)';
859
671
  }
860
672
  } catch {
861
- document.getElementById('status').textContent = '● Server Offline';
862
- document.getElementById('status').style.background = '#ef4444';
673
+ statusBadge.textContent = '● Offline';
674
+ statusBadge.style.backgroundColor = 'var(--ios-text-secondary)';
863
675
  }
864
676
  })();
677
+
865
678
  </script>
866
679
  </body>
867
680