@becrafter/prompt-manager 0.0.8

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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/app/cli/cli.js +17 -0
  4. package/app/cli/commands/start.js +20 -0
  5. package/app/cli/index.js +37 -0
  6. package/app/cli/support/argv.js +7 -0
  7. package/app/cli/support/signals.js +34 -0
  8. package/app/desktop/assets/icon.png +0 -0
  9. package/app/desktop/main.js +496 -0
  10. package/app/desktop/package-lock.json +4091 -0
  11. package/app/desktop/package.json +63 -0
  12. package/examples/prompts/developer/code-review.yaml +32 -0
  13. package/examples/prompts/developer/code_refactoring.yaml +31 -0
  14. package/examples/prompts/developer/doc-generator.yaml +36 -0
  15. package/examples/prompts/developer/error-code-fixer.yaml +35 -0
  16. package/examples/prompts/generator/gen_3d_edu_webpage_html.yaml +117 -0
  17. package/examples/prompts/generator/gen_3d_webpage_html.yaml +75 -0
  18. package/examples/prompts/generator/gen_bento_grid_html.yaml +112 -0
  19. package/examples/prompts/generator/gen_html_web_page.yaml +88 -0
  20. package/examples/prompts/generator/gen_knowledge_card_html.yaml +83 -0
  21. package/examples/prompts/generator/gen_magazine_card_html.yaml +82 -0
  22. package/examples/prompts/generator/gen_mimeng_headline_title.yaml +71 -0
  23. package/examples/prompts/generator/gen_podcast_script.yaml +69 -0
  24. package/examples/prompts/generator/gen_prd_prototype_html.yaml +175 -0
  25. package/examples/prompts/generator/gen_summarize.yaml +157 -0
  26. package/examples/prompts/generator/gen_title.yaml +119 -0
  27. package/examples/prompts/generator/others/api_documentation.yaml +32 -0
  28. package/examples/prompts/generator/others/build_mcp_server.yaml +26 -0
  29. package/examples/prompts/generator/others/project_architecture.yaml +31 -0
  30. package/examples/prompts/generator/others/test_case_generator.yaml +30 -0
  31. package/examples/prompts/generator/others/writing_assistant.yaml +72 -0
  32. package/package.json +54 -0
  33. package/packages/admin-ui/admin.html +4959 -0
  34. package/packages/admin-ui/css/codemirror-theme_xq-light.css +43 -0
  35. package/packages/admin-ui/css/codemirror.css +344 -0
  36. package/packages/admin-ui/js/closebrackets.min.js +8 -0
  37. package/packages/admin-ui/js/codemirror.min.js +8 -0
  38. package/packages/admin-ui/js/js-yaml.min.js +2 -0
  39. package/packages/admin-ui/js/markdown.min.js +8 -0
  40. package/packages/server/config.js +283 -0
  41. package/packages/server/logger.js +55 -0
  42. package/packages/server/manager.js +473 -0
  43. package/packages/server/mcp.js +234 -0
  44. package/packages/server/mcpManager.js +205 -0
  45. package/packages/server/server.js +1001 -0
  46. package/scripts/postinstall.js +34 -0
