@adversity/coding-tool-x 2.2.0
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/CHANGELOG.md +333 -0
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
- package/dist/web/assets/index-aL3cKxSK.css +41 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/logo.png +0 -0
- package/docs/CHANGELOG.md +582 -0
- package/docs/DIRECTORY_MIGRATION.md +112 -0
- package/docs/PROJECT_STRUCTURE.md +396 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +73 -0
- package/src/commands/channels.js +504 -0
- package/src/commands/cli-type.js +99 -0
- package/src/commands/daemon.js +286 -0
- package/src/commands/doctor.js +332 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +259 -0
- package/src/commands/port-config.js +115 -0
- package/src/commands/proxy-control.js +258 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/stats.js +224 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +222 -0
- package/src/commands/ui.js +92 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +40 -0
- package/src/config/loader.js +75 -0
- package/src/config/paths.js +121 -0
- package/src/index.js +373 -0
- package/src/reset-config.js +92 -0
- package/src/server/api/agents.js +248 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +258 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +312 -0
- package/src/server/api/codex-projects.js +91 -0
- package/src/server/api/codex-proxy.js +182 -0
- package/src/server/api/codex-sessions.js +491 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +245 -0
- package/src/server/api/config-templates.js +182 -0
- package/src/server/api/config.js +147 -0
- package/src/server/api/convert.js +127 -0
- package/src/server/api/dashboard.js +125 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +261 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +160 -0
- package/src/server/api/gemini-sessions.js +397 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +118 -0
- package/src/server/api/mcp.js +336 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +235 -0
- package/src/server/api/rules.js +271 -0
- package/src/server/api/sessions.js +595 -0
- package/src/server/api/settings.js +61 -0
- package/src/server/api/skills.js +305 -0
- package/src/server/api/statistics.js +91 -0
- package/src/server/api/terminal.js +202 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +407 -0
- package/src/server/codex-proxy-server.js +538 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +518 -0
- package/src/server/index.js +305 -0
- package/src/server/proxy-server.js +469 -0
- package/src/server/services/agents-service.js +354 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +234 -0
- package/src/server/services/channels.js +347 -0
- package/src/server/services/codex-channels.js +625 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +665 -0
- package/src/server/services/codex-settings-manager.js +397 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +255 -0
- package/src/server/services/commands-service.js +360 -0
- package/src/server/services/config-templates-service.js +732 -0
- package/src/server/services/env-checker.js +307 -0
- package/src/server/services/env-manager.js +300 -0
- package/src/server/services/favorites.js +163 -0
- package/src/server/services/gemini-channels.js +333 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +253 -0
- package/src/server/services/health-check.js +399 -0
- package/src/server/services/mcp-service.js +1188 -0
- package/src/server/services/prompts-service.js +492 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/pty-manager.js +435 -0
- package/src/server/services/rules-service.js +401 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +757 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +965 -0
- package/src/server/services/speed-test.js +545 -0
- package/src/server/services/statistics-service.js +386 -0
- package/src/server/services/terminal-commands.js +155 -0
- package/src/server/services/terminal-config.js +140 -0
- package/src/server/services/terminal-detector.js +306 -0
- package/src/server/services/ui-config.js +130 -0
- package/src/server/services/workspace-service.js +662 -0
- package/src/server/utils/pricing.js +41 -0
- package/src/server/websocket-server.js +557 -0
- package/src/ui/menu.js +129 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +94 -0
- package/src/utils/session.js +239 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
// Gemini 配置文件路径
|
|
6
|
+
function getEnvPath() {
|
|
7
|
+
return path.join(os.homedir(), '.gemini', '.env');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getSettingsPath() {
|
|
11
|
+
return path.join(os.homedir(), '.gemini', 'settings.json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 备份文件路径
|
|
15
|
+
function getEnvBackupPath() {
|
|
16
|
+
return path.join(os.homedir(), '.gemini', '.env.cc-tool-backup');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getSettingsBackupPath() {
|
|
20
|
+
return path.join(os.homedir(), '.gemini', 'settings.json.cc-tool-backup');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 检查配置文件是否存在
|
|
24
|
+
function configExists() {
|
|
25
|
+
return fs.existsSync(getEnvPath());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function settingsExists() {
|
|
29
|
+
return fs.existsSync(getSettingsPath());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 检查是否已经有备份
|
|
33
|
+
function hasBackup() {
|
|
34
|
+
return fs.existsSync(getEnvBackupPath()) || fs.existsSync(getSettingsBackupPath());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 读取 .env
|
|
38
|
+
function readEnv() {
|
|
39
|
+
try {
|
|
40
|
+
const content = fs.readFileSync(getEnvPath(), 'utf8');
|
|
41
|
+
const env = {};
|
|
42
|
+
|
|
43
|
+
content.split('\n').forEach(line => {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (!trimmed || trimmed.startsWith('#')) return;
|
|
46
|
+
|
|
47
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
48
|
+
if (match) {
|
|
49
|
+
env[match[1].trim()] = match[2].trim();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return env;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
throw new Error('Failed to read .env: ' + err.message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 将环境对象转换为 .env 字符串
|
|
60
|
+
function envToString(env) {
|
|
61
|
+
let content = '';
|
|
62
|
+
for (const [key, value] of Object.entries(env)) {
|
|
63
|
+
content += `${key}=${value}\n`;
|
|
64
|
+
}
|
|
65
|
+
return content;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 写入 .env
|
|
69
|
+
function writeEnv(env) {
|
|
70
|
+
try {
|
|
71
|
+
const content = envToString(env);
|
|
72
|
+
fs.writeFileSync(getEnvPath(), content, 'utf8');
|
|
73
|
+
|
|
74
|
+
// 设置文件权限为 600 (仅所有者可读写)
|
|
75
|
+
if (process.platform !== 'win32') {
|
|
76
|
+
fs.chmodSync(getEnvPath(), 0o600);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
throw new Error('Failed to write .env: ' + err.message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 读取 settings.json
|
|
84
|
+
function readSettings() {
|
|
85
|
+
try {
|
|
86
|
+
if (!settingsExists()) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const content = fs.readFileSync(getSettingsPath(), 'utf8');
|
|
90
|
+
return JSON.parse(content);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
throw new Error('Failed to read settings.json: ' + err.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 写入 settings.json
|
|
97
|
+
function writeSettings(settings) {
|
|
98
|
+
try {
|
|
99
|
+
const content = JSON.stringify(settings, null, 2);
|
|
100
|
+
fs.writeFileSync(getSettingsPath(), content, 'utf8');
|
|
101
|
+
} catch (err) {
|
|
102
|
+
throw new Error('Failed to write settings.json: ' + err.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 备份当前配置
|
|
107
|
+
function backupSettings() {
|
|
108
|
+
try {
|
|
109
|
+
if (!configExists()) {
|
|
110
|
+
throw new Error('.env not found');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 如果已经有备份,不覆盖
|
|
114
|
+
if (hasBackup()) {
|
|
115
|
+
console.log('Backup already exists, skipping backup');
|
|
116
|
+
return { success: true, alreadyExists: true };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 备份 .env
|
|
120
|
+
const envContent = fs.readFileSync(getEnvPath(), 'utf8');
|
|
121
|
+
fs.writeFileSync(getEnvBackupPath(), envContent, 'utf8');
|
|
122
|
+
|
|
123
|
+
// 设置备份文件权限为 600
|
|
124
|
+
if (process.platform !== 'win32') {
|
|
125
|
+
fs.chmodSync(getEnvBackupPath(), 0o600);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 备份 settings.json (如果存在)
|
|
129
|
+
if (settingsExists()) {
|
|
130
|
+
const settingsContent = fs.readFileSync(getSettingsPath(), 'utf8');
|
|
131
|
+
fs.writeFileSync(getSettingsBackupPath(), settingsContent, 'utf8');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log('Gemini settings backed up');
|
|
135
|
+
return { success: true, alreadyExists: false };
|
|
136
|
+
} catch (err) {
|
|
137
|
+
throw new Error('Failed to backup settings: ' + err.message);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 恢复配置
|
|
142
|
+
function restoreSettings() {
|
|
143
|
+
try {
|
|
144
|
+
if (!hasBackup()) {
|
|
145
|
+
throw new Error('No backup found');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 恢复 .env
|
|
149
|
+
if (fs.existsSync(getEnvBackupPath())) {
|
|
150
|
+
const content = fs.readFileSync(getEnvBackupPath(), 'utf8');
|
|
151
|
+
fs.writeFileSync(getEnvPath(), content, 'utf8');
|
|
152
|
+
fs.unlinkSync(getEnvBackupPath());
|
|
153
|
+
|
|
154
|
+
// 设置文件权限为 600
|
|
155
|
+
if (process.platform !== 'win32') {
|
|
156
|
+
fs.chmodSync(getEnvPath(), 0o600);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 恢复 settings.json
|
|
161
|
+
if (fs.existsSync(getSettingsBackupPath())) {
|
|
162
|
+
const content = fs.readFileSync(getSettingsBackupPath(), 'utf8');
|
|
163
|
+
fs.writeFileSync(getSettingsPath(), content, 'utf8');
|
|
164
|
+
fs.unlinkSync(getSettingsBackupPath());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('Gemini settings restored from backup');
|
|
168
|
+
return { success: true };
|
|
169
|
+
} catch (err) {
|
|
170
|
+
throw new Error('Failed to restore settings: ' + err.message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 设置代理配置
|
|
175
|
+
function setProxyConfig(proxyPort) {
|
|
176
|
+
try {
|
|
177
|
+
// 先备份
|
|
178
|
+
backupSettings();
|
|
179
|
+
|
|
180
|
+
// 读取当前配置
|
|
181
|
+
const env = configExists() ? readEnv() : {};
|
|
182
|
+
|
|
183
|
+
// 设置代理 URL
|
|
184
|
+
env.GOOGLE_GEMINI_BASE_URL = `http://127.0.0.1:${proxyPort}`;
|
|
185
|
+
env.GEMINI_API_KEY = 'PROXY_KEY';
|
|
186
|
+
// 保留或设置默认模型
|
|
187
|
+
if (!env.GEMINI_MODEL) {
|
|
188
|
+
env.GEMINI_MODEL = 'gemini-2.5-pro';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 写入 .env
|
|
192
|
+
writeEnv(env);
|
|
193
|
+
|
|
194
|
+
// 确保 settings.json 存在并配置正确的认证模式
|
|
195
|
+
const settings = settingsExists() ? readSettings() : {};
|
|
196
|
+
settings.security = settings.security || {};
|
|
197
|
+
settings.security.auth = settings.security.auth || {};
|
|
198
|
+
settings.security.auth.selectedType = 'gemini-api-key';
|
|
199
|
+
|
|
200
|
+
writeSettings(settings);
|
|
201
|
+
|
|
202
|
+
console.log(`Gemini settings updated to use proxy on port ${proxyPort}`);
|
|
203
|
+
return { success: true, port: proxyPort };
|
|
204
|
+
} catch (err) {
|
|
205
|
+
throw new Error('Failed to set proxy config: ' + err.message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 检查当前是否是代理配置
|
|
210
|
+
function isProxyConfig() {
|
|
211
|
+
try {
|
|
212
|
+
if (!configExists()) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const env = readEnv();
|
|
217
|
+
|
|
218
|
+
// 检查 GOOGLE_GEMINI_BASE_URL 是否指向本地代理
|
|
219
|
+
const baseUrl = env.GOOGLE_GEMINI_BASE_URL || '';
|
|
220
|
+
if (baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost')) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return false;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 获取当前代理端口(如果是代理配置)
|
|
231
|
+
function getCurrentProxyPort() {
|
|
232
|
+
try {
|
|
233
|
+
if (!isProxyConfig()) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const env = readEnv();
|
|
238
|
+
const baseUrl = env.GOOGLE_GEMINI_BASE_URL || '';
|
|
239
|
+
const match = baseUrl.match(/:(\d+)/);
|
|
240
|
+
return match ? parseInt(match[1]) : null;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
getEnvPath,
|
|
248
|
+
getSettingsPath,
|
|
249
|
+
getEnvBackupPath,
|
|
250
|
+
getSettingsBackupPath,
|
|
251
|
+
configExists,
|
|
252
|
+
settingsExists,
|
|
253
|
+
hasBackup,
|
|
254
|
+
readEnv,
|
|
255
|
+
writeEnv,
|
|
256
|
+
readSettings,
|
|
257
|
+
writeSettings,
|
|
258
|
+
backupSettings,
|
|
259
|
+
restoreSettings,
|
|
260
|
+
setProxyConfig,
|
|
261
|
+
isProxyConfig,
|
|
262
|
+
getCurrentProxyPort
|
|
263
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gemini 统计服务 - 数据采集和存储
|
|
7
|
+
*
|
|
8
|
+
* 文件结构:
|
|
9
|
+
* ~/.claude/cc-tool/
|
|
10
|
+
* ├── gemini-statistics.json # Gemini 总体统计
|
|
11
|
+
* └── gemini-daily-stats/
|
|
12
|
+
* ├── 2025-12-05.json # 每日汇总统计
|
|
13
|
+
* └── ...
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// 获取基础目录
|
|
17
|
+
function getBaseDir() {
|
|
18
|
+
const dir = path.join(os.homedir(), '.claude', 'cc-tool');
|
|
19
|
+
if (!fs.existsSync(dir)) {
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 获取每日统计目录
|
|
26
|
+
function getDailyStatsDir() {
|
|
27
|
+
const dir = path.join(getBaseDir(), 'gemini-daily-stats');
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 获取统计文件路径
|
|
35
|
+
function getStatisticsFilePath() {
|
|
36
|
+
return path.join(getBaseDir(), 'gemini-statistics.json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 获取每日统计文件路径
|
|
40
|
+
function getDailyStatsFilePath(date) {
|
|
41
|
+
return path.join(getDailyStatsDir(), `${date}.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 初始化统计对象
|
|
45
|
+
function initStatsObject() {
|
|
46
|
+
return {
|
|
47
|
+
requests: 0,
|
|
48
|
+
tokens: {
|
|
49
|
+
input: 0,
|
|
50
|
+
output: 0,
|
|
51
|
+
cached: 0,
|
|
52
|
+
total: 0
|
|
53
|
+
},
|
|
54
|
+
cost: 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 加载总体统计
|
|
59
|
+
function loadStatistics() {
|
|
60
|
+
const filePath = getStatisticsFilePath();
|
|
61
|
+
try {
|
|
62
|
+
if (fs.existsSync(filePath)) {
|
|
63
|
+
const data = fs.readFileSync(filePath, 'utf8');
|
|
64
|
+
return JSON.parse(data);
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error('[Gemini Statistics] Failed to load statistics:', err);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
version: '1.0',
|
|
72
|
+
lastUpdated: new Date().toISOString(),
|
|
73
|
+
global: {
|
|
74
|
+
totalRequests: 0,
|
|
75
|
+
totalTokens: 0,
|
|
76
|
+
totalCost: 0
|
|
77
|
+
},
|
|
78
|
+
byChannel: {},
|
|
79
|
+
byModel: {}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 保存总体统计
|
|
84
|
+
function saveStatistics(stats) {
|
|
85
|
+
const filePath = getStatisticsFilePath();
|
|
86
|
+
stats.lastUpdated = new Date().toISOString();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(filePath, JSON.stringify(stats, null, 2), 'utf8');
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error('[Gemini Statistics] Failed to save statistics:', err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 加载每日统计
|
|
96
|
+
function loadDailyStats(date) {
|
|
97
|
+
const filePath = getDailyStatsFilePath(date);
|
|
98
|
+
try {
|
|
99
|
+
if (fs.existsSync(filePath)) {
|
|
100
|
+
const data = fs.readFileSync(filePath, 'utf8');
|
|
101
|
+
return JSON.parse(data);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error('[Gemini Statistics] Failed to load daily stats:', err);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
date: date,
|
|
109
|
+
summary: {
|
|
110
|
+
requests: 0,
|
|
111
|
+
tokens: 0,
|
|
112
|
+
cost: 0
|
|
113
|
+
},
|
|
114
|
+
byChannel: {},
|
|
115
|
+
byModel: {}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 保存每日统计
|
|
120
|
+
function saveDailyStats(date, stats) {
|
|
121
|
+
const filePath = getDailyStatsFilePath(date);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
fs.writeFileSync(filePath, JSON.stringify(stats, null, 2), 'utf8');
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error('[Gemini Statistics] Failed to save daily stats:', err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 更新统计数据
|
|
131
|
+
function updateStats(stats, tokens, cost) {
|
|
132
|
+
stats.requests += 1;
|
|
133
|
+
if (stats.tokens) {
|
|
134
|
+
stats.tokens.input += tokens.input || 0;
|
|
135
|
+
stats.tokens.output += tokens.output || 0;
|
|
136
|
+
stats.tokens.cached += tokens.cached || 0;
|
|
137
|
+
stats.tokens.total += tokens.total || 0;
|
|
138
|
+
}
|
|
139
|
+
stats.cost += cost || 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 记录一次请求
|
|
144
|
+
* @param {Object} requestData - 请求数据
|
|
145
|
+
*/
|
|
146
|
+
function recordRequest(requestData) {
|
|
147
|
+
try {
|
|
148
|
+
const {
|
|
149
|
+
timestamp = new Date().toISOString(),
|
|
150
|
+
channel,
|
|
151
|
+
channelId,
|
|
152
|
+
model,
|
|
153
|
+
tokens = {},
|
|
154
|
+
cost = 0
|
|
155
|
+
} = requestData;
|
|
156
|
+
|
|
157
|
+
// 计算 total tokens
|
|
158
|
+
const totalTokens = (tokens.input || 0) + (tokens.output || 0);
|
|
159
|
+
tokens.total = totalTokens;
|
|
160
|
+
|
|
161
|
+
// 1. 更新总体统计
|
|
162
|
+
const globalStats = loadStatistics();
|
|
163
|
+
|
|
164
|
+
globalStats.global.totalRequests += 1;
|
|
165
|
+
globalStats.global.totalTokens += totalTokens;
|
|
166
|
+
globalStats.global.totalCost += cost || 0;
|
|
167
|
+
|
|
168
|
+
// 按渠道统计
|
|
169
|
+
if (channelId) {
|
|
170
|
+
if (!globalStats.byChannel[channelId]) {
|
|
171
|
+
globalStats.byChannel[channelId] = {
|
|
172
|
+
name: channel || channelId,
|
|
173
|
+
...initStatsObject(),
|
|
174
|
+
firstUsed: timestamp,
|
|
175
|
+
lastUsed: timestamp
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
updateStats(globalStats.byChannel[channelId], tokens, cost);
|
|
179
|
+
globalStats.byChannel[channelId].lastUsed = timestamp;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 按模型统计
|
|
183
|
+
if (model) {
|
|
184
|
+
if (!globalStats.byModel[model]) {
|
|
185
|
+
globalStats.byModel[model] = initStatsObject();
|
|
186
|
+
}
|
|
187
|
+
updateStats(globalStats.byModel[model], tokens, cost);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
saveStatistics(globalStats);
|
|
191
|
+
|
|
192
|
+
// 2. 更新每日统计
|
|
193
|
+
const date = new Date(timestamp).toISOString().split('T')[0];
|
|
194
|
+
const dailyStats = loadDailyStats(date);
|
|
195
|
+
|
|
196
|
+
dailyStats.summary.requests += 1;
|
|
197
|
+
dailyStats.summary.tokens += totalTokens;
|
|
198
|
+
dailyStats.summary.cost += cost || 0;
|
|
199
|
+
|
|
200
|
+
// 每日 - 按渠道统计
|
|
201
|
+
if (channelId) {
|
|
202
|
+
if (!dailyStats.byChannel[channelId]) {
|
|
203
|
+
dailyStats.byChannel[channelId] = {
|
|
204
|
+
name: channel || channelId,
|
|
205
|
+
...initStatsObject()
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
updateStats(dailyStats.byChannel[channelId], tokens, cost);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 每日 - 按模型统计
|
|
212
|
+
if (model) {
|
|
213
|
+
if (!dailyStats.byModel[model]) {
|
|
214
|
+
dailyStats.byModel[model] = initStatsObject();
|
|
215
|
+
}
|
|
216
|
+
updateStats(dailyStats.byModel[model], tokens, cost);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
saveDailyStats(date, dailyStats);
|
|
220
|
+
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error('[Gemini Statistics] Failed to record request:', err);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 获取总体统计
|
|
228
|
+
*/
|
|
229
|
+
function getStatistics() {
|
|
230
|
+
return loadStatistics();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 获取每日统计
|
|
235
|
+
*/
|
|
236
|
+
function getDailyStatistics(date) {
|
|
237
|
+
return loadDailyStats(date);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 获取今日统计
|
|
242
|
+
*/
|
|
243
|
+
function getTodayStatistics() {
|
|
244
|
+
const today = new Date().toISOString().split('T')[0];
|
|
245
|
+
return loadDailyStats(today);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = {
|
|
249
|
+
recordRequest,
|
|
250
|
+
getStatistics,
|
|
251
|
+
getDailyStatistics,
|
|
252
|
+
getTodayStatistics
|
|
253
|
+
};
|