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.
- package/README.md +318 -92
- package/{vg/apps/api/src/assets/.gitkeep → change.sh} +0 -0
- package/package.json +4 -11
- package/src/index.js +0 -56
- package/src/server/views/dashboard.html +391 -613
- package/vg-coder-cli-1.0.17.tgz +0 -0
- package/vg-coder-cli-2.0.4.tgz +0 -0
- package/CHANGELOG.md +0 -59
- package/PUBLISHING.md +0 -86
- package/SYSTEM_PROMPT.md +0 -157
- package/init-nx-monorepo.sh +0 -107
- package/vg/.vscode/extensions.json +0 -8
- package/vg/.vscode/launch.json +0 -23
- package/vg/README.md +0 -85
- package/vg/apps/api/project.json +0 -83
- package/vg/apps/api/src/app/analyze.controller.ts +0 -17
- package/vg/apps/api/src/app/analyze.service.ts +0 -57
- package/vg/apps/api/src/app/app.controller.ts +0 -12
- package/vg/apps/api/src/app/app.module.ts +0 -29
- package/vg/apps/api/src/app/app.service.ts +0 -8
- package/vg/apps/api/src/app/clean.controller.ts +0 -40
- package/vg/apps/api/src/app/execute.controller.ts +0 -19
- package/vg/apps/api/src/app/execute.service.ts +0 -46
- package/vg/apps/api/src/app/info.controller.ts +0 -12
- package/vg/apps/api/src/app/info.service.ts +0 -65
- package/vg/apps/api/src/main.ts +0 -28
- package/vg/apps/api/webpack.config.js +0 -25
- package/vg/apps/api-e2e/jest.config.cts +0 -18
- package/vg/apps/api-e2e/project.json +0 -17
- package/vg/apps/api-e2e/src/support/global-setup.ts +0 -16
- package/vg/apps/api-e2e/src/support/global-teardown.ts +0 -10
- package/vg/apps/api-e2e/src/support/test-setup.ts +0 -9
- package/vg/apps/ng-app/jest.config.ts +0 -21
- package/vg/apps/ng-app/project.json +0 -110
- package/vg/apps/ng-app/proxy.conf.json +0 -8
- package/vg/apps/ng-app/public/favicon.ico +0 -0
- package/vg/apps/ng-app/src/app/app.config.ts +0 -17
- package/vg/apps/ng-app/src/app/app.html +0 -1
- package/vg/apps/ng-app/src/app/app.routes.ts +0 -7
- package/vg/apps/ng-app/src/app/app.scss +0 -0
- package/vg/apps/ng-app/src/app/app.ts +0 -12
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.html +0 -87
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.scss +0 -290
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.ts +0 -236
- package/vg/apps/ng-app/src/app/nx-welcome.ts +0 -872
- package/vg/apps/ng-app/src/app/services/api.service.ts +0 -28
- package/vg/apps/ng-app/src/index.html +0 -13
- package/vg/apps/ng-app/src/main.ts +0 -5
- package/vg/apps/ng-app/src/styles.scss +0 -1
- package/vg/apps/ng-app/src/test-setup.ts +0 -6
- package/vg/jest.config.ts +0 -6
- package/vg/nx.json +0 -85
- package/vg/package-lock.json +0 -30707
- package/vg/package.json +0 -75
- package/vg/packages/client/data-access/README.md +0 -7
- package/vg/packages/client/data-access/jest.config.ts +0 -21
- package/vg/packages/client/data-access/project.json +0 -21
- package/vg/packages/client/data-access/src/index.ts +0 -1
- package/vg/packages/client/data-access/src/lib/data-access/data-access.html +0 -1
- package/vg/packages/client/data-access/src/lib/data-access/data-access.scss +0 -0
- package/vg/packages/client/data-access/src/lib/data-access/data-access.ts +0 -9
- package/vg/packages/client/data-access/src/test-setup.ts +0 -6
- package/vg/packages/core/README.md +0 -11
- package/vg/packages/core/jest.config.ts +0 -10
- package/vg/packages/core/package.json +0 -11
- package/vg/packages/core/project.json +0 -26
- package/vg/packages/core/src/index.ts +0 -6
- package/vg/packages/core/src/lib/core.ts +0 -3
- package/vg/packages/core/src/lib/detectors/project-detector.ts +0 -343
- package/vg/packages/core/src/lib/ignore/ignore-manager.ts +0 -315
- package/vg/packages/core/src/lib/scanner/file-scanner.ts +0 -675
- package/vg/packages/core/src/lib/tokenizer/token-manager.ts +0 -435
- package/vg/packages/core/src/lib/utils/bash-executor.ts +0 -146
- package/vg/packages/shared/data-types/README.md +0 -11
- package/vg/packages/shared/data-types/jest.config.ts +0 -10
- package/vg/packages/shared/data-types/package.json +0 -11
- package/vg/packages/shared/data-types/project.json +0 -26
- package/vg/packages/shared/data-types/src/index.ts +0 -1
- package/vg/packages/shared/data-types/src/lib/data-types.ts +0 -3
- 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"
|
|
7
|
-
|
|
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:
|
|
17
|
-
background:
|
|
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:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
.
|
|
36
|
-
|
|
37
|
-
font-
|
|
38
|
-
|
|
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
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
background: #10b981;
|
|
50
|
-
color: white;
|
|
84
|
+
/* iOS Cards */
|
|
85
|
+
.card {
|
|
86
|
+
background: var(--ios-card);
|
|
51
87
|
border-radius: 20px;
|
|
52
|
-
|
|
53
|
-
margin-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
.
|
|
95
|
+
.card-header {
|
|
65
96
|
display: flex;
|
|
66
|
-
justify-content: space-between;
|
|
67
97
|
align-items: center;
|
|
68
|
-
margin-bottom:
|
|
69
|
-
|
|
70
|
-
user-select: none;
|
|
98
|
+
margin-bottom: 16px;
|
|
99
|
+
gap: 12px;
|
|
71
100
|
}
|
|
72
101
|
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
109
|
+
justify-content: center;
|
|
110
|
+
font-size: 1.2rem;
|
|
111
|
+
color: var(--ios-blue);
|
|
79
112
|
}
|
|
80
113
|
|
|
81
|
-
.
|
|
82
|
-
|
|
114
|
+
.card-title {
|
|
115
|
+
font-size: 1.1rem;
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
letter-spacing: -0.3px;
|
|
83
118
|
}
|
|
84
119
|
|
|
85
|
-
.
|
|
86
|
-
|
|
120
|
+
.card-desc {
|
|
121
|
+
color: var(--ios-text-secondary);
|
|
122
|
+
font-size: 0.9rem;
|
|
123
|
+
margin-bottom: 20px;
|
|
87
124
|
}
|
|
88
125
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
transition: max-height 0.3s ease;
|
|
126
|
+
/* Forms */
|
|
127
|
+
.form-group {
|
|
128
|
+
margin-bottom: 20px;
|
|
93
129
|
}
|
|
94
130
|
|
|
95
|
-
.
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
textarea {
|
|
156
|
+
min-height: 120px;
|
|
157
|
+
resize: vertical;
|
|
158
|
+
line-height: 1.4;
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
/* Buttons */
|
|
168
|
+
.btn-group {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: 1fr;
|
|
171
|
+
gap: 12px;
|
|
130
172
|
}
|
|
131
173
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
.
|
|
140
|
-
|
|
180
|
+
.btn {
|
|
181
|
+
border: none;
|
|
182
|
+
background: var(--ios-blue);
|
|
141
183
|
color: white;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
.
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
.
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
border-color: #667eea;
|
|
212
|
+
.btn-copy.copied {
|
|
213
|
+
background: var(--ios-green);
|
|
214
|
+
color: white;
|
|
185
215
|
}
|
|
186
216
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
/* System Prompt Accordion */
|
|
218
|
+
.prompt-accordion {
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
user-select: none;
|
|
190
221
|
}
|
|
191
222
|
|
|
192
|
-
.
|
|
223
|
+
.prompt-header {
|
|
193
224
|
display: flex;
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
230
|
+
.chevron {
|
|
231
|
+
color: var(--ios-text-secondary);
|
|
232
|
+
transition: transform 0.3s ease;
|
|
233
|
+
font-size: 0.8rem;
|
|
223
234
|
}
|
|
224
235
|
|
|
225
|
-
.
|
|
226
|
-
|
|
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
|
-
.
|
|
230
|
-
|
|
231
|
-
|
|
243
|
+
.prompt-content.open {
|
|
244
|
+
max-height: 500px;
|
|
245
|
+
opacity: 1;
|
|
246
|
+
margin-top: 15px;
|
|
232
247
|
}
|
|
233
248
|
|
|
234
|
-
.
|
|
235
|
-
background: #
|
|
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
|
-
|
|
241
|
-
border-radius:
|
|
242
|
-
|
|
243
|
-
border-left:
|
|
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:
|
|
253
|
-
background: #
|
|
278
|
+
border-left-color: var(--ios-green);
|
|
279
|
+
background: #F0FFF4;
|
|
254
280
|
}
|
|
255
281
|
|
|
256
282
|
.response.error {
|
|
257
|
-
border-left-color:
|
|
258
|
-
background: #
|
|
283
|
+
border-left-color: var(--ios-red);
|
|
284
|
+
background: #FFF5F5;
|
|
259
285
|
}
|
|
260
286
|
|
|
261
287
|
.response pre {
|
|
262
|
-
|
|
263
|
-
font-
|
|
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-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
transform: translateY(0);
|
|
318
|
+
transform: translateX(-50%) translateY(0);
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
.
|
|
306
|
-
|
|
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
|
-
|
|
310
|
-
|
|
330
|
+
@keyframes spin {
|
|
331
|
+
to {
|
|
332
|
+
transform: rotate(360deg);
|
|
333
|
+
}
|
|
311
334
|
}
|
|
312
335
|
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
328
|
-
<div class="
|
|
329
|
-
<div class="
|
|
330
|
-
<
|
|
331
|
-
<
|
|
332
|
-
<
|
|
333
|
-
</
|
|
334
|
-
<
|
|
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="
|
|
337
|
-
<div class="
|
|
338
|
-
<button class="btn btn-
|
|
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
|
|
372
|
+
<span id="copy-text">Copy Prompt</span>
|
|
341
373
|
</button>
|
|
342
374
|
</div>
|
|
343
375
|
</div>
|
|
344
376
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<div class="
|
|
348
|
-
<div class="
|
|
349
|
-
|
|
350
|
-
<
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
</
|
|
386
|
-
<
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
454
|
+
... content ...
|
|
458
455
|
EOF
|
|
459
456
|
\`\`\`
|
|
460
457
|
|
|
461
|
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
|
|
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.
|
|
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 = '<
|
|
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
|
-
|
|
571
|
-
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
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('
|
|
567
|
+
showToast('Analysis failed', 'error');
|
|
609
568
|
}
|
|
610
569
|
} catch (err) {
|
|
611
570
|
showResponse('analyze-response', { error: err.message }, true);
|
|
612
|
-
showToast('
|
|
571
|
+
showToast('Connection error', 'error');
|
|
613
572
|
}
|
|
614
573
|
resetButton(btn);
|
|
615
574
|
}
|
|
616
575
|
|
|
617
576
|
async function copyAnalyzeResult() {
|
|
618
|
-
const
|
|
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
|
-
|
|
624
|
-
|
|
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
|
|
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
|
-
|
|
711
|
-
|
|
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
|
-
|
|
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, !
|
|
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
|
-
|
|
792
|
-
|
|
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
|
-
|
|
806
|
-
|
|
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
|
-
|
|
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
|
-
|
|
858
|
-
|
|
634
|
+
statusBadge.textContent = '● Online';
|
|
635
|
+
statusBadge.style.backgroundColor = 'var(--ios-green)';
|
|
859
636
|
}
|
|
860
637
|
} catch {
|
|
861
|
-
|
|
862
|
-
|
|
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
|
|