@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,160 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
startGeminiProxyServer,
|
|
5
|
+
stopGeminiProxyServer,
|
|
6
|
+
getGeminiProxyStatus
|
|
7
|
+
} = require('../gemini-proxy-server');
|
|
8
|
+
const {
|
|
9
|
+
setProxyConfig,
|
|
10
|
+
restoreSettings,
|
|
11
|
+
isProxyConfig,
|
|
12
|
+
getCurrentProxyPort,
|
|
13
|
+
configExists,
|
|
14
|
+
hasBackup
|
|
15
|
+
} = require('../services/gemini-settings-manager');
|
|
16
|
+
const { getChannels, getEnabledChannels } = require('../services/gemini-channels');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
|
|
21
|
+
function sanitizeChannel(channel) {
|
|
22
|
+
if (!channel) return null;
|
|
23
|
+
const { apiKey, ...rest } = channel;
|
|
24
|
+
return rest;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 保存激活渠道ID
|
|
28
|
+
function saveActiveChannelId(channelId) {
|
|
29
|
+
const dir = path.join(os.homedir(), '.claude', 'cc-tool');
|
|
30
|
+
if (!fs.existsSync(dir)) {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const filePath = path.join(dir, 'gemini-active-channel.json');
|
|
34
|
+
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 获取代理状态
|
|
38
|
+
router.get('/status', (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const proxyStatus = getGeminiProxyStatus();
|
|
41
|
+
const configStatus = {
|
|
42
|
+
isProxyConfig: isProxyConfig(),
|
|
43
|
+
configExists: configExists(),
|
|
44
|
+
hasBackup: hasBackup(),
|
|
45
|
+
currentProxyPort: getCurrentProxyPort()
|
|
46
|
+
};
|
|
47
|
+
const { channels } = getChannels();
|
|
48
|
+
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
49
|
+
const activeChannel = enabledChannels[0]; // 多渠道模式:第一个启用的渠道
|
|
50
|
+
|
|
51
|
+
res.json({
|
|
52
|
+
proxy: proxyStatus,
|
|
53
|
+
config: configStatus,
|
|
54
|
+
activeChannel: sanitizeChannel(activeChannel)
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
res.status(500).json({ error: error.message });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// 启动代理
|
|
62
|
+
router.post('/start', async (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
// 1. 检查 Gemini 配置文件是否存在
|
|
65
|
+
if (!configExists()) {
|
|
66
|
+
return res.status(400).json({
|
|
67
|
+
error: 'Gemini .env not found. Please run Gemini CLI at least once or create ~/.gemini/.env manually.'
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. 获取当前启用的渠道(多渠道模式)
|
|
72
|
+
const enabledChannels = getEnabledChannels();
|
|
73
|
+
const currentChannel = enabledChannels[0];
|
|
74
|
+
if (!currentChannel) {
|
|
75
|
+
return res.status(400).json({
|
|
76
|
+
error: 'No enabled Gemini channel found. Please create and enable a channel first.'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. 保存当前激活渠道ID(用于代理模式)
|
|
81
|
+
saveActiveChannelId(currentChannel.id);
|
|
82
|
+
console.log(`[Gemini Proxy] Saved active channel: ${currentChannel.name} (${currentChannel.id})`);
|
|
83
|
+
|
|
84
|
+
// 4. 启动代理服务器
|
|
85
|
+
const proxyResult = await startGeminiProxyServer();
|
|
86
|
+
|
|
87
|
+
if (!proxyResult.success) {
|
|
88
|
+
return res.status(500).json({ error: 'Failed to start Gemini proxy server' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 5. 设置代理配置(备份并修改 .env 和 settings.json)
|
|
92
|
+
setProxyConfig(proxyResult.port);
|
|
93
|
+
|
|
94
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
95
|
+
const proxyStatus = getGeminiProxyStatus();
|
|
96
|
+
const { channels: allChannels } = getChannels();
|
|
97
|
+
const activeChannel = allChannels.filter(ch => ch.enabled !== false)[0];
|
|
98
|
+
broadcastProxyState('gemini', proxyStatus, activeChannel, allChannels);
|
|
99
|
+
|
|
100
|
+
res.json({
|
|
101
|
+
success: true,
|
|
102
|
+
port: proxyResult.port,
|
|
103
|
+
activeChannel: sanitizeChannel(currentChannel),
|
|
104
|
+
message: `Gemini proxy started on port ${proxyResult.port}, active channel: ${currentChannel.name}`
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('[Gemini Proxy] Error starting proxy:', error);
|
|
108
|
+
res.status(500).json({ error: error.message });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 停止代理
|
|
113
|
+
router.post('/stop', async (req, res) => {
|
|
114
|
+
try {
|
|
115
|
+
// 1. 获取当前启用的渠道(多渠道模式)
|
|
116
|
+
const { channels } = getChannels();
|
|
117
|
+
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
118
|
+
const activeChannel = enabledChannels[0];
|
|
119
|
+
|
|
120
|
+
// 2. 停止代理服务器
|
|
121
|
+
const proxyResult = await stopGeminiProxyServer();
|
|
122
|
+
|
|
123
|
+
// 3. 恢复原始配置
|
|
124
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
125
|
+
if (hasBackup()) {
|
|
126
|
+
restoreSettings();
|
|
127
|
+
console.log('[Gemini Proxy] Restored settings from backup');
|
|
128
|
+
|
|
129
|
+
// 删除 gemini-active-channel.json
|
|
130
|
+
const activeChannelPath = path.join(os.homedir(), '.claude', 'cc-tool', 'gemini-active-channel.json');
|
|
131
|
+
if (fs.existsSync(activeChannelPath)) {
|
|
132
|
+
fs.unlinkSync(activeChannelPath);
|
|
133
|
+
console.log('[Gemini Proxy] Removed gemini-active-channel.json');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const response = {
|
|
137
|
+
success: true,
|
|
138
|
+
message: `Gemini proxy stopped, settings restored${activeChannel ? ' (channel: ' + activeChannel.name + ')' : ''}`,
|
|
139
|
+
port: proxyResult.port,
|
|
140
|
+
restoredChannel: activeChannel?.name
|
|
141
|
+
};
|
|
142
|
+
res.json(response);
|
|
143
|
+
} else {
|
|
144
|
+
res.json({
|
|
145
|
+
success: true,
|
|
146
|
+
message: 'Gemini proxy stopped (no backup to restore)',
|
|
147
|
+
port: proxyResult.port
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const proxyStatus = getGeminiProxyStatus();
|
|
152
|
+
const { channels: latestChannels } = getChannels();
|
|
153
|
+
broadcastProxyState('gemini', proxyStatus, activeChannel, latestChannels);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('[Gemini Proxy] Error stopping proxy:', error);
|
|
156
|
+
res.status(500).json({ error: error.message });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
module.exports = router;
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
getProjectSessions,
|
|
5
|
+
getSessionById,
|
|
6
|
+
searchSessions,
|
|
7
|
+
forkSession,
|
|
8
|
+
deleteSession,
|
|
9
|
+
getRecentSessions,
|
|
10
|
+
saveSessionOrder,
|
|
11
|
+
getProjectPath,
|
|
12
|
+
getAllSessions
|
|
13
|
+
} = require('../services/gemini-sessions');
|
|
14
|
+
const { isGeminiInstalled } = require('../services/gemini-config');
|
|
15
|
+
const { loadAliases } = require('../services/alias');
|
|
16
|
+
const { getTerminalLaunchCommand } = require('../services/terminal-config');
|
|
17
|
+
|
|
18
|
+
module.exports = (config) => {
|
|
19
|
+
/**
|
|
20
|
+
* GET /api/gemini/sessions/search/global?keyword=xxx
|
|
21
|
+
* 全局搜索
|
|
22
|
+
*/
|
|
23
|
+
router.get('/search/global', (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
if (!isGeminiInstalled()) {
|
|
26
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { keyword } = req.query;
|
|
30
|
+
|
|
31
|
+
if (!keyword) {
|
|
32
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const results = searchSessions(keyword);
|
|
36
|
+
|
|
37
|
+
res.json({
|
|
38
|
+
keyword,
|
|
39
|
+
totalMatches: results.reduce((sum, r) => sum + r.matchCount, 0),
|
|
40
|
+
sessions: results,
|
|
41
|
+
source: 'gemini'
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error('[Gemini API] Failed to search sessions:', err);
|
|
45
|
+
res.status(500).json({ error: err.message });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* GET /api/gemini/sessions/recent/list?limit=10
|
|
51
|
+
* 获取最近会话
|
|
52
|
+
*/
|
|
53
|
+
router.get('/recent/list', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
if (!isGeminiInstalled()) {
|
|
56
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const limit = parseInt(req.query.limit) || 5;
|
|
60
|
+
const sessions = getRecentSessions(limit);
|
|
61
|
+
|
|
62
|
+
res.json({
|
|
63
|
+
sessions,
|
|
64
|
+
source: 'gemini'
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error('[Gemini API] Failed to get recent sessions:', err);
|
|
68
|
+
res.status(500).json({ error: err.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GET /api/gemini/sessions/:projectHash
|
|
74
|
+
* 获取项目的所有会话
|
|
75
|
+
*/
|
|
76
|
+
router.get('/:projectHash', (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
if (!isGeminiInstalled()) {
|
|
79
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { projectHash } = req.params;
|
|
83
|
+
const sessions = getProjectSessions(projectHash);
|
|
84
|
+
|
|
85
|
+
// 计算总大小
|
|
86
|
+
const totalSize = sessions.reduce((sum, session) => {
|
|
87
|
+
return sum + (session.size || 0);
|
|
88
|
+
}, 0);
|
|
89
|
+
|
|
90
|
+
// 获取别名
|
|
91
|
+
const aliases = loadAliases();
|
|
92
|
+
|
|
93
|
+
// 使用彩虹表解析真实路径
|
|
94
|
+
const realPath = getProjectPath(projectHash);
|
|
95
|
+
const path = require('path');
|
|
96
|
+
const displayName = realPath ? path.basename(realPath) : `Project ${projectHash.substring(0, 8)}`;
|
|
97
|
+
|
|
98
|
+
res.json({
|
|
99
|
+
sessions,
|
|
100
|
+
totalSize,
|
|
101
|
+
aliases,
|
|
102
|
+
projectInfo: {
|
|
103
|
+
name: projectHash,
|
|
104
|
+
fullPath: realPath || projectHash,
|
|
105
|
+
path: realPath || projectHash,
|
|
106
|
+
displayName
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error('[Gemini API] Failed to get sessions:', err);
|
|
111
|
+
res.status(500).json({ error: err.message });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* GET /api/gemini/sessions/:projectHash/search
|
|
117
|
+
* 搜索项目内会话内容
|
|
118
|
+
*/
|
|
119
|
+
router.get('/:projectHash/search', (req, res) => {
|
|
120
|
+
try {
|
|
121
|
+
if (!isGeminiInstalled()) {
|
|
122
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { projectHash } = req.params;
|
|
126
|
+
const { keyword, context } = req.query;
|
|
127
|
+
|
|
128
|
+
if (!keyword) {
|
|
129
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const contextLength = context ? parseInt(context) : 35;
|
|
133
|
+
|
|
134
|
+
// 搜索所有会话,然后过滤该项目的会话
|
|
135
|
+
const allResults = searchSessions(keyword, contextLength);
|
|
136
|
+
const results = allResults.filter(r => r.projectHash === projectHash);
|
|
137
|
+
|
|
138
|
+
res.json({
|
|
139
|
+
keyword,
|
|
140
|
+
totalMatches: results.reduce((sum, r) => sum + r.matchCount, 0),
|
|
141
|
+
sessions: results
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error('[Gemini API] Failed to search sessions:', err);
|
|
145
|
+
res.status(500).json({ error: err.message });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* GET /api/gemini/sessions/:projectHash/:sessionId/messages
|
|
151
|
+
* 获取会话的消息列表
|
|
152
|
+
*/
|
|
153
|
+
router.get('/:projectHash/:sessionId/messages', (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
if (!isGeminiInstalled()) {
|
|
156
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { sessionId } = req.params;
|
|
160
|
+
const { page = 1, limit = 20, order = 'desc' } = req.query;
|
|
161
|
+
|
|
162
|
+
const session = getSessionById(sessionId);
|
|
163
|
+
|
|
164
|
+
if (!session) {
|
|
165
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 转换消息格式为前端期望的格式
|
|
169
|
+
const convertedMessages = [];
|
|
170
|
+
|
|
171
|
+
for (const msg of session.messages || []) {
|
|
172
|
+
// 用户消息
|
|
173
|
+
if (msg.type === 'user') {
|
|
174
|
+
convertedMessages.push({
|
|
175
|
+
type: 'user',
|
|
176
|
+
content: msg.content || '[空消息]',
|
|
177
|
+
timestamp: msg.timestamp,
|
|
178
|
+
model: null
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Gemini 助手消息(type 是 'gemini' 而不是 'assistant')
|
|
182
|
+
else if (msg.type === 'gemini' || msg.type === 'assistant') {
|
|
183
|
+
let content = msg.content || '[空消息]';
|
|
184
|
+
|
|
185
|
+
// 如果有 thoughts(思考过程),添加到内容前面
|
|
186
|
+
if (msg.thoughts && Array.isArray(msg.thoughts) && msg.thoughts.length > 0) {
|
|
187
|
+
const thoughtsText = msg.thoughts.map(t =>
|
|
188
|
+
`**[思考: ${t.subject}]**\n${t.description}`
|
|
189
|
+
).join('\n\n');
|
|
190
|
+
|
|
191
|
+
content = `**[思考过程]**\n${thoughtsText}\n\n---\n\n${content}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
convertedMessages.push({
|
|
195
|
+
type: 'assistant',
|
|
196
|
+
content,
|
|
197
|
+
timestamp: msg.timestamp,
|
|
198
|
+
model: msg.model || session.model || 'gemini'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 分页处理
|
|
204
|
+
const pageNum = parseInt(page);
|
|
205
|
+
const limitNum = parseInt(limit);
|
|
206
|
+
|
|
207
|
+
// 排序
|
|
208
|
+
let messages = convertedMessages;
|
|
209
|
+
if (order === 'desc') {
|
|
210
|
+
messages = [...messages].reverse();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 分页
|
|
214
|
+
const totalMessages = messages.length;
|
|
215
|
+
const start = (pageNum - 1) * limitNum;
|
|
216
|
+
const end = start + limitNum;
|
|
217
|
+
const paginatedMessages = messages.slice(start, end);
|
|
218
|
+
|
|
219
|
+
res.json({
|
|
220
|
+
messages: paginatedMessages,
|
|
221
|
+
metadata: {
|
|
222
|
+
gitBranch: null,
|
|
223
|
+
gitRepository: null,
|
|
224
|
+
cwd: null,
|
|
225
|
+
model: session.model || 'gemini'
|
|
226
|
+
},
|
|
227
|
+
pagination: {
|
|
228
|
+
page: pageNum,
|
|
229
|
+
limit: limitNum,
|
|
230
|
+
total: totalMessages,
|
|
231
|
+
hasMore: end < totalMessages
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error('[Gemini API] Failed to get session messages:', err);
|
|
236
|
+
res.status(500).json({ error: err.message });
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* DELETE /api/gemini/sessions/:projectHash/:sessionId
|
|
242
|
+
* 删除会话
|
|
243
|
+
*/
|
|
244
|
+
router.delete('/:projectHash/:sessionId', (req, res) => {
|
|
245
|
+
try {
|
|
246
|
+
if (!isGeminiInstalled()) {
|
|
247
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { sessionId } = req.params;
|
|
251
|
+
const result = deleteSession(sessionId);
|
|
252
|
+
|
|
253
|
+
res.json(result);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[Gemini API] Failed to delete session:', err);
|
|
256
|
+
res.status(500).json({ error: err.message });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* POST /api/gemini/sessions/:projectHash/:sessionId/fork
|
|
262
|
+
* Fork 一个会话
|
|
263
|
+
*/
|
|
264
|
+
router.post('/:projectHash/:sessionId/fork', (req, res) => {
|
|
265
|
+
try {
|
|
266
|
+
if (!isGeminiInstalled()) {
|
|
267
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const { sessionId } = req.params;
|
|
271
|
+
const result = forkSession(sessionId);
|
|
272
|
+
|
|
273
|
+
res.json(result);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.error('[Gemini API] Failed to fork session:', err);
|
|
276
|
+
res.status(500).json({ error: err.message });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* POST /api/gemini/sessions/:projectHash/order
|
|
282
|
+
* 保存会话排序
|
|
283
|
+
*/
|
|
284
|
+
router.post('/:projectHash/order', (req, res) => {
|
|
285
|
+
try {
|
|
286
|
+
if (!isGeminiInstalled()) {
|
|
287
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { projectHash } = req.params;
|
|
291
|
+
const { order } = req.body;
|
|
292
|
+
|
|
293
|
+
if (!Array.isArray(order)) {
|
|
294
|
+
return res.status(400).json({ error: 'order must be an array' });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
saveSessionOrder(projectHash, order);
|
|
298
|
+
|
|
299
|
+
res.json({ success: true });
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error('[Gemini API] Failed to save session order:', err);
|
|
302
|
+
res.status(500).json({ error: err.message });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* POST /api/gemini/sessions/:projectHash/:sessionId/launch
|
|
308
|
+
* 启动会话(打开终端)
|
|
309
|
+
*/
|
|
310
|
+
router.post('/:projectHash/:sessionId/launch', (req, res) => {
|
|
311
|
+
try {
|
|
312
|
+
if (!isGeminiInstalled()) {
|
|
313
|
+
return res.status(404).json({ error: 'Gemini CLI not installed' });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const { exec } = require('child_process');
|
|
317
|
+
const { projectHash, sessionId } = req.params;
|
|
318
|
+
|
|
319
|
+
// 获取会话详情
|
|
320
|
+
const session = getSessionById(sessionId);
|
|
321
|
+
|
|
322
|
+
if (!session) {
|
|
323
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 使用彩虹表方法获取项目路径
|
|
327
|
+
const projectPath = getProjectPath(projectHash);
|
|
328
|
+
|
|
329
|
+
if (!projectPath) {
|
|
330
|
+
return res.status(400).json({
|
|
331
|
+
error: 'Could not resolve project path. The original directory may have been moved or deleted.'
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 获取该项目的所有会话文件,按 startTime 升序排列(与 gemini --list-sessions 一致)
|
|
336
|
+
const allSessions = getAllSessions()
|
|
337
|
+
.filter(s => s.projectHash === projectHash)
|
|
338
|
+
.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));
|
|
339
|
+
|
|
340
|
+
// 找到该 sessionId 对应的最新文件的索引
|
|
341
|
+
// 注意:同一个 sessionId 可能有多个文件(继续对话),我们要找最新的那个
|
|
342
|
+
let sessionIndex = -1;
|
|
343
|
+
for (let i = allSessions.length - 1; i >= 0; i--) {
|
|
344
|
+
if (allSessions[i].sessionId === sessionId) {
|
|
345
|
+
sessionIndex = i;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (sessionIndex === -1) {
|
|
351
|
+
return res.status(404).json({ error: 'Session not found in project sessions list' });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Gemini 的索引从 1 开始
|
|
355
|
+
const resumeIndex = sessionIndex + 1;
|
|
356
|
+
|
|
357
|
+
// 构建 Gemini CLI 命令(使用 --resume <index> 恢复特定会话)
|
|
358
|
+
const geminiCommand = `gemini --resume ${resumeIndex}`;
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
// 获取终端启动命令
|
|
362
|
+
const { command, terminalId, terminalName } = getTerminalLaunchCommand(projectPath, null, geminiCommand);
|
|
363
|
+
|
|
364
|
+
console.log(`[Gemini] Launching terminal: ${terminalName} (${terminalId})`);
|
|
365
|
+
console.log(`[Gemini] Resuming session: ${sessionId} (index ${resumeIndex})`);
|
|
366
|
+
console.log(`[Gemini] Command: ${command}`);
|
|
367
|
+
|
|
368
|
+
// 异步执行命令,不等待结果
|
|
369
|
+
const shellOption = process.platform === 'win32' ? { shell: 'cmd.exe' } : { shell: true };
|
|
370
|
+
exec(command, shellOption, (error, stdout, stderr) => {
|
|
371
|
+
if (error) {
|
|
372
|
+
console.error(`[Gemini] Failed to launch terminal ${terminalName}:`, error.message);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// 立即返回成功响应
|
|
377
|
+
res.json({
|
|
378
|
+
success: true,
|
|
379
|
+
sessionId,
|
|
380
|
+
projectPath,
|
|
381
|
+
terminal: terminalName,
|
|
382
|
+
terminalId
|
|
383
|
+
});
|
|
384
|
+
} catch (terminalError) {
|
|
385
|
+
console.error('[Gemini] Failed to get terminal command:', terminalError);
|
|
386
|
+
return res.status(500).json({
|
|
387
|
+
error: 'Failed to launch terminal: ' + terminalError.message
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error('[Gemini API] Failed to launch session:', err);
|
|
392
|
+
res.status(500).json({ error: err.message });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
return router;
|
|
397
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
getStatistics,
|
|
5
|
+
getDailyStatistics,
|
|
6
|
+
getTodayStatistics
|
|
7
|
+
} = require('../services/gemini-statistics-service');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 获取 Gemini 总体统计数据
|
|
11
|
+
* GET /api/gemini/statistics/summary
|
|
12
|
+
*/
|
|
13
|
+
router.get('/summary', (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const stats = getStatistics();
|
|
16
|
+
res.json(stats);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('[Gemini] Failed to get statistics:', error);
|
|
19
|
+
res.status(500).json({ error: 'Failed to get statistics' });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 获取 Gemini 今日统计数据
|
|
25
|
+
* GET /api/gemini/statistics/today
|
|
26
|
+
*/
|
|
27
|
+
router.get('/today', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const stats = getTodayStatistics();
|
|
30
|
+
res.json(stats);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('[Gemini] Failed to get today statistics:', error);
|
|
33
|
+
res.status(500).json({ error: 'Failed to get today statistics' });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取 Gemini 指定日期的统计数据
|
|
39
|
+
* GET /api/gemini/statistics/daily/:date
|
|
40
|
+
*/
|
|
41
|
+
router.get('/daily/:date', (req, res) => {
|
|
42
|
+
try {
|
|
43
|
+
const { date } = req.params;
|
|
44
|
+
|
|
45
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
46
|
+
return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const stats = getDailyStatistics(date);
|
|
50
|
+
res.json(stats);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('[Gemini] Failed to get daily statistics:', error);
|
|
53
|
+
res.status(500).json({ error: 'Failed to get daily statistics' });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
module.exports = router;
|