Files
ftb-project-management/apps/web/lib/wenfan-help-search.ts
Script Generator e5ce2d221e feat(问翻小宝): 增强帮助搜索与会话历史
关键改动:

- 增加问翻小宝会话历史记录规则与测试

- 优化帮助搜索、兜底建议和页面交互结构

- 更新帮助截图资产和截图采集脚本

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-01 09:20:10 +08:00

117 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}