@becrafter/prompt-manager 0.0.16 → 0.0.19
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/IFLOW.md +175 -0
- package/README.md +66 -80
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon_1024x1024.png +0 -0
- package/app/desktop/assets/icons/icon_128x128.png +0 -0
- package/app/desktop/assets/icons/icon_16x16.png +0 -0
- package/app/desktop/assets/icons/icon_24x24.png +0 -0
- package/app/desktop/assets/icons/icon_256x256.png +0 -0
- package/app/desktop/assets/icons/icon_32x32.png +0 -0
- package/app/desktop/assets/icons/icon_48x48.png +0 -0
- package/app/desktop/assets/icons/icon_512x512.png +0 -0
- package/app/desktop/assets/icons/icon_64x64.png +0 -0
- package/app/desktop/assets/icons/icon_96x96.png +0 -0
- package/app/desktop/assets/templates/about.html +147 -0
- package/app/desktop/main.js +178 -460
- package/app/desktop/package-lock.json +1150 -412
- package/app/desktop/package.json +54 -11
- package/app/desktop/preload.js +7 -0
- package/app/desktop/src/core/error-handler.js +108 -0
- package/app/desktop/src/core/event-emitter.js +84 -0
- package/app/desktop/src/core/logger.js +108 -0
- package/app/desktop/src/core/state-manager.js +125 -0
- package/app/desktop/src/services/module-loader.js +193 -0
- package/app/desktop/src/services/runtime-manager.js +152 -0
- package/app/desktop/src/services/service-manager.js +169 -0
- package/app/desktop/src/services/update-manager.js +268 -0
- package/app/desktop/src/ui/about-dialog-manager.js +208 -0
- package/app/desktop/src/ui/tray-manager.js +202 -0
- package/app/desktop/src/utils/icon-manager.js +141 -0
- package/app/desktop/src/utils/path-utils.js +58 -0
- package/app/desktop/src/utils/resource-paths.js +72 -0
- package/app/desktop/src/utils/template-renderer.js +284 -0
- package/app/desktop/src/utils/version-utils.js +59 -0
- package/examples/prompts/engineer/engineer-professional.yaml +92 -0
- package/examples/prompts/engineer/laowang-engineer.yaml +132 -0
- package/examples/prompts/engineer/nekomata-engineer.yaml +123 -0
- package/examples/prompts/engineer/ojousama-engineer.yaml +124 -0
- package/examples/prompts/workflow/sixstep-workflow.yaml +192 -0
- package/package.json +10 -3
- package/packages/admin-ui/admin.html +2 -2
- package/packages/resources/tools/filesystem/filesystem.tool.js +184 -0
- package/packages/resources/tools/index.js +16 -0
- 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/mcp.handler.js +265 -0
- package/packages/server/mcp/mcp.server.js +181 -0
- package/packages/server/mcp/toolx.handler.js +131 -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/scripts/build-icons.js +105 -0
- package/scripts/icns-builder/package.json +12 -0
- package/packages/server/mcp.js +0 -234
- package/packages/server/mcpManager.js +0 -205
- /package/app/desktop/assets/{icon.png → icons/icon.png} +0 -0
- /package/packages/server/{logger.js → utils/logger.js} +0 -0
|
@@ -1,850 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
-
import { config } from './config.js';
|
|
9
|
-
import { logger } from './logger.js';
|
|
10
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
13
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
-
import { mcpManager } from './mcpManager.js';
|
|
15
|
-
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
|
-
|
|
19
|
-
const app = express();
|
|
20
|
-
app.use(express.json());
|
|
21
|
-
|
|
22
|
-
const adminUiRoot = path.join(__dirname, '..', 'admin-ui');
|
|
23
|
-
const examplesPromptsRoot = path.join(__dirname, '..', '..', 'examples', 'prompts');
|
|
24
|
-
|
|
25
|
-
// 为管理员界面提供静态文件服务 - 根路径
|
|
26
|
-
app.use(config.adminPath, express.static(adminUiRoot));
|
|
27
|
-
|
|
28
|
-
// 为管理员界面提供根路径访问(当用户访问 /admin 时显示 admin.html)
|
|
29
|
-
app.get(config.adminPath, (req, res) => {
|
|
30
|
-
res.sendFile(path.join(adminUiRoot, 'admin.html'));
|
|
31
|
-
});
|
|
1
|
+
import app from './app.js';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import { config } from './utils/config.js';
|
|
4
|
+
import { logger } from './utils/logger.js';
|
|
5
|
+
import { util } from './utils/util.js';
|
|
6
|
+
import { promptManager } from './services/manager.js';
|
|
32
7
|
|
|
33
8
|
// 获取prompts目录路径(在启动时可能被覆盖)
|
|
34
9
|
let promptsDir = config.getPromptsDir();
|
|
35
10
|
|
|
36
|
-
async function seedPromptsIfEmpty() {
|
|
37
|
-
try {
|
|
38
|
-
const entries = await fse.readdir(promptsDir);
|
|
39
|
-
if (entries.length > 0) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
} catch (error) {
|
|
43
|
-
logger.warn('读取Prompts目录失败,尝试同步示例数据:', error.message);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const exists = await fse.pathExists(examplesPromptsRoot);
|
|
48
|
-
if (!exists) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
await fse.copy(examplesPromptsRoot, promptsDir, {
|
|
52
|
-
overwrite: false,
|
|
53
|
-
errorOnExist: false,
|
|
54
|
-
recursive: true
|
|
55
|
-
});
|
|
56
|
-
logger.info(`已将示例Prompts同步到 ${promptsDir}`);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
logger.warn('同步示例Prompts失败:', error.message);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const GROUP_META_FILENAME = '.groupmeta.json';
|
|
62
|
-
const GROUP_NAME_REGEX = /^[a-zA-Z0-9-_\u4e00-\u9fa5]{1,64}$/;
|
|
63
|
-
|
|
64
|
-
function generateUniqueId(relativePath) {
|
|
65
|
-
const hash = crypto.createHash('sha256');
|
|
66
|
-
hash.update(relativePath);
|
|
67
|
-
const hashHex = hash.digest('hex');
|
|
68
|
-
return hashHex.substring(0, 8);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function getPromptsFromFiles() {
|
|
72
|
-
const prompts = [];
|
|
73
|
-
|
|
74
|
-
function traverseDir(currentPath, relativeDir = '', inheritedEnabled = true) {
|
|
75
|
-
let currentEnabled = inheritedEnabled;
|
|
76
|
-
if (relativeDir) {
|
|
77
|
-
const meta = readGroupMeta(currentPath);
|
|
78
|
-
currentEnabled = currentEnabled && (meta.enabled !== false);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
85
|
-
if (entry.isDirectory()) {
|
|
86
|
-
const childRelativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
87
|
-
traverseDir(fullPath, childRelativePath, currentEnabled);
|
|
88
|
-
} else if (entry.isFile() && entry.name.endsWith('.yaml')) {
|
|
89
|
-
try {
|
|
90
|
-
const fileContent = fs.readFileSync(fullPath, 'utf8');
|
|
91
|
-
const prompt = yaml.load(fileContent);
|
|
92
|
-
if (prompt && prompt.name) {
|
|
93
|
-
const relativePath = path.relative(promptsDir, fullPath);
|
|
94
|
-
const normalizedRelativePath = relativePath.split(path.sep).join('/');
|
|
95
|
-
const relativeDirForFile = path.dirname(normalizedRelativePath);
|
|
96
|
-
const topLevelGroup = relativeDirForFile && relativeDirForFile !== '.' ? relativeDirForFile.split('/')[0] : (prompt.group || 'default');
|
|
97
|
-
const groupPath = relativeDirForFile && relativeDirForFile !== '.' ? relativeDirForFile : topLevelGroup;
|
|
98
|
-
prompts.push({
|
|
99
|
-
...prompt,
|
|
100
|
-
uniqueId: generateUniqueId(prompt.name + '.yaml'),
|
|
101
|
-
fileName: entry.name,
|
|
102
|
-
relativePath: normalizedRelativePath,
|
|
103
|
-
group: topLevelGroup,
|
|
104
|
-
groupPath,
|
|
105
|
-
groupEnabled: currentEnabled
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
logger.error(`Error processing file ${fullPath}:`, error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
logger.error(`Error reading directory ${currentPath}:`, error);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
traverseDir(promptsDir);
|
|
119
|
-
return prompts;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 计算搜索关键词与prompt的相似度得分
|
|
124
|
-
* @param {string} searchTerm - 搜索关键词
|
|
125
|
-
* @param {Object} prompt - prompt对象
|
|
126
|
-
* @returns {number} 相似度得分 (0-100)
|
|
127
|
-
*/
|
|
128
|
-
function calculateSimilarityScore(searchTerm, prompt) {
|
|
129
|
-
const searchLower = searchTerm.toLowerCase();
|
|
130
|
-
let totalScore = 0;
|
|
131
|
-
|
|
132
|
-
// 搜索字段权重配置(专注于内容搜索,不包含ID检索)
|
|
133
|
-
const fieldWeights = {
|
|
134
|
-
name: 60, // 名称权重高,是主要匹配字段
|
|
135
|
-
description: 40 // 描述权重适中,是辅助匹配字段
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// 计算name匹配得分
|
|
139
|
-
if (prompt.name) {
|
|
140
|
-
const nameScore = getStringMatchScore(searchLower, prompt.name.toLowerCase());
|
|
141
|
-
totalScore += nameScore * fieldWeights.name;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 计算description匹配得分
|
|
145
|
-
if (prompt.description) {
|
|
146
|
-
const descScore = getStringMatchScore(searchLower, prompt.description.toLowerCase());
|
|
147
|
-
totalScore += descScore * fieldWeights.description;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 标准化得分到0-100范围
|
|
151
|
-
const maxPossibleScore = Object.values(fieldWeights).reduce((sum, weight) => sum + weight, 0);
|
|
152
|
-
return Math.round((totalScore / maxPossibleScore) * 100);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 计算两个字符串的匹配得分
|
|
157
|
-
* @param {string} search - 搜索词 (已转小写)
|
|
158
|
-
* @param {string} target - 目标字符串 (已转小写)
|
|
159
|
-
* @returns {number} 匹配得分 (0-1)
|
|
160
|
-
*/
|
|
161
|
-
function getStringMatchScore(search, target) {
|
|
162
|
-
if (!search || !target) return 0;
|
|
163
|
-
|
|
164
|
-
// 完全匹配得分最高
|
|
165
|
-
if (target === search) return 1.0;
|
|
166
|
-
|
|
167
|
-
// 完全包含得分较高
|
|
168
|
-
if (target.includes(search)) return 0.8;
|
|
169
|
-
|
|
170
|
-
// 部分词匹配
|
|
171
|
-
const searchWords = search.split(/\s+/).filter(word => word.length > 0);
|
|
172
|
-
const targetWords = target.split(/\s+/).filter(word => word.length > 0);
|
|
173
|
-
|
|
174
|
-
let matchedWords = 0;
|
|
175
|
-
for (const searchWord of searchWords) {
|
|
176
|
-
for (const targetWord of targetWords) {
|
|
177
|
-
if (targetWord.includes(searchWord) || searchWord.includes(targetWord)) {
|
|
178
|
-
matchedWords++;
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (searchWords.length > 0) {
|
|
185
|
-
const wordMatchRatio = matchedWords / searchWords.length;
|
|
186
|
-
return wordMatchRatio * 0.6; // 部分词匹配得分
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return 0;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 获取服务器信息
|
|
193
|
-
app.get('/', (req, res) => {
|
|
194
|
-
res.status(404).send('404 Not Found');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
app.get('/prompts', (req, res) => {
|
|
198
|
-
try {
|
|
199
|
-
const prompts = getPromptsFromFiles();
|
|
200
|
-
|
|
201
|
-
// 过滤出启用的提示词
|
|
202
|
-
const filtered = prompts.filter(prompt => {
|
|
203
|
-
const groupActive = prompt.groupEnabled !== false;
|
|
204
|
-
const promptActive = prompt.enabled === true;
|
|
205
|
-
return groupActive && promptActive;
|
|
206
|
-
});
|
|
207
|
-
logger.debug(`filtered prompts: ${JSON.stringify(filtered)}`);
|
|
208
|
-
|
|
209
|
-
// 判断是否有搜索参数,且搜索参数名为search
|
|
210
|
-
if (req.query.search) {
|
|
211
|
-
const search = req.query.search;
|
|
212
|
-
|
|
213
|
-
// 实现相似度匹配算法
|
|
214
|
-
const searchResults = filtered.map(prompt => {
|
|
215
|
-
const score = calculateSimilarityScore(search, prompt);
|
|
216
|
-
return {
|
|
217
|
-
prompt: {
|
|
218
|
-
id: prompt.uniqueId,
|
|
219
|
-
name: prompt.name,
|
|
220
|
-
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
221
|
-
metadata: {
|
|
222
|
-
// fileName: prompt.fileName,
|
|
223
|
-
fullPath: prompt.relativePath
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
score: score
|
|
227
|
-
};
|
|
228
|
-
})
|
|
229
|
-
.filter(result => result.score > 0) // 只返回有匹配的结果
|
|
230
|
-
.sort((a, b) => b.score - a.score); // 按相似度得分降序排列
|
|
231
|
-
|
|
232
|
-
// 只返回prompt字段
|
|
233
|
-
res.json(searchResults.map(result => result.prompt));
|
|
234
|
-
} else {
|
|
235
|
-
// 无搜索参数时,返回精简信息
|
|
236
|
-
const simplifiedPrompts = filtered.map(prompt => ({
|
|
237
|
-
id: prompt.uniqueId,
|
|
238
|
-
name: prompt.name,
|
|
239
|
-
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
240
|
-
metadata: {
|
|
241
|
-
// fileName: prompt.fileName,
|
|
242
|
-
fullPath: prompt.relativePath
|
|
243
|
-
}
|
|
244
|
-
}));
|
|
245
|
-
|
|
246
|
-
res.json(simplifiedPrompts);
|
|
247
|
-
}
|
|
248
|
-
} catch (error) {
|
|
249
|
-
res.status(500).json({ error: error.message });
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
async function processPromptContent(prompt, args) {
|
|
254
|
-
let content = '';
|
|
255
|
-
|
|
256
|
-
if (prompt.messages && Array.isArray(prompt.messages)) {
|
|
257
|
-
const userMessages = prompt.messages.filter(msg => msg.role === 'user');
|
|
258
|
-
|
|
259
|
-
for (const message of userMessages) {
|
|
260
|
-
if (message.content && typeof message.content.text === 'string') {
|
|
261
|
-
let text = message.content.text;
|
|
262
|
-
|
|
263
|
-
// Replace arguments
|
|
264
|
-
if (args) {
|
|
265
|
-
for (const [key, value] of Object.entries(args)) {
|
|
266
|
-
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
267
|
-
text = text.replace(placeholder, String(value));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
content += text + '\n\n';
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return content.trim();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// 管理员API中间件
|
|
280
|
-
function adminAuthMiddleware(req, res, next) {
|
|
281
|
-
// 检查是否启用了管理员功能
|
|
282
|
-
if (!config.adminEnable) {
|
|
283
|
-
return res.status(404).json({ error: 'Admin功能未启用' });
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const authHeader = req.headers.authorization;
|
|
287
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
288
|
-
return res.status(401).json({ error: '未提供认证令牌' });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const token = authHeader.substring(7);
|
|
292
|
-
|
|
293
|
-
// 验证令牌
|
|
294
|
-
const admin = config.admins.find(a => a.token === token);
|
|
295
|
-
if (!admin) {
|
|
296
|
-
return res.status(401).json({ error: '无效的认证令牌' });
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
req.admin = admin;
|
|
300
|
-
next();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 登录端点
|
|
304
|
-
app.post('/api/login', (req, res) => {
|
|
305
|
-
// 检查是否启用了管理员功能
|
|
306
|
-
if (!config.adminEnable) {
|
|
307
|
-
return res.status(404).json({ error: 'Admin功能未启用' });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const { username, password } = req.body;
|
|
311
|
-
|
|
312
|
-
if (!username || !password) {
|
|
313
|
-
return res.status(400).json({ error: '用户名和密码是必需的' });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// 验证凭据
|
|
317
|
-
const admin = config.admins.find(a => a.username === username && a.password === password);
|
|
318
|
-
if (!admin) {
|
|
319
|
-
return res.status(401).json({ error: '无效的用户名或密码' });
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
res.json({ token: admin.token });
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
function isValidGroupName(name) {
|
|
326
|
-
return GROUP_NAME_REGEX.test(name);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function validateGroupPath(relativePath) {
|
|
330
|
-
if (!relativePath || typeof relativePath !== 'string') {
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
const segments = relativePath.split('/').filter(Boolean);
|
|
334
|
-
if (!segments.length) {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
for (const segment of segments) {
|
|
338
|
-
if (!isValidGroupName(segment)) {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return segments;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function resolveGroupDir(relativePath) {
|
|
346
|
-
const segments = validateGroupPath(relativePath);
|
|
347
|
-
if (!segments) return null;
|
|
348
|
-
const targetPath = path.resolve(promptsDir, ...segments);
|
|
349
|
-
const normalizedPromptsDir = path.resolve(promptsDir);
|
|
350
|
-
if (!targetPath.startsWith(normalizedPromptsDir)) {
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
return { dir: targetPath, segments };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function getGroupMetaPath(dir) {
|
|
357
|
-
return path.join(dir, GROUP_META_FILENAME);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function readGroupMeta(dir) {
|
|
361
|
-
try {
|
|
362
|
-
const metaPath = getGroupMetaPath(dir);
|
|
363
|
-
if (!fs.existsSync(metaPath)) {
|
|
364
|
-
return { enabled: true };
|
|
365
|
-
}
|
|
366
|
-
const raw = fs.readFileSync(metaPath, 'utf8');
|
|
367
|
-
const data = JSON.parse(raw);
|
|
368
|
-
return {
|
|
369
|
-
enabled: data.enabled !== false
|
|
370
|
-
};
|
|
371
|
-
} catch (error) {
|
|
372
|
-
logger.warn('读取类目元数据失败:', error);
|
|
373
|
-
return { enabled: true };
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function writeGroupMeta(dir, meta = {}) {
|
|
378
|
-
const metaPath = getGroupMetaPath(dir);
|
|
379
|
-
const data = {
|
|
380
|
-
enabled: meta.enabled !== false
|
|
381
|
-
};
|
|
382
|
-
fs.writeFileSync(metaPath, JSON.stringify(data, null, 2), 'utf8');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// 获取所有分组(直接从目录读取)
|
|
386
|
-
function buildGroupTree(baseDir, relativePath = '') {
|
|
387
|
-
const nodes = [];
|
|
388
|
-
try {
|
|
389
|
-
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
390
|
-
for (const entry of entries) {
|
|
391
|
-
if (!entry.isDirectory()) continue;
|
|
392
|
-
if (entry.name.startsWith('.')) continue;
|
|
393
|
-
const childRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
394
|
-
const childDir = path.join(baseDir, entry.name);
|
|
395
|
-
const children = buildGroupTree(childDir, childRelativePath);
|
|
396
|
-
const meta = readGroupMeta(childDir);
|
|
397
|
-
nodes.push({
|
|
398
|
-
name: entry.name,
|
|
399
|
-
path: childRelativePath,
|
|
400
|
-
children,
|
|
401
|
-
enabled: meta.enabled !== false
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
} catch (error) {
|
|
405
|
-
logger.error('读取分组目录失败:', error);
|
|
406
|
-
}
|
|
407
|
-
nodes.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
|
|
408
|
-
return nodes;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
app.get('/api/groups', adminAuthMiddleware, (req, res) => {
|
|
412
|
-
try {
|
|
413
|
-
const tree = buildGroupTree(promptsDir);
|
|
414
|
-
const hasDefault = tree.some(node => node.path === 'default');
|
|
415
|
-
if (!hasDefault) {
|
|
416
|
-
tree.unshift({ name: 'default', path: 'default', children: [], enabled: true });
|
|
417
|
-
}
|
|
418
|
-
res.json(tree);
|
|
419
|
-
} catch (error) {
|
|
420
|
-
res.status(500).json({ error: error.message });
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// 获取所有提示词(支持搜索、过滤和分组)
|
|
425
|
-
app.get('/api/prompts', adminAuthMiddleware, (req, res) => {
|
|
426
|
-
try {
|
|
427
|
-
const prompts = getPromptsFromFiles();
|
|
428
|
-
|
|
429
|
-
// 处理搜索参数
|
|
430
|
-
const search = req.query.search;
|
|
431
|
-
const enabled = req.query.enabled === 'true';
|
|
432
|
-
const groupPathFilter = req.query.groupPath;
|
|
433
|
-
const group = req.query.group;
|
|
434
|
-
|
|
435
|
-
let filteredPrompts = prompts;
|
|
436
|
-
|
|
437
|
-
// 应用分组过滤
|
|
438
|
-
if (groupPathFilter) {
|
|
439
|
-
filteredPrompts = filteredPrompts.filter(prompt => (prompt.groupPath || prompt.group || 'default') === groupPathFilter);
|
|
440
|
-
} else if (group) {
|
|
441
|
-
filteredPrompts = filteredPrompts.filter(prompt => (prompt.group || 'default') === group);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// 应用搜索过滤
|
|
445
|
-
if (search) {
|
|
446
|
-
filteredPrompts = filteredPrompts.filter(prompt =>
|
|
447
|
-
prompt.name.includes(search) ||
|
|
448
|
-
(prompt.description && prompt.description.includes(search))
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// 应用启用状态过滤
|
|
453
|
-
if (enabled) {
|
|
454
|
-
filteredPrompts = filteredPrompts.filter(prompt => prompt.enabled);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
filteredPrompts.sort((a, b) => (a.name || '').localeCompare(b.name || '', 'zh-CN'));
|
|
458
|
-
|
|
459
|
-
res.json(filteredPrompts);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
res.status(500).json({ error: error.message });
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// 获取单个提示词
|
|
466
|
-
app.get('/api/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
467
|
-
try {
|
|
468
|
-
const prompts = getPromptsFromFiles();
|
|
469
|
-
const targetPath = req.query.path;
|
|
470
|
-
let prompt;
|
|
471
|
-
if (targetPath) {
|
|
472
|
-
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
473
|
-
}
|
|
474
|
-
if (!prompt) {
|
|
475
|
-
prompt = prompts.find(p => p.name === req.params.name);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
if (!prompt) {
|
|
479
|
-
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// 读取原始YAML文件内容
|
|
483
|
-
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
484
|
-
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
485
|
-
|
|
486
|
-
res.json({
|
|
487
|
-
...prompt,
|
|
488
|
-
yaml: yamlContent
|
|
489
|
-
});
|
|
490
|
-
} catch (error) {
|
|
491
|
-
res.status(500).json({ error: error.message });
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// 保存提示词
|
|
496
|
-
app.post('/api/prompts', adminAuthMiddleware, (req, res) => {
|
|
497
|
-
try {
|
|
498
|
-
const { name, group, yaml: yamlContent, relativePath: originalRelativePath } = req.body;
|
|
499
|
-
|
|
500
|
-
if (!name || !yamlContent) {
|
|
501
|
-
return res.status(400).json({ error: '名称和YAML内容是必需的' });
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// 验证名称格式
|
|
505
|
-
if (!/^[a-zA-Z0-9-_]{1,64}$/.test(name)) {
|
|
506
|
-
return res.status(400).json({ error: '名称格式无效' });
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// 计算目标路径
|
|
510
|
-
const groupName = group || 'default';
|
|
511
|
-
const normalizedOriginalPath = originalRelativePath ? path.normalize(originalRelativePath).replace(/\\/g, '/') : null;
|
|
512
|
-
const originalDirParts = normalizedOriginalPath ? path.posix.dirname(normalizedOriginalPath).split('/').filter(Boolean) : [];
|
|
513
|
-
let subPathSegments = [];
|
|
514
|
-
|
|
515
|
-
if (normalizedOriginalPath && originalDirParts.length > 1) {
|
|
516
|
-
subPathSegments = originalDirParts.slice(1);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
const targetSegments = [];
|
|
520
|
-
if (groupName) {
|
|
521
|
-
targetSegments.push(groupName);
|
|
522
|
-
}
|
|
523
|
-
if (subPathSegments.length) {
|
|
524
|
-
targetSegments.push(...subPathSegments);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const finalFileName = `${name}.yaml`;
|
|
528
|
-
targetSegments.push(finalFileName);
|
|
529
|
-
|
|
530
|
-
const targetRelativePath = path.posix.join(...targetSegments);
|
|
531
|
-
const targetDir = path.join(promptsDir, path.posix.dirname(targetRelativePath));
|
|
532
|
-
const filePath = path.join(promptsDir, targetRelativePath);
|
|
533
|
-
|
|
534
|
-
fse.ensureDirSync(targetDir);
|
|
535
|
-
|
|
536
|
-
// 检查是否重名(同目录下)
|
|
537
|
-
const prompts = getPromptsFromFiles();
|
|
538
|
-
const existingPrompt = prompts.find(p => {
|
|
539
|
-
if (p.name !== name) return false;
|
|
540
|
-
const isOriginalFile = normalizedOriginalPath && p.relativePath === normalizedOriginalPath;
|
|
541
|
-
if (isOriginalFile) return false;
|
|
542
|
-
const sameRelativePath = p.relativePath === targetRelativePath;
|
|
543
|
-
if (sameRelativePath) return false;
|
|
544
|
-
const sameDirectory = path.posix.dirname(p.relativePath || '') === path.posix.dirname(targetRelativePath);
|
|
545
|
-
return sameDirectory;
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
if (existingPrompt) {
|
|
549
|
-
return res.status(400).json({ error: '名称已存在' });
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// 保存文件
|
|
553
|
-
fs.writeFileSync(filePath, yamlContent, 'utf8');
|
|
554
|
-
|
|
555
|
-
// 如果目标路径与原始路径不同,删除旧文件
|
|
556
|
-
if (normalizedOriginalPath && normalizedOriginalPath !== targetRelativePath) {
|
|
557
|
-
const originalFilePath = path.join(promptsDir, normalizedOriginalPath);
|
|
558
|
-
if (fs.existsSync(originalFilePath)) {
|
|
559
|
-
fs.unlinkSync(originalFilePath);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
res.json({ message: '保存成功', relativePath: targetRelativePath, group: groupName });
|
|
564
|
-
} catch (error) {
|
|
565
|
-
res.status(500).json({ error: error.message });
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
// 创建新分组目录
|
|
570
|
-
app.post('/api/groups', adminAuthMiddleware, (req, res) => {
|
|
571
|
-
try {
|
|
572
|
-
const { name } = req.body;
|
|
573
|
-
|
|
574
|
-
if (!name) {
|
|
575
|
-
return res.status(400).json({ error: '分组名称是必需的' });
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// 验证名称格式
|
|
579
|
-
if (!isValidGroupName(name)) {
|
|
580
|
-
return res.status(400).json({ error: '名称格式无效,只能包含字母、数字、中划线、下划线和中文' });
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const groupDir = path.join(promptsDir, name);
|
|
584
|
-
|
|
585
|
-
// 检查目录是否已存在
|
|
586
|
-
if (fs.existsSync(groupDir)) {
|
|
587
|
-
return res.status(400).json({ error: '分组已存在' });
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// 创建目录
|
|
591
|
-
fs.mkdirSync(groupDir, { recursive: true });
|
|
592
|
-
|
|
593
|
-
res.json({ message: '分组创建成功' });
|
|
594
|
-
} catch (error) {
|
|
595
|
-
res.status(500).json({ error: error.message });
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
app.patch('/api/groups/rename', adminAuthMiddleware, (req, res) => {
|
|
600
|
-
try {
|
|
601
|
-
const { path: groupPath, newName } = req.body || {};
|
|
602
|
-
if (!groupPath || !newName) {
|
|
603
|
-
return res.status(400).json({ error: '分组路径和新名称是必需的' });
|
|
604
|
-
}
|
|
605
|
-
if (!isValidGroupName(newName)) {
|
|
606
|
-
return res.status(400).json({ error: '名称格式无效,只能包含字母、数字、中划线、下划线和中文' });
|
|
607
|
-
}
|
|
608
|
-
if (groupPath === 'default') {
|
|
609
|
-
return res.status(400).json({ error: '默认分组不允许重命名' });
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const resolved = resolveGroupDir(groupPath);
|
|
613
|
-
if (!resolved) {
|
|
614
|
-
return res.status(400).json({ error: '无效的分组路径' });
|
|
615
|
-
}
|
|
616
|
-
const { dir: oldDir, segments } = resolved;
|
|
617
|
-
if (!fs.existsSync(oldDir) || !fs.lstatSync(oldDir).isDirectory()) {
|
|
618
|
-
return res.status(404).json({ error: '分组不存在' });
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const parentSegments = segments.slice(0, -1);
|
|
622
|
-
const oldName = segments[segments.length - 1];
|
|
623
|
-
if (newName === oldName) {
|
|
624
|
-
return res.json({ message: '分组名称未变更', path: groupPath });
|
|
625
|
-
}
|
|
626
|
-
const newSegments = [...parentSegments, newName];
|
|
627
|
-
const newDir = path.resolve(promptsDir, ...newSegments);
|
|
628
|
-
if (fs.existsSync(newDir)) {
|
|
629
|
-
return res.status(400).json({ error: '目标名称已存在,请选择其他名称' });
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
fse.moveSync(oldDir, newDir);
|
|
633
|
-
|
|
634
|
-
res.json({ message: '分组重命名成功', path: newSegments.join('/') });
|
|
635
|
-
} catch (error) {
|
|
636
|
-
logger.error('分组重命名失败:', error);
|
|
637
|
-
res.status(500).json({ error: error.message });
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
app.patch('/api/groups/status', adminAuthMiddleware, (req, res) => {
|
|
642
|
-
try {
|
|
643
|
-
const { path: groupPath, enabled } = req.body || {};
|
|
644
|
-
if (typeof enabled !== 'boolean') {
|
|
645
|
-
return res.status(400).json({ error: '状态值无效' });
|
|
646
|
-
}
|
|
647
|
-
if (!groupPath) {
|
|
648
|
-
return res.status(400).json({ error: '分组路径是必需的' });
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const resolved = resolveGroupDir(groupPath);
|
|
652
|
-
if (!resolved) {
|
|
653
|
-
return res.status(400).json({ error: '无效的分组路径' });
|
|
654
|
-
}
|
|
655
|
-
const { dir } = resolved;
|
|
656
|
-
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
657
|
-
return res.status(404).json({ error: '分组不存在' });
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
writeGroupMeta(dir, { enabled });
|
|
661
|
-
|
|
662
|
-
res.json({ message: '分组状态已更新', enabled });
|
|
663
|
-
} catch (error) {
|
|
664
|
-
logger.error('更新分组状态失败:', error);
|
|
665
|
-
res.status(500).json({ error: error.message });
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
app.delete('/api/groups', adminAuthMiddleware, (req, res) => {
|
|
670
|
-
try {
|
|
671
|
-
const groupPath = req.query.path;
|
|
672
|
-
if (!groupPath) {
|
|
673
|
-
return res.status(400).json({ error: '分组路径是必需的' });
|
|
674
|
-
}
|
|
675
|
-
if (groupPath === 'default') {
|
|
676
|
-
return res.status(400).json({ error: '默认分组不允许删除' });
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const resolved = resolveGroupDir(groupPath);
|
|
680
|
-
if (!resolved) {
|
|
681
|
-
return res.status(400).json({ error: '无效的分组路径' });
|
|
682
|
-
}
|
|
683
|
-
const { dir } = resolved;
|
|
684
|
-
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory()) {
|
|
685
|
-
return res.status(404).json({ error: '分组不存在' });
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
689
|
-
.filter(entry => entry.name !== GROUP_META_FILENAME && !entry.name.startsWith('.'));
|
|
690
|
-
|
|
691
|
-
if (entries.length > 0) {
|
|
692
|
-
return res.status(400).json({ error: '目录非空,请先移除其下的Prompt或子目录' });
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
fse.removeSync(dir);
|
|
696
|
-
res.json({ message: '分组删除成功' });
|
|
697
|
-
} catch (error) {
|
|
698
|
-
logger.error('删除分组失败:', error);
|
|
699
|
-
res.status(500).json({ error: error.message });
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
// 切换提示词启用状态
|
|
704
|
-
app.post('/api/prompts/:name/toggle', adminAuthMiddleware, (req, res) => {
|
|
705
|
-
try {
|
|
706
|
-
const prompts = getPromptsFromFiles();
|
|
707
|
-
const targetPath = req.query.path;
|
|
708
|
-
let prompt;
|
|
709
|
-
if (targetPath) {
|
|
710
|
-
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
711
|
-
}
|
|
712
|
-
if (!prompt) {
|
|
713
|
-
prompt = prompts.find(p => p.name === req.params.name);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (!prompt) {
|
|
717
|
-
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// 读取原始YAML文件内容
|
|
721
|
-
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
722
|
-
const yamlContent = fs.readFileSync(promptPath, 'utf8');
|
|
723
|
-
|
|
724
|
-
// 解析YAML
|
|
725
|
-
const promptData = yaml.load(yamlContent);
|
|
726
|
-
|
|
727
|
-
// 切换启用状态
|
|
728
|
-
promptData.enabled = !promptData.enabled;
|
|
729
|
-
|
|
730
|
-
// 保存更新后的YAML
|
|
731
|
-
const newYamlContent = yaml.dump(promptData);
|
|
732
|
-
fs.writeFileSync(promptPath, newYamlContent, 'utf8');
|
|
733
|
-
|
|
734
|
-
res.json({ message: '状态切换成功', enabled: promptData.enabled });
|
|
735
|
-
} catch (error) {
|
|
736
|
-
res.status(500).json({ error: error.message });
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
// 删除提示词(软删)
|
|
741
|
-
app.delete('/api/prompts/:name', adminAuthMiddleware, (req, res) => {
|
|
742
|
-
try {
|
|
743
|
-
const prompts = getPromptsFromFiles();
|
|
744
|
-
const targetPath = req.query.path;
|
|
745
|
-
let prompt;
|
|
746
|
-
if (targetPath) {
|
|
747
|
-
prompt = prompts.find(p => p.relativePath === targetPath);
|
|
748
|
-
}
|
|
749
|
-
if (!prompt) {
|
|
750
|
-
prompt = prompts.find(p => p.name === req.params.name);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
if (!prompt) {
|
|
754
|
-
return res.status(404).json({ error: `Prompt "${req.params.name}" 未找到` });
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// 读取原始文件路径
|
|
758
|
-
const promptPath = path.join(promptsDir, prompt.relativePath);
|
|
759
|
-
|
|
760
|
-
// 直接删除文件
|
|
761
|
-
fse.unlinkSync(promptPath);
|
|
762
|
-
|
|
763
|
-
res.json({ message: '删除成功' });
|
|
764
|
-
} catch (error) {
|
|
765
|
-
res.status(500).json({ error: error.message });
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
// Markdown预览
|
|
770
|
-
app.post('/api/md-preview', adminAuthMiddleware, (req, res) => {
|
|
771
|
-
try {
|
|
772
|
-
const { yaml: yamlContent, vars } = req.body;
|
|
773
|
-
|
|
774
|
-
if (!yamlContent) {
|
|
775
|
-
return res.status(400).json({ error: 'YAML内容是必需的' });
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// 解析YAML
|
|
779
|
-
const promptData = yaml.load(yamlContent);
|
|
780
|
-
|
|
781
|
-
// 处理变量替换
|
|
782
|
-
let content = '';
|
|
783
|
-
if (promptData.messages && Array.isArray(promptData.messages)) {
|
|
784
|
-
const userMessages = promptData.messages.filter(msg => msg.role === 'user');
|
|
785
|
-
|
|
786
|
-
for (const message of userMessages) {
|
|
787
|
-
if (message.content && typeof message.content.text === 'string') {
|
|
788
|
-
let text = message.content.text;
|
|
789
|
-
|
|
790
|
-
// 替换变量
|
|
791
|
-
if (vars) {
|
|
792
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
793
|
-
const placeholder = new RegExp(`{{${key}}}`, 'g');
|
|
794
|
-
text = text.replace(placeholder, String(value));
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
content += text + '\n\n';
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// 简单的Markdown转HTML(实际应用中可以使用专门的库)
|
|
804
|
-
const html = content
|
|
805
|
-
.replace(/&/g, '&')
|
|
806
|
-
.replace(/</g, '<')
|
|
807
|
-
.replace(/>/g, '>')
|
|
808
|
-
.replace(/\n/g, '<br>')
|
|
809
|
-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
810
|
-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
811
|
-
.replace(/`(.*?)`/g, '<code>$1</code>')
|
|
812
|
-
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
|
813
|
-
.replace(/### (.*?)(<br>|$)/g, '<h3>$1</h3>')
|
|
814
|
-
.replace(/## (.*?)(<br>|$)/g, '<h2>$1</h2>')
|
|
815
|
-
.replace(/# (.*?)(<br>|$)/g, '<h1>$1</h1>');
|
|
816
|
-
|
|
817
|
-
res.json({ html });
|
|
818
|
-
} catch (error) {
|
|
819
|
-
res.status(500).json({ error: error.message });
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
app.post('/process', async (req, res) => {
|
|
824
|
-
try {
|
|
825
|
-
const { promptName, arguments: args } = req.body;
|
|
826
|
-
|
|
827
|
-
if (!promptName) {
|
|
828
|
-
return res.status(400).json({ error: 'Missing promptName' });
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const prompts = getPromptsFromFiles();
|
|
832
|
-
const prompt = prompts.find(p => p.name === promptName);
|
|
833
|
-
|
|
834
|
-
if (!prompt) {
|
|
835
|
-
return res.status(404).json({ error: `Prompt "${promptName}" not found` });
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
const processedPrompt = await processPromptContent(prompt, args);
|
|
839
|
-
res.json({ processedText: processedPrompt });
|
|
840
|
-
} catch (error) {
|
|
841
|
-
res.status(500).json({ error: error.message });
|
|
842
|
-
}
|
|
843
|
-
});
|
|
844
|
-
|
|
845
11
|
let serverInstance = null;
|
|
846
12
|
let serverStartingPromise = null;
|
|
847
|
-
let mcpManagerInstance = null;
|
|
848
13
|
|
|
849
14
|
export function getServerAddress() {
|
|
850
15
|
return `http://127.0.0.1:${config.getPort()}`;
|
|
@@ -854,34 +19,37 @@ export function isServerRunning() {
|
|
|
854
19
|
return Boolean(serverInstance);
|
|
855
20
|
}
|
|
856
21
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
if (serverStartingPromise) {
|
|
862
|
-
return serverStartingPromise;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const { configOverrides } = options;
|
|
866
|
-
if (configOverrides) {
|
|
867
|
-
config.applyOverrides(configOverrides);
|
|
22
|
+
// 封装配置处理逻辑
|
|
23
|
+
async function _handleConfig(options) {
|
|
24
|
+
if (options.configOverrides) {
|
|
25
|
+
config.applyOverrides(options.configOverrides);
|
|
868
26
|
}
|
|
869
27
|
promptsDir = config.getPromptsDir();
|
|
28
|
+
promptManager.promptsDir = promptsDir;
|
|
29
|
+
await config.ensurePromptsDir();
|
|
30
|
+
await util.seedPromptsIfEmpty();
|
|
31
|
+
await config.validate();
|
|
32
|
+
config.showConfig();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function startServer(options = {}) {
|
|
36
|
+
if (serverInstance) return serverInstance;
|
|
37
|
+
if (serverStartingPromise) return serverStartingPromise;
|
|
870
38
|
|
|
871
39
|
serverStartingPromise = (async () => {
|
|
872
40
|
try {
|
|
873
|
-
await
|
|
874
|
-
|
|
875
|
-
await seedPromptsIfEmpty();
|
|
876
|
-
await config.validate();
|
|
877
|
-
config.showConfig();
|
|
41
|
+
await _handleConfig(options);
|
|
42
|
+
await promptManager.loadPrompts();
|
|
878
43
|
|
|
879
44
|
return await new Promise((resolve, reject) => {
|
|
880
45
|
const server = app.listen(config.getPort(), () => {
|
|
881
|
-
logger.info(
|
|
46
|
+
logger.info(`MCP服务启动成功 http://localhost:${config.getPort()}/mcp`);
|
|
882
47
|
if (config.adminEnable) {
|
|
883
48
|
logger.info(`管理员界面可通过 http://localhost:${config.getPort()}${config.adminPath} 访问`);
|
|
49
|
+
process.stderr.write('\n======================================================================================\n');
|
|
884
50
|
}
|
|
51
|
+
// 保存服务器实例引用,以便后续可以关闭它
|
|
52
|
+
serverInstance = server;
|
|
885
53
|
resolve(server);
|
|
886
54
|
});
|
|
887
55
|
|
|
@@ -891,27 +59,20 @@ export async function startServer(options = {}) {
|
|
|
891
59
|
});
|
|
892
60
|
});
|
|
893
61
|
} catch (error) {
|
|
894
|
-
logger.error('
|
|
62
|
+
logger.error('服务器启动失败::', error.message);
|
|
895
63
|
throw error;
|
|
64
|
+
} finally {
|
|
65
|
+
serverStartingPromise = null;
|
|
896
66
|
}
|
|
897
67
|
})();
|
|
898
68
|
|
|
899
|
-
|
|
900
|
-
serverInstance = await serverStartingPromise;
|
|
901
|
-
|
|
902
|
-
// 启动MCP服务器
|
|
903
|
-
startMCPServer();
|
|
904
|
-
|
|
905
|
-
return serverInstance;
|
|
906
|
-
} finally {
|
|
907
|
-
serverStartingPromise = null;
|
|
908
|
-
}
|
|
69
|
+
return serverStartingPromise;
|
|
909
70
|
}
|
|
910
71
|
|
|
911
72
|
export async function stopServer() {
|
|
912
73
|
if (serverStartingPromise) {
|
|
913
74
|
try {
|
|
914
|
-
await serverStartingPromise;
|
|
75
|
+
await serverStartingPromise.exitCode();
|
|
915
76
|
} catch (error) {
|
|
916
77
|
// ignore failing start when stopping
|
|
917
78
|
}
|
|
@@ -921,6 +82,16 @@ export async function stopServer() {
|
|
|
921
82
|
return;
|
|
922
83
|
}
|
|
923
84
|
|
|
85
|
+
// 清理MCP会话
|
|
86
|
+
try {
|
|
87
|
+
const appModule = await import('./app.js');
|
|
88
|
+
if (appModule.cleanupMcpSessions) {
|
|
89
|
+
appModule.cleanupMcpSessions();
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.warn('清理MCP会话时出错:', error.message);
|
|
93
|
+
}
|
|
94
|
+
|
|
924
95
|
await new Promise((resolve, reject) => {
|
|
925
96
|
serverInstance.close((err) => {
|
|
926
97
|
if (err) {
|
|
@@ -934,12 +105,6 @@ export async function stopServer() {
|
|
|
934
105
|
});
|
|
935
106
|
|
|
936
107
|
serverInstance = null;
|
|
937
|
-
|
|
938
|
-
// 停止MCP服务器
|
|
939
|
-
if (mcpManagerInstance) {
|
|
940
|
-
await mcpManagerInstance.close();
|
|
941
|
-
mcpManagerInstance = null;
|
|
942
|
-
}
|
|
943
108
|
}
|
|
944
109
|
|
|
945
110
|
export function getServerState() {
|
|
@@ -951,38 +116,7 @@ export function getServerState() {
|
|
|
951
116
|
};
|
|
952
117
|
}
|
|
953
118
|
|
|
954
|
-
//
|
|
955
|
-
async function startMCPServer() {
|
|
956
|
-
try {
|
|
957
|
-
// 初始化MCP管理器
|
|
958
|
-
mcpManagerInstance = mcpManager;
|
|
959
|
-
await mcpManagerInstance.initialize();
|
|
960
|
-
|
|
961
|
-
// 添加MCP HTTP端点
|
|
962
|
-
app.post('/mcp', express.json(), async (req, res) => {
|
|
963
|
-
try {
|
|
964
|
-
await mcpManagerInstance.handleHTTPRequest(req, res);
|
|
965
|
-
} catch (error) {
|
|
966
|
-
logger.error('MCP HTTP请求处理失败:', error.message);
|
|
967
|
-
if (!res.headersSent) {
|
|
968
|
-
res.status(500).json({
|
|
969
|
-
jsonrpc: '2.0',
|
|
970
|
-
error: {
|
|
971
|
-
code: -32603,
|
|
972
|
-
message: 'Internal error'
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
logger.info(`MCP服务器已启动 http://localhost:${config.getPort()}/mcp 端点`);
|
|
980
|
-
} catch (error) {
|
|
981
|
-
logger.error('启动MCP服务器失败:', error.message);
|
|
982
|
-
mcpManagerInstance = null;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
119
|
+
// 是否直接运行
|
|
986
120
|
const isDirectRun = (() => {
|
|
987
121
|
try {
|
|
988
122
|
const executed = process.argv[1];
|