liuw15 commited on
Commit
d17c19c
·
1 Parent(s): 5e659f0

优化代码结构,减少重复,更新readme

Browse files
README.md CHANGED
@@ -35,12 +35,19 @@ npm install
35
  cp .env.example .env
36
  ```
37
 
38
- 编辑 `.env` 文件配置服务器和 API 参数:
39
 
40
  ```env
41
- PORT=8045
42
- HOST=0.0.0.0
43
  API_KEY=sk-text
 
 
 
 
 
 
 
 
44
  ```
45
 
46
  ### 3. 登录获取 Token
@@ -59,6 +66,65 @@ npm start
59
 
60
  服务将在 `http://localhost:8045` 启动。
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  ## API 使用
63
 
64
  ### 获取模型列表
@@ -195,23 +261,45 @@ curl http://localhost:8045/v1/chat/completions \
195
 
196
  ## 配置说明
197
 
198
- ### 环境变量 (.env)
199
-
200
- | 环境变量 | 说明 | 默认值 |
201
- |--------|------|--------|
202
- | `PORT` | 服务端口 | 8045 |
203
- | `HOST` | 监听地址 | 127.0.0.1 |
204
- | `API_KEY` | API 认证密钥 | - |
205
- | `MAX_REQUEST_SIZE` | 最大请求体大小 | 50mb |
206
- | `DEFAULT_TEMPERATURE` | 默认温度参数 | 1 |
207
- | `DEFAULT_TOP_P` | 默认 top_p | 0.85 |
208
- | `DEFAULT_TOP_K` | 默认 top_k | 50 |
209
- | `DEFAULT_MAX_TOKENS` | 默认最大 token 数 | 8096 |
210
- | `USE_NATIVE_AXIOS` | 使用原生 axios | false |
211
- | `TIMEOUT` | 请求超时时间(毫秒) | 30000 |
212
- | `PROXY` | 代理地址 | - |
213
- | `SYSTEM_INSTRUCTION` | 系统提示词 | - |
214
- | `SKIP_PROJECT_ID_FETCH` | 跳过 API 获取 ProjectId,直接随机生成(Pro 账号可用) | false |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
  完整配置示例请参考 `.env.example` 文件。
217
 
@@ -234,6 +322,10 @@ npm run login
234
  .
235
  ├── data/
236
  │ └── accounts.json # Token 存储(自动生��)
 
 
 
 
237
  ├── scripts/
238
  │ ├── oauth-server.js # OAuth 登录服务
239
  │ └── refresh-tokens.js # Token 刷新脚本
@@ -241,7 +333,10 @@ npm run login
241
  │ ├── api/
242
  │ │ └── client.js # API 调用逻辑
243
  │ ├── auth/
 
244
  │ │ └── token_manager.js # Token 管理
 
 
245
  │ ├── bin/
246
  │ │ ├── antigravity_requester_android_arm64 # Android ARM64 TLS 请求器
247
  │ │ ├── antigravity_requester_linux_amd64 # Linux AMD64 TLS 请求器
@@ -258,8 +353,9 @@ npm run login
258
  ├── test/
259
  │ ├── test-request.js # 请求测试
260
  │ └── test-transform.js # 转换测试
261
- ├── .env # 环境变量配置
262
  ├── .env.example # 环境变量配置示例
 
263
  └── package.json # 项目配置
264
  ```
265
 
@@ -267,9 +363,13 @@ npm run login
267
 
268
  对于 Pro 订阅账号,可以跳过 API 验证直接使用随机生成的 ProjectId:
269
 
270
- 1. 在 `.env` 文件中设置:
271
- ```env
272
- SKIP_PROJECT_ID_FETCH=true
 
 
 
 
273
  ```
274
 
275
  2. 运行 `npm run login` 登录时会自动使用随机生成的 ProjectId
 
35
  cp .env.example .env
36
  ```
37
 
38
+ 编辑 `.env` 文件配置必要参数:
39
 
40
  ```env
41
+ # 必填配置
 
42
  API_KEY=sk-text
43
+ ADMIN_USERNAME=admin
44
+ ADMIN_PASSWORD=admin123
45
+ JWT_SECRET=your-jwt-secret-key-change-this-in-production
46
+
47
+ # 可选配置
48
+ # PROXY=http://127.0.0.1:7897
49
+ # SYSTEM_INSTRUCTION=你是聊天机器人
50
+ # IMAGE_BASE_URL=http://your-domain.com
51
  ```
