From 3ab730baae2d00e05cf54c22aebc20e1ff02c66c Mon Sep 17 00:00:00 2001 From: digua Date: Sun, 15 Mar 2026 15:13:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dnlp=E5=81=9C=E7=94=A8?= =?UTF-8?q?=E8=AF=8D=E8=B0=83=E7=94=A8=E9=A1=BA=E5=BA=8F=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/nlp/segmenter.ts | 5 +- electron/main/nlp/stopwords.ts | 251 ++++++++++++++++++++++++++---- electron/main/worker/query/nlp.ts | 3 +- 3 files changed, 228 insertions(+), 31 deletions(-) diff --git a/electron/main/nlp/segmenter.ts b/electron/main/nlp/segmenter.ts index 0b30185..80d7498 100644 --- a/electron/main/nlp/segmenter.ts +++ b/electron/main/nlp/segmenter.ts @@ -166,8 +166,9 @@ export function collectPosTagStats( const tagged = jieba.tag(cleaned) for (const item of tagged) { - // 检查词是否有效(长度和停用词过滤) - if (!isValidWord(item.word, minWordLength, 'zh-CN', enableStopwords)) { + // 这里固定按中文停用词规则校验,但参数顺序必须与 isValidWord 保持一致, + // 否则会把最小词长误传成 locale,最终触发 locale.startsWith 的运行时错误。 + if (!isValidWord(item.word, 'zh-CN', minWordLength, enableStopwords)) { continue } posStats.set(item.tag, (posStats.get(item.tag) || 0) + 1) diff --git a/electron/main/nlp/stopwords.ts b/electron/main/nlp/stopwords.ts index c5cf416..188e0a5 100644 --- a/electron/main/nlp/stopwords.ts +++ b/electron/main/nlp/stopwords.ts @@ -1,3 +1,5 @@ +import type { SupportedLocale } from './types' + /** * 停用词表 * 用于过滤无意义的高频词 @@ -549,38 +551,208 @@ export const ENGLISH_STOPWORDS = new Set([ /** 日语停用词 */ export const JAPANESE_STOPWORDS = new Set([ // 助詞 - 'の', 'に', 'は', 'を', 'た', 'が', 'で', 'て', 'と', 'し', 'れ', 'さ', - 'ある', 'いる', 'も', 'する', 'から', 'な', 'こと', 'として', 'い', 'や', - 'れる', 'など', 'なっ', 'ない', 'この', 'ため', 'その', 'あっ', 'よう', - 'また', 'もの', 'という', 'あり', 'まで', 'られ', 'なる', 'へ', 'か', - 'だ', 'これ', 'によって', 'により', 'おり', 'より', 'による', 'ず', 'なり', - 'られる', 'において', 'ば', 'なかっ', 'なく', 'しかし', 'について', 'せ', - 'だっ', 'その後', 'できる', 'それ', 'う', 'ので', 'なお', 'のみ', 'でき', - 'き', 'つ', 'における', 'および', 'いう', 'さらに', 'でも', 'ら', 'たり', - 'その他', 'に関する', 'たち', 'ます', 'ん', 'なら', 'に対して', + 'の', + 'に', + 'は', + 'を', + 'た', + 'が', + 'で', + 'て', + 'と', + 'し', + 'れ', + 'さ', + 'ある', + 'いる', + 'も', + 'する', + 'から', + 'な', + 'こと', + 'として', + 'い', + 'や', + 'れる', + 'など', + 'なっ', + 'ない', + 'この', + 'ため', + 'その', + 'あっ', + 'よう', + 'また', + 'もの', + 'という', + 'あり', + 'まで', + 'られ', + 'なる', + 'へ', + 'か', + 'だ', + 'これ', + 'によって', + 'により', + 'おり', + 'より', + 'による', + 'ず', + 'なり', + 'られる', + 'において', + 'ば', + 'なかっ', + 'なく', + 'しかし', + 'について', + 'せ', + 'だっ', + 'その後', + 'できる', + 'それ', + 'う', + 'ので', + 'なお', + 'のみ', + 'でき', + 'き', + 'つ', + 'における', + 'および', + 'いう', + 'さらに', + 'でも', + 'ら', + 'たり', + 'その他', + 'に関する', + 'たち', + 'ます', + 'ん', + 'なら', + 'に対して', // 代名詞 - '私', '僕', '俺', '自分', 'あなた', '彼', '彼女', 'それ', 'これ', 'あれ', - 'ここ', 'そこ', 'あそこ', 'どこ', 'みんな', '皆', + '私', + '僕', + '俺', + '自分', + 'あなた', + '彼', + '彼女', + 'それ', + 'これ', + 'あれ', + 'ここ', + 'そこ', + 'あそこ', + 'どこ', + 'みんな', + '皆', // 接続詞 - 'そして', 'しかし', 'でも', 'だから', 'それで', 'だけど', 'けど', - 'ところで', 'さて', 'つまり', 'すなわち', 'ただし', 'もし', 'また', + 'そして', + 'しかし', + 'でも', + 'だから', + 'それで', + 'だけど', + 'けど', + 'ところで', + 'さて', + 'つまり', + 'すなわち', + 'ただし', + 'もし', + 'また', // 副詞 - 'とても', 'すごく', 'もう', 'まだ', 'よく', 'ちょっと', 'ちょと', 'もっと', - 'やっぱり', 'やはり', 'たぶん', 'きっと', 'ぜんぜん', 'ほんとに', 'ほんと', - 'かなり', 'だいたい', 'ほとんど', 'まあ', 'なんか', 'なんとなく', + 'とても', + 'すごく', + 'もう', + 'まだ', + 'よく', + 'ちょっと', + 'ちょと', + 'もっと', + 'やっぱり', + 'やはり', + 'たぶん', + 'きっと', + 'ぜんぜん', + 'ほんとに', + 'ほんと', + 'かなり', + 'だいたい', + 'ほとんど', + 'まあ', + 'なんか', + 'なんとなく', // 感動詞・フィラー - 'あ', 'ああ', 'えー', 'うん', 'えっ', 'おお', 'へー', 'ふーん', 'はい', - 'いいえ', 'うーん', 'まあ', 'ねえ', 'ほら', 'あのね', 'えっと', 'その', + 'あ', + 'ああ', + 'えー', + 'うん', + 'えっ', + 'おお', + 'へー', + 'ふーん', + 'はい', + 'いいえ', + 'うーん', + 'まあ', + 'ねえ', + 'ほら', + 'あのね', + 'えっと', + 'その', // 動詞(高頻度) - 'いる', 'ある', 'する', 'なる', 'できる', 'いく', 'くる', 'みる', 'おもう', - 'いう', 'やる', 'くれる', 'もらう', 'あげる', 'しまう', 'おく', + 'いる', + 'ある', + 'する', + 'なる', + 'できる', + 'いく', + 'くる', + 'みる', + 'おもう', + 'いう', + 'やる', + 'くれる', + 'もらう', + 'あげる', + 'しまう', + 'おく', // 形容詞(高頻度) - 'いい', 'ない', 'よい', 'すごい', 'おおきい', 'ちいさい', + 'いい', + 'ない', + 'よい', + 'すごい', + 'おおきい', + 'ちいさい', // 助動詞 - 'です', 'ます', 'でした', 'ました', 'ません', 'だった', 'でしょう', + 'です', + 'ます', + 'でした', + 'ました', + 'ません', + 'だった', + 'でしょう', // チャットでの高頻語 - 'www', 'ww', 'lol', 'ok', 'おけ', 'りょ', 'おつ', 'わら', '笑', - 'それな', 'たしかに', 'マジ', 'まじ', 'ガチ', 'がち', + 'www', + 'ww', + 'lol', + 'ok', + 'おけ', + 'りょ', + 'おつ', + 'わら', + '笑', + 'それな', + 'たしかに', + 'マジ', + 'まじ', + 'ガチ', + 'がち', ]) /** @@ -589,10 +761,12 @@ export const JAPANESE_STOPWORDS = new Set([ * @returns 停用词集合 */ export function getStopwords(locale: string): Set { - if (locale.startsWith('zh')) { + const normalizedLocale = normalizeStopwordLocale(locale) + + if (normalizedLocale.startsWith('zh')) { return CHINESE_STOPWORDS } - if (locale === 'ja-JP') { + if (normalizedLocale === 'ja-JP') { return JAPANESE_STOPWORDS } return ENGLISH_STOPWORDS @@ -605,7 +779,28 @@ export function getStopwords(locale: string): Set { * @returns 是否为停用词 */ export function isStopword(word: string, locale: string): boolean { - const stopwords = getStopwords(locale) - const normalizedWord = locale === 'en-US' ? word.toLowerCase() : word + const normalizedLocale = normalizeStopwordLocale(locale) + const stopwords = getStopwords(normalizedLocale) + const normalizedWord = normalizedLocale === 'en-US' ? word.toLowerCase() : word return stopwords.has(normalizedWord) } + +/** + * 规范化停用词处理使用的 locale。 + * 这里额外兜底一次,避免上游误传数字或其他异常值时直接导致运行时崩溃。 + */ +function normalizeStopwordLocale(locale: string): SupportedLocale { + if (typeof locale !== 'string') { + return 'en-US' + } + + if (locale.startsWith('zh')) { + return 'zh-CN' + } + + if (locale === 'ja-JP') { + return 'ja-JP' + } + + return 'en-US' +} diff --git a/electron/main/worker/query/nlp.ts b/electron/main/worker/query/nlp.ts index 183d211..2c016a8 100644 --- a/electron/main/worker/query/nlp.ts +++ b/electron/main/worker/query/nlp.ts @@ -79,7 +79,8 @@ export function getWordFrequency(params: WordFrequencyParams): WordFrequencyResu // 收集词性统计(用于显示每个词性有多少词,仅中文有效) let posTagStats: PosTagStat[] | undefined - if ((locale as string).startsWith('zh')) { + // 词性统计只对中文生效,这里先做类型兜底,避免异常 locale 直接触发 startsWith 报错。 + if (typeof locale === 'string' && locale.startsWith('zh')) { const posStatsMap = collectPosTagStats(texts, minWordLength ?? 2, enableStopwords) posTagStats = [...posStatsMap.entries()].map(([tag, count]) => ({ tag, count })) }