mcp-probe-kit 1.11.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -21
- package/build/index.js +21 -1
- package/build/schemas/index.d.ts +234 -0
- package/build/schemas/index.js +4 -0
- package/build/schemas/interview-tools.d.ts +72 -0
- package/build/schemas/interview-tools.js +64 -0
- package/build/schemas/orchestration-tools.d.ts +58 -0
- package/build/schemas/orchestration-tools.js +59 -0
- package/build/schemas/ui-ux-schemas.d.ts +248 -0
- package/build/schemas/ui-ux-schemas.js +147 -0
- package/build/tools/__tests__/start_ui.integration.test.d.ts +6 -0
- package/build/tools/__tests__/start_ui.integration.test.js +179 -0
- package/build/tools/__tests__/start_ui.property.test.d.ts +6 -0
- package/build/tools/__tests__/start_ui.property.test.js +263 -0
- package/build/tools/__tests__/start_ui.unit.test.d.ts +6 -0
- package/build/tools/__tests__/start_ui.unit.test.js +109 -0
- package/build/tools/ask_user.d.ts +17 -0
- package/build/tools/ask_user.js +124 -0
- package/build/tools/index.d.ts +7 -0
- package/build/tools/index.js +9 -0
- package/build/tools/init_component_catalog.d.ts +22 -0
- package/build/tools/init_component_catalog.js +809 -0
- package/build/tools/interview.d.ts +18 -0
- package/build/tools/interview.js +418 -0
- package/build/tools/render_ui.d.ts +22 -0
- package/build/tools/render_ui.js +384 -0
- package/build/tools/start_ralph.d.ts +16 -0
- package/build/tools/start_ralph.js +779 -0
- package/build/tools/start_ui.d.ts +25 -0
- package/build/tools/start_ui.js +299 -0
- package/build/tools/ui-ux-tools.d.ts +116 -0
- package/build/tools/ui-ux-tools.js +756 -0
- package/build/tools/ui-ux-tools.test.d.ts +6 -0
- package/build/tools/ui-ux-tools.test.js +132 -0
- package/build/utils/ascii-box-formatter.d.ts +29 -0
- package/build/utils/ascii-box-formatter.js +195 -0
- package/build/utils/bm25.d.ts +60 -0
- package/build/utils/bm25.js +139 -0
- package/build/utils/cache-manager.d.ts +65 -0
- package/build/utils/cache-manager.js +156 -0
- package/build/utils/design-docs-generator.d.ts +1 -0
- package/build/utils/design-docs-generator.js +1 -0
- package/build/utils/design-reasoning-engine.d.ts +158 -0
- package/build/utils/design-reasoning-engine.js +363 -0
- package/build/utils/design-system-json-formatter.d.ts +41 -0
- package/build/utils/design-system-json-formatter.js +165 -0
- package/build/utils/ui-data-loader.d.ts +56 -0
- package/build/utils/ui-data-loader.js +164 -0
- package/build/utils/ui-search-engine.d.ts +57 -0
- package/build/utils/ui-search-engine.js +123 -0
- package/build/utils/ui-sync.d.ts +13 -0
- package/build/utils/ui-sync.js +241 -0
- package/docs/BEST_PRACTICES.md +456 -6
- package/docs/HOW_TO_TRIGGER.md +195 -64
- package/docs/MCP-Probe-Kit-/344/275/277/347/224/250/346/211/213/345/206/214.html +158 -63
- package/docs/MCP-Probe-Kit-/344/275/277/347/224/250/346/211/213/345/206/214.md +872 -34
- package/package.json +18 -5
- package/docs/HOW_TO_TRIGGER.html +0 -243
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for generateFileIndex and generateCreationGuidance functions
|
|
3
|
+
*
|
|
4
|
+
* Requirements: 3.2, 3.3, 4.2, 4.3, 4.4, 4.5
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { generateFileIndex, generateCreationGuidance } from './ui-ux-tools.js';
|
|
8
|
+
describe('generateFileIndex', () => {
|
|
9
|
+
it('should return the correct number of files', () => {
|
|
10
|
+
const fileIndex = generateFileIndex();
|
|
11
|
+
// 验证返回7个文件
|
|
12
|
+
expect(fileIndex).toHaveLength(7);
|
|
13
|
+
});
|
|
14
|
+
it('should return files in the correct order', () => {
|
|
15
|
+
const fileIndex = generateFileIndex();
|
|
16
|
+
// 验证文件按 order 字段正确排序
|
|
17
|
+
for (let i = 0; i < fileIndex.length; i++) {
|
|
18
|
+
expect(fileIndex[i].order).toBe(i + 1);
|
|
19
|
+
}
|
|
20
|
+
// 验证具体的文件顺序
|
|
21
|
+
expect(fileIndex[0].path).toBe('docs/design-system.json');
|
|
22
|
+
expect(fileIndex[1].path).toBe('docs/design-guidelines/README.md');
|
|
23
|
+
expect(fileIndex[2].path).toBe('docs/design-guidelines/01-principles.md');
|
|
24
|
+
expect(fileIndex[3].path).toBe('docs/design-guidelines/02-interaction.md');
|
|
25
|
+
expect(fileIndex[4].path).toBe('docs/design-guidelines/03-layout.md');
|
|
26
|
+
expect(fileIndex[5].path).toBe('docs/design-guidelines/04-config.md');
|
|
27
|
+
expect(fileIndex[6].path).toBe('docs/design-system.md');
|
|
28
|
+
});
|
|
29
|
+
it('should have design-system.md as the last file', () => {
|
|
30
|
+
const fileIndex = generateFileIndex();
|
|
31
|
+
// 验证最后一个文件是 design-system.md
|
|
32
|
+
const lastFile = fileIndex[fileIndex.length - 1];
|
|
33
|
+
expect(lastFile.path).toBe('docs/design-system.md');
|
|
34
|
+
expect(lastFile.order).toBe(7);
|
|
35
|
+
});
|
|
36
|
+
it('should mark all files as required', () => {
|
|
37
|
+
const fileIndex = generateFileIndex();
|
|
38
|
+
// 验证所有文件都标记为必需
|
|
39
|
+
fileIndex.forEach(file => {
|
|
40
|
+
expect(file.required).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
it('should have valid file metadata', () => {
|
|
44
|
+
const fileIndex = generateFileIndex();
|
|
45
|
+
// 验证每个文件都有必需的元数据
|
|
46
|
+
fileIndex.forEach(file => {
|
|
47
|
+
expect(file.path).toBeTruthy();
|
|
48
|
+
expect(file.purpose).toBeTruthy();
|
|
49
|
+
expect(file.order).toBeGreaterThan(0);
|
|
50
|
+
expect(typeof file.required).toBe('boolean');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('generateCreationGuidance', () => {
|
|
55
|
+
it('should include topics for all four document types', () => {
|
|
56
|
+
const guidance = generateCreationGuidance('SaaS');
|
|
57
|
+
// 验证所有四个文档类型都有主题
|
|
58
|
+
expect(guidance.principles).toBeDefined();
|
|
59
|
+
expect(guidance.interaction).toBeDefined();
|
|
60
|
+
expect(guidance.layout).toBeDefined();
|
|
61
|
+
expect(guidance.config).toBeDefined();
|
|
62
|
+
// 验证每个类型都有至少一个主题
|
|
63
|
+
expect(guidance.principles.length).toBeGreaterThan(0);
|
|
64
|
+
expect(guidance.interaction.length).toBeGreaterThan(0);
|
|
65
|
+
expect(guidance.layout.length).toBeGreaterThan(0);
|
|
66
|
+
expect(guidance.config.length).toBeGreaterThan(0);
|
|
67
|
+
});
|
|
68
|
+
it('should include productType in tips', () => {
|
|
69
|
+
const productType = 'E-commerce';
|
|
70
|
+
const guidance = generateCreationGuidance(productType);
|
|
71
|
+
// 验证提示中包含 productType
|
|
72
|
+
expect(guidance.tips).toBeDefined();
|
|
73
|
+
expect(guidance.tips.length).toBeGreaterThan(0);
|
|
74
|
+
// 检查是否有提示提到了产品类型
|
|
75
|
+
const hasProductTypeMention = guidance.tips.some(tip => tip.includes(productType));
|
|
76
|
+
expect(hasProductTypeMention).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
it('should include stack in tips when provided', () => {
|
|
79
|
+
const stack = 'react';
|
|
80
|
+
const guidance = generateCreationGuidance('SaaS', stack);
|
|
81
|
+
// 验证提示中包含 stack
|
|
82
|
+
const hasStackMention = guidance.tips.some(tip => tip.toLowerCase().includes(stack.toLowerCase()));
|
|
83
|
+
expect(hasStackMention).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
it('should add stack-specific config topics when stack is provided', () => {
|
|
86
|
+
const guidanceWithoutStack = generateCreationGuidance('SaaS');
|
|
87
|
+
const guidanceWithReact = generateCreationGuidance('SaaS', 'react');
|
|
88
|
+
const guidanceWithVue = generateCreationGuidance('SaaS', 'vue');
|
|
89
|
+
const guidanceWithTailwind = generateCreationGuidance('SaaS', 'tailwind');
|
|
90
|
+
// React 应该有额外的配置主题
|
|
91
|
+
expect(guidanceWithReact.config.length).toBeGreaterThan(guidanceWithoutStack.config.length);
|
|
92
|
+
// Vue 应该有额外的配置主题
|
|
93
|
+
expect(guidanceWithVue.config.length).toBeGreaterThan(guidanceWithoutStack.config.length);
|
|
94
|
+
// Tailwind 应该有额外的配置主题
|
|
95
|
+
expect(guidanceWithTailwind.config.length).toBeGreaterThan(guidanceWithoutStack.config.length);
|
|
96
|
+
});
|
|
97
|
+
it('should include tips array', () => {
|
|
98
|
+
const guidance = generateCreationGuidance('SaaS');
|
|
99
|
+
// 验证 tips 数组存在且不为空
|
|
100
|
+
expect(guidance.tips).toBeDefined();
|
|
101
|
+
expect(Array.isArray(guidance.tips)).toBe(true);
|
|
102
|
+
expect(guidance.tips.length).toBeGreaterThan(0);
|
|
103
|
+
});
|
|
104
|
+
it('should provide different tips for different product types', () => {
|
|
105
|
+
const saasGuidance = generateCreationGuidance('SaaS');
|
|
106
|
+
const ecommerceGuidance = generateCreationGuidance('E-commerce');
|
|
107
|
+
const healthcareGuidance = generateCreationGuidance('Healthcare');
|
|
108
|
+
// 验证不同产品类型有不同的提示
|
|
109
|
+
// 至少应该有一个提示是不同的(因为包含了产品类型)
|
|
110
|
+
const saasSpecificTips = saasGuidance.tips.filter(tip => tip.includes('SaaS'));
|
|
111
|
+
const ecommerceSpecificTips = ecommerceGuidance.tips.filter(tip => tip.includes('E-commerce'));
|
|
112
|
+
const healthcareSpecificTips = healthcareGuidance.tips.filter(tip => tip.includes('Healthcare'));
|
|
113
|
+
expect(saasSpecificTips.length).toBeGreaterThan(0);
|
|
114
|
+
expect(ecommerceSpecificTips.length).toBeGreaterThan(0);
|
|
115
|
+
expect(healthcareSpecificTips.length).toBeGreaterThan(0);
|
|
116
|
+
});
|
|
117
|
+
it('should have valid structure for all guidance fields', () => {
|
|
118
|
+
const guidance = generateCreationGuidance('SaaS', 'react');
|
|
119
|
+
// 验证所有字段都是字符串数组
|
|
120
|
+
expect(Array.isArray(guidance.principles)).toBe(true);
|
|
121
|
+
expect(Array.isArray(guidance.interaction)).toBe(true);
|
|
122
|
+
expect(Array.isArray(guidance.layout)).toBe(true);
|
|
123
|
+
expect(Array.isArray(guidance.config)).toBe(true);
|
|
124
|
+
expect(Array.isArray(guidance.tips)).toBe(true);
|
|
125
|
+
// 验证数组中的元素都是字符串
|
|
126
|
+
guidance.principles.forEach(item => expect(typeof item).toBe('string'));
|
|
127
|
+
guidance.interaction.forEach(item => expect(typeof item).toBe('string'));
|
|
128
|
+
guidance.layout.forEach(item => expect(typeof item).toBe('string'));
|
|
129
|
+
guidance.config.forEach(item => expect(typeof item).toBe('string'));
|
|
130
|
+
guidance.tips.forEach(item => expect(typeof item).toBe('string'));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII Box 格式化工具
|
|
3
|
+
*
|
|
4
|
+
* 将设计系统推荐格式化为漂亮的 ASCII Box 格式
|
|
5
|
+
*/
|
|
6
|
+
import { DesignSystemRecommendation } from './design-reasoning-engine.js';
|
|
7
|
+
export declare class ASCIIBoxFormatter {
|
|
8
|
+
private readonly BOX_WIDTH;
|
|
9
|
+
/**
|
|
10
|
+
* 格式化设计系统推荐
|
|
11
|
+
*/
|
|
12
|
+
format(recommendation: DesignSystemRecommendation): string;
|
|
13
|
+
/**
|
|
14
|
+
* 填充字符串到指定宽度
|
|
15
|
+
*/
|
|
16
|
+
private pad;
|
|
17
|
+
/**
|
|
18
|
+
* 文本换行 - 返回字符串数组
|
|
19
|
+
*/
|
|
20
|
+
private wrapText;
|
|
21
|
+
/**
|
|
22
|
+
* 截断文本
|
|
23
|
+
*/
|
|
24
|
+
private truncate;
|
|
25
|
+
/**
|
|
26
|
+
* 生成 Markdown 格式(备用)
|
|
27
|
+
*/
|
|
28
|
+
formatMarkdown(recommendation: DesignSystemRecommendation): string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII Box 格式化工具
|
|
3
|
+
*
|
|
4
|
+
* 将设计系统推荐格式化为漂亮的 ASCII Box 格式
|
|
5
|
+
*/
|
|
6
|
+
export class ASCIIBoxFormatter {
|
|
7
|
+
BOX_WIDTH = 92; // 总宽度(包括两侧的 | 符号)
|
|
8
|
+
/**
|
|
9
|
+
* 格式化设计系统推荐
|
|
10
|
+
*/
|
|
11
|
+
format(recommendation) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
const w = this.BOX_WIDTH - 2; // 内容宽度(不包括两侧的 |)
|
|
14
|
+
// 顶部边框
|
|
15
|
+
lines.push('+' + '-'.repeat(w) + '+');
|
|
16
|
+
// 标题
|
|
17
|
+
const title = ` TARGET: ${recommendation.target} - RECOMMENDED DESIGN SYSTEM`;
|
|
18
|
+
lines.push('|' + this.pad(title, w) + '|');
|
|
19
|
+
// 分隔线
|
|
20
|
+
lines.push('+' + '-'.repeat(w) + '+');
|
|
21
|
+
// 空行
|
|
22
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
23
|
+
// 落地页模式
|
|
24
|
+
lines.push('|' + this.pad(` PATTERN: ${recommendation.pattern.name}`, w) + '|');
|
|
25
|
+
lines.push('|' + this.pad(` Conversion: ${recommendation.pattern.conversion}`, w) + '|');
|
|
26
|
+
lines.push('|' + this.pad(` CTA: ${recommendation.pattern.cta}`, w) + '|');
|
|
27
|
+
lines.push('|' + this.pad(' Sections:', w) + '|');
|
|
28
|
+
recommendation.pattern.sections.forEach((section, i) => {
|
|
29
|
+
lines.push('|' + this.pad(` ${i + 1}. ${section}`, w) + '|');
|
|
30
|
+
});
|
|
31
|
+
// 空行
|
|
32
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
33
|
+
// UI 风格
|
|
34
|
+
lines.push('|' + this.pad(` STYLE: ${recommendation.style.primary}`, w) + '|');
|
|
35
|
+
// Keywords - 处理长文本换行
|
|
36
|
+
const keywords = recommendation.style.keywords.slice(0, 8).join(', ');
|
|
37
|
+
const keywordsLines = this.wrapText(`Keywords: ${keywords}`, w - 7);
|
|
38
|
+
keywordsLines.forEach(line => {
|
|
39
|
+
lines.push('|' + this.pad(` ${line}`, w) + '|');
|
|
40
|
+
});
|
|
41
|
+
const bestFor = recommendation.style.bestFor.slice(0, 3).join(', ');
|
|
42
|
+
lines.push('|' + this.pad(` Best For: ${bestFor}`, w) + '|');
|
|
43
|
+
lines.push('|' + this.pad(` Performance: ${recommendation.style.performance} | Accessibility: ${recommendation.style.accessibility}`, w) + '|');
|
|
44
|
+
// 空行
|
|
45
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
46
|
+
// 配色方案
|
|
47
|
+
lines.push('|' + this.pad(' COLORS:', w) + '|');
|
|
48
|
+
lines.push('|' + this.pad(` Primary: ${recommendation.colors.primary}`, w) + '|');
|
|
49
|
+
lines.push('|' + this.pad(` Secondary: ${recommendation.colors.secondary}`, w) + '|');
|
|
50
|
+
lines.push('|' + this.pad(` CTA: ${recommendation.colors.cta}`, w) + '|');
|
|
51
|
+
lines.push('|' + this.pad(` Background: ${recommendation.colors.background}`, w) + '|');
|
|
52
|
+
lines.push('|' + this.pad(` Text: ${recommendation.colors.text}`, w) + '|');
|
|
53
|
+
const notesLines = this.wrapText(`Notes: ${recommendation.colors.notes}`, w - 7);
|
|
54
|
+
notesLines.forEach(line => {
|
|
55
|
+
lines.push('|' + this.pad(` ${line}`, w) + '|');
|
|
56
|
+
});
|
|
57
|
+
// 空行
|
|
58
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
59
|
+
// 字体配对
|
|
60
|
+
lines.push('|' + this.pad(` TYPOGRAPHY: ${recommendation.typography.heading} / ${recommendation.typography.body}`, w) + '|');
|
|
61
|
+
const moodLines = this.wrapText(`Mood: ${recommendation.typography.mood}`, w - 7);
|
|
62
|
+
moodLines.forEach(line => {
|
|
63
|
+
lines.push('|' + this.pad(` ${line}`, w) + '|');
|
|
64
|
+
});
|
|
65
|
+
const typoBestFor = recommendation.typography.bestFor.slice(0, 3).join(', ');
|
|
66
|
+
lines.push('|' + this.pad(` Best For: ${typoBestFor}`, w) + '|');
|
|
67
|
+
if (recommendation.typography.googleFontsUrl) {
|
|
68
|
+
const url = this.truncate(recommendation.typography.googleFontsUrl, 60);
|
|
69
|
+
lines.push('|' + this.pad(` Google Fonts: ${url}`, w) + '|');
|
|
70
|
+
}
|
|
71
|
+
// 空行
|
|
72
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
73
|
+
// 效果和动画
|
|
74
|
+
lines.push('|' + this.pad(' KEY EFFECTS:', w) + '|');
|
|
75
|
+
const effects = `${recommendation.effects.shadows} + ${recommendation.effects.transitions} + ${recommendation.effects.hover}`;
|
|
76
|
+
const effectsLines = this.wrapText(effects, w - 7);
|
|
77
|
+
effectsLines.forEach(line => {
|
|
78
|
+
lines.push('|' + this.pad(` ${line}`, w) + '|');
|
|
79
|
+
});
|
|
80
|
+
// 空行
|
|
81
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
82
|
+
// 反模式
|
|
83
|
+
lines.push('|' + this.pad(' AVOID (Anti-patterns):', w) + '|');
|
|
84
|
+
const antiPatternsText = recommendation.antiPatterns.slice(0, 4).join(' + ');
|
|
85
|
+
const antiLines = this.wrapText(antiPatternsText, w - 7);
|
|
86
|
+
antiLines.forEach(line => {
|
|
87
|
+
lines.push('|' + this.pad(` ${line}`, w) + '|');
|
|
88
|
+
});
|
|
89
|
+
// 空行
|
|
90
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
91
|
+
// 检查清单
|
|
92
|
+
lines.push('|' + this.pad(' PRE-DELIVERY CHECKLIST:', w) + '|');
|
|
93
|
+
recommendation.checklist.slice(0, 7).forEach(item => {
|
|
94
|
+
lines.push('|' + this.pad(` [ ] ${item}`, w) + '|');
|
|
95
|
+
});
|
|
96
|
+
// 空行
|
|
97
|
+
lines.push('|' + ' '.repeat(w) + '|');
|
|
98
|
+
// 底部边框
|
|
99
|
+
lines.push('+' + '-'.repeat(w) + '+');
|
|
100
|
+
return lines.join('\n');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 填充字符串到指定宽度
|
|
104
|
+
*/
|
|
105
|
+
pad(text, width) {
|
|
106
|
+
if (text.length >= width) {
|
|
107
|
+
return text.substring(0, width);
|
|
108
|
+
}
|
|
109
|
+
return text + ' '.repeat(width - text.length);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 文本换行 - 返回字符串数组
|
|
113
|
+
*/
|
|
114
|
+
wrapText(text, maxWidth) {
|
|
115
|
+
const words = text.split(' ');
|
|
116
|
+
const lines = [];
|
|
117
|
+
let currentLine = '';
|
|
118
|
+
for (const word of words) {
|
|
119
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
120
|
+
if (testLine.length <= maxWidth) {
|
|
121
|
+
currentLine = testLine;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (currentLine) {
|
|
125
|
+
lines.push(currentLine);
|
|
126
|
+
}
|
|
127
|
+
currentLine = word;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (currentLine) {
|
|
131
|
+
lines.push(currentLine);
|
|
132
|
+
}
|
|
133
|
+
return lines;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 截断文本
|
|
137
|
+
*/
|
|
138
|
+
truncate(text, maxLength) {
|
|
139
|
+
if (text.length <= maxLength) {
|
|
140
|
+
return text;
|
|
141
|
+
}
|
|
142
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 生成 Markdown 格式(备用)
|
|
146
|
+
*/
|
|
147
|
+
formatMarkdown(recommendation) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
lines.push(`# ${recommendation.target} - Design System`);
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(`## Landing Page Pattern: ${recommendation.pattern.name}`);
|
|
152
|
+
lines.push(`- **Conversion**: ${recommendation.pattern.conversion}`);
|
|
153
|
+
lines.push(`- **CTA**: ${recommendation.pattern.cta}`);
|
|
154
|
+
lines.push(`- **Sections**: ${recommendation.pattern.sections.join(', ')}`);
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push(`## UI Style: ${recommendation.style.primary}`);
|
|
157
|
+
lines.push(`- **Keywords**: ${recommendation.style.keywords.join(', ')}`);
|
|
158
|
+
lines.push(`- **Best For**: ${recommendation.style.bestFor.join(', ')}`);
|
|
159
|
+
lines.push(`- **Performance**: ${recommendation.style.performance}`);
|
|
160
|
+
lines.push(`- **Accessibility**: ${recommendation.style.accessibility}`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push('## Color Palette');
|
|
163
|
+
lines.push(`- **Primary**: ${recommendation.colors.primary}`);
|
|
164
|
+
lines.push(`- **Secondary**: ${recommendation.colors.secondary}`);
|
|
165
|
+
lines.push(`- **CTA**: ${recommendation.colors.cta}`);
|
|
166
|
+
lines.push(`- **Background**: ${recommendation.colors.background}`);
|
|
167
|
+
lines.push(`- **Text**: ${recommendation.colors.text}`);
|
|
168
|
+
lines.push(`- **Notes**: ${recommendation.colors.notes}`);
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push(`## Typography: ${recommendation.typography.heading} / ${recommendation.typography.body}`);
|
|
171
|
+
lines.push(`- **Mood**: ${recommendation.typography.mood}`);
|
|
172
|
+
lines.push(`- **Best For**: ${recommendation.typography.bestFor.join(', ')}`);
|
|
173
|
+
if (recommendation.typography.googleFontsUrl) {
|
|
174
|
+
lines.push(`- **Google Fonts**: ${recommendation.typography.googleFontsUrl}`);
|
|
175
|
+
}
|
|
176
|
+
lines.push('');
|
|
177
|
+
lines.push('## Key Effects');
|
|
178
|
+
lines.push(`- **Shadows**: ${recommendation.effects.shadows}`);
|
|
179
|
+
lines.push(`- **Transitions**: ${recommendation.effects.transitions}`);
|
|
180
|
+
lines.push(`- **Hover**: ${recommendation.effects.hover}`);
|
|
181
|
+
lines.push(`- **Animations**: ${recommendation.effects.animations}`);
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push('## Anti-Patterns (Avoid)');
|
|
184
|
+
recommendation.antiPatterns.forEach(pattern => {
|
|
185
|
+
lines.push(`- ${pattern}`);
|
|
186
|
+
});
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push('## Pre-Delivery Checklist');
|
|
189
|
+
recommendation.checklist.forEach(item => {
|
|
190
|
+
lines.push(`- [ ] ${item}`);
|
|
191
|
+
});
|
|
192
|
+
lines.push('');
|
|
193
|
+
return lines.join('\n');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25 搜索引擎实现
|
|
3
|
+
*
|
|
4
|
+
* BM25 是一种基于概率的信息检索算法,用于计算文档与查询的相关性得分
|
|
5
|
+
*/
|
|
6
|
+
export interface BM25Document {
|
|
7
|
+
id: string;
|
|
8
|
+
text: string;
|
|
9
|
+
metadata?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export interface BM25SearchResult {
|
|
12
|
+
id: string;
|
|
13
|
+
score: number;
|
|
14
|
+
metadata?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
export interface BM25Options {
|
|
17
|
+
k1?: number;
|
|
18
|
+
b?: number;
|
|
19
|
+
fields?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* BM25 搜索引擎
|
|
23
|
+
*/
|
|
24
|
+
export declare class BM25 {
|
|
25
|
+
private documents;
|
|
26
|
+
private invertedIndex;
|
|
27
|
+
private documentLengths;
|
|
28
|
+
private avgDocLength;
|
|
29
|
+
private k1;
|
|
30
|
+
private b;
|
|
31
|
+
constructor(options?: BM25Options);
|
|
32
|
+
/**
|
|
33
|
+
* 分词(简单实现)
|
|
34
|
+
*/
|
|
35
|
+
private tokenize;
|
|
36
|
+
/**
|
|
37
|
+
* 添加文档到索引
|
|
38
|
+
*/
|
|
39
|
+
addDocument(doc: BM25Document): void;
|
|
40
|
+
/**
|
|
41
|
+
* 批量添加文档
|
|
42
|
+
*/
|
|
43
|
+
addDocuments(docs: BM25Document[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* 计算 IDF (Inverse Document Frequency)
|
|
46
|
+
*/
|
|
47
|
+
private calculateIDF;
|
|
48
|
+
/**
|
|
49
|
+
* 计算 BM25 得分
|
|
50
|
+
*/
|
|
51
|
+
private calculateScore;
|
|
52
|
+
/**
|
|
53
|
+
* 搜索文档
|
|
54
|
+
*/
|
|
55
|
+
search(query: string, limit?: number): BM25SearchResult[];
|
|
56
|
+
/**
|
|
57
|
+
* 清空索引
|
|
58
|
+
*/
|
|
59
|
+
clear(): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25 搜索引擎实现
|
|
3
|
+
*
|
|
4
|
+
* BM25 是一种基于概率的信息检索算法,用于计算文档与查询的相关性得分
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* BM25 搜索引擎
|
|
8
|
+
*/
|
|
9
|
+
export class BM25 {
|
|
10
|
+
documents = [];
|
|
11
|
+
invertedIndex = new Map();
|
|
12
|
+
documentLengths = new Map();
|
|
13
|
+
avgDocLength = 0;
|
|
14
|
+
k1;
|
|
15
|
+
b;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.k1 = options.k1 ?? 1.5;
|
|
18
|
+
this.b = options.b ?? 0.75;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 分词(简单实现)
|
|
22
|
+
*/
|
|
23
|
+
tokenize(text) {
|
|
24
|
+
return text
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^\w\s\u4e00-\u9fa5]/g, ' ')
|
|
27
|
+
.split(/\s+/)
|
|
28
|
+
.filter(token => token.length > 0);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 添加文档到索引
|
|
32
|
+
*/
|
|
33
|
+
addDocument(doc) {
|
|
34
|
+
this.documents.push(doc);
|
|
35
|
+
const tokens = this.tokenize(doc.text);
|
|
36
|
+
const docLength = tokens.length;
|
|
37
|
+
this.documentLengths.set(doc.id, docLength);
|
|
38
|
+
// 构建倒排索引
|
|
39
|
+
const termFreq = new Map();
|
|
40
|
+
for (const token of tokens) {
|
|
41
|
+
termFreq.set(token, (termFreq.get(token) || 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
for (const [term, freq] of termFreq) {
|
|
44
|
+
if (!this.invertedIndex.has(term)) {
|
|
45
|
+
this.invertedIndex.set(term, new Map());
|
|
46
|
+
}
|
|
47
|
+
this.invertedIndex.get(term).set(doc.id, freq);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 批量添加文档
|
|
52
|
+
*/
|
|
53
|
+
addDocuments(docs) {
|
|
54
|
+
for (const doc of docs) {
|
|
55
|
+
this.addDocument(doc);
|
|
56
|
+
}
|
|
57
|
+
// 计算平均文档长度
|
|
58
|
+
let totalLength = 0;
|
|
59
|
+
for (const length of this.documentLengths.values()) {
|
|
60
|
+
totalLength += length;
|
|
61
|
+
}
|
|
62
|
+
this.avgDocLength = totalLength / this.documents.length;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 计算 IDF (Inverse Document Frequency)
|
|
66
|
+
*/
|
|
67
|
+
calculateIDF(term) {
|
|
68
|
+
const N = this.documents.length;
|
|
69
|
+
const df = this.invertedIndex.get(term)?.size || 0;
|
|
70
|
+
if (df === 0)
|
|
71
|
+
return 0;
|
|
72
|
+
return Math.log((N - df + 0.5) / (df + 0.5) + 1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 计算 BM25 得分
|
|
76
|
+
*/
|
|
77
|
+
calculateScore(docId, queryTerms) {
|
|
78
|
+
let score = 0;
|
|
79
|
+
const docLength = this.documentLengths.get(docId) || 0;
|
|
80
|
+
for (const term of queryTerms) {
|
|
81
|
+
const termDocs = this.invertedIndex.get(term);
|
|
82
|
+
if (!termDocs || !termDocs.has(docId)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const tf = termDocs.get(docId);
|
|
86
|
+
const idf = this.calculateIDF(term);
|
|
87
|
+
// BM25 公式
|
|
88
|
+
const numerator = tf * (this.k1 + 1);
|
|
89
|
+
const denominator = tf + this.k1 * (1 - this.b + this.b * (docLength / this.avgDocLength));
|
|
90
|
+
score += idf * (numerator / denominator);
|
|
91
|
+
}
|
|
92
|
+
return score;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 搜索文档
|
|
96
|
+
*/
|
|
97
|
+
search(query, limit = 10) {
|
|
98
|
+
const queryTerms = this.tokenize(query);
|
|
99
|
+
if (queryTerms.length === 0) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
// 获取所有相关文档
|
|
103
|
+
const relevantDocs = new Set();
|
|
104
|
+
for (const term of queryTerms) {
|
|
105
|
+
const termDocs = this.invertedIndex.get(term);
|
|
106
|
+
if (termDocs) {
|
|
107
|
+
for (const docId of termDocs.keys()) {
|
|
108
|
+
relevantDocs.add(docId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 计算得分
|
|
113
|
+
const results = [];
|
|
114
|
+
for (const docId of relevantDocs) {
|
|
115
|
+
const score = this.calculateScore(docId, queryTerms);
|
|
116
|
+
const doc = this.documents.find(d => d.id === docId);
|
|
117
|
+
if (doc && score > 0) {
|
|
118
|
+
results.push({
|
|
119
|
+
id: docId,
|
|
120
|
+
score,
|
|
121
|
+
metadata: doc.metadata,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 按得分排序并返回前 N 个
|
|
126
|
+
return results
|
|
127
|
+
.sort((a, b) => b.score - a.score)
|
|
128
|
+
.slice(0, limit);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 清空索引
|
|
132
|
+
*/
|
|
133
|
+
clear() {
|
|
134
|
+
this.documents = [];
|
|
135
|
+
this.invertedIndex.clear();
|
|
136
|
+
this.documentLengths.clear();
|
|
137
|
+
this.avgDocLength = 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存管理器
|
|
3
|
+
*
|
|
4
|
+
* 管理 UI/UX 数据的缓存,支持版本检查和自动更新
|
|
5
|
+
*/
|
|
6
|
+
export interface CacheMetadata {
|
|
7
|
+
version: string;
|
|
8
|
+
syncedAt: string;
|
|
9
|
+
source: string;
|
|
10
|
+
format: 'csv' | 'json';
|
|
11
|
+
}
|
|
12
|
+
export interface CacheOptions {
|
|
13
|
+
cacheDir?: string;
|
|
14
|
+
packageName?: string;
|
|
15
|
+
autoUpdate?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 缓存管理器
|
|
19
|
+
*/
|
|
20
|
+
export declare class CacheManager {
|
|
21
|
+
private cacheDir;
|
|
22
|
+
private packageName;
|
|
23
|
+
private autoUpdate;
|
|
24
|
+
constructor(options?: CacheOptions);
|
|
25
|
+
/**
|
|
26
|
+
* 确保缓存目录存在
|
|
27
|
+
*/
|
|
28
|
+
private ensureCacheDir;
|
|
29
|
+
/**
|
|
30
|
+
* 获取缓存元数据
|
|
31
|
+
*/
|
|
32
|
+
getMetadata(): CacheMetadata | null;
|
|
33
|
+
/**
|
|
34
|
+
* 检查缓存是否存在
|
|
35
|
+
*/
|
|
36
|
+
hasCache(): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* 获取 npm 包的最新版本
|
|
39
|
+
*/
|
|
40
|
+
getLatestVersion(): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* 检查是否有更新
|
|
43
|
+
*/
|
|
44
|
+
checkUpdate(): Promise<{
|
|
45
|
+
hasUpdate: boolean;
|
|
46
|
+
currentVersion?: string;
|
|
47
|
+
latestVersion: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* 读取缓存文件
|
|
51
|
+
*/
|
|
52
|
+
readFile(filename: string): any;
|
|
53
|
+
/**
|
|
54
|
+
* 列出所有缓存文件
|
|
55
|
+
*/
|
|
56
|
+
listFiles(): string[];
|
|
57
|
+
/**
|
|
58
|
+
* 清空缓存
|
|
59
|
+
*/
|
|
60
|
+
clear(): void;
|
|
61
|
+
/**
|
|
62
|
+
* 获取缓存目录路径
|
|
63
|
+
*/
|
|
64
|
+
getCacheDir(): string;
|
|
65
|
+
}
|