52
 
53
  ### 3. 登录获取 Token
 
66
 
67
  服务将在 `http://localhost:8045` 启动。
68
 
69
+ ## Web 管理界面
70
+
71
+ 服务启动后,访问 `http://localhost:8045` 即可打开 Web 管理界面。
72
+
73
+ ### 功能特性
74
+
75
+ - 🔐 **安全登录**:JWT Token 认证,保护管理接口
76
+ - 📊 **实时统计**:显示总 Token 数、启用/禁用状态统计
77
+ - ➕ **多种添加方式**:
78
+ - OAuth 授权登录(推荐):自动完成 Google 授权流程
79
+ - 手动填入:直接输入 Access Token 和 Refresh Token
80
+ - 🎯 **Token 管理**:
81
+ - 查看所有 Token 的详细信息(Access Token 后缀、Project ID、过期时间)
82
+ - 一键启用/禁用 Token
83
+ - 删除无效 Token
84
+ - 实时刷新 Token 列表
85
+ - ⚙️ **配置管理**:
86
+ - 在线编辑服务器配置(端口、监听地址)
87
+ - 调整默认参数(温度、Top P/K、最大 Token 数)
88
+ - 修改安全配置(API 密钥、请求大小限制)
89
+ - 配置代理、系统提示词等可选项
90
+ - 热重载配置(部分配置需重启生效)
91
+
92
+ ### 使用流程
93
+
94
+ 1. **登录系统**
95
+ - 使用 `.env` 中配置的 `ADMIN_USERNAME` 和 `ADMIN_PASSWORD` 登录
96
+ - 登录成功后会自动保存 JWT Token 到浏览器
97
+
98
+ 2. **添加 Token**
99
+ - **OAuth 方式**(推荐):
100
+ 1. 点击「OAuth登录」按钮
101
+ 2. 在弹窗中点击「打开授权页面」
102
+ 3. 在新窗口完成 Google 授权
103
+ 4. 复制浏览器地址栏的完整回调 URL
104
+ 5. 粘贴到输入框并提交
105
+ - **手动方式**:
106
+ 1. 点击「手动填入」按钮
107
+ 2. 填写 Access Token、Refresh Token 和过期时间
108
+ 3. 提交保存
109
+
110
+ 3. **管理 Token**
111
+ - 查看 Token 卡片显示的状态和信息
112
+ - 使用「启用/禁用」按钮控制 Token 状态
113
+ - 使用「删除」按钮移除无效 Token
114
+ - 点击「刷新」按钮更新列表
115
+
116
+ 4. **修改配置**
117
+ - 切换到「设置」标签页
118
+ - 修改需要调整的配置项
119
+ - 点击「保存配置」按钮应用更改
120
+ - 注意:端口和监听地址修改需要重启服务
121
+
122
+ ### 界面预览
123
+
124
+ - **Token 管理页面**:卡片式展示所有 Token,支持快速操作
125
+ - **设置页面**:分类展示所有配置项,支持在线编辑
126
+ - **响应式设计**:支持桌面和移动设备访问
127
+
128
  ## API 使用
129
 
130
  ### 获取模型列表
 
261
 
262
  ## 配置说明
263
 
