vg-coder-cli 2.0.10 → 2.0.11
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/DEVELOPMENT.md +95 -0
- package/package.json +1 -1
- package/src/server/views/css/iframe.css +84 -0
- package/src/server/views/dashboard.css +6 -53
- package/src/server/views/dashboard.html +49 -32
- package/src/server/views/js/features/iframe-manager.js +56 -0
- package/src/server/views/js/main.js +4 -0
- package/src/server/views/vg-coder/background.js +1 -1
- package/src/server/views/vg-coder/options.js +1 -1
- package/{vg-coder-cli-2.0.10.tgz → vg-coder-cli-2.0.11.tgz} +0 -0
- package/vg-coder.zip +0 -0
package/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# VG Coder - Development & Architecture Guide
|
|
2
|
+
|
|
3
|
+
Tài liệu này quy định kiến trúc Frontend (Dashboard) để đảm bảo tính dễ bảo trì, mở rộng và code sạch (Clean Code).
|
|
4
|
+
|
|
5
|
+
## 1. Directory Structure (Cấu trúc thư mục)
|
|
6
|
+
|
|
7
|
+
Frontend nằm trong `src/server/views/`.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
src/server/views/
|
|
11
|
+
├── dashboard.html # File HTML chính (Layout & Markup)
|
|
12
|
+
├── dashboard.css # CSS Global (Variables, Reset, Layout khung sườn)
|
|
13
|
+
├── css/ # 📁 MODULE CSS (Chứa style riêng biệt cho từng feature)
|
|
14
|
+
│ ├── structure.css # Style cho cây thư mục
|
|
15
|
+
│ ├── iframe.css # Style cho Iframe/AI Panel
|
|
16
|
+
│ └── [feature].css # -> Style cho tính năng mới đặt tại đây
|
|
17
|
+
├── js/ # 📁 JAVASCRIPT
|
|
18
|
+
│ ├── main.js # Entry Point (Khởi tạo, Import các feature)
|
|
19
|
+
│ ├── config.js # Constants & Config
|
|
20
|
+
│ ├── api.js # API Layer (Fetch requests)
|
|
21
|
+
│ ├── utils.js # Helper functions
|
|
22
|
+
│ ├── handlers.js # Global event handlers (để bind vào window)
|
|
23
|
+
│ └── features/ # 📁 MODULE JS (Logic riêng biệt cho từng feature)
|
|
24
|
+
│ ├── structure.js # Logic cây thư mục
|
|
25
|
+
│ ├── iframe-manager.js # Logic Iframe AI
|
|
26
|
+
│ └── [feature].js # -> Logic tính năng mới đặt tại đây
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. Quy trình thêm tính năng mới (Workflow)
|
|
32
|
+
|
|
33
|
+
Khi thêm một tính năng mới (ví dụ: `Settings`), tuân thủ 4 bước sau:
|
|
34
|
+
|
|
35
|
+
### Bước 1: Tạo CSS Module
|
|
36
|
+
Tạo file `src/server/views/css/settings.css`.
|
|
37
|
+
* **Quy tắc:** Chỉ viết style liên quan đến settings.
|
|
38
|
+
* **Import:** Thêm thẻ `<link>` vào `dashboard.html`.
|
|
39
|
+
|
|
40
|
+
### Bước 2: Tạo JS Module
|
|
41
|
+
Tạo file `src/server/views/js/features/settings.js`.
|
|
42
|
+
* **Quy tắc:** Export hàm khởi tạo (`initSettings`) hoặc các hàm xử lý logic.
|
|
43
|
+
* **Không** viết code chạy ngay lập tức (IIFE) trừ khi cần thiết.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Example: src/server/views/js/features/settings.js
|
|
47
|
+
export function initSettings() {
|
|
48
|
+
console.log('Settings initialized');
|
|
49
|
+
// Logic here
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Bước 3: Cập nhật HTML
|
|
54
|
+
Thêm Markup vào `src/server/views/dashboard.html`.
|
|
55
|
+
* Thêm ID cụ thể để JS dễ query (ví dụ: `id="settings-panel"`).
|
|
56
|
+
* Thêm link CSS mới vào `<head>`.
|
|
57
|
+
|
|
58
|
+
### Bước 4: Đăng ký (Register) trong `main.js`
|
|
59
|
+
Import và gọi hàm khởi tạo trong `src/server/views/js/main.js`.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// src/server/views/js/main.js
|
|
63
|
+
import { initSettings } from './features/settings.js';
|
|
64
|
+
|
|
65
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
66
|
+
// ... các init khác
|
|
67
|
+
initSettings();
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. Coding Standards (Quy chuẩn Code)
|
|
74
|
+
|
|
75
|
+
### CSS
|
|
76
|
+
* Sử dụng **CSS Variables** (`var(--ios-bg)`) định nghĩa trong `dashboard.css` để đồng bộ Dark/Light mode.
|
|
77
|
+
* Tránh sửa trực tiếp `dashboard.css` trừ khi thay đổi Layout toàn cục.
|
|
78
|
+
|
|
79
|
+
### JavaScript
|
|
80
|
+
* **ES Modules:** Sử dụng `import/export`.
|
|
81
|
+
* **Global Scope:** Hạn chế gán biến vào `window`. Nếu cần dùng cho `onclick=""` trong HTML, hãy gán thông qua file `handlers.js` hoặc gán explicit trong `main.js`.
|
|
82
|
+
* **API Calls:** Mọi lệnh `fetch` gọi về server nên được viết trong `js/api.js`, sau đó feature import về dùng.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 4. Prompt mẫu cho AI
|
|
87
|
+
|
|
88
|
+
Khi yêu cầu AI code tính năng mới, hãy dùng prompt sau để đảm bảo AI tuân thủ kiến trúc:
|
|
89
|
+
|
|
90
|
+
> "Hãy thêm tính năng [TÊN_TÍNH_NĂNG].
|
|
91
|
+
> Tuân thủ kiến trúc trong `DEVELOPMENT.md`:
|
|
92
|
+
> 1. Tạo file CSS riêng trong `views/css/`.
|
|
93
|
+
> 2. Tạo file JS logic riêng trong `views/js/features/`.
|
|
94
|
+
> 3. Cập nhật `dashboard.html` và `main.js`.
|
|
95
|
+
> 4. Sử dụng style từ biến CSS có sẵn."
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Right Panel Layout */
|
|
2
|
+
.right-panel {
|
|
3
|
+
flex: 1;
|
|
4
|
+
height: 100%;
|
|
5
|
+
background: #fff;
|
|
6
|
+
position: relative;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* AI Header Styles */
|
|
12
|
+
.ai-header {
|
|
13
|
+
height: 50px;
|
|
14
|
+
background: var(--ios-bg);
|
|
15
|
+
border-bottom: 1px solid var(--ios-separator);
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
padding: 0 15px;
|
|
19
|
+
gap: 10px;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ai-select-group {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 10px;
|
|
27
|
+
flex: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ai-select {
|
|
31
|
+
padding: 6px 12px;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
border: 1px solid var(--ios-separator);
|
|
34
|
+
background: var(--ios-card);
|
|
35
|
+
color: var(--text-primary);
|
|
36
|
+
font-size: 13px;
|
|
37
|
+
font-weight: 500;
|
|
38
|
+
min-width: 200px;
|
|
39
|
+
outline: none;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Iframe Container */
|
|
44
|
+
.ai-iframe-container {
|
|
45
|
+
flex: 1;
|
|
46
|
+
position: relative;
|
|
47
|
+
width: 100%;
|
|
48
|
+
background: #fff;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.right-panel iframe {
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
border: none;
|
|
55
|
+
position: relative;
|
|
56
|
+
z-index: 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Placeholder underneath iframe */
|
|
60
|
+
.iframe-placeholder {
|
|
61
|
+
position: absolute;
|
|
62
|
+
top: 50%;
|
|
63
|
+
left: 50%;
|
|
64
|
+
transform: translate(-50%, -50%);
|
|
65
|
+
text-align: center;
|
|
66
|
+
color: var(--text-secondary);
|
|
67
|
+
z-index: 1;
|
|
68
|
+
width: 100%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.link-fallback {
|
|
72
|
+
color: var(--ios-blue);
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
margin-top: 10px;
|
|
75
|
+
display: inline-block;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Mobile Responsive adjustment for right panel */
|
|
79
|
+
@media (max-width: 768px) {
|
|
80
|
+
.right-panel {
|
|
81
|
+
flex: 1;
|
|
82
|
+
height: 50vh;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -43,9 +43,7 @@ body {
|
|
|
43
43
|
background-color: var(--ios-bg);
|
|
44
44
|
color: var(--text-primary);
|
|
45
45
|
height: 100vh;
|
|
46
|
-
/* Full viewport height */
|
|
47
46
|
overflow: hidden;
|
|
48
|
-
/* Disable body scroll */
|
|
49
47
|
font-size: 13px;
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -59,12 +57,9 @@ body {
|
|
|
59
57
|
/* Left Panel: VG Coder Tool */
|
|
60
58
|
.left-panel {
|
|
61
59
|
flex: 0 0 450px;
|
|
62
|
-
/* Fixed width: 450px */
|
|
63
60
|
max-width: 50vw;
|
|
64
|
-
/* Max 50% on small screens */
|
|
65
61
|
height: 100%;
|
|
66
62
|
overflow-y: auto;
|
|
67
|
-
/* Scrollable independently */
|
|
68
63
|
background: var(--ios-bg);
|
|
69
64
|
border-right: 1px solid var(--ios-separator);
|
|
70
65
|
position: relative;
|
|
@@ -74,48 +69,9 @@ body {
|
|
|
74
69
|
.container {
|
|
75
70
|
padding: 15px;
|
|
76
71
|
padding-top: 15px;
|
|
77
|
-
/* Removed max-width/margin centering since it's inside panel */
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
/*
|
|
81
|
-
.right-panel {
|
|
82
|
-
flex: 1;
|
|
83
|
-
/* Take remaining space */
|
|
84
|
-
height: 100%;
|
|
85
|
-
background: #fff;
|
|
86
|
-
position: relative;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.right-panel iframe {
|
|
90
|
-
width: 100%;
|
|
91
|
-
height: 100%;
|
|
92
|
-
border: none;
|
|
93
|
-
position: relative;
|
|
94
|
-
z-index: 2;
|
|
95
|
-
/* Sit above placeholder */
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/* Placeholder underneath iframe (visible if iframe fails to load) */
|
|
99
|
-
.iframe-placeholder {
|
|
100
|
-
position: absolute;
|
|
101
|
-
top: 50%;
|
|
102
|
-
left: 50%;
|
|
103
|
-
transform: translate(-50%, -50%);
|
|
104
|
-
text-align: center;
|
|
105
|
-
color: var(--text-secondary);
|
|
106
|
-
z-index: 1;
|
|
107
|
-
width: 100%;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.link-fallback {
|
|
111
|
-
color: var(--ios-blue);
|
|
112
|
-
text-decoration: none;
|
|
113
|
-
margin-top: 10px;
|
|
114
|
-
display: inline-block;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* --- EXISTING COMPACT STYLES (Preserved & Adjusted) --- */
|
|
118
|
-
|
|
74
|
+
/* --- COMPONENT STYLES --- */
|
|
119
75
|
.header {
|
|
120
76
|
display: flex;
|
|
121
77
|
justify-content: space-between;
|
|
@@ -176,6 +132,11 @@ body {
|
|
|
176
132
|
border: none;
|
|
177
133
|
border-radius: 6px;
|
|
178
134
|
cursor: pointer;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
text-decoration: none;
|
|
139
|
+
color: var(--text-primary);
|
|
179
140
|
}
|
|
180
141
|
|
|
181
142
|
.system-prompt-content {
|
|
@@ -321,16 +282,13 @@ body {
|
|
|
321
282
|
position: fixed;
|
|
322
283
|
bottom: 20px;
|
|
323
284
|
left: 20px;
|
|
324
|
-
/* Move toast to bottom left */
|
|
325
285
|
transform: translateY(100px);
|
|
326
|
-
/* Adjust animation */
|
|
327
286
|
}
|
|
328
287
|
|
|
329
288
|
.toast.show {
|
|
330
289
|
transform: translateY(0);
|
|
331
290
|
}
|
|
332
291
|
|
|
333
|
-
/* Mobile Responsiveness */
|
|
334
292
|
@media (max-width: 768px) {
|
|
335
293
|
.split-layout {
|
|
336
294
|
flex-direction: column;
|
|
@@ -344,9 +302,4 @@ body {
|
|
|
344
302
|
border-right: none;
|
|
345
303
|
border-bottom: 1px solid var(--ios-separator);
|
|
346
304
|
}
|
|
347
|
-
|
|
348
|
-
.right-panel {
|
|
349
|
-
flex: 1;
|
|
350
|
-
height: 50vh;
|
|
351
|
-
}
|
|
352
305
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<title>VG Coder - Split View</title>
|
|
9
9
|
<link rel="stylesheet" href="/dashboard.css">
|
|
10
10
|
<link rel="stylesheet" href="/css/structure.css">
|
|
11
|
+
<link rel="stylesheet" href="/css/iframe.css">
|
|
11
12
|
<script>
|
|
12
13
|
(function () {
|
|
13
14
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
|
|
19
20
|
<body>
|
|
20
21
|
<div class="split-layout">
|
|
21
|
-
<!-- CỘT TRÁI: Giao diện VG Coder
|
|
22
|
+
<!-- CỘT TRÁI: Giao diện VG Coder -->
|
|
22
23
|
<div class="left-panel">
|
|
23
24
|
<div class="container">
|
|
24
25
|
<div class="header">
|
|
@@ -68,8 +69,11 @@
|
|
|
68
69
|
Copy link này và dán vào tab mới:
|
|
69
70
|
<div class="form-group" style="margin-top: 5px; margin-bottom: 0;">
|
|
70
71
|
<div style="display: flex; gap: 5px;">
|
|
71
|
-
<input type="text" id="chrome-url-input" readonly
|
|
72
|
-
|
|
72
|
+
<input type="text" id="chrome-url-input" readonly
|
|
73
|
+
value="chrome://extensions" onclick="this.select()"
|
|
74
|
+
style="font-family: monospace;">
|
|
75
|
+
<button class="btn btn-copy" style="flex: 0 0 40px; padding: 0;"
|
|
76
|
+
onclick="copyChromeUrl(event)" title="Copy URL">
|
|
73
77
|
<span>📋</span>
|
|
74
78
|
</button>
|
|
75
79
|
</div>
|
|
@@ -81,7 +85,8 @@
|
|
|
81
85
|
</ol>
|
|
82
86
|
</div>
|
|
83
87
|
<div class="form-group">
|
|
84
|
-
<input type="text" id="extension-path-input" readonly value="Loading path..."
|
|
88
|
+
<input type="text" id="extension-path-input" readonly value="Loading path..."
|
|
89
|
+
onclick="this.select()">
|
|
85
90
|
</div>
|
|
86
91
|
<button class="btn btn-copy" onclick="copyExtensionPath(event)">
|
|
87
92
|
<span id="ext-copy-icon">📋</span>
|
|
@@ -91,29 +96,6 @@
|
|
|
91
96
|
</div>
|
|
92
97
|
|
|
93
98
|
<div class="endpoints">
|
|
94
|
-
<!-- Analyze -->
|
|
95
|
-
<div class="endpoint-card">
|
|
96
|
-
<div class="endpoint-header">
|
|
97
|
-
<div class="endpoint-title-group">
|
|
98
|
-
<span class="method post">POST</span>
|
|
99
|
-
<span class="endpoint-path">/analyze</span>
|
|
100
|
-
</div>
|
|
101
|
-
<button class="btn-icon-head" onclick="testAnalyze(event)" title="Download Project Source">
|
|
102
|
-
📥
|
|
103
|
-
</button>
|
|
104
|
-
</div>
|
|
105
|
-
<div class="form-group">
|
|
106
|
-
<input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
|
|
107
|
-
</div>
|
|
108
|
-
<div class="btn-group">
|
|
109
|
-
<button class="btn btn-copy" onclick="copyAnalyzeResult(event)">
|
|
110
|
-
<span id="analyze-copy-icon">📋</span>
|
|
111
|
-
<span id="analyze-copy-text">Copy Text</span>
|
|
112
|
-
</button>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="response" id="analyze-response"></div>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
99
|
<!-- Execute Bash -->
|
|
118
100
|
<div class="endpoint-card">
|
|
119
101
|
<div class="endpoint-header">
|
|
@@ -165,18 +147,53 @@
|
|
|
165
147
|
</div>
|
|
166
148
|
<div class="response" id="structure-response" style="display: none;"></div>
|
|
167
149
|
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Analyze -->
|
|
152
|
+
<div class="endpoint-card">
|
|
153
|
+
<div class="endpoint-header">
|
|
154
|
+
<div class="endpoint-title-group">
|
|
155
|
+
<span class="method post">POST</span>
|
|
156
|
+
<span class="endpoint-path">/analyze</span>
|
|
157
|
+
</div>
|
|
158
|
+
<button class="btn-icon-head" onclick="testAnalyze(event)" title="Download Project Source">
|
|
159
|
+
📥
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="form-group">
|
|
163
|
+
<input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
|
|
164
|
+
</div>
|
|
165
|
+
<div class="btn-group">
|
|
166
|
+
<button class="btn btn-copy" onclick="copyAnalyzeResult(event)">
|
|
167
|
+
<span id="analyze-copy-icon">📋</span>
|
|
168
|
+
<span id="analyze-copy-text">Copy Text</span>
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="response" id="analyze-response"></div>
|
|
172
|
+
</div>
|
|
168
173
|
</div>
|
|
169
174
|
<div style="height: 50px;"></div> <!-- Spacer for scrolling -->
|
|
170
175
|
</div>
|
|
171
176
|
</div>
|
|
172
177
|
|
|
173
|
-
<!-- CỘT PHẢI: Iframe
|
|
178
|
+
<!-- CỘT PHẢI: AI Iframe with Selector -->
|
|
174
179
|
<div class="right-panel">
|
|
175
|
-
<div class="
|
|
176
|
-
<
|
|
177
|
-
|
|
180
|
+
<div class="ai-header">
|
|
181
|
+
<div class="ai-select-group">
|
|
182
|
+
<span style="font-size: 16px;">🤖</span>
|
|
183
|
+
<select id="ai-provider-select" class="ai-select">
|
|
184
|
+
<!-- Options filled by JS -->
|
|
185
|
+
</select>
|
|
186
|
+
</div>
|
|
187
|
+
<a id="ai-external-link" href="#" target="_blank" class="btn-icon-head" title="Open in new tab">↗</a>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="ai-iframe-container">
|
|
191
|
+
<div class="iframe-placeholder">
|
|
192
|
+
<p>⚠️ Nếu trang trắng, hãy cài Extension <b>"Ignore X-Frame-Options"</b></p>
|
|
193
|
+
<a id="ai-placeholder-link" href="#" target="_blank" class="link-fallback">Mở tab mới ↗</a>
|
|
194
|
+
</div>
|
|
195
|
+
<iframe id="ai-iframe" src="" title="AI Integration"></iframe>
|
|
178
196
|
</div>
|
|
179
|
-
<iframe src="https://chatgpt.com" title="ChatGPT Integration"></iframe>
|
|
180
197
|
</div>
|
|
181
198
|
</div>
|
|
182
199
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quản lý tính năng Iframe AI Provider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Danh sách các AI Providers
|
|
6
|
+
const AI_PROVIDERS = [
|
|
7
|
+
{ id: 'chatgpt', name: 'ChatGPT', url: 'https://chat.openai.com' },
|
|
8
|
+
{ id: 'kimi', name: 'Kimi AI', url: 'https://www.kimi.com' },
|
|
9
|
+
{ id: 'deepseek', name: 'DeepSeek', url: 'https://chat.deepseek.com' },
|
|
10
|
+
{ id: 'gemini', name: 'Google Gemini', url: 'https://gemini.google.com/app' },
|
|
11
|
+
{ id: 'aistudio', name: 'Google AI Studio', url: 'https://aistudio.google.com/prompts/new_chat' }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function initIframeManager() {
|
|
15
|
+
const select = document.getElementById('ai-provider-select');
|
|
16
|
+
const iframe = document.getElementById('ai-iframe');
|
|
17
|
+
const externalLink = document.getElementById('ai-external-link');
|
|
18
|
+
const placeholderLink = document.getElementById('ai-placeholder-link');
|
|
19
|
+
|
|
20
|
+
if (!select || !iframe) return;
|
|
21
|
+
|
|
22
|
+
// 1. Populate options
|
|
23
|
+
AI_PROVIDERS.forEach(provider => {
|
|
24
|
+
const option = document.createElement('option');
|
|
25
|
+
option.value = provider.id;
|
|
26
|
+
option.textContent = provider.name;
|
|
27
|
+
select.appendChild(option);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 2. Load saved selection or default
|
|
31
|
+
const savedProviderId = localStorage.getItem('ai_provider') || 'chatgpt';
|
|
32
|
+
|
|
33
|
+
// Validate if saved provider still exists
|
|
34
|
+
const isValid = AI_PROVIDERS.some(p => p.id === savedProviderId);
|
|
35
|
+
select.value = isValid ? savedProviderId : AI_PROVIDERS[0].id;
|
|
36
|
+
|
|
37
|
+
// 3. Function to update UI
|
|
38
|
+
const updateProvider = (providerId) => {
|
|
39
|
+
const provider = AI_PROVIDERS.find(p => p.id === providerId) || AI_PROVIDERS[0];
|
|
40
|
+
|
|
41
|
+
iframe.src = provider.url;
|
|
42
|
+
externalLink.href = provider.url;
|
|
43
|
+
placeholderLink.href = provider.url;
|
|
44
|
+
placeholderLink.textContent = `Mở ${provider.name} tab mới ↗`;
|
|
45
|
+
|
|
46
|
+
localStorage.setItem('ai_provider', providerId);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 4. Initial update
|
|
50
|
+
updateProvider(select.value);
|
|
51
|
+
|
|
52
|
+
// 5. Event listener
|
|
53
|
+
select.addEventListener('change', (e) => {
|
|
54
|
+
updateProvider(e.target.value);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -3,6 +3,7 @@ import { SYSTEM_PROMPT } from './config.js';
|
|
|
3
3
|
import { checkHealth } from './api.js';
|
|
4
4
|
import './handlers.js'; // Import to register global functions
|
|
5
5
|
import { showToast, showCopiedState } from './utils.js';
|
|
6
|
+
import { initIframeManager } from './features/iframe-manager.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Initialize application on DOM ready
|
|
@@ -19,6 +20,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
19
20
|
|
|
20
21
|
// Load Extension Path
|
|
21
22
|
loadExtensionPath();
|
|
23
|
+
|
|
24
|
+
// Initialize Iframe Manager (New Feature)
|
|
25
|
+
initIframeManager();
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
/**
|