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.
- 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 -60
- package/src/server/views/dashboard.html +432 -619
- package/vg-coder-cli-1.0.17.tgz +0 -0
- package/vg-coder-cli-2.0.4.tgz +0 -0
- package/vg-coder-cli-2.0.5.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-WORKSPACE.md +0 -82
- 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/bin/vg-workspace.js +0 -43
- package/vg/jest.config.ts +0 -6
- package/vg/nx.json +0 -85
- package/vg/package-lock.json +0 -30707
- package/vg/package-workspace.json +0 -38
- package/vg/package.json +0 -38
- 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:
|
|
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
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
59
|
+
.app-title {
|
|
60
|
+
font-weight: 700;
|
|
61
|
+
font-size: 1.2rem;
|
|
62
|
+
letter-spacing: -0.5px;
|
|
25
63
|
}
|
|
26
64
|
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
padding:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
.
|
|
114
|
-
display:
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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:
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
margin-bottom: 20px;
|
|
155
|
+
textarea {
|
|
156
|
+
min-height: 120px;
|
|
157
|
+
resize: vertical;
|
|
158
|
+
line-height: 1.4;
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
font-size: 0.9em;
|
|
161
|
+
input:focus,
|
|
162
|
+
textarea:focus {
|
|
163
|
+
outline: none;
|
|
164
|
+
background: #D1D1D6;
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
.
|
|
159
|
-
|
|
197
|
+
.btn-secondary {
|
|
198
|
+
background: rgba(0, 122, 255, 0.15);
|
|
199
|
+
color: var(--ios-blue);
|
|
160
200
|
}
|
|
161
201
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
227
|
+
padding: 4px 0;
|
|
211
228
|
}
|
|
212
229
|
|
|
213
|
-
.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
.
|
|
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,315 @@
|
|
|
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 btn-full" onclick="copySystemPrompt()">
|
|
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
|
+
</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
|
-
|
|
445
|
+
## ⚠️ QUY TẮC BẮT BUỘC /code
|
|
452
446
|
|
|
453
|
-
**
|
|
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
|
-
...
|
|
451
|
+
... content ...
|
|
458
452
|
EOF
|
|
459
453
|
\`\`\`
|
|
460
454
|
|
|
461
|
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
|
|
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.
|
|
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 = '<
|
|
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
|
-
|
|
571
|
-
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
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('
|
|
611
|
+
showToast('Analysis failed', 'error');
|
|
609
612
|
}
|
|
610
613
|
} catch (err) {
|
|
611
614
|
showResponse('analyze-response', { error: err.message }, true);
|
|
612
|
-
showToast('
|
|
615
|
+
showToast('Connection error', 'error');
|
|
613
616
|
}
|
|
614
617
|
resetButton(btn);
|
|
615
618
|
}
|
|
616
619
|
|
|
617
620
|
async function copyAnalyzeResult() {
|
|
618
|
-
const
|
|
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
|
-
//
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
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, !
|
|
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
|
-
|
|
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
|
-
}
|
|
653
|
+
const text = await navigator.clipboard.readText();
|
|
654
|
+
if (!text.trim()) return showToast('Clipboard is empty', 'error');
|
|
804
655
|
|
|
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
|
-
}
|
|
656
|
+
document.getElementById('execute-bash').value = text;
|
|
657
|
+
showToast('Pasted from clipboard');
|
|
827
658
|
} 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
|
-
}
|
|
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
|
-
|
|
858
|
-
|
|
669
|
+
statusBadge.textContent = '● Online';
|
|
670
|
+
statusBadge.style.backgroundColor = 'var(--ios-green)';
|
|
859
671
|
}
|
|
860
672
|
} catch {
|
|
861
|
-
|
|
862
|
-
|
|
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
|
|