264
+ 项目配置分为两部分:
265
+
266
+ ### 1. config.json(基础配置)
267
+
268
+ 基础配置文件,包含服务器、API 和默认参数设置:
269
+
270
+ ```json
271
+ {
272
+ "server": {
273
+ "port": 8045, // 服务端口
274
+ "host": "0.0.0.0", // 监听地址
275
+ "maxRequestSize": "500mb" // 最大请求体大小
276
+ },
277
+ "defaults": {
278
+ "temperature": 1, // 默认温度参数
279
+ "topP": 0.85, // 默认 top_p
280
+ "topK": 50, // 默认 top_k
281
+ "maxTokens": 8096 // 默认最大 token 数
282
+ },
283
+ "other": {
284
+ "timeout": 180000, // 请求超时时间(毫秒)
285
+ "skipProjectIdFetch": true // 跳过 ProjectId 获取,直接随机生成
286
+ }
287
+ }
288
+ ```
289
+
290
+ ### 2. .env(敏感配置)
291
+
292
+ 环境变量配置文件,包含敏感信息和可选配置:
293
+
294
+ | 环境变量 | 说明 | 必填 |
295
+ |--------|------|------|
296
+ | `API_KEY` | API 认证密钥 | ✅ |
297
+ | `ADMIN_USERNAME` | 管理员用户名 | ✅ |
298
+ | `ADMIN_PASSWORD` | 管理员密码 | ✅ |
299
+ | `JWT_SECRET` | JWT 密钥 | ✅ |
300
+ | `PROXY` | 代理地址(如:http://127.0.0.1:7897) | ❌ |
301
+ | `SYSTEM_INSTRUCTION` | 系统提示词 | ❌ |
302
+ | `IMAGE_BASE_URL` | 图片服务基础 URL | ❌ |
303
 
304
  完整配置示例请参考 `.env.example` 文件。
305
 
 
322
  .
323
  ├── data/
324
  │ └── accounts.json # Token 存储(自动生��)
325
+ ├── public/
326
+ │ ├── index.html # Web 管理界面
327
+ │ ├── app.js # 前端逻辑
328
+ │ └── style.css # 界面样式
329
  ├── scripts/
330
  │ ├── oauth-server.js # OAuth 登录服务
331
  │ └── refresh-tokens.js # Token 刷新脚本
 
333
  │ ├── api/
334
  │ │ └── client.js # API 调用逻辑
335
  │ ├── auth/
336
+ │ │ ├── jwt.js # JWT 认证
337
  │ │ └── token_manager.js # Token 管理
338
+ │ ├── routes/
339
+ │ │ └── admin.js # 管理接口路由
340
  │ ├── bin/
341
  │ │ ├── antigravity_requester_android_arm64 # Android ARM64 TLS 请求器
342
  │ │ ├── antigravity_requester_linux_amd64 # Linux AMD64 TLS 请求器
 
353
  ├── test/
354
  │ ├── test-request.js # 请求测试
355
  │ └── test-transform.js # 转换测试
356
+ ├── .env # 环境变量配置(敏感信息)
357
  ├── .env.example # 环境变量配置示例
358
+ ├── config.json # 基础配置文件
359
  └── package.json # 项目配置
360
  ```
361
 
 
363
 
364
  对于 Pro 订阅账号,可以跳过 API 验证直接使用随机生成的 ProjectId:
365
 
366
+ 1. 在 `config.json` 文件中设置:
367
+ ```json
368
+ {
369
+ "other": {
370
+ "skipProjectIdFetch": true
371
+ }
372
+ }
373
  ```
374
 
375
  2. 运行 `npm run login` 登录时会自动使用随机生成的 ProjectId
config.json CHANGED
@@ -4,7 +4,13 @@
4
  "host": "0.0.0.0",
5
  "maxRequestSize": "500mb"
6
  },
7
- "api": {},
 
 
 
 
 
 
8
  "defaults": {
9
  "temperature": 1,
10
  "topP": 0.85,
@@ -13,6 +19,6 @@
13
  },
14
  "other": {
15
  "timeout": 180000,
16
- "skipProjectIdFetch": true
17
  }
18
  }
 
4
  "host": "0.0.0.0",
5
  "maxRequestSize": "500mb"
6
  },
7
+ "api": {
8
+ "url": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse",
9
+ "modelsUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels",
10
+ "noStreamUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent",
11
+ "host": "daily-cloudcode-pa.sandbox.googleapis.com",
12
+ "userAgent": "antigravity/1.11.3 windows/amd64"
13
+ },
14
  "defaults": {
15
  "temperature": 1,
16
  "topP": 0.85,
 
19
  },
20
  "other": {
21
  "timeout": 180000,
22
+ "skipProjectIdFetch": false
23
  }
24
  }
src/auth/token_manager.js CHANGED
@@ -5,13 +5,11 @@ import axios from 'axios';
5
  import { log } from '../utils/logger.js';
6
  import { generateSessionId, generateProjectId } from '../utils/idGenerator.js';
7
  import config from '../config/config.js';
 
8
 
9
  const __filename = fileURLToPath(import.meta.url);
10
  const __dirname = path.dirname(__filename);
11
 
12
- const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
13
- const CLIENT_SECRET = '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';
14
-
15
  class TokenManager {
16
  constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) {
17
  this.filePath = filePath;
@@ -87,8 +85,8 @@ class TokenManager {
87
  async refreshToken(token) {
88
  log.info('正在刷新token...');
89
  const body = new URLSearchParams({
90
- client_id: CLIENT_ID,
91
- client_secret: CLIENT_SECRET,
92
  grant_type: 'refresh_token',
93
  refresh_token: token.refresh_token
94
  });
@@ -96,7 +94,7 @@ class TokenManager {
96
  try {
97
  const response = await axios({
98
  method: 'POST',
99
- url: 'https://oauth2.googleapis.com/token',
100
  headers: {
101
  'Host': 'oauth2.googleapis.com',
102
  'User-Agent': 'Go-http-client/1.1',
 
5
  import { log } from '../utils/logger.js';
6
  import { generateSessionId, generateProjectId } from '../utils/idGenerator.js';
7
  import config from '../config/config.js';
8
+ import { OAUTH_CONFIG } from '../constants/oauth.js';
9
 
10
  const __filename = fileURLToPath(import.meta.url);
11
  const __dirname = path.dirname(__filename);
12
 
 
 
 
13
  class TokenManager {
14
  constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) {
15
  this.filePath = filePath;
 
85
  async refreshToken(token) {
86
  log.info('正在刷新token...');
87
  const body = new URLSearchParams({
88
+ client_id: OAUTH_CONFIG.CLIENT_ID,
89
+ client_secret: OAUTH_CONFIG.CLIENT_SECRET,
90
  grant_type: 'refresh_token',
91
  refresh_token: token.refresh_token
92
  });
 
94
  try {
95
  const response = await axios({
96
  method: 'POST',
97
+ url: OAUTH_CONFIG.TOKEN_URL,
98
  headers: {
99
  'Host': 'oauth2.googleapis.com',
100
  'User-Agent': 'Go-http-client/1.1',
src/constants/oauth.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Google OAuth 凭证
3
+ * 统一管理,避免在多个文件中重复定义
4
+ */
5
+ export const OAUTH_CONFIG = {
6
+ CLIENT_ID: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com',
7
+ CLIENT_SECRET: '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',
8
+ TOKEN_URL: 'https://oauth2.googleapis.com/token'
9
+ };
src/routes/admin.js CHANGED
@@ -4,8 +4,10 @@ import tokenManager from '../auth/token_manager.js';
4
  import config, { getConfigJson, saveConfigJson } from '../config/config.js';
5
  import logger from '../utils/logger.js';
6
  import { generateProjectId } from '../utils/idGenerator.js';
7
- import axios from 'axios';
8
- import fs from 'fs';
 
 
9
  import path from 'path';
10
  import { fileURLToPath } from 'url';
11
  import dotenv from 'dotenv';
@@ -13,7 +15,6 @@ import dotenv from 'dotenv';
13
  const __filename = fileURLToPath(import.meta.url);
14
  const __dirname = path.dirname(__filename);
15
  const envPath = path.join(__dirname, '../../.env');
16
- const configJsonPath = path.join(__dirname, '../../config.json');
17
 
18
  const router = express.Router();
19
 
@@ -78,19 +79,16 @@ router.post('/oauth/exchange', authMiddleware, async (req, res) => {
78
  return res.status(400).json({ success: false, message: 'code和port必填' });
79
  }
80
 
81
- const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
82
- const CLIENT_SECRET = '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';
83
-
84
  try {
85
  const postData = new URLSearchParams({
86
  code,
87
- client_id: CLIENT_ID,
88
- client_secret: CLIENT_SECRET,
89
  redirect_uri: `http://localhost:${port}/oauth-callback`,
90
  grant_type: 'authorization_code'
91
  });
