@becrafter/prompt-manager 0.0.16 → 0.0.18
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/README.md +66 -80
- package/app/desktop/main.js +321 -31
- package/app/desktop/package-lock.json +784 -79
- package/app/desktop/package.json +11 -3
- package/package.json +2 -1
- package/packages/admin-ui/admin.html +1 -1
- package/packages/server/api/admin.routes.js +450 -0
- package/packages/server/api/open.routes.js +89 -0
- package/packages/server/app.js +163 -0
- package/packages/server/{mcp.js → mcp/mcp.handler.js} +11 -79
- package/packages/server/mcp/mcp.server.js +82 -0
- package/packages/server/middlewares/auth.middleware.js +34 -0
- package/packages/server/server.js +42 -908
- package/packages/server/{manager.js → services/manager.js} +13 -5
- package/packages/server/{config.js → utils/config.js} +27 -27
- package/packages/server/utils/util.js +356 -0
- package/packages/server/mcpManager.js +0 -205
- /package/packages/server/{logger.js → utils/logger.js} +0 -0
package/app/desktop/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@becrafter/prompt-desktop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Menu bar desktop wrapper for @becrafter/prompt-manager",
|
|
5
5
|
"private": true,
|
|
6
6
|
"main": "main.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"electron-builder": "^24.13.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
17
18
|
"tar": "^6.2.1"
|
|
18
19
|
},
|
|
19
20
|
"build": {
|
|
@@ -33,10 +34,17 @@
|
|
|
33
34
|
"to": "prompt-manager",
|
|
34
35
|
"filter": [
|
|
35
36
|
"package.json",
|
|
36
|
-
"package-lock.json",
|
|
37
37
|
"packages/**/*",
|
|
38
38
|
"examples/**/*",
|
|
39
|
-
"node_modules
|
|
39
|
+
"!node_modules",
|
|
40
|
+
"!dist",
|
|
41
|
+
"!app/desktop",
|
|
42
|
+
"!scripts",
|
|
43
|
+
"!*.md",
|
|
44
|
+
"!*.lock",
|
|
45
|
+
"!*.log",
|
|
46
|
+
"!*.gitignore",
|
|
47
|
+
"!*.npmignore"
|
|
40
48
|
]
|
|
41
49
|
}
|
|
42
50
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@becrafter/prompt-manager",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Remote MCP Server for managing prompts",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
43
43
|
"dotenv": "^17.2.3",
|
|
44
|
+
"electron-builder": "^26.0.12",
|
|
44
45
|
"express": "^5.1.0",
|
|
45
46
|
"fs-extra": "^11.2.0",
|
|
46
47
|
"js-yaml": "^4.1.0",
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理后台接口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import fse from 'fs-extra';
|
|
9
|
+
import yaml from 'js-yaml';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { util, GROUP_META_FILENAME } from '../utils/util.js';
|
|
13
|
+
import { config } from '../utils/config.js';
|
|
14
|
+
import {adminAuthMiddleware} from '../middlewares/auth.middleware.js'
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
|
|
18
|
+
// 获取prompts目录路径(在启动时可能被覆盖)
|
|
19
|
+
let promptsDir = config.getPromptsDir();
|
|
20
|
+
|
|
21
|
+
// 登录端点
|
|
22
|
+
router.post('/login', (req, res) => {
|
|
23
|
+
// 检查是否启用了管理员功能
|
|
24
|
+
if (!config.adminEnable) {
|
|
25
|
+
return res.status(404).json({ error: 'Admin功能未启用' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { username, password } = req.body;
|
|
29
|
+
|
|
30
|
+
if (!username || !password) {
|
|
31
|
+
return res.status(400).json({ error: '用户名和密码是必需的' });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 验证凭据
|
|
35
|
+
const admin = config.admins.find(a => a.username === username && a.password === password);
|
|
36
|
+
if (!admin) {
|
|
37
|
+
return res.status(401).json({ error: '无效的用户名或密码' });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
res.json({ token: admin.token });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 获取分组目录
|
|
44
|
+
router.get('/groups', adminAuthMiddleware, (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const tree = util.buildGroupTree(promptsDir);
|
|
47
|
+
const hasDefault = tree.some(node => node.path === 'default');
|
|
48
|
+
if (!hasDefault) {
|
|
49
|
+
tree.unshift({ name: 'default', path: 'default', children: [], enabled: true });
|
|
50
|
+
}
|
|
51
|
+
res.json(tree);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
res.status(500).json({ error: error.message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 获取所有提示词(支持搜索、过滤和分组)
|
|
58
|
+
router.get('/prompts', adminAuthMiddleware, (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const prompts = util.getPromptsFromFiles();
|
|
61
|
+
|
|
62
|
+
// 处理搜索参数
|
|
63
|
+
const search = req.query.search;
|
|
64
|
+
const enabled = req.query.enabled === 'true';
|
|
65
|
+
const groupPathFilter = req.query.groupPath;
|
|
66
|
+
const group = req.query.group;
|
|
67
|
+
|
|
68
|
+
let filteredPrompts = prompts;
|
|
69
|
+
|
|
70
|
+
// 应用分组过滤
|
|
71
|
+
if (groupPathFilter) {
|
|
72
|
+
filteredPrompts = filteredPrompts.filter(prompt => (prompt.groupPath || prompt.group || 'default') === groupPathFilter);
|
|
73
|
+
} else if (group) {
|
|
74
|
+
filteredPrompts = filteredPrompts.filter(prompt => (prompt.group || 'default') === group);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 应用搜索过滤
|
|
78
|
+
if (search) {
|
|
79
|
+
filteredPrompts = filteredPrompts.filter(prompt =>
|
|
80
|
+
prompt.name.includes(search) ||
|
|
81
|
+
(prompt.description && prompt.description.includes(search))
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 应用启用状态过滤
|
|
86
|
+
if (enabled) {
|
|
87
|
+
filteredPrompts = filteredPrompts.filter(prompt => prompt.enabled);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
filteredPrompts.sort((a, b) => (a.name || '').localeCompare(b.name || '', 'zh-CN'));
|
|
91
|
+
|
|
92
|
+
res.json(filteredPrompts);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
res.status(500).json({ error: error.message });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// 获取单个提示词
|
|
99
|
+
router.get('/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const prompts = util.getPromptsFromFiles();
|
|
102
|
+
const targetPath = req.query.path;
|
|
103
|
+
let prompt;
|
|
104
|
+
if (targetPath) {
|
|
105
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
106
|
+
}
|
|
107
|
+
if (!prompt) {
|
|
108
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!prompt) {
|
|
112
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 读取原始YAML文件内容
|
|
116
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
117
|
+
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
118
|
+
|
|
119
|
+
res.json({
|
|
120
|
+
...prompt,
|
|
121
|
+
yaml: yamlContent
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
res.status(500).json({ error: error.message });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 保存提示词
|
|
129
|
+
router.post('/prompts', adminAuthMiddleware, (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const { name, group, yaml: yamlContent, relativePath: originalRelativePath } = req.body;
|
|
132
|
+
|
|
133
|
+
if (!name || !yamlContent) {
|
|
134
|
+
return res.status(400).json({ error: '名称和YAML内容是必需的' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 验证名称格式
|
|
138
|
+
if (!/^[a-zA-Z0-9-_]{1,64}$/.test(name)) {
|
|
139
|
+
return res.status(400).json({ error: '名称格式无效' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 计算目标路径
|
|
143
|
+
const groupName = group || 'default';
|
|
144
|
+
const normalizedOriginalPath = originalRelativePath ? path.normalize(originalRelativePath).replace(/\\/g, '/') : null;
|
|
145
|
+
|
|
146
|
+
const targetSegments = [];
|
|
147
|
+
if (groupName) {
|
|
148
|
+
targetSegments.push(groupName);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const finalFileName = `${name}.yaml`;
|
|
152
|
+
targetSegments.push(finalFileName);
|
|
153
|
+
|
|
154
|
+
const targetRelativePath = path.posix.join(...targetSegments);
|
|
155
|
+
const targetDir = path.join(promptsDir, path.posix.dirname(targetRelativePath));
|
|
156
|
+
const filePath = path.join(promptsDir, targetRelativePath);
|
|
157
|
+
|
|
158
|
+
fse.ensureDirSync(targetDir);
|
|
159
|
+
|
|
160
|
+
// 检查是否重名(同目录下)
|
|
161
|
+
const prompts = util.getPromptsFromFiles();
|
|
162
|
+
const existingPrompt = prompts.find(p => {
|
|
163
|
+
if (p.name !== name) return false;
|
|
164
|
+
const isOriginalFile = normalizedOriginalPath && p.relativePath === normalizedOriginalPath;
|
|
165
|
+
if (isOriginalFile) return false;
|
|
166
|
+
const sameRelativePath = p.relativePath === targetRelativePath;
|
|
167
|
+
if (sameRelativePath) return false;
|
|
168
|
+
const sameDirectory = path.posix.dirname(p.relativePath || '') === path.posix.dirname(targetRelativePath);
|
|
169
|
+
return sameDirectory;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (existingPrompt) {
|
|
173
|
+
return res.status(400).json({ error: '名称已存在' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 保存文件
|
|
177
|
+
fs.writeFileSync(filePath, yamlContent, 'utf8');
|
|
178
|
+
|
|
179
|
+
// 如果目标路径与原始路径不同,删除旧文件
|
|
180
|
+
if (normalizedOriginalPath && normalizedOriginalPath !== targetRelativePath) {
|
|
181
|
+
const originalFilePath = path.join(promptsDir, normalizedOriginalPath);
|
|
182
|
+
if (fs.existsSync(originalFilePath)) {
|
|
183
|
+
fs.unlinkSync(originalFilePath);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
res.json({ message: '保存成功', relativePath: targetRelativePath, group: groupName });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
res.status(500).json({ error: error.message });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// 创建新分组目录
|
|
194
|
+
router.post('/groups', adminAuthMiddleware, (req, res) => {
|
|
195
|
+
try {
|
|
196
|
+
const { name } = req.body;
|
|
197
|
+
|
|
198
|
+
if (!name) {
|
|
199
|
+
return res.status(400).json({ error: '分组名称是必需的' });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 验证名称格式
|
|
203
|
+
if (!util.isValidGroupName(name)) {
|
|
204
|
+
return res.status(400).json({ error: '名称格式无效,只能包含字母、数字、中划线、下划线和中文' });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const groupDir = path.join(promptsDir, name);
|
|
208
|
+
|
|
209
|
+
// 检查目录是否已存在
|
|
210
|
+
if (fs.existsSync(groupDir)) {
|
|
211
|
+
return res.status(400).json({ error: '分组已存在' });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 创建目录
|
|
215
|
+
fs.mkdirSync(groupDir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
res.json({ message: '分组创建成功' });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
res.status(500).json({ error: error.message });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// 重命名分组目录
|
|
224
|
+
router.patch('/groups/rename', adminAuthMiddleware, (req, res) => {
|
|
225
|
+
try {
|
|
226
|
+
const { path: groupPath, newName } = req.body || {};
|
|
227
|
+
if (!groupPath || !newName) {
|
|
228
|
+
return res.status(400).json({ error: '分组路径和新名称是必需的' });
|
|
229
|
+
}
|
|
230
|
+
if (!util.isValidGroupName(newName)) {
|
|
231
|
+
return res.status(400).json({ error: '名称格式无效,只能包含字母、数字、中划线、下划线和中文' });
|
|
232
|
+
}
|
|
233
|
+
if (groupPath === 'default') {
|
|
234
|
+
return res.status(400).json({ error: '默认分组不允许重命名' });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
238
|
+
if (!resolved) {
|
|
239
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
240
|
+
}
|
|
241
|
+
const { dir: oldDir, segments } = resolved;
|
|
242
|
+
if (!fs.existsSync(oldDir) || !fs.lstatSync(oldDir).isDirectory()) {
|
|
243
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const parentSegments = segments.slice(0, -1);
|
|
247
|
+
const oldName = segments[segments.length - 1];
|
|
248
|
+
if (newName === oldName) {
|
|
249
|
+
return res.json({ message: '分组名称未变更', path: groupPath });
|
|
250
|
+
}
|
|
251
|
+
const newSegments = [...parentSegments, newName];
|
|
252
|
+
const newDir = path.resolve(promptsDir, ...newSegments);
|
|
253
|
+
if (fs.existsSync(newDir)) {
|
|
254
|
+
return res.status(400).json({ error: '目标名称已存在,请选择其他名称' });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fse.moveSync(oldDir, newDir);
|
|
258
|
+
|
|
259
|
+
res.json({ message: '分组重命名成功', path: newSegments.join('/') });
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error('分组重命名失败:', error);
|
|
262
|
+
res.status(500).json({ error: error.message });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// 设置分组目录状态
|
|
267
|
+
router.patch('/groups/status', adminAuthMiddleware, (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const { path: groupPath, enabled } = req.body || {};
|
|
270
|
+
if (typeof enabled !== 'boolean') {
|
|
271
|
+
return res.status(400).json({ error: '状态值无效' });
|
|
272
|
+
}
|
|
273
|
+
if (!groupPath) {
|
|
274
|
+
return res.status(400).json({ error: '分组路径是必需的' });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
278
|
+
if (!resolved) {
|
|
279
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
280
|
+
}
|
|
281
|
+
const { dir } = resolved;
|
|
282
|
+
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
283
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
util.writeGroupMeta(dir, { enabled });
|
|
287
|
+
|
|
288
|
+
res.json({ message: '分组状态已更新', enabled });
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logger.error('更新分组状态失败:', error);
|
|
291
|
+
res.status(500).json({ error: error.message });
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// 删除分组目录
|
|
296
|
+
router.delete('/groups', adminAuthMiddleware, (req, res) => {
|
|
297
|
+
try {
|
|
298
|
+
const groupPath = req.query.path;
|
|
299
|
+
if (!groupPath) {
|
|
300
|
+
return res.status(400).json({ error: '分组路径是必需的' });
|
|
301
|
+
}
|
|
302
|
+
if (groupPath === 'default') {
|
|
303
|
+
return res.status(400).json({ error: '默认分组不允许删除' });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const resolved = util.resolveGroupDir(groupPath);
|
|
307
|
+
if (!resolved) {
|
|
308
|
+
return res.status(400).json({ error: '无效的分组路径' });
|
|
309
|
+
}
|
|
310
|
+
const { dir } = resolved;
|
|
311
|
+
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
312
|
+
return res.status(404).json({ error: '分组不存在' });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
316
|
+
.filter(entry => entry.name !== GROUP_META_FILENAME && !entry.name.startsWith('.'));
|
|
317
|
+
|
|
318
|
+
if (entries.length > 0) {
|
|
319
|
+
return res.status(400).json({ error: '目录非空,请先移除其下的Prompt或子目录' });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fse.removeSync(dir);
|
|
323
|
+
res.json({ message: '分组删除成功' });
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.error('删除分组失败:', error);
|
|
326
|
+
res.status(500).json({ error: error.message });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// 切换提示词启用状态
|
|
331
|
+
router.post('/prompts/:name/toggle', adminAuthMiddleware, (req, res) => {
|
|
332
|
+
try {
|
|
333
|
+
const prompts = util.getPromptsFromFiles();
|
|
334
|
+
const targetPath = req.query.path;
|
|
335
|
+
let prompt;
|
|
336
|
+
if (targetPath) {
|
|
337
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
338
|
+
}
|
|
339
|
+
if (!prompt) {
|
|
340
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!prompt) {
|
|
344
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 读取原始YAML文件内容
|
|
348
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
349
|
+
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
350
|
+
|
|
351
|
+
// 解析YAML
|
|
352
|
+
const promptData = yaml.load(yamlContent);
|
|
353
|
+
|
|
354
|
+
// 切换启用状态
|
|
355
|
+
promptData.enabled = !promptData.enabled;
|
|
356
|
+
|
|
357
|
+
// 保存更新后的YAML
|
|
358
|
+
const newYamlContent = yaml.dump(promptData);
|
|
359
|
+
fs.writeFileSync(promptPath, newYamlContent, 'utf8');
|
|
360
|
+
|
|
361
|
+
res.json({ message: '状态切换成功', enabled: promptData.enabled });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
res.status(500).json({ error: error.message });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// 删除提示词(软删)
|
|
368
|
+
router.delete('/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
369
|
+
try {
|
|
370
|
+
const prompts = util.getPromptsFromFiles();
|
|
371
|
+
const targetPath = req.query.path;
|
|
372
|
+
let prompt;
|
|
373
|
+
if (targetPath) {
|
|
374
|
+
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
375
|
+
}
|
|
376
|
+
if (!prompt) {
|
|
377
|
+
prompt = prompts.find(p => p.name === req.params.name);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!prompt) {
|
|
381
|
+
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 读取原始文件路径
|
|
385
|
+
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
386
|
+
|
|
387
|
+
// 直接删除文件
|
|
388
|
+
fse.unlinkSync(promptPath);
|
|
389
|
+
|
|
390
|
+
res.json({ message: '删除成功' });
|
|
391
|
+
} catch (error) {
|
|
392
|
+
res.status(500).json({ error: error.message });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Markdown预览
|
|
397
|
+
router.post('/md-preview', adminAuthMiddleware, (req, res) => {
|
|
398
|
+
try {
|
|
399
|
+
const { yaml: yamlContent, vars } = req.body;
|
|
400
|
+
|
|
401
|
+
if (!yamlContent) {
|
|
402
|
+
return res.status(400).json({ error: 'YAML内容是必需的' });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 解析YAML
|
|
406
|
+
const promptData = yaml.load(yamlContent);
|
|
407
|
+
|
|
408
|
+
// 处理变量替换
|
|
409
|
+
let content = '';
|
|
410
|
+
if (promptData.messages && Array.isArray(promptData.messages)) {
|
|
411
|
+
const userMessages = promptData.messages.filter(msg => msg.role === 'user');
|
|
412
|
+
|
|
413
|
+
for (const message of userMessages) {
|
|
414
|
+
if (message.content && typeof message.content.text === 'string') {
|
|
415
|
+
let text = message.content.text;
|
|
416
|
+
|
|
417
|
+
// 替换变量
|
|
418
|
+
if (vars) {
|
|
419
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
420
|
+
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
421
|
+
text = text.replace(placeholder, String(value));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
content += text + '\n\n';
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 简单的Markdown转HTML(实际应用中可以使用专门的库)
|
|
431
|
+
const html = content
|
|
432
|
+
.replace(/&/g, '&')
|
|
433
|
+
.replace(/</g, '<')
|
|
434
|
+
.replace(/>/g, '>')
|
|
435
|
+
.replace(/\n/g, '<br>')
|
|
436
|
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
437
|
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
438
|
+
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
439
|
+
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
|
440
|
+
.replace(/### (.*?)(<br>|$)/g, '<h3>$1</h3>')
|
|
441
|
+
.replace(/## (.*?)(<br>|$)/g, '<h2>$1</h2>')
|
|
442
|
+
.replace(/# (.*?)(<br>|$)/g, '<h1>$1</h1>');
|
|
443
|
+
|
|
444
|
+
res.json({ html });
|
|
445
|
+
} catch (error) {
|
|
446
|
+
res.status(500).json({ error: error.message });
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
export const adminRouter = router;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 开放数据接口
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { util } from '../utils/util.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
router.get('/prompts', (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const prompts = util.getPromptsFromFiles();
|
|
13
|
+
|
|
14
|
+
// 过滤出启用的提示词
|
|
15
|
+
const filtered = prompts.filter(prompt => {
|
|
16
|
+
const groupActive = prompt.groupEnabled !== false;
|
|
17
|
+
const promptActive = prompt.enabled === true;
|
|
18
|
+
return groupActive && promptActive;
|
|
19
|
+
});
|
|
20
|
+
logger.debug(`filtered prompts: ${JSON.stringify(filtered)}`);
|
|
21
|
+
|
|
22
|
+
// 判断是否有搜索参数,且搜索参数名为search
|
|
23
|
+
if (req.query.search) {
|
|
24
|
+
const search = req.query.search;
|
|
25
|
+
|
|
26
|
+
// 实现相似度匹配算法
|
|
27
|
+
const searchResults = filtered.map(prompt => {
|
|
28
|
+
const score = util.calculateSimilarityScore(search, prompt);
|
|
29
|
+
return {
|
|
30
|
+
prompt: {
|
|
31
|
+
id: prompt.uniqueId,
|
|
32
|
+
name: prompt.name,
|
|
33
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
34
|
+
metadata: {
|
|
35
|
+
// fileName: prompt.fileName,
|
|
36
|
+
fullPath: prompt.relativePath
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
score: score
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.filter(result => result.score > 0) // 只返回有匹配的结果
|
|
43
|
+
.sort((a, b) => b.score - a.score); // 按相似度得分降序排列
|
|
44
|
+
|
|
45
|
+
// 只返回prompt字段
|
|
46
|
+
res.json(searchResults.map(result => result.prompt));
|
|
47
|
+
} else {
|
|
48
|
+
// 无搜索参数时,返回精简信息
|
|
49
|
+
const simplifiedPrompts = filtered.map(prompt => ({
|
|
50
|
+
id: prompt.uniqueId,
|
|
51
|
+
name: prompt.name,
|
|
52
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
53
|
+
metadata: {
|
|
54
|
+
// fileName: prompt.fileName,
|
|
55
|
+
fullPath: prompt.relativePath
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
res.json(simplifiedPrompts);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
res.status(500).json({ error: error.message });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 查看提示词内容
|
|
67
|
+
router.post('/process', async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const { promptName, arguments: args } = req.body;
|
|
70
|
+
|
|
71
|
+
if (!promptName) {
|
|
72
|
+
return res.status(400).json({ error: 'Missing promptName' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const prompts = util.getPromptsFromFiles();
|
|
76
|
+
const prompt = prompts.find(p => p.name === promptName);
|
|
77
|
+
|
|
78
|
+
if (!prompt) {
|
|
79
|
+
return res.status(404).json({ error: `Prompt "${promptName}" not found` });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const processedPrompt = await util.processPromptContent(prompt, args);
|
|
83
|
+
res.json({ processedText: processedPrompt });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
res.status(500).json({ error: error.message });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const openRouter = router;
|