@@ -0,0 +1,473 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import YAML from 'yaml';
4
+ import { z } from 'zod';
5
+ import crypto from 'crypto';
6
+ import { logger } from './logger.js';
7
+ import { config } from './config.js';
8
+
9
+ /**
10
+ * Prompt数据结构验证schema
11
+ */
12
+ const PromptSchema = z.object({
13
+ name: z.string().min(1, 'Prompt名称不能为空'),
14
+ description: z.string().optional(),
15
+ messages: z.array(z.object({
16
+ role: z.enum(['user', 'assistant', 'system']),
17
+ content: z.object({
18
+ text: z.string()
19
+ })
20
+ })).optional(),
21
+ arguments: z.array(z.object({
22
+ name: z.string().min(1, '参数名不能为空'),
23
+ description: z.string().optional(),
24
+ type: z.enum(['string', 'number', 'boolean']).optional().default('string'),
25
+ required: z.boolean().optional().default(true)
26
+ })).optional().default([]),
27
+ // 元数据字段(可选,用于远程服务)
28
+ uniqueId: z.string().optional(),
29
+ filePath: z.string().optional(),
30
+ fileName: z.string().optional(),
31
+ relativePath: z.string().optional()
32
+ });
33
+
34
+ /**
35
+ * Prompt管理器类
36
+ */
37
+ export class PromptManager {
38
+ constructor(promptsDir) {
39
+ this.promptsDir = promptsDir;
40
+ this.loadedPrompts = new Map();
41
+ this.loadErrors = new Map();
42
+ this.idToPathMap = new Map(); // ID到文件路径的映射
43
+ }
44
+
45
+ /**
46
+ * 基于文件路径生成固定长度的唯一ID
47
+ * @param {string} relativePath - 相对于prompts目录的路径
48
+ * @returns {string} 固定长度的唯一ID字符串(8位)
49
+ */
50
+ generateUniqueId(relativePath) {
51
+ // 使用SHA-256哈希算法生成固定长度的ID
52
+ const hash = crypto.createHash('sha256');
53
+ hash.update(relativePath);
54
+ const hashHex = hash.digest('hex');
55
+
56
+ // 取前8位作为ID,保证长度一致
57
+ // 8位十六进制可以表示 16^8 = 4,294,967,296 种不同的值,足够保证唯一性
58
+ const shortId = hashHex.substring(0, 8);
59
+
60
+ return shortId;
61
+ }
62
+
63
+ /**
64
+ * 基于文件路径生成固定长度的唯一ID(可配置长度版本)
65
+ * @param {string} relativePath - 相对于prompts目录的路径
66
+ * @param {number} length - ID长度,默认为8
67
+ * @returns {string} 固定长度的唯一ID字符串
68
+ */
69
+ generateUniqueIdWithLength(relativePath, length = 8) {
70
+ const hash = crypto.createHash('sha256');
71
+ hash.update(relativePath);
72
+ const hashHex = hash.digest('hex');
73
+
74
+ // 取指定长度的字符作为ID
75
+ const shortId = hashHex.substring(0, length);
76
+
77
+ return shortId;
78
+ }
79
+
80
+ /**
81
+ * 基于ID反解文件路径
82
+ * @param {string} id - 唯一ID
83
+ * @returns {string|null} 文件路径,如果找不到则返回null
84
+ */
85
+ getIdToPath(id) {
86
+ return this.idToPathMap.get(id) || null;
87
+ }
88
+
89
+ /**
90
+ * 读取组元数据的函数
91
+ * @param {string} dir - 目录路径
92
+ * @returns {Object} 组元数据
93
+ */
94
+ readGroupMeta(dir) {
95
+ const GROUP_META_FILENAME = '.groupmeta.json';
96
+ try {
97
+ const metaPath = path.join(dir, GROUP_META_FILENAME);
98
+ if (!fs.existsSync(metaPath)) {
99
+ return { enabled: true };
100
+ }
101
+ const raw = fs.readFileSync(metaPath, 'utf8');
102
+ const data = JSON.parse(raw);
103
+ return {
104
+ enabled: data.enabled !== false
105
+ };
106
+ } catch (error) {
107
+ console.warn('读取类目元数据失败:', error);
108
+ return { enabled: true };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 注册ID到路径的映射
114
+ * @param {string} id - 唯一ID
115
+ * @param {string} relativePath - 相对路径
116
+ */
117
+ registerIdPathMapping(id, relativePath) {
118
+ this.idToPathMap.set(id, relativePath);
119
+ }
120
+
121
+ /**
122
+ * 从远程服务器加载prompts
123
+ */
124
+ async loadRemotePrompts() {
125
+ try {
126
+ logger.info(`开始从远程服务器 ${config.remoteUrl} 加载prompts`);
127
+
128
+ const headers = {
129
+ 'Content-Type': 'application/json',
130
+ ...(config.remoteHeaders || {})
131
+ };
132
+
133
+ const response = await fetch(config.remoteUrl, { headers });
134
+
135
+ if (!response.ok) {
136
+ throw new Error(`远程服务器返回错误: ${response.status} ${response.statusText}`);
137
+ }
138
+
139
+ const data = await response.json();
140
+
141
+ // 清空之前的加载结果
142
+ this.loadedPrompts.clear();
143
+ this.loadErrors.clear();
144
+
145
+ let successCount = 0;
146
+ let errorCount = 0;
147
+
148
+ // 处理远程数据
149
+ for (const promptData of data) {
150
+ try {
151
+ const validatedPrompt = PromptSchema.parse(promptData);
152
+
153
+ // 检查远程服务是否提供了uniqueId
154
+ if (promptData.uniqueId) {
155
+ // 远程服务提供了uniqueId,直接使用
156
+ validatedPrompt.uniqueId = promptData.uniqueId;
157
+
158
+ // 可选:如果提供了其他元数据字段就使用,没提供就设置默认值
159
+ validatedPrompt.filePath = promptData.filePath || `remote://${validatedPrompt.name}`;
160
+ validatedPrompt.fileName = promptData.fileName || `${validatedPrompt.name}.yaml`;
161
+ validatedPrompt.relativePath = promptData.relativePath || `${validatedPrompt.name}.yaml`;
162
+
163
+ // 注册ID到路径的映射
164
+ this.registerIdPathMapping(promptData.uniqueId, validatedPrompt.relativePath);
165
+
166
+ // 使用唯一ID作为存储键
167
+ this.loadedPrompts.set(promptData.uniqueId, validatedPrompt);
168
+ logger.debug(`使用远程服务提供的uniqueId: ${validatedPrompt.name} -> ID: ${promptData.uniqueId}`);
169
+ } else {
170
+ // 远程服务未提供uniqueId,使用兼容模式
171
+ const virtualPath = `${validatedPrompt.name}.yaml`;
172
+ const uniqueId = this.generateUniqueId(virtualPath);
173
+ validatedPrompt.uniqueId = uniqueId;
174
+ validatedPrompt.filePath = `remote://${validatedPrompt.name}`;
175
+ validatedPrompt.fileName = `${validatedPrompt.name}.yaml`;
176
+ validatedPrompt.relativePath = virtualPath;
177
+
178
+ // 注册ID到路径的映射
179
+ this.registerIdPathMapping(uniqueId, virtualPath);
180
+
181
+ // 使用唯一ID作为存储键
182
+ this.loadedPrompts.set(uniqueId, validatedPrompt);
183
+ logger.debug(`使用兼容模式加载远程prompt: ${validatedPrompt.name} -> ID: ${uniqueId}`);
184
+ }
185
+
186
+ successCount++;
187
+ } catch (error) {
188
+ errorCount++;
189
+ this.loadErrors.set(promptData.name || 'unknown', error.message);
190
+ logger.error(`验证远程prompt失败:`, error.message);
191
+ }
192
+ }
193
+
194
+ logger.info(`远程Prompt加载完成: 成功 ${successCount} 个, 失败 ${errorCount} 个`);
195
+
196
+ return {
197
+ success: successCount,
198
+ errorCount: errorCount,
199
+ prompts: Array.from(this.loadedPrompts.values()),
200
+ loadErrors: Object.fromEntries(this.loadErrors)
201
+ };
202
+ } catch (error) {
203
+ logger.error('加载远程prompts时发生错误:', error);
204
+ throw error;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * 递归扫描目录,获取所有prompt文件
210
+ *
211
+ * @param {string} currentPath - 当前目录路径
212
+ * @param {string} relativePath - 相对于当前目录的路径
213
+ * @returns {Array} 所有prompt文件
214
+ */
215
+ async scanPromptFiles(currentPath, relativePath = '') {
216
+ const promptFiles = [];
217
+
218
+ if (relativePath) {
219
+ // console.log('读取组元数据:', "{relativePath:", relativePath, ", currentPath:", currentPath, "}");
220
+ const meta = this.readGroupMeta(path.join(currentPath, relativePath));
221
+ if (meta.enabled === false) {
222
+ return [];
223
+ }
224
+ }
225
+
226
+ try {
227
+ const items = await fs.readdir(currentPath, { withFileTypes: true });
228
+ // console.log('扫描目录:', "{currentPath:", currentPath, ", items:", items, "}");
229
+
230
+ for (const item of items) {
231
+ const itemPath = path.join(currentPath, item.name); // 文件的绝对路径,相对于当前目录,如:/home/work/prompt-manager/prompts/xxx/xxx.yaml
232
+ const itemRelativePath = relativePath ? path.join(relativePath, item.name) : item.name; // 文件的相对路径,相对于当前目录,如:xxx/xxx.yaml
233
+
234
+ if (item.isDirectory()) {
235
+ // 递归扫描子目录
236
+ const subFiles = await this.scanPromptFiles(itemPath, itemRelativePath);
237
+ promptFiles.push(...subFiles);
238
+ } else if (item.isFile()) {
239
+ // 检查文件扩展名
240
+ const ext = path.extname(item.name).toLowerCase();
241
+ if (['.yaml', '.yml', '.json'].includes(ext)) {
242
+ promptFiles.push({
243
+ fileName: item.name,
244
+ filePath: itemPath,
245
+ relativePath: itemRelativePath
246
+ });
247
+ }
248
+ }
249
+ }
250
+ } catch (error) {
251
+ logger.warn(`扫描目录 ${currentPath} 时发生错误:`, error.message);
252
+ }
253
+
254
+ return promptFiles;
255
+ }
256
+
257
+ /**
258
+ * 加载所有prompts
259
+ */
260
+ async loadPrompts() {
261
+ // 如果配置了远程URL,则从远程加载
262
+ if (config.remoteUrl) {
263
+ return await this.loadRemotePrompts();
264
+ }
265
+
266
+ try {
267
+ logger.info(`开始从 ${this.promptsDir} 加载prompts`);
268
+
269
+ // 确保目录存在
270
+ await fs.ensureDir(this.promptsDir);
271
+
272
+ // 根据配置决定是否递归扫描
273
+ let promptFiles;
274
+ if (config.recursiveScan) {
275
+ promptFiles = await this.scanPromptFiles(this.promptsDir);
276
+ } else {
277
+ // 只扫描根目录
278
+ const files = await fs.readdir(this.promptsDir);
279
+ promptFiles = files
280
+ .filter(file => {
281
+ const ext = path.extname(file).toLowerCase();
282
+ return ['.yaml', '.yml', '.json'].includes(ext);
283
+ })
284
+ .map(file => ({
285
+ fileName: file,
286
+ filePath: path.join(this.promptsDir, file),
287
+ relativePath: file
288
+ }));
289
+ }
290
+
291
+ logger.debug(`找到 ${promptFiles.length} 个prompt文件`);
292
+
293
+ // 清空之前的加载结果
294
+ this.loadedPrompts.clear();
295
+ this.loadErrors.clear();
296
+
297
+ // 并行加载所有文件
298
+ const loadPromises = promptFiles.map(fileInfo => this.loadPromptFile(fileInfo));
299
+ const results = await Promise.allSettled(loadPromises);
300
+
301
+ // 统计加载结果
302
+ let successCount = 0;
303
+ let errorCount = 0;
304
+
305
+ let loadedCount = 0;
306
+ let disabledCount = 0;
307
+
308
+ results.forEach((result, index) => {
309
+ if (result.status === 'fulfilled') {
310
+ successCount++;
311
+ if (result.value !== null) {
312
+ loadedCount++;
313
+ }else{
314
+ disabledCount++;
315
+ }
316
+ } else {
317
+ errorCount++;
318
+ const fileInfo = promptFiles[index];
319
+ const errorKey = fileInfo.relativePath || fileInfo.fileName;
320
+ this.loadErrors.set(errorKey, result.reason.message);
321
+ logger.error(`加载prompt文件 ${errorKey} 失败:`, result.reason.message);
322
+ }
323
+ });
324
+
325
+ logger.info(`本地Prompt加载完成: 启用 ${loadedCount} 个, 禁用 ${disabledCount} 个, 失败 ${errorCount} 个`);
326
+
327
+ return {
328
+ success: loadedCount,
329
+ errorCount: errorCount,
330
+ prompts: Array.from(this.loadedPrompts.values()),
331
+ loadErrors: Object.fromEntries(this.loadErrors)
332
+ };
333
+ } catch (error) {
334
+ logger.error('加载prompts时发生错误:', error);
335
+ throw error;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * 加载单个prompt文件
341
+ */
342
+ async loadPromptFile(fileInfo) {
343
+ // 支持旧的字符串参数格式(向后兼容)
344
+ const fileName = typeof fileInfo === 'string' ? fileInfo : fileInfo.fileName;
345
+ const filePath = typeof fileInfo === 'string' ? path.join(this.promptsDir, fileInfo) : fileInfo.filePath;
346
+ const relativePath = typeof fileInfo === 'string' ? fileInfo : fileInfo.relativePath;
347
+
348
+ try {
349
+ const content = await fs.readFile(filePath, 'utf8');
350
+ const ext = path.extname(fileName).toLowerCase();
351
+
352
+ let promptData;
353
+ if (ext === '.json') {
354
+ promptData = JSON.parse(content);
355
+ } else {
356
+ promptData = YAML.parse(content);
357
+ }
358
+
359
+ if (promptData.enabled !== true) {
360
+ logger.debug(`skipped loading prompt: ${promptData.name} -> disabled`);
361
+ return null;
362
+ }
363
+ logger.debug(`loaded prompt: ${promptData.name} -> ${filePath}`);
364
+
365
+ // 验证prompt数据结构
366
+ const validatedPrompt = PromptSchema.parse(promptData);
367
+
368
+ // 添加文件路径信息
369
+ validatedPrompt.filePath = filePath;
370
+ validatedPrompt.fileName = fileName;
371
+ validatedPrompt.relativePath = relativePath;
372
+
373
+ // 生成基于文件路径的唯一ID
374
+ const uniqueId = this.generateUniqueId(relativePath);
375
+ validatedPrompt.uniqueId = uniqueId;
376
+
377
+ // 注册ID到路径的映射
378
+ this.registerIdPathMapping(uniqueId, relativePath);
379
+
380
+ // 使用唯一ID作为存储键
381
+ this.loadedPrompts.set(uniqueId, validatedPrompt);
382
+ logger.debug(`成功加载prompt: ${validatedPrompt.name} -> ID: ${uniqueId} (${relativePath})`);
383
+
384
+ return validatedPrompt;
385
+ } catch (error) {
386
+ if (error instanceof z.ZodError) {
387
+ const errorMessage = `数据验证失败: ${error.errors.map(e => e.message).join(', ')}`;
388
+ throw new Error(errorMessage);
389
+ }
390
+ throw error;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * 获取所有已加载的prompts
396
+ */
397
+ getPrompts() {
398
+ return Array.from(this.loadedPrompts.values());
399
+ }
400
+
401
+ /**
402
+ * 根据ID获取prompt
403
+ * @param {string} id - 唯一ID或原始名称(向后兼容)
404
+ */
405
+ getPrompt(id) {
406
+ // 首先尝试直接匹配唯一ID
407
+ if (this.loadedPrompts.has(id)) {
408
+ return this.loadedPrompts.get(id);
409
+ }
410
+
411
+ // 如果直接匹配失败,尝试匹配原始名称(向后兼容)
412
+ for (const [key, prompt] of this.loadedPrompts.entries()) {
413
+ if (prompt.name === id || prompt.relativePath === id) {
414
+ return prompt;
415
+ }
416
+ }
417
+
418
+ return null;
419
+ }
420
+
421
+ /**
422
+ * 根据名称获取prompt(向后兼容方法)
423
+ * @deprecated 建议使用 getPrompt(id) 方法
424
+ */
425
+ getPromptByName(name) {
426
+ return this.getPrompt(name);
427
+ }
428
+
429
+ /**
430
+ * 获取prompt ID列表
431
+ */
432
+ getPromptNames() {
433
+ return Array.from(this.loadedPrompts.keys());
434
+ }
435
+
436
+ /**
437
+ * 验证prompt数据结构
438
+ * @param {Object} promptData - 要验证的prompt数据
439
+ * @returns {Object} 验证后的prompt数据
440
+ */
441
+ validatePromptData(promptData) {
442
+ return PromptSchema.parse(promptData);
443
+ }
444
+
445
+ /**
446
+ * 获取所有prompt的ID和路径映射
447
+ */
448
+ getIdPathMappings() {
449
+ return Object.fromEntries(this.idToPathMap);
450
+ }
451
+
452
+ /**
453
+ * 检查prompt是否存在
454
+ */
455
+ hasPrompt(name) {
456
+ return this.loadedPrompts.has(name);
457
+ }
458
+
459
+ /**
460
+ * 获取加载错误信息
461
+ */
462
+ getLoadErrors() {
463
+ return Object.fromEntries(this.loadErrors);
464
+ }
465
+
466
+ /**
467
+ * 重新加载prompts
468
+ */
469
+ async reloadPrompts() {
470
+ logger.info('重新加载prompts');
471
+ return await this.loadPrompts();
472
+ }
473
+ }
@@ -0,0 +1,234 @@
1
+ // 导入自定义模块
2
+ import { config } from './config.js';
3
+ import { logger } from './logger.js';
4
+
5
+ // 处理 get_prompt 工具调用
6
+ export async function handleGetPrompt(args, promptManager) {
7
+ // 注意:这里为了兼容性,我们同时支持prompt_id和name参数
8
+ const promptId = args.prompt_id || args.name;
9
+
10
+ if (!promptId) {
11
+ throw new Error("缺少必需参数: prompt_id 或 name");
12
+ }
13
+
14
+ const prompt = promptManager.getPrompt(promptId);
15
+ if (!prompt) {
16
+ throw new Error(`未找到ID为 "${promptId}" 的prompt`);
17
+ }
18
+
19
+ // 返回完整的prompt信息
20
+ const promptInfo = {
21
+ id: prompt.uniqueId, // 使用基于文件路径的唯一ID
22
+ name: prompt.name,
23
+ description: prompt.description || `Prompt: ${prompt.name}`,
24
+ messages: prompt.messages || [],
25
+ arguments: prompt.arguments || [],
26
+ filePath: prompt.relativePath, // 添加文件路径信息
27
+ };
28
+
29
+ if (config.getLogLevel() === 'debug') {
30
+ promptInfo.metadata = {
31
+ uniqueId: prompt.uniqueId,
32
+ fileName: prompt.fileName,
33
+ fullPath: prompt.filePath,
34
+ };
35
+ }
36
+
37
+ return convertToText({
38
+ success: true,
39
+ prompt: promptInfo
40
+ });
41
+ }
42
+
43
+ // 处理 search_prompts 工具调用
44
+ export async function handleSearchPrompts(args, promptManager) {
45
+ // 注意:这里为了兼容性,我们同时支持title和name参数
46
+ const searchTerm = args.title || args.name;
47
+
48
+ const logLevel = config.getLogLevel();
49
+ const allPrompts = promptManager.getPrompts();
50
+
51
+ // 如果搜索词为空,则返回所有提示词
52
+ if (!searchTerm) {
53
+ let simplifiedPrompts = formatResults(allPrompts);
54
+
55
+ return convertToText({
56
+ success: true,
57
+ query: searchTerm || '',
58
+ count: simplifiedPrompts.length,
59
+ results: simplifiedPrompts
60
+ });
61
+ }
62
+
63
+ // 实现相似度匹配算法
64
+ const searchResults = allPrompts.map(prompt => {
65
+ prompt.description = prompt.description || `Prompt: ${prompt.name}`;
66
+ prompt.arguments = prompt.arguments || [];
67
+ prompt.hasArguments = prompt.arguments && prompt.arguments.length > 0;
68
+ return {
69
+ prompt: prompt,
70
+ score: calculateSimilarityScore(searchTerm, prompt),
71
+ };
72
+ })
73
+ .filter(result => result.score > 0) // 只返回有匹配的结果
74
+ .sort((a, b) => b.score - a.score); // 按相似度得分降序排列
75
+
76
+
77
+ let result = {
78
+ success: true,
79
+ query: searchTerm || '',
80
+ count: searchResults.length,
81
+ results: formatResults(searchResults),
82
+ }
83
+
84
+ if (logLevel === 'debug') {
85
+ result.debug = {
86
+ scores: searchResults.map(result => ({
87
+ id: result.prompt.id,
88
+ name: result.prompt.name,
89
+ score: result.score,
90
+ fullPath: result.prompt.filePath,
91
+ }))
92
+ }
93
+ }
94
+
95
+ return convertToText(result);
96
+ }
97
+
98
+ /**
99
+ * 处理 reload_prompts 工具调用
100
+ */
101
+ export async function handleReloadPrompts(promptManager) {
102
+ logger.info('重新加载prompts...');
103
+
104
+ const result = await promptManager.reloadPrompts();
105
+
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: JSON.stringify({
111
+ success: true,
112
+ message: `重新加载完成: 成功 ${result.success} 个, 失败 ${result.errorCount} 个`,
113
+ result: formatResults(result.prompts)
114
+ }, null, 2)
115
+ }
116
+ ]
117
+ };
118
+ }
119
+
120
+ /**
121
+ * 格式化搜索结果
122
+ * @param {*} results
123
+ * @returns
124
+ */
125
+ function formatResults(results = []) {
126
+ if (!Array.isArray(results)) return [];
127
+
128
+ // console.log(results);
129
+
130
+ return results.map(result => {
131
+ const prompt = result.prompt ? result.prompt : result;
132
+ const baseItem = {
133
+ id: prompt.id || prompt.uniqueId || '',
134
+ name: prompt.name || 'Unnamed Prompt',
135
+ description: prompt.description || `Prompt: ${prompt.name || 'Unnamed'}`
136
+ };
137
+
138
+ if (config.getLogLevel() === 'debug') {
139
+ return {
140
+ ...baseItem,
141
+ metadata: {
142
+ fullPath: prompt.filePath || ''
143
+ }
144
+ };
145
+ }
146
+ return baseItem;
147
+ });
148
+ }
149
+
150
+ /**
151
+ * 将对象转换为text类型
152
+ * @param {*} object
153
+ * @returns
154
+ */
155
+ function convertToText(result) {
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: JSON.stringify(result, null, 2)
161
+ }
162
+ ]
163
+ };
164
+ }
165
+
166
+ /**
167
+ * 计算搜索关键词与prompt的相似度得分
168
+ * @param {string} searchTerm - 搜索关键词
169
+ * @param {Object} prompt - prompt对象
170
+ * @returns {number} 相似度得分 (0-100)
171
+ */
172
+ function calculateSimilarityScore(searchTerm, prompt) {
173
+ let totalScore = 0;
174
+ const searchLower = searchTerm ? searchTerm.toLowerCase() : '';
175
+
176
+ // 搜索字段权重配置(专注于内容搜索,不包含ID检索)
177
+ const fieldWeights = {
178
+ name: 60, // 名称权重高,是主要匹配字段
179
+ description: 40 // 描述权重适中,是辅助匹配字段
180
+ };
181
+
182
+ // 计算name匹配得分
183
+ if (prompt && prompt.name && typeof prompt.name === 'string') {
184
+ const nameScore = getStringMatchScore(searchLower, prompt.name.toLowerCase());
185
+ totalScore += nameScore * fieldWeights.name;
186
+ }
187
+
188
+ // 计算description匹配得分
189
+ if (prompt.description) {
190
+ const descScore = getStringMatchScore(searchLower, prompt.description.toLowerCase());
191
+ totalScore += descScore * fieldWeights.description;
192
+ }
193
+
194
+ // 标准化得分到0-100范围
195
+ const maxPossibleScore = Object.values(fieldWeights).reduce((sum, weight) => sum + weight, 0);
196
+ return Math.round((totalScore / maxPossibleScore) * 100);
197
+ }
198
+
199
+ /**
200
+ * 计算两个字符串的匹配得分
201
+ * @param {string} search - 搜索词 (已转小写)
202
+ * @param {string} target - 目标字符串 (已转小写)
203
+ * @returns {number} 匹配得分 (0-1)
204
+ */
205
+ function getStringMatchScore(search, target) {
206
+ if (!search || !target) return 0;
207
+
208
+ // 完全匹配得分最高
209
+ if (target === search) return 1.0;
210
+
211
+ // 完全包含得分较高
212
+ if (target.includes(search)) return 0.8;
213
+
214
+ // 部分词匹配
215
+ const searchWords = search.split(/\s+/).filter(word => word.length > 0);
216
+ const targetWords = target.split(/\s+/).filter(word => word.length > 0);
217
+
218
+ let matchedWords = 0;
219
+ for (const searchWord of searchWords) {
220
+ for (const targetWord of targetWords) {
221
+ if (targetWord.includes(searchWord) || searchWord.includes(targetWord)) {
222
+ matchedWords++;
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ if (searchWords.length > 0) {
229
+ const wordMatchRatio = matchedWords / searchWords.length;
230
+ return wordMatchRatio * 0.6; // 部分词匹配得分
231
+ }
232
+
233
+ return 0;
234
+ }