92
 
93
- const response = await fetch('https://oauth2.googleapis.com/token', {
94
  method: 'POST',
95
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
96
  body: postData.toString()
@@ -115,25 +113,7 @@ router.post('/oauth/exchange', authMiddleware, async (req, res) => {
115
  logger.info('使用随机生成的projectId: ' + account.projectId);
116
  } else {
117
  try {
118
- const projectResponse = await axios({
119
- method: 'POST',
120
- url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist',
121
- headers: {
122
- 'Host': 'daily-cloudcode-pa.sandbox.googleapis.com',
123
- 'User-Agent': 'antigravity/1.11.9 windows/amd64',
124
- 'Authorization': `Bearer ${account.access_token}`,
125
- 'Content-Type': 'application/json',
126
- 'Accept-Encoding': 'gzip'
127
- },
128
- data: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),
129
- timeout: config.timeout,
130
- proxy: config.proxy ? (() => {
131
- const proxyUrl = new URL(config.proxy);
132
- return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) };
133
- })() : false
134
- });
135
-
136
- const projectId = projectResponse.data?.cloudaicompanionProject;
137
  if (projectId === undefined) {
138
  return res.status(400).json({ success: false, message: '该账号无资格使用(无法获取projectId)' });
139
  }
@@ -155,16 +135,7 @@ router.post('/oauth/exchange', authMiddleware, async (req, res) => {
155
  // 获取配置
156
  router.get('/config', authMiddleware, (req, res) => {
157
  try {
158
- const envData = {};
159
- const envContent = fs.readFileSync(envPath, 'utf8');
160
- envContent.split('\n').forEach(line => {
161
- line = line.trim();
162
- if (line && !line.startsWith('#')) {
163
- const [key, ...valueParts] = line.split('=');
164
- if (key) envData[key.trim()] = valueParts.join('=').trim();
165
- }
166
- });
167
-
168
  const jsonData = getConfigJson();
169
  res.json({ success: true, data: { env: envData, json: jsonData } });
170
  } catch (error) {
@@ -178,48 +149,18 @@ router.put('/config', authMiddleware, (req, res) => {
178
  try {
179
  const { env: envUpdates, json: jsonUpdates } = req.body;
180
 
181
- // 更新 .env(只保留敏感信息)
182
  if (envUpdates) {
183
- let envContent = fs.readFileSync(envPath, 'utf8');
184
- Object.entries(envUpdates).forEach(([key, value]) => {
185
- const regex = new RegExp(`^${key}=.*$`, 'm');
186
- if (regex.test(envContent)) {
187
- envContent = envContent.replace(regex, `${key}=${value}`);
188
- } else {
189
- envContent += `\n${key}=${value}`;
190
- }
191
- });
192
- fs.writeFileSync(envPath, envContent, 'utf8');
193
  }
194
 
195
- // 更新 config.json
196
  if (jsonUpdates) {
197
- saveConfigJson(jsonUpdates);
 
 
198
  }
199
 
200
- // 重新加载环境变量
201
  dotenv.config({ override: true });
202
-
203
- // 更新config对象
204
- const jsonConfig = getConfigJson();
205
- config.server.port = jsonConfig.server?.port || 8045;
206
- config.server.host = jsonConfig.server?.host || '0.0.0.0';
207
- config.defaults.temperature = jsonConfig.defaults?.temperature || 1;
208
- config.defaults.top_p = jsonConfig.defaults?.topP || 0.85;
209
- config.defaults.top_k = jsonConfig.defaults?.topK || 50;
210
- config.defaults.max_tokens = jsonConfig.defaults?.maxTokens || 8096;
211
- config.security.apiKey = process.env.API_KEY || null;
212
- config.timeout = jsonConfig.other?.timeout || 180000;
213
- config.proxy = process.env.PROXY || null;
214
- config.systemInstruction = process.env.SYSTEM_INSTRUCTION || '';
215
- config.skipProjectIdFetch = jsonConfig.other?.skipProjectIdFetch === true;
216
- config.maxImages = jsonConfig.other?.maxImages || 10;
217
- config.useNativeAxios = jsonConfig.other?.useNativeAxios !== false;
218
- config.api.url = jsonConfig.api?.url || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse';
219
- config.api.modelsUrl = jsonConfig.api?.modelsUrl || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels';
220
- config.api.noStreamUrl = jsonConfig.api?.noStreamUrl || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent';
221
- config.api.host = jsonConfig.api?.host || 'daily-cloudcode-pa.sandbox.googleapis.com';
222
- config.api.userAgent = jsonConfig.api?.userAgent || 'antigravity/1.11.3 windows/amd64';
223
 
224
  logger.info('配置已更新并热重载');
225
  res.json({ success: true, message: '配置已保存并生效(端口/HOST修改需重启)' });
 
4
  import config, { getConfigJson, saveConfigJson } from '../config/config.js';
5
  import logger from '../utils/logger.js';
6
  import { generateProjectId } from '../utils/idGenerator.js';
7
+ import { parseEnvFile, updateEnvFile } from '../utils/envParser.js';
8
+ import { reloadConfig } from '../utils/configReloader.js';
9
+ import { OAUTH_CONFIG } from '../constants/oauth.js';
10
+ import { deepMerge } from '../utils/deepMerge.js';
11
  import path from 'path';
12
  import { fileURLToPath } from 'url';
13
  import dotenv from 'dotenv';
 
15
  const __filename = fileURLToPath(import.meta.url);
16
  const __dirname = path.dirname(__filename);
17
  const envPath = path.join(__dirname, '../../.env');
 
18
 
19
  const router = express.Router();
20
 
 
79
  return res.status(400).json({ success: false, message: 'code和port必填' });
80
  }
81
 
 
 
 
82
  try {
83
  const postData = new URLSearchParams({
84
  code,
85
+ client_id: OAUTH_CONFIG.CLIENT_ID,
86
+ client_secret: OAUTH_CONFIG.CLIENT_SECRET,
87
  redirect_uri: `http://localhost:${port}/oauth-callback`,
88
  grant_type: 'authorization_code'
89
  });
90
 
91
+ const response = await fetch(OAUTH_CONFIG.TOKEN_URL, {
92
  method: 'POST',
93
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
94
  body: postData.toString()
 
113
  logger.info('使用随机生成的projectId: ' + account.projectId);
114
  } else {
115
  try {
116
+ const projectId = await tokenManager.fetchProjectId(account);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  if (projectId === undefined) {
118
  return res.status(400).json({ success: false, message: '该账号无资格使用(无法获取projectId)' });
119
  }
 
135
  // 获取配置
136
  router.get('/config', authMiddleware, (req, res) => {
137
  try {
138
+ const envData = parseEnvFile(envPath);
 
 
 
 
 
 
 
 
 
139
  const jsonData = getConfigJson();
140
  res.json({ success: true, data: { env: envData, json: jsonData } });
141
  } catch (error) {
 
149
  try {
150
  const { env: envUpdates, json: jsonUpdates } = req.body;
151
 
 
152
  if (envUpdates) {
153
+ updateEnvFile(envPath, envUpdates);
 
 
 
 
 
 
 
 
 
154
  }
155
 
 
156
  if (jsonUpdates) {
157
+ const currentConfig = getConfigJson();
158
+ const mergedConfig = deepMerge(currentConfig, jsonUpdates);
159
+ saveConfigJson(mergedConfig);
160
  }
161
 
 
162
  dotenv.config({ override: true });
163
+ reloadConfig();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  logger.info('配置已更新并热重载');
166
  res.json({ success: true, message: '配置已保存并生效(端口/HOST修改需重启)' });
src/utils/configReloader.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import config, { getConfigJson } from '../config/config.js';
2
+
3
+ /**
4
+ * 配置字段映射表:config对象路径 -> config.json路径 / 环境变量
5
+ */
6
+ const CONFIG_MAPPING = [
7
+ { target: 'server.port', source: 'server.port', default: 8045 },
8
+ { target: 'server.host', source: 'server.host', default: '0.0.0.0' },
9
+ { target: 'defaults.temperature', source: 'defaults.temperature', default: 1 },
10
+ { target: 'defaults.top_p', source: 'defaults.topP', default: 0.85 },
11
+ { target: 'defaults.top_k', source: 'defaults.topK', default: 50 },
12
+ { target: 'defaults.max_tokens', source: 'defaults.maxTokens', default: 8096 },
13
+ { target: 'timeout', source: 'other.timeout', default: 180000 },
14
+ { target: 'skipProjectIdFetch', source: 'other.skipProjectIdFetch', default: false, transform: v => v === true },
15
+ { target: 'maxImages', source: 'other.maxImages', default: 10 },
16
+ { target: 'useNativeAxios', source: 'other.useNativeAxios', default: true, transform: v => v !== false },
17
+ { target: 'api.url', source: 'api.url', default: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse' },
18
+ { target: 'api.modelsUrl', source: 'api.modelsUrl', default: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels' },
19
+ { target: 'api.noStreamUrl', source: 'api.noStreamUrl', default: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent' },
20
+ { target: 'api.host', source: 'api.host', default: 'daily-cloudcode-pa.sandbox.googleapis.com' },
21
+ { target: 'api.userAgent', source: 'api.userAgent', default: 'antigravity/1.11.3 windows/amd64' }
22
+ ];
23
+
24
+ const ENV_MAPPING = [
25
+ { target: 'security.apiKey', env: 'API_KEY', default: null },
26
+ { target: 'proxy', env: 'PROXY', default: null },
27
+ { target: 'systemInstruction', env: 'SYSTEM_INSTRUCTION', default: '' }
28
+ ];
29
+
30
+ /**
31
+ * 从嵌套路径获取值
32
+ */
33
+ function getNestedValue(obj, path) {
34
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
35
+ }
36
+
37
+ /**
38
+ * 设置嵌套路径的值
39
+ */
40
+ function setNestedValue(obj, path, value) {
41
+ const keys = path.split('.');
42
+ const lastKey = keys.pop();
43
+ const target = keys.reduce((acc, key) => acc[key], obj);
44
+ target[lastKey] = value;
45
+ }
46
+
47
+ /**
48
+ * 重新加载配置到 config 对象
49
+ */
50
+ export function reloadConfig() {
51
+ const jsonConfig = getConfigJson();
52
+
53
+ // 更新 JSON 配置
54
+ CONFIG_MAPPING.forEach(({ target, source, default: defaultValue, transform }) => {
55
+ let value = getNestedValue(jsonConfig, source) ?? defaultValue;
56
+ if (transform) value = transform(value);
57
+ setNestedValue(config, target, value);
58
+ });
59
+
60
+ // 更新环境变量配置
61
+ ENV_MAPPING.forEach(({ target, env, default: defaultValue }) => {
62
+ const value = process.env[env] || defaultValue;
63
+ setNestedValue(config, target, value);
64
+ });
65
+ }
src/utils/deepMerge.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 深度合并对象,保留未修改的字段
3
+ */
4
+ export function deepMerge(target, source) {
5
+ const result = { ...target };
6
+
7
+ for (const key in source) {
8
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
9
+ result[key] = deepMerge(target[key] || {}, source[key]);
10
+ } else {
11
+ result[key] = source[key];
12
+ }
13
+ }
14
+
15
+ return result;
16
+ }
src/utils/envParser.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+
3
+ /**
4
+ * 解析 .env 文件内容为对象
5
+ */
6
+ export function parseEnvFile(filePath) {
7
+ const envData = {};
8
+ const content = fs.readFileSync(filePath, 'utf8');
9
+
10
+ content.split('\n').forEach(line => {
11
+ line = line.trim();
12
+ if (line && !line.startsWith('#')) {
13
+ const [key, ...valueParts] = line.split('=');
14
+ if (key) {
15
+ envData[key.trim()] = valueParts.join('=').trim();
16
+ }
17
+ }
18
+ });
19
+
20
+ return envData;
21
+ }
22
+
23
+ /**
24
+ * 更新 .env 文件中的键值对
25
+ */
26
+ export function updateEnvFile(filePath, updates) {
27
+ let content = fs.readFileSync(filePath, 'utf8');
28
+
29
+ Object.entries(updates).forEach(([key, value]) => {
30
+ const regex = new RegExp(`^${key}=.*$`, 'm');
31
+ if (regex.test(content)) {
32
+ content = content.replace(regex, `${key}=${value}`);
33
+ } else {
34
+ content += `\n${key}=${value}`;
35
+ }
36
+ });
37
+
38
+ fs.writeFileSync(filePath, content, 'utf8');
39
+ }