88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import type {
|
|
StoredParagraph,
|
|
StoredSentence,
|
|
} from "../infrastructure/storage/lectureStore";
|
|
|
|
export function normalizeSelectionText(text: string): string {
|
|
return text
|
|
.replace(/[\u0000-\u001f\f]/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim()
|
|
.toLowerCase();
|
|
}
|
|
|
|
export interface PreparedSentenceMatch {
|
|
paragraph: StoredParagraph;
|
|
sentence: StoredSentence;
|
|
}
|
|
|
|
function overlapScore(a: string, b: string): number {
|
|
const na = normalizeSelectionText(a);
|
|
const nb = normalizeSelectionText(b);
|
|
if (!na || !nb) return 0;
|
|
if (na === nb) return 100;
|
|
if (na.includes(nb) || nb.includes(na)) {
|
|
const shorter = Math.min(na.length, nb.length);
|
|
const longer = Math.max(na.length, nb.length);
|
|
return Math.round((shorter / longer) * 90);
|
|
}
|
|
|
|
const aWords = new Set(na.split(/\s+/).filter((w) => w.length > 2));
|
|
const bWords = nb.split(/\s+/).filter((w) => w.length > 2);
|
|
if (!aWords.size || !bWords.length) return 0;
|
|
let shared = 0;
|
|
for (const w of bWords) {
|
|
if (aWords.has(w)) shared += 1;
|
|
}
|
|
return Math.round((shared / bWords.length) * 70);
|
|
}
|
|
|
|
export function findMatchingPreparedSentence(
|
|
paragraphs: StoredParagraph[] | undefined,
|
|
selectedText: string,
|
|
options?: { requireTts?: boolean },
|
|
): PreparedSentenceMatch | undefined {
|
|
if (!paragraphs?.length || !selectedText.trim()) return undefined;
|
|
|
|
const requireTts = options?.requireTts !== false;
|
|
let best: PreparedSentenceMatch | undefined;
|
|
let bestScore = 0;
|
|
|
|
for (const paragraph of paragraphs) {
|
|
if (paragraph.genStatus !== "ready") continue;
|
|
|
|
for (const sentence of paragraph.sentences ?? []) {
|
|
if (requireTts && sentence.ttsZhStatus !== "ready") continue;
|
|
const score = overlapScore(selectedText, sentence.text);
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
best = { paragraph, sentence };
|
|
}
|
|
}
|
|
|
|
if (!paragraph.sentences?.length) {
|
|
const score = overlapScore(selectedText, paragraph.originalText);
|
|
if (score > bestScore && (!requireTts || paragraph.ttsZhStatus === "ready")) {
|
|
bestScore = score;
|
|
best = {
|
|
paragraph,
|
|
sentence: {
|
|
id: `${paragraph.id}-whole`,
|
|
orderIndex: 0,
|
|
text: paragraph.originalText,
|
|
translation: paragraph.translation,
|
|
page: paragraph.page,
|
|
searchText: paragraph.searchText,
|
|
audioPathEn: paragraph.audioPathEn,
|
|
audioPathZh: paragraph.audioPathZh,
|
|
ttsEnStatus: paragraph.ttsEnStatus,
|
|
ttsZhStatus: paragraph.ttsZhStatus,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestScore >= 55 ? best : undefined;
|
|
}
|