@becrafter/prompt-manager 0.1.22 → 0.2.2
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/package.json +31 -24
- package/packages/resources/tools/agent-browser/README.md +640 -0
- package/packages/resources/tools/agent-browser/agent-browser.tool.js +1389 -0
- package/packages/resources/tools/thinking/README.md +324 -0
- package/packages/resources/tools/thinking/thinking.tool.js +911 -0
- package/packages/server/README.md +3 -4
- package/packages/server/api/admin.routes.js +668 -664
- package/packages/server/api/open.routes.js +68 -67
- package/packages/server/api/surge.routes.js +5 -6
- package/packages/server/api/tool.routes.js +70 -71
- package/packages/server/app.js +70 -73
- package/packages/server/configs/authors.json +40 -0
- package/packages/server/configs/models/built-in/bigmodel.yaml +6 -6
- package/packages/server/configs/models/providers.yaml +4 -4
- package/packages/server/configs/templates/built-in/general-iteration.yaml +1 -1
- package/packages/server/configs/templates/built-in/general-optimize.yaml +1 -1
- package/packages/server/configs/templates/built-in/output-format-optimize.yaml +1 -1
- package/packages/server/index.js +3 -9
- package/packages/server/mcp/heartbeat-patch.js +1 -3
- package/packages/server/mcp/mcp.server.js +64 -134
- package/packages/server/mcp/prompt.handler.js +101 -95
- package/packages/server/middlewares/auth.middleware.js +31 -31
- package/packages/server/server.js +60 -45
- package/packages/server/services/TerminalService.js +156 -70
- package/packages/server/services/WebSocketService.js +35 -34
- package/packages/server/services/author-config.service.js +199 -0
- package/packages/server/services/manager.js +66 -60
- package/packages/server/services/model.service.js +5 -9
- package/packages/server/services/optimization.service.js +25 -22
- package/packages/server/services/template.service.js +3 -8
- package/packages/server/toolm/author-sync.service.js +97 -0
- package/packages/server/toolm/index.js +1 -2
- package/packages/server/toolm/package-installer.service.js +47 -50
- package/packages/server/toolm/tool-context.service.js +64 -62
- package/packages/server/toolm/tool-dependency.service.js +28 -30
- package/packages/server/toolm/tool-description-generator-optimized.service.js +55 -55
- package/packages/server/toolm/tool-description-generator.service.js +20 -23
- package/packages/server/toolm/tool-environment.service.js +45 -44
- package/packages/server/toolm/tool-execution.service.js +49 -48
- package/packages/server/toolm/tool-loader.service.js +13 -18
- package/packages/server/toolm/tool-logger.service.js +33 -39
- package/packages/server/toolm/tool-manager.handler.js +17 -15
- package/packages/server/toolm/tool-manual-generator.service.js +107 -87
- package/packages/server/toolm/tool-mode-handlers.service.js +52 -59
- package/packages/server/toolm/tool-storage.service.js +11 -12
- package/packages/server/toolm/tool-sync.service.js +36 -39
- package/packages/server/toolm/tool-utils.js +0 -1
- package/packages/server/toolm/tool-yaml-parser.service.js +12 -11
- package/packages/server/toolm/validate-system.js +56 -84
- package/packages/server/utils/config.js +97 -12
- package/packages/server/utils/logger.js +1 -1
- package/packages/server/utils/port-checker.js +8 -8
- package/packages/server/utils/util.js +470 -467
- package/packages/resources/tools/cognitive-thinking/README.md +0 -284
- package/packages/resources/tools/cognitive-thinking/cognitive-thinking.tool.js +0 -837
- package/packages/server/mcp/sequential-thinking.handler.js +0 -318
- package/packages/server/mcp/think-plan.handler.js +0 -274
- package/packages/server/mcp/thinking-toolkit.handler.js +0 -380
- package/packages/web/0.d1c5a72339dfc32ad86a.js +0 -1
- package/packages/web/112.8807b976372b2b0541a8.js +0 -1
- package/packages/web/130.584c7e365da413f5d9be.js +0 -1
- package/packages/web/142.72c985bc29720f975cca.js +0 -1
- package/packages/web/165.a05fc53bf84d18db36b8.js +0 -2
- package/packages/web/165.a05fc53bf84d18db36b8.js.LICENSE.txt +0 -9
- package/packages/web/203.724ab9f717b80554c397.js +0 -1
- package/packages/web/241.bf941d4f02866795f64a.js +0 -1
- package/packages/web/249.54cfb224af63f5f5ec55.js +0 -1
- package/packages/web/291.6df35042f8f296fca7cd.js +0 -1
- package/packages/web/319.2fab900a31b29873f666.js +0 -1
- package/packages/web/32.c78d866281995ec33a7b.js +0 -1
- package/packages/web/325.9ca297d0f73f38468ce9.js +0 -1
- package/packages/web/366.2f9b48fdbf8eee039e57.js +0 -1
- package/packages/web/378.6be08c612cd5a3ef97dc.js +0 -1
- package/packages/web/393.7a2f817515c5e90623d7.js +0 -1
- package/packages/web/412.062df5f732d5ba203415.js +0 -1
- package/packages/web/426.08656fef4918b3fb19ad.js +0 -1
- package/packages/web/465.2be8018327130a3bd798.js +0 -1
- package/packages/web/48.8ca96fc93667a715e67a.js +0 -1
- package/packages/web/480.44c1f1a2927486ac3d4f.js +0 -1
- package/packages/web/489.e041a8d0db15dc96d607.js +0 -1
- package/packages/web/490.9ffb26c907de020d671b.js +0 -1
- package/packages/web/492.58781369e348d91fc06a.js +0 -1
- package/packages/web/495.ed63e99791a87167c6b3.js +0 -1
- package/packages/web/510.4cc07ab7d30d5c1cd17f.js +0 -1
- package/packages/web/543.3af155ed4fa237664308.js +0 -1
- package/packages/web/567.f04ab60f8e2c2fb0745a.js +0 -1
- package/packages/web/592.f3ad085fa9c1849daa06.js +0 -1
- package/packages/web/616.b03fb801b3433b17750f.js +0 -1
- package/packages/web/617.d88def54921d2c4dc44c.js +0 -1
- package/packages/web/641.d30787d674f548928261.js +0 -1
- package/packages/web/672.5269c8399fa42a5af95d.js +0 -1
- package/packages/web/731.97cab92b71811c502bda.js +0 -1
- package/packages/web/746.3947c6f0235407e420fb.js +0 -1
- package/packages/web/756.a53233b3f3913900d5ac.js +0 -1
- package/packages/web/77.68801af593a28a631fbf.js +0 -1
- package/packages/web/802.53b2bff3cf2a69f7b80c.js +0 -1
- package/packages/web/815.b6dfab82265f56c7e046.js +0 -1
- package/packages/web/821.f5a13e5c735aac244eb9.js +0 -1
- package/packages/web/846.b9bf97d5f559270675ce.js +0 -1
- package/packages/web/869.7c10403f500e6201407f.js +0 -1
- package/packages/web/885.135050364f99e6924fb5.js +0 -1
- package/packages/web/901.fd5aeb9df630609a2b43.js +0 -1
- package/packages/web/928.f67e590de3caa4daa3ae.js +0 -1
- package/packages/web/955.d833403521ba4dd567ee.js +0 -1
- package/packages/web/981.a45cb745cf424044c8c8.js +0 -1
- package/packages/web/992.645320b60c74c8787482.js +0 -1
- package/packages/web/996.ed9a963dc9e7439eca9a.js +0 -1
- package/packages/web/css/codemirror-theme_xq-light.css +0 -43
- package/packages/web/css/codemirror.css +0 -344
- package/packages/web/css/main.196f434e6a88cd448158.css +0 -7278
- package/packages/web/css/terminal-fix.css +0 -571
- package/packages/web/index.html +0 -3
- package/packages/web/main.dceff50c7307dda04873.js +0 -2
- package/packages/web/main.dceff50c7307dda04873.js.LICENSE.txt +0 -3
|
@@ -7,12 +7,11 @@ import path from 'path';
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import fse from 'fs-extra';
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
10
|
import { spawn } from 'child_process';
|
|
12
11
|
import { logger } from '../utils/logger.js';
|
|
13
12
|
import { util, GROUP_META_FILENAME } from '../utils/util.js';
|
|
14
13
|
import { config } from '../utils/config.js';
|
|
15
|
-
import {adminAuthMiddleware} from '../middlewares/auth.middleware.js'
|
|
14
|
+
import { adminAuthMiddleware } from '../middlewares/auth.middleware.js';
|
|
16
15
|
import { templateManager } from '../services/template.service.js';
|
|
17
16
|
import { modelManager } from '../services/model.service.js';
|
|
18
17
|
import { optimizationService } from '../services/optimization.service.js';
|
|
@@ -21,20 +20,20 @@ import { webSocketService } from '../services/WebSocketService.js';
|
|
|
21
20
|
const router = express.Router();
|
|
22
21
|
|
|
23
22
|
// 获取prompts目录路径(在启动时可能被覆盖)
|
|
24
|
-
|
|
23
|
+
const promptsDir = config.getPromptsDir();
|
|
25
24
|
const PROMPT_NAME_REGEX = /^(?![.]{1,2}$)[^\\/:*?"<>|\r\n]{1,64}$/;
|
|
26
25
|
|
|
27
26
|
// 获取服务器配置端点
|
|
28
27
|
router.get('/config', (req, res) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// 检查是否启用了管理员功能
|
|
29
|
+
if (!config.adminEnable) {
|
|
30
|
+
return res.status(404).json({ error: 'Admin功能未启用' });
|
|
31
|
+
}
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
res.json({
|
|
34
|
+
requireAuth: config.adminRequireAuth,
|
|
35
|
+
adminEnable: config.adminEnable
|
|
36
|
+
});
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
// 获取公开配置端点(无需认证)
|
|
@@ -57,852 +56,857 @@ router.get('/config/public', (req, res) => {
|
|
|
57
56
|
|
|
58
57
|
// 登录端点
|
|
59
58
|
router.post('/login', (req, res) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
// 检查是否启用了管理员功能
|
|
60
|
+
if (!config.adminEnable) {
|
|
61
|
+
return res.status(404).json({ error: 'Admin功能未启用' });
|
|
62
|
+
}
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
// 如果不需要认证,返回默认token
|
|
65
|
+
if (!config.adminRequireAuth) {
|
|
66
|
+
return res.json({ token: config.admins[0].token });
|
|
67
|
+
}
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
const { username, password } = req.body;
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (!username || !password) {
|
|
72
|
+
return res.status(400).json({ error: '用户名和密码是必需的' });
|
|
73
|
+
}
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
// 验证凭据
|
|
76
|
+
const admin = config.admins.find(a => a.username === username && a.password === password);
|
|
77
|
+
if (!admin) {
|
|
78
|
+
return res.status(401).json({ error: '无效的用户名或密码' });
|
|
79
|
+
}
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
res.json({ token: admin.token });
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
// 获取分组目录
|
|
86
85
|
router.get('/groups', adminAuthMiddleware, (req, res) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
try {
|
|
87
|
+
const tree = util.buildGroupTree(promptsDir);
|
|
88
|
+
const hasDefault = tree.some(node => node.path === 'default');
|
|
89
|
+
if (!hasDefault) {
|
|
90
|
+
tree.unshift({ name: 'default', path: 'default', children: [], enabled: true });
|
|
91
|
+
}
|
|
92
|
+
res.json(tree);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
res.status(500).json({ error: error.message });
|
|
95
|
+
}
|
|
97
96
|
});
|
|
98
97
|
|
|
99
98
|
// 获取所有提示词(支持搜索、过滤和分组)
|
|
100
99
|
router.get('/prompts', adminAuthMiddleware, (req, res) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// 处理搜索参数
|
|
105
|
-
const search = req.query.search;
|
|
106
|
-
const enabled = req.query.enabled === 'true';
|
|
107
|
-
const groupPathFilter = req.query.groupPath;
|
|
108
|
-
const group = req.query.group;
|
|
109
|
-
|
|
110
|
-
let filteredPrompts = prompts;
|
|
111
|
-
|
|
112
|
-
// 应用分组过滤
|
|
113
|
-
if (groupPathFilter) {
|
|
114
|
-
filteredPrompts = filteredPrompts.filter(prompt => (prompt.groupPath || prompt.group || 'default') === groupPathFilter);
|
|
115
|
-
} else if (group) {
|
|
116
|
-
filteredPrompts = filteredPrompts.filter(prompt => (prompt.group || 'default') === group);
|
|
117
|
-
}
|
|
100
|
+
try {
|
|
101
|
+
const prompts = util.getPromptsFromFiles();
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
}
|
|
103
|
+
// 处理搜索参数
|
|
104
|
+
const search = req.query.search;
|
|
105
|
+
const enabled = req.query.enabled === 'true';
|
|
106
|
+
const groupPathFilter = req.query.groupPath;
|
|
107
|
+
const group = req.query.group;
|
|
126
108
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return groupActive;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
109
|
+
let filteredPrompts = prompts;
|
|
110
|
+
|
|
111
|
+
// 应用分组过滤
|
|
112
|
+
if (groupPathFilter) {
|
|
113
|
+
filteredPrompts = filteredPrompts.filter(
|
|
114
|
+
prompt => (prompt.groupPath || prompt.group || 'default') === groupPathFilter
|
|
115
|
+
);
|
|
116
|
+
} else if (group) {
|
|
117
|
+
filteredPrompts = filteredPrompts.filter(prompt => (prompt.group || 'default') === group);
|
|
118
|
+
}
|
|
140
119
|
|
|
141
|
-
|
|
120
|
+
// 应用搜索过滤
|
|
121
|
+
if (search) {
|
|
122
|
+
filteredPrompts = filteredPrompts.filter(
|
|
123
|
+
prompt => prompt.name.includes(search) || (prompt.description && prompt.description.includes(search))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 应用启用状态过滤
|
|
128
|
+
if (enabled) {
|
|
129
|
+
filteredPrompts = filteredPrompts.filter(prompt => {
|
|
130
|
+
// 检查提示词本身是否启用
|
|
131
|
+
const promptActive = prompt.enabled === true;
|
|
132
|
+
if (!promptActive) return false;
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
// 检查目录状态 - util.getPromptsFromFiles() 已经正确处理了继承的启用状态
|
|
135
|
+
// groupEnabled 已经考虑了父目录的禁用状态
|
|
136
|
+
const groupActive = prompt.groupEnabled !== false;
|
|
137
|
+
return groupActive;
|
|
138
|
+
});
|
|
146
139
|
}
|
|
140
|
+
|
|
141
|
+
filteredPrompts.sort((a, b) => (a.name || '').localeCompare(b.name || '', 'zh-CN'));
|
|
142
|
+
|
|
143
|
+
res.json(filteredPrompts);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
res.status(500).json({ error: error.message });
|
|
146
|
+
}
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
// 获取单个提示词
|
|
150
150
|
router.get('/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
try {
|
|
152
|
+
const prompts = util.getPromptsFromFiles();
|
|
153
|
+
const targetPath = req.query.path;
|
|
154
|
+
let prompt;
|
|
155
|
+
if (targetPath) {
|
|
156
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
157
|
+
}
|
|
158
|
+
if (!prompt) {
|
|
159
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
160
|
+
}
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
if (!prompt) {
|
|
163
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
164
|
+
}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
// 读取原始YAML文件内容
|
|
167
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
168
|
+
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
res.json({
|
|
171
|
+
...prompt,
|
|
172
|
+
yaml: yamlContent
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
res.status(500).json({ error: error.message });
|
|
176
|
+
}
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
// 保存提示词
|
|
180
180
|
router.post('/prompts', adminAuthMiddleware, (req, res) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
try {
|
|
182
|
+
const { name, group, yaml: yamlContent, relativePath: originalRelativePath } = req.body;
|
|
183
|
+
const trimmedName = (name || '').trim();
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
if (!trimmedName || !yamlContent) {
|
|
186
|
+
return res.status(400).json({ error: '名称和YAML内容是必需的' });
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
// 验证名称格式
|
|
190
|
+
if (!PROMPT_NAME_REGEX.test(trimmedName)) {
|
|
191
|
+
return res.status(400).json({ error: '名称格式无效,不能包含 / \\ : * ? " < > | 或换行,长度需在1-64字符' });
|
|
192
|
+
}
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
// 计算目标路径
|
|
195
|
+
const groupName = group || 'default';
|
|
196
|
+
const normalizedOriginalPath = originalRelativePath
|
|
197
|
+
? path.normalize(originalRelativePath).replace(/\\/g, '/')
|
|
198
|
+
: null;
|
|
197
199
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
const targetSegments = [];
|
|
201
|
+
if (groupName) {
|
|
202
|
+
targetSegments.push(groupName);
|
|
203
|
+
}
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const targetRelativePath = path.posix.join(...targetSegments);
|
|
207
|
-
const targetDir = path.join(promptsDir, path.posix.dirname(targetRelativePath));
|
|
208
|
-
const filePath = path.join(promptsDir, targetRelativePath);
|
|
209
|
-
|
|
210
|
-
fse.ensureDirSync(targetDir);
|
|
211
|
-
|
|
212
|
-
// 检查是否重名(同目录下)
|
|
213
|
-
const prompts = util.getPromptsFromFiles();
|
|
214
|
-
const existingPrompt = prompts.find(p => {
|
|
215
|
-
if (p.name !== trimmedName) return false;
|
|
216
|
-
const isOriginalFile = normalizedOriginalPath && p.relativePath === normalizedOriginalPath;
|
|
217
|
-
if (isOriginalFile) return false;
|
|
218
|
-
const sameRelativePath = p.relativePath === targetRelativePath;
|
|
219
|
-
if (sameRelativePath) return false;
|
|
220
|
-
const sameDirectory = path.posix.dirname(p.relativePath || '') === path.posix.dirname(targetRelativePath);
|
|
221
|
-
return sameDirectory;
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (existingPrompt) {
|
|
225
|
-
return res.status(400).json({ error: '名称已存在' });
|
|
226
|
-
}
|
|
205
|
+
const finalFileName = `${trimmedName}.yaml`;
|
|
206
|
+
targetSegments.push(finalFileName);
|
|
227
207
|
|
|
228
|
-
|
|
229
|
-
|
|
208
|
+
const targetRelativePath = path.posix.join(...targetSegments);
|
|
209
|
+
const targetDir = path.join(promptsDir, path.posix.dirname(targetRelativePath));
|
|
210
|
+
const filePath = path.join(promptsDir, targetRelativePath);
|
|
230
211
|
|
|
231
|
-
|
|
232
|
-
if (normalizedOriginalPath && normalizedOriginalPath !== targetRelativePath) {
|
|
233
|
-
const originalFilePath = path.join(promptsDir, normalizedOriginalPath);
|
|
234
|
-
if (fs.existsSync(originalFilePath)) {
|
|
235
|
-
fs.unlinkSync(originalFilePath);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
212
|
+
fse.ensureDirSync(targetDir);
|
|
238
213
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
214
|
+
// 检查是否重名(同目录下)
|
|
215
|
+
const prompts = util.getPromptsFromFiles();
|
|
216
|
+
const existingPrompt = prompts.find(p => {
|
|
217
|
+
if (p.name !== trimmedName) return false;
|
|
218
|
+
const isOriginalFile = normalizedOriginalPath && p.relativePath === normalizedOriginalPath;
|
|
219
|
+
if (isOriginalFile) return false;
|
|
220
|
+
const sameRelativePath = p.relativePath === targetRelativePath;
|
|
221
|
+
if (sameRelativePath) return false;
|
|
222
|
+
const sameDirectory = path.posix.dirname(p.relativePath || '') === path.posix.dirname(targetRelativePath);
|
|
223
|
+
return sameDirectory;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (existingPrompt) {
|
|
227
|
+
return res.status(400).json({ error: '名称已存在' });
|
|
242
228
|
}
|
|
229
|
+
|
|
230
|
+
// 保存文件
|
|
231
|
+
fs.writeFileSync(filePath, yamlContent, 'utf8');
|
|
232
|
+
|
|
233
|
+
// 如果目标路径与原始路径不同,删除旧文件
|
|
234
|
+
if (normalizedOriginalPath && normalizedOriginalPath !== targetRelativePath) {
|
|
235
|
+
const originalFilePath = path.join(promptsDir, normalizedOriginalPath);
|
|
236
|
+
if (fs.existsSync(originalFilePath)) {
|
|
237
|
+
fs.unlinkSync(originalFilePath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
res.json({ message: '保存成功', relativePath: targetRelativePath, group: groupName });
|
|
242
|
+
} catch (error) {
|
|
243
|
+
res.status(500).json({ error: error.message });
|
|
244
|
+
}
|
|
243
245
|
});
|
|
244
246
|
|
|
245
247
|
// 创建新分组目录
|
|
246
248
|
router.post('/groups', adminAuthMiddleware, (req, res) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
try {
|
|
250
|
+
const { name, parent } = req.body;
|
|
249
251
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
if (!name) {
|
|
253
|
+
return res.status(400).json({ error: '分组名称是必需的' });
|
|
254
|
+
}
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
// 验证名称格式
|
|
257
|
+
if (!util.isValidGroupName(name)) {
|
|
258
|
+
return res.status(400).json({ error: '名称格式无效,不能包含 / \\ : * ? " < > | 或换行,长度需在1-64字符' });
|
|
259
|
+
}
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
261
|
+
// 构建目标目录路径
|
|
262
|
+
let targetPath;
|
|
263
|
+
if (parent) {
|
|
264
|
+
// 验证父级目录路径
|
|
265
|
+
const resolvedParent = util.resolveGroupDir(parent);
|
|
266
|
+
if (!resolvedParent) {
|
|
267
|
+
return res.status(400).json({ error: '无效的父级目录路径' });
|
|
268
|
+
}
|
|
269
|
+
targetPath = path.join(resolvedParent.dir, name);
|
|
270
|
+
} else {
|
|
271
|
+
targetPath = path.join(promptsDir, name);
|
|
272
|
+
}
|
|
271
273
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
// 检查目录是否已存在
|
|
275
|
+
if (fs.existsSync(targetPath)) {
|
|
276
|
+
return res.status(400).json({ error: '分组已存在' });
|
|
277
|
+
}
|
|
276
278
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
// 创建目录
|
|
280
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
res.json({ message: '分组创建成功' });
|
|
283
|
+
} catch (error) {
|
|
284
|
+
res.status(500).json({ error: error.message });
|
|
285
|
+
}
|
|
284
286
|
});
|
|
285
287
|
|
|
286
288
|
// 重命名分组目录
|
|
287
289
|
router.patch('/groups/rename', adminAuthMiddleware, (req, res) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
try {
|
|
291
|
+
const { path: groupPath, newName } = req.body || {};
|
|
292
|
+
if (!groupPath || !newName) {
|
|
293
|
+
return res.status(400).json({ error: '分组路径和新名称是必需的' });
|
|
294
|
+
}
|
|
295
|
+
if (!util.isValidGroupName(newName)) {
|
|
296
|
+
return res.status(400).json({ error: '名称格式无效,不能包含 / \\ : * ? " < > | 或换行,长度需在1-64字符' });
|
|
297
|
+
}
|
|
298
|
+
if (groupPath === 'default') {
|
|
299
|
+
return res.status(400).json({ error: '默认分组不允许重命名' });
|
|
300
|
+
}
|
|
299
301
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
303
|
+
if (!resolved) {
|
|
304
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
305
|
+
}
|
|
306
|
+
const { dir: oldDir, segments } = resolved;
|
|
307
|
+
if (!fs.existsSync(oldDir) || !fs.lstatSync(oldDir).isDirectory()) {
|
|
308
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
309
|
+
}
|
|
308
310
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
const parentSegments = segments.slice(0, -1);
|
|
312
|
+
const oldName = segments[segments.length - 1];
|
|
313
|
+
if (newName === oldName) {
|
|
314
|
+
return res.json({ message: '分组名称未变更', path: groupPath });
|
|
315
|
+
}
|
|
316
|
+
const newSegments = [...parentSegments, newName];
|
|
317
|
+
const newDir = path.resolve(promptsDir, ...newSegments);
|
|
318
|
+
if (fs.existsSync(newDir)) {
|
|
319
|
+
return res.status(400).json({ error: '目标名称已存在,请选择其他名称' });
|
|
320
|
+
}
|
|
319
321
|
|
|
320
|
-
|
|
322
|
+
fse.moveSync(oldDir, newDir);
|
|
321
323
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
res.json({ message: '分组重命名成功', path: newSegments.join('/') });
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error('分组重命名失败:', error);
|
|
327
|
+
res.status(500).json({ error: error.message });
|
|
328
|
+
}
|
|
327
329
|
});
|
|
328
330
|
|
|
329
331
|
// 设置分组目录状态
|
|
330
332
|
router.patch('/groups/status', adminAuthMiddleware, (req, res) => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
try {
|
|
334
|
+
const { path: groupPath, enabled } = req.body || {};
|
|
335
|
+
if (typeof enabled !== 'boolean') {
|
|
336
|
+
return res.status(400).json({ error: '状态值无效' });
|
|
337
|
+
}
|
|
338
|
+
if (!groupPath) {
|
|
339
|
+
return res.status(400).json({ error: '分组路径是必需的' });
|
|
340
|
+
}
|
|
339
341
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
342
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
343
|
+
if (!resolved) {
|
|
344
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
345
|
+
}
|
|
346
|
+
const { dir } = resolved;
|
|
347
|
+
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
348
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
349
|
+
}
|
|
348
350
|
|
|
349
|
-
|
|
351
|
+
util.writeGroupMeta(dir, { enabled });
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
res.json({ message: '分组状态已更新', enabled });
|
|
354
|
+
} catch (error) {
|
|
355
|
+
logger.error('更新分组状态失败:', error);
|
|
356
|
+
res.status(500).json({ error: error.message });
|
|
357
|
+
}
|
|
356
358
|
});
|
|
357
359
|
|
|
358
360
|
// 删除分组目录
|
|
359
361
|
router.delete('/groups', adminAuthMiddleware, (req, res) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const resolved = util.resolveGroupDir(groupPath);
|
|
370
|
-
if (!resolved) {
|
|
371
|
-
return res.status(400).json({ error: '无效的分组路径' });
|
|
372
|
-
}
|
|
373
|
-
const { dir } = resolved;
|
|
374
|
-
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
375
|
-
return res.status(404).json({ error: '分组不存在' });
|
|
376
|
-
}
|
|
362
|
+
try {
|
|
363
|
+
const groupPath = req.query.path;
|
|
364
|
+
if (!groupPath) {
|
|
365
|
+
return res.status(400).json({ error: '分组路径是必需的' });
|
|
366
|
+
}
|
|
367
|
+
if (groupPath === 'default') {
|
|
368
|
+
return res.status(400).json({ error: '默认分组不允许删除' });
|
|
369
|
+
}
|
|
377
370
|
|
|
378
|
-
|
|
379
|
-
|
|
371
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
372
|
+
if (!resolved) {
|
|
373
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
374
|
+
}
|
|
375
|
+
const { dir } = resolved;
|
|
376
|
+
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
377
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
378
|
+
}
|
|
380
379
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
380
|
+
const entries = fs
|
|
381
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
382
|
+
.filter(entry => entry.name !== GROUP_META_FILENAME && !entry.name.startsWith('.'));
|
|
384
383
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
} catch (error) {
|
|
388
|
-
logger.error('删除分组失败:', error);
|
|
389
|
-
res.status(500).json({ error: error.message });
|
|
384
|
+
if (entries.length > 0) {
|
|
385
|
+
return res.status(400).json({ error: '目录非空,请先移除其下的Prompt或子目录' });
|
|
390
386
|
}
|
|
387
|
+
|
|
388
|
+
fse.removeSync(dir);
|
|
389
|
+
res.json({ message: '分组删除成功' });
|
|
390
|
+
} catch (error) {
|
|
391
|
+
logger.error('删除分组失败:', error);
|
|
392
|
+
res.status(500).json({ error: error.message });
|
|
393
|
+
}
|
|
391
394
|
});
|
|
392
395
|
|
|
393
396
|
// 切换提示词启用状态
|
|
394
397
|
router.post('/prompts/:name/toggle', adminAuthMiddleware, (req, res) => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
try {
|
|
399
|
+
const prompts = util.getPromptsFromFiles();
|
|
400
|
+
const targetPath = req.query.path;
|
|
401
|
+
let prompt;
|
|
402
|
+
if (targetPath) {
|
|
403
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
404
|
+
}
|
|
405
|
+
if (!prompt) {
|
|
406
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
407
|
+
}
|
|
405
408
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
+
if (!prompt) {
|
|
410
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
411
|
+
}
|
|
409
412
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
+
// 读取原始YAML文件内容
|
|
414
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
415
|
+
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
413
416
|
|
|
414
|
-
|
|
415
|
-
|
|
417
|
+
// 解析YAML
|
|
418
|
+
const promptData = yaml.load(yamlContent);
|
|
416
419
|
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
// 切换启用状态
|
|
421
|
+
promptData.enabled = !promptData.enabled;
|
|
419
422
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
+
// 保存更新后的YAML
|
|
424
|
+
const newYamlContent = yaml.dump(promptData);
|
|
425
|
+
fs.writeFileSync(promptPath, newYamlContent, 'utf8');
|
|
423
426
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
res.json({ message: '状态切换成功', enabled: promptData.enabled });
|
|
428
|
+
} catch (error) {
|
|
429
|
+
res.status(500).json({ error: error.message });
|
|
430
|
+
}
|
|
428
431
|
});
|
|
429
432
|
|
|
430
433
|
// 删除提示词(软删)
|
|
431
434
|
router.delete('/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
435
|
+
try {
|
|
436
|
+
const prompts = util.getPromptsFromFiles();
|
|
437
|
+
const targetPath = req.query.path;
|
|
438
|
+
let prompt;
|
|
439
|
+
if (targetPath) {
|
|
440
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
441
|
+
}
|
|
442
|
+
if (!prompt) {
|
|
443
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
444
|
+
}
|
|
442
445
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
+
if (!prompt) {
|
|
447
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
448
|
+
}
|
|
446
449
|
|
|
447
|
-
|
|
448
|
-
|
|
450
|
+
// 读取原始文件路径
|
|
451
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
449
452
|
|
|
450
|
-
|
|
451
|
-
|
|
453
|
+
// 直接删除文件
|
|
454
|
+
fse.unlinkSync(promptPath);
|
|
452
455
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
456
|
+
res.json({ message: '删除成功' });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
res.status(500).json({ error: error.message });
|
|
459
|
+
}
|
|
457
460
|
});
|
|
458
461
|
|
|
459
462
|
// Markdown预览
|
|
460
463
|
router.post('/md-preview', adminAuthMiddleware, (req, res) => {
|
|
461
|
-
|
|
462
|
-
|
|
464
|
+
try {
|
|
465
|
+
const { yaml: yamlContent, vars } = req.body;
|
|
463
466
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
// 解析YAML
|
|
469
|
-
const promptData = yaml.load(yamlContent);
|
|
467
|
+
if (!yamlContent) {
|
|
468
|
+
return res.status(400).json({ error: 'YAML内容是必需的' });
|
|
469
|
+
}
|
|
470
470
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (promptData.messages && Array.isArray(promptData.messages)) {
|
|
474
|
-
const userMessages = promptData.messages.filter(msg => msg.role === 'user');
|
|
471
|
+
// 解析YAML
|
|
472
|
+
const promptData = yaml.load(yamlContent);
|
|
475
473
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
474
|
+
// 处理变量替换
|
|
475
|
+
let content = '';
|
|
476
|
+
if (promptData.messages && Array.isArray(promptData.messages)) {
|
|
477
|
+
const userMessages = promptData.messages.filter(msg => msg.role === 'user');
|
|
479
478
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
484
|
-
text = text.replace(placeholder, String(value));
|
|
485
|
-
}
|
|
486
|
-
}
|
|
479
|
+
for (const message of userMessages) {
|
|
480
|
+
if (message.content && typeof message.content.text === 'string') {
|
|
481
|
+
let text = message.content.text;
|
|
487
482
|
|
|
488
|
-
|
|
489
|
-
|
|
483
|
+
// 替换变量
|
|
484
|
+
if (vars) {
|
|
485
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
486
|
+
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
487
|
+
text = text.replace(placeholder, String(value));
|
|
490
488
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
content += `${text}\n\n`;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 简单的Markdown转HTML(实际应用中可以使用专门的库)
|
|
497
|
+
const html = content
|
|
498
|
+
.replace(/&/g, '&')
|
|
499
|
+
.replace(/</g, '<')
|
|
500
|
+
.replace(/>/g, '>')
|
|
501
|
+
.replace(/\n/g, '<br>')
|
|
502
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
503
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
504
|
+
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
505
|
+
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
|
506
|
+
.replace(/### (.*?)(<br>|$)/g, '<h3>$1</h3>')
|
|
507
|
+
.replace(/## (.*?)(<br>|$)/g, '<h2>$1</h2>')
|
|
508
|
+
.replace(/# (.*?)(<br>|$)/g, '<h1>$1</h1>');
|
|
509
|
+
|
|
510
|
+
res.json({ html });
|
|
511
|
+
} catch (error) {
|
|
512
|
+
res.status(500).json({ error: error.message });
|
|
513
|
+
}
|
|
511
514
|
});
|
|
512
515
|
|
|
513
516
|
// 执行终端命令
|
|
514
517
|
router.post('/terminal/execute', adminAuthMiddleware, (req, res) => {
|
|
515
|
-
|
|
516
|
-
|
|
518
|
+
try {
|
|
519
|
+
const { command, cwd } = req.body;
|
|
520
|
+
|
|
521
|
+
if (!command) {
|
|
522
|
+
return res.status(400).json({ error: '命令是必需的' });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 设置执行选项
|
|
526
|
+
const options = {
|
|
527
|
+
cwd: cwd || process.cwd(),
|
|
528
|
+
shell: true,
|
|
529
|
+
env: { ...process.env, FORCE_COLOR: '1' } // 启用颜色输出
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
let output = '';
|
|
533
|
+
let errorOutput = '';
|
|
534
|
+
let exitCode = null;
|
|
535
|
+
|
|
536
|
+
const child = spawn(command, [], options);
|
|
537
|
+
|
|
538
|
+
// 设置超时(5分钟)
|
|
539
|
+
const timeout = setTimeout(
|
|
540
|
+
() => {
|
|
541
|
+
child.kill('SIGTERM');
|
|
542
|
+
res.status(408).json({ error: '命令执行超时' });
|
|
543
|
+
},
|
|
544
|
+
5 * 60 * 1000
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
// 监听标准输出
|
|
548
|
+
child.stdout.on('data', data => {
|
|
549
|
+
const chunk = data.toString();
|
|
550
|
+
output += chunk;
|
|
551
|
+
});
|
|
517
552
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
553
|
+
// 监听错误输出
|
|
554
|
+
child.stderr.on('data', data => {
|
|
555
|
+
const chunk = data.toString();
|
|
556
|
+
errorOutput += chunk;
|
|
557
|
+
});
|
|
521
558
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// 监听错误输出
|
|
548
|
-
child.stderr.on('data', (data) => {
|
|
549
|
-
const chunk = data.toString();
|
|
550
|
-
errorOutput += chunk;
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// 监听进程结束
|
|
554
|
-
child.on('close', (code) => {
|
|
555
|
-
clearTimeout(timeout);
|
|
556
|
-
exitCode = code;
|
|
557
|
-
|
|
558
|
-
res.json({
|
|
559
|
-
success: true,
|
|
560
|
-
command,
|
|
561
|
-
output,
|
|
562
|
-
errorOutput,
|
|
563
|
-
exitCode,
|
|
564
|
-
cwd: options.cwd
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// 监听错误
|
|
569
|
-
child.on('error', (error) => {
|
|
570
|
-
clearTimeout(timeout);
|
|
571
|
-
logger.error('终端命令执行错误:', error);
|
|
572
|
-
res.status(500).json({ error: `命令执行失败: ${error.message}` });
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
} catch (error) {
|
|
576
|
-
logger.error('终端命令执行异常:', error);
|
|
577
|
-
res.status(500).json({ error: error.message });
|
|
578
|
-
}
|
|
559
|
+
// 监听进程结束
|
|
560
|
+
child.on('close', code => {
|
|
561
|
+
clearTimeout(timeout);
|
|
562
|
+
exitCode = code;
|
|
563
|
+
|
|
564
|
+
res.json({
|
|
565
|
+
success: true,
|
|
566
|
+
command,
|
|
567
|
+
output,
|
|
568
|
+
errorOutput,
|
|
569
|
+
exitCode,
|
|
570
|
+
cwd: options.cwd
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// 监听错误
|
|
575
|
+
child.on('error', error => {
|
|
576
|
+
clearTimeout(timeout);
|
|
577
|
+
logger.error('终端命令执行错误:', error);
|
|
578
|
+
res.status(500).json({ error: `命令执行失败: ${error.message}` });
|
|
579
|
+
});
|
|
580
|
+
} catch (error) {
|
|
581
|
+
logger.error('终端命令执行异常:', error);
|
|
582
|
+
res.status(500).json({ error: error.message });
|
|
583
|
+
}
|
|
579
584
|
});
|
|
580
585
|
|
|
581
586
|
// 获取当前工作目录
|
|
582
587
|
router.get('/terminal/cwd', adminAuthMiddleware, (req, res) => {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
588
|
+
try {
|
|
589
|
+
res.json({
|
|
590
|
+
cwd: process.cwd(),
|
|
591
|
+
home: process.env.HOME || process.env.USERPROFILE
|
|
592
|
+
});
|
|
593
|
+
} catch (error) {
|
|
594
|
+
res.status(500).json({ error: error.message });
|
|
595
|
+
}
|
|
591
596
|
});
|
|
592
597
|
|
|
593
598
|
// 列出目录内容
|
|
594
599
|
router.get('/terminal/ls', adminAuthMiddleware, (req, res) => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
600
|
+
try {
|
|
601
|
+
const { path: targetPath = '.' } = req.query;
|
|
602
|
+
const fullPath = path.resolve(targetPath);
|
|
598
603
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
604
|
+
// 检查路径是否存在
|
|
605
|
+
if (!fs.existsSync(fullPath)) {
|
|
606
|
+
return res.status(404).json({ error: '路径不存在' });
|
|
607
|
+
}
|
|
603
608
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
+
// 检查是否是目录
|
|
610
|
+
const stat = fs.statSync(fullPath);
|
|
611
|
+
if (!stat.isDirectory()) {
|
|
612
|
+
return res.status(400).json({ error: '路径不是目录' });
|
|
613
|
+
}
|
|
609
614
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
return {
|
|
616
|
-
name: item,
|
|
617
|
-
path: itemPath,
|
|
618
|
-
type: itemStat.isDirectory() ? 'directory' : 'file',
|
|
619
|
-
size: itemStat.size,
|
|
620
|
-
modified: itemStat.mtime.toISOString()
|
|
621
|
-
};
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// 排序:目录在前,文件在后,按名称排序
|
|
625
|
-
items.sort((a, b) => {
|
|
626
|
-
if (a.type !== b.type) {
|
|
627
|
-
return a.type === 'directory' ? -1 : 1;
|
|
628
|
-
}
|
|
629
|
-
return a.name.localeCompare(b.name);
|
|
630
|
-
});
|
|
615
|
+
// 读取目录内容
|
|
616
|
+
const items = fs.readdirSync(fullPath).map(item => {
|
|
617
|
+
const itemPath = path.join(fullPath, item);
|
|
618
|
+
const itemStat = fs.statSync(itemPath);
|
|
631
619
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
620
|
+
return {
|
|
621
|
+
name: item,
|
|
622
|
+
path: itemPath,
|
|
623
|
+
type: itemStat.isDirectory() ? 'directory' : 'file',
|
|
624
|
+
size: itemStat.size,
|
|
625
|
+
modified: itemStat.mtime.toISOString()
|
|
626
|
+
};
|
|
627
|
+
});
|
|
637
628
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
629
|
+
// 排序:目录在前,文件在后,按名称排序
|
|
630
|
+
items.sort((a, b) => {
|
|
631
|
+
if (a.type !== b.type) {
|
|
632
|
+
return a.type === 'directory' ? -1 : 1;
|
|
633
|
+
}
|
|
634
|
+
return a.name.localeCompare(b.name);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
res.json({
|
|
638
|
+
success: true,
|
|
639
|
+
path: fullPath,
|
|
640
|
+
items
|
|
641
|
+
});
|
|
642
|
+
} catch (error) {
|
|
643
|
+
logger.error('列出目录内容失败:', error);
|
|
644
|
+
res.status(500).json({ error: error.message });
|
|
645
|
+
}
|
|
642
646
|
});
|
|
643
647
|
|
|
644
648
|
// ==================== 优化相关路由 ====================
|
|
645
649
|
|
|
646
650
|
// 优化提示词(流式)
|
|
647
651
|
router.post('/prompts/optimize', adminAuthMiddleware, async (req, res) => {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
652
|
+
try {
|
|
653
|
+
const { prompt, templateId, modelId, sessionId } = req.body;
|
|
654
|
+
|
|
655
|
+
if (!prompt || !templateId || !modelId) {
|
|
656
|
+
return res.status(400).json({ error: '提示词、模板ID和模型ID是必需的' });
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 设置 SSE 响应头
|
|
660
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
661
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
662
|
+
res.setHeader('Connection', 'keep-alive');
|
|
663
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
664
|
+
|
|
665
|
+
// 调用优化服务(流式)
|
|
666
|
+
await optimizationService.optimizePrompt(
|
|
667
|
+
prompt,
|
|
668
|
+
templateId,
|
|
669
|
+
modelId,
|
|
670
|
+
chunk => {
|
|
671
|
+
// 流式输出回调
|
|
672
|
+
res.write(`data: ${JSON.stringify({ chunk })}\n\n`);
|
|
673
|
+
},
|
|
674
|
+
sessionId
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
// 发送完成信号
|
|
678
|
+
res.write('data: [DONE]\n\n');
|
|
679
|
+
res.end();
|
|
680
|
+
} catch (error) {
|
|
681
|
+
logger.error('优化提示词失败:', error);
|
|
682
|
+
// 格式化错误信息,添加用户友好的前缀
|
|
683
|
+
const errorMessage = `模型执行失败: ${error.message}`;
|
|
684
|
+
res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`);
|
|
685
|
+
res.end();
|
|
686
|
+
}
|
|
683
687
|
});
|
|
684
688
|
|
|
685
689
|
// 迭代优化(流式)
|
|
686
690
|
router.post('/prompts/optimize/iterate', adminAuthMiddleware, async (req, res) => {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
691
|
+
try {
|
|
692
|
+
const { currentResult, templateId, modelId, sessionId, guideText } = req.body;
|
|
693
|
+
|
|
694
|
+
if (!currentResult || !templateId || !modelId || !sessionId) {
|
|
695
|
+
return res.status(400).json({ error: '当前结果、模板ID、模型ID和会话ID是必需的' });
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// 设置 SSE 响应头
|
|
699
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
700
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
701
|
+
res.setHeader('Connection', 'keep-alive');
|
|
702
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
703
|
+
|
|
704
|
+
// 调用迭代优化服务(流式)
|
|
705
|
+
await optimizationService.iterateOptimization(
|
|
706
|
+
currentResult,
|
|
707
|
+
templateId,
|
|
708
|
+
modelId,
|
|
709
|
+
chunk => {
|
|
710
|
+
// 流式输出回调
|
|
711
|
+
res.write(`data: ${JSON.stringify({ chunk })}\n\n`);
|
|
712
|
+
},
|
|
713
|
+
sessionId,
|
|
714
|
+
guideText // 传递优化指导参数
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// 发送完成信号
|
|
718
|
+
res.write('data: [DONE]\n\n');
|
|
719
|
+
res.end();
|
|
720
|
+
} catch (error) {
|
|
721
|
+
logger.error('迭代优化失败:', error);
|
|
722
|
+
// 格式化错误信息,添加用户友好的前缀
|
|
723
|
+
const errorMessage = `模型执行失败: ${error.message}`;
|
|
724
|
+
res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`);
|
|
725
|
+
res.end();
|
|
726
|
+
}
|
|
723
727
|
});
|
|
724
728
|
|
|
725
729
|
// 清除会话迭代信息
|
|
726
730
|
router.delete('/prompts/optimize/session/:sessionId', adminAuthMiddleware, (req, res) => {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
731
|
+
try {
|
|
732
|
+
const { sessionId } = req.params;
|
|
733
|
+
optimizationService.clearSession(sessionId);
|
|
734
|
+
res.json({ message: '会话已清除' });
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.error('清除会话失败:', error);
|
|
737
|
+
res.status(500).json({ error: error.message });
|
|
738
|
+
}
|
|
735
739
|
});
|
|
736
740
|
|
|
737
741
|
// 获取会话迭代信息
|
|
738
742
|
router.get('/prompts/optimize/session/:sessionId', adminAuthMiddleware, (req, res) => {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
743
|
+
try {
|
|
744
|
+
const { sessionId } = req.params;
|
|
745
|
+
const sessionInfo = optimizationService.getSessionInfo(sessionId);
|
|
746
|
+
res.json(sessionInfo);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
logger.error('获取会话信息失败:', error);
|
|
749
|
+
res.status(500).json({ error: error.message });
|
|
750
|
+
}
|
|
747
751
|
});
|
|
748
752
|
|
|
749
753
|
// ==================== 模板管理路由 ====================
|
|
750
754
|
|
|
751
755
|
// 获取所有模板
|
|
752
756
|
router.get('/optimization/templates', adminAuthMiddleware, (req, res) => {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
757
|
+
try {
|
|
758
|
+
const templates = templateManager.getTemplates();
|
|
759
|
+
res.json(templates);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
logger.error('获取模板列表失败:', error);
|
|
762
|
+
res.status(500).json({ error: error.message });
|
|
763
|
+
}
|
|
760
764
|
});
|
|
761
765
|
|
|
762
766
|
// 创建模板
|
|
763
767
|
router.post('/optimization/templates', adminAuthMiddleware, async (req, res) => {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
768
|
+
try {
|
|
769
|
+
const template = await templateManager.createTemplate(req.body);
|
|
770
|
+
res.json(template);
|
|
771
|
+
} catch (error) {
|
|
772
|
+
logger.error('创建模板失败:', error);
|
|
773
|
+
res.status(500).json({ error: error.message });
|
|
774
|
+
}
|
|
771
775
|
});
|
|
772
776
|
|
|
773
777
|
// 更新模板
|
|
774
778
|
router.put('/optimization/templates/:id', adminAuthMiddleware, async (req, res) => {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
779
|
+
try {
|
|
780
|
+
const { id } = req.params;
|
|
781
|
+
const template = await templateManager.updateTemplate(id, req.body);
|
|
782
|
+
res.json(template);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
logger.error('更新模板失败:', error);
|
|
785
|
+
res.status(500).json({ error: error.message });
|
|
786
|
+
}
|
|
783
787
|
});
|
|
784
788
|
|
|
785
789
|
// 删除模板
|
|
786
790
|
router.delete('/optimization/templates/:id', adminAuthMiddleware, async (req, res) => {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
791
|
+
try {
|
|
792
|
+
const { id } = req.params;
|
|
793
|
+
await templateManager.deleteTemplate(id);
|
|
794
|
+
res.json({ message: '模板删除成功' });
|
|
795
|
+
} catch (error) {
|
|
796
|
+
logger.error('删除模板失败:', error);
|
|
797
|
+
res.status(500).json({ error: error.message });
|
|
798
|
+
}
|
|
795
799
|
});
|
|
796
800
|
|
|
797
801
|
// ==================== 模型管理路由 ====================
|
|
798
802
|
|
|
799
803
|
// 获取模型提供商列表
|
|
800
804
|
router.get('/optimization/models/providers', adminAuthMiddleware, async (req, res) => {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
805
|
+
try {
|
|
806
|
+
const providers = await modelManager.getProviders();
|
|
807
|
+
res.json(providers);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
logger.error('获取模型提供商列表失败:', error);
|
|
810
|
+
res.status(500).json({ error: error.message });
|
|
811
|
+
}
|
|
808
812
|
});
|
|
809
813
|
|
|
810
814
|
// 获取模型提供商的默认配置
|
|
811
815
|
router.get('/optimization/models/providers/:key', adminAuthMiddleware, async (req, res) => {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
816
|
+
try {
|
|
817
|
+
const { key } = req.params;
|
|
818
|
+
const defaults = await modelManager.getProviderDefaults(key);
|
|
819
|
+
if (!defaults) {
|
|
820
|
+
return res.status(404).json({ error: '提供商不存在' });
|
|
821
|
+
}
|
|
822
|
+
res.json(defaults);
|
|
823
|
+
} catch (error) {
|
|
824
|
+
logger.error('获取提供商默认配置失败:', error);
|
|
825
|
+
res.status(500).json({ error: error.message });
|
|
826
|
+
}
|
|
823
827
|
});
|
|
824
828
|
|
|
825
829
|
// 获取所有模型
|
|
826
830
|
router.get('/optimization/models', adminAuthMiddleware, (req, res) => {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
831
|
+
try {
|
|
832
|
+
const models = modelManager.getModels();
|
|
833
|
+
res.json(models);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
logger.error('获取模型列表失败:', error);
|
|
836
|
+
res.status(500).json({ error: error.message });
|
|
837
|
+
}
|
|
834
838
|
});
|
|
835
839
|
|
|
836
840
|
// 创建模型
|
|
837
841
|
router.post('/optimization/models', adminAuthMiddleware, async (req, res) => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
842
|
+
try {
|
|
843
|
+
const model = await modelManager.createModel(req.body);
|
|
844
|
+
res.json(model);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
logger.error('创建模型失败:', error);
|
|
847
|
+
res.status(500).json({ error: error.message });
|
|
848
|
+
}
|
|
845
849
|
});
|
|
846
850
|
|
|
847
851
|
// 更新模型
|
|
848
852
|
router.put('/optimization/models/:id', adminAuthMiddleware, async (req, res) => {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
853
|
+
try {
|
|
854
|
+
const { id } = req.params;
|
|
855
|
+
const model = await modelManager.updateModel(id, req.body);
|
|
856
|
+
res.json(model);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
logger.error('更新模型失败:', error);
|
|
859
|
+
res.status(500).json({ error: error.message });
|
|
860
|
+
}
|
|
857
861
|
});
|
|
858
862
|
|
|
859
863
|
// 删除模型
|
|
860
864
|
router.delete('/optimization/models/:id', adminAuthMiddleware, async (req, res) => {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
865
|
+
try {
|
|
866
|
+
const { id } = req.params;
|
|
867
|
+
await modelManager.deleteModel(id);
|
|
868
|
+
res.json({ message: '模型删除成功' });
|
|
869
|
+
} catch (error) {
|
|
870
|
+
logger.error('删除模型失败:', error);
|
|
871
|
+
res.status(500).json({ error: error.message });
|
|
872
|
+
}
|
|
869
873
|
});
|
|
870
874
|
|
|
871
875
|
// 测试模型连接
|
|
872
876
|
router.post('/optimization/models/:id/test', adminAuthMiddleware, async (req, res) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
877
|
+
try {
|
|
878
|
+
const { id } = req.params;
|
|
879
|
+
const result = await optimizationService.testModel(id);
|
|
880
|
+
res.json(result);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
logger.error('测试模型连接失败:', error);
|
|
883
|
+
res.status(500).json({ error: error.message });
|
|
884
|
+
}
|
|
881
885
|
});
|
|
882
886
|
|
|
883
887
|
// ==================== 优化配置路由 ====================
|
|
884
888
|
|
|
885
889
|
// 获取优化配置(预留接口,可用于全局配置)
|
|
886
890
|
router.get('/optimization/config', adminAuthMiddleware, (req, res) => {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
891
|
+
try {
|
|
892
|
+
res.json({
|
|
893
|
+
maxIterations: 10,
|
|
894
|
+
encryptionEnabled: true
|
|
895
|
+
});
|
|
896
|
+
} catch (error) {
|
|
897
|
+
logger.error('获取优化配置失败:', error);
|
|
898
|
+
res.status(500).json({ error: error.message });
|
|
899
|
+
}
|
|
896
900
|
});
|
|
897
901
|
|
|
898
902
|
// 更新优化配置(预留接口)
|
|
899
903
|
router.put('/optimization/config', adminAuthMiddleware, (req, res) => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
904
|
+
try {
|
|
905
|
+
res.json({ message: '配置更新成功' });
|
|
906
|
+
} catch (error) {
|
|
907
|
+
logger.error('更新优化配置失败:', error);
|
|
908
|
+
res.status(500).json({ error: error.message });
|
|
909
|
+
}
|
|
906
910
|
});
|
|
907
911
|
|
|
908
|
-
export const adminRouter = router;
|
|
912
|
+
export const adminRouter = router;
|