关键改动: - 增加问翻小宝会话历史记录规则与测试 - 优化帮助搜索、兜底建议和页面交互结构 - 更新帮助截图资产和截图采集脚本 Co-Authored-By: Codex GPT-5 <codex@openai.com>
117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
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;
|
||
}
|