import { WENFAN_HELP_ARTICLES, type HelpArticle } from './wenfan-help-articles'; export type HelpSearchResult = { article: HelpArticle; score: number; matchedKeywords: string[]; relatedArticles: HelpArticle[]; }; const MIN_RESULT_SCORE = 15; const FALLBACK_SUGGESTIONS = [ '怎么新建产品?', '怎么把需求纳入版本?', '开发任务怎么提测?', '测试用例失败后怎么提 Bug?', 'AI 拆解在哪里使用?', '日志记录会记录哪些行为?', ]; export function normalizeHelpQuery(input: string): string { return input .toLowerCase() .replace(/[,。!?、,.!?;;::"'“”‘’()()【】[\]{}<>《》\s]/g, '') .trim(); } function isSubsequence(needle: string, haystack: string): boolean { if (needle.length < 4) return false; let index = 0; for (const char of haystack) { if (char === needle[index]) index += 1; if (index === needle.length) return true; } return false; } function scoreText(query: string, text: string, exactScore: number, fuzzyScore: number): number { const normalized = normalizeHelpQuery(text); if (!normalized) return 0; if (normalized.includes(query) || query.includes(normalized)) return exactScore; if (isSubsequence(normalized, query) || isSubsequence(query, normalized)) return fuzzyScore; return 0; } function resolveRelatedArticles(article: HelpArticle, articles: HelpArticle[]): HelpArticle[] { return article.relatedIds .map((id) => articles.find((item) => item.id === id)) .filter((item): item is HelpArticle => Boolean(item)); } export function searchHelpArticles( query: string, articles: HelpArticle[] = WENFAN_HELP_ARTICLES, ): HelpSearchResult[] { const normalizedQuery = normalizeHelpQuery(query); if (normalizedQuery.length < 2) return []; return articles .map((article) => { let score = 0; const matchedKeywords: string[] = []; score += scoreText(normalizedQuery, article.title, 20, 10); score += scoreText(normalizedQuery, article.category, 12, 0); for (const keyword of article.keywords) { const keywordScore = scoreText(normalizedQuery, keyword, 10, 6); if (keywordScore > 0) { score += keywordScore; matchedKeywords.push(keyword); } } const secondaryTexts = [ article.scenario, article.entry, ...article.steps, ...article.requiredFields, ...article.statusFlow, ...article.notes, ]; for (const text of secondaryTexts) { score += scoreText(normalizedQuery, text, 3, 1); } return { article, score, matchedKeywords, relatedArticles: resolveRelatedArticles(article, articles), }; }) .filter((result) => result.score >= MIN_RESULT_SCORE) .sort((a, b) => { if (b.score !== a.score) return b.score - a.score; return a.article.title.localeCompare(b.article.title, 'zh-CN'); }); } export function getFallbackHelpSuggestions(): string[] { return FALLBACK_SUGGESTIONS; } export function getFallbackHelpMessage(showGenericSuggestions: boolean): string { if (showGenericSuggestions) { return '暂时没有找到对应的内置帮助内容。你可以换个关键词,或者从下面这些常见问题开始。'; } return '暂时没有找到对应的内置帮助内容。你可以换个更具体的系统关键词,或继续描述你要操作的模块。'; } export function shouldShowGenericHelpSuggestions(userQuestionCount: number): boolean { return userQuestionCount < 2; }