From 99a8a355c0e90d68d93f61d75e3069b5a4dbf4f8 Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Tue, 22 Sep 2026 01:13:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(engine):=20=E4=BA=91/AI=20=E8=81=94?= =?UTF-8?q?=E6=83=B3=E5=80=99=E9=80=89=E4=B8=8A=E5=B1=8F=E5=90=8E=E6=8C=89?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=88=86=E8=AF=8D=E8=90=BD=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 选中云或 AI 联想候选上屏后,再次输入同样的拼音却不出现该词。根因有二: - create_word_from_canonical_pinyin 已按 canonical 键里的撇号校验过分段,却又调用 do_validate 二次校验;do_validate 会先去掉撇号、再用贪心 correction 重新断句,把 qi'e'huan 切成 qie'huan(音节数 3→2),与字数不符而静默拒绝入库。移除这次多余且 有害的二次校验,分段以调用方给定的 canonical 键为准(do_validate 本身保持不变, 造词/调频/删除三条旧路径不受影响)。 - 上屏时 AI/云候选自身不带 canonical_pinyin,落库用的是去掉音节边界的 committed_pinyin,同样会被重新贪心断句。改用会话里带撇号的 normalized_segmentation 作 canonical 键,经 create_word_from_canonical_pinyin 落库。 --- engine/quanpin/quanpin_dictionary.cpp | 8 ++--- server/src/ipc/event_listener.cpp | 47 +++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/engine/quanpin/quanpin_dictionary.cpp b/engine/quanpin/quanpin_dictionary.cpp index 4d35b572..15bae78c 100644 --- a/engine/quanpin/quanpin_dictionary.cpp +++ b/engine/quanpin/quanpin_dictionary.cpp @@ -915,10 +915,10 @@ int QuanpinDictionary::create_word_from_canonical_pinyin(std::string pinyin, std pinyin = quanpin::join_segments(segments); const std::string jp = quanpin::segments_to_jianpin(segments); - if (!do_validate(pinyin, jp, word)) - { - return ERROR_CODE; - } + // 不再走 do_validate:这里的音节边界由调用方(canonical 键)显式给定,上面已按 + // split_segments 校验过段数、逐段完整性,jp 也是每段一字母、长度必然等于字数。 + // do_validate 会先 remove_delimiters 再用贪心 correction 重切,把 qi'e'huan 断成 + // qie'huan(段数 3→2),从而误杀这条合法整句——正是 AI / 云联想选中却入不了库的根因。 if (check_data(build_sql_for_checking_word(pinyin, word))) { return OK; diff --git a/server/src/ipc/event_listener.cpp b/server/src/ipc/event_listener.cpp index 9a19c634..fb170bf1 100644 --- a/server/src/ipc/event_listener.cpp +++ b/server/src/ipc/event_listener.cpp @@ -4798,20 +4798,55 @@ void ProcessSelectionKey(UINT keycode, uint64_t client_id, uint64_t activation_e return; } std::string cloudCommittedPinyin; + bool cloudCommittedPinyinIsCanonical = false; std::string aiCommittedPinyin; bool aiCommittedPinyinIsCanonical = false; if (curWordItem.source == CandidateSource::CloudSuggestion) { - cloudCommittedPinyin = g_inputSession->get_cloud_query_state().committed_pinyin; + if (!curWordItem.canonical_pinyin.empty()) + { + cloudCommittedPinyin = curWordItem.canonical_pinyin; + cloudCommittedPinyinIsCanonical = true; + } + else if (g_inputSession->is_all_complete_pure_pinyin()) + { + // 与 AI 联想同一个坑:committed_pinyin 走的是 normalized_input,丢掉了音节 + // 边界,create_word 会用贪心的 "correction" 切分重新断句,qi'e'huan 落成 + // qie'huan,音节数与字数对不上、do_validate 静默失败而不入库。整串是完整 + // 拼音时改用带撇号的 normalized_segmentation 作 canonical 键,按用户实际断句 + // 落库。简拼 / 带 helpcode 等非完整拼音的云候选仍走原来的 committed_pinyin + // 路径(见下面的 else),避免回归。 + cloudCommittedPinyin = g_inputSession->get_pinyin_segmentation(); + cloudCommittedPinyinIsCanonical = true; + } + else + { + cloudCommittedPinyin = g_inputSession->get_cloud_query_state().committed_pinyin; + } } if (curWordItem.source == CandidateSource::AiSuggestion) { if (g_inputSession->is_all_complete_pure_pinyin()) { - aiCommittedPinyin = curWordItem.canonical_pinyin.empty() - ? g_inputSession->get_cloud_query_state().committed_pinyin - : curWordItem.canonical_pinyin; - aiCommittedPinyinIsCanonical = !curWordItem.canonical_pinyin.empty(); + if (!curWordItem.canonical_pinyin.empty()) + { + aiCommittedPinyin = curWordItem.canonical_pinyin; + aiCommittedPinyinIsCanonical = true; + } + else + { + // AI 候选自己不带 canonical_pinyin。committed_pinyin 走的是 + // normalized_input,那串已经去掉了音节边界(见 quanpin_scheme 里 + // 只往 normalized_input 里塞非撇号字符),create_word 会用贪心的 + // "correction" 切分重新断句:qi'e'huan 落成 qie'huan,音节数与字数 + // 对不上,do_validate 直接判失败、静默不入库,于是用户再打同样的音 + // 时这条 AI 联想仍然要靠现场猜。这里改用带撇号的 normalized_segmentation + // (即屏幕上的分段,is_all_complete_pure_pinyin 已保证它整串是完整音节) + // 作 canonical 键,create_word_from_canonical_pinyin 按撇号 split、不再 + // 重新贪心断句,用户实际选的那条读音就能正确落库。 + aiCommittedPinyin = g_inputSession->get_pinyin_segmentation(); + aiCommittedPinyinIsCanonical = true; + } } isNeedUpdateWeight = false; } @@ -4893,7 +4928,7 @@ void ProcessSelectionKey(UINT keycode, uint64_t client_id, uint64_t activation_e // 看看云联想出来的词是否需要被插入到数据库 if (curWordItem.source == CandidateSource::CloudSuggestion && !cloudCommittedPinyin.empty()) { - EnqueueStoreUserPhraseTask(cloudCommittedPinyin, curWord); + EnqueueStoreUserPhraseTask(cloudCommittedPinyin, curWord, cloudCommittedPinyinIsCanonical); // 清理云联想变量状态 Global::cloud_candidate.added = false; Global::cloud_candidate.word.clear(); From 837af70b7ef007c785256f62e18ca64ca1bab5cd Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Tue, 22 Sep 2026 01:13:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(engine):=20=E5=85=A8=E6=8B=BC=E4=BA=91?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=B8=A6=E4=B8=8A=E6=89=8B=E5=8A=A8=E5=88=86?= =?UTF-8?q?=E8=AF=8D=E8=80=8C=E9=9D=9E=E5=8E=BB=E8=BE=B9=E7=95=8C=E7=9A=84?= =?UTF-8?q?=E8=A3=B8=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 手动把 qi'e'huan 分成三段,云端却返回二段的「切换」。因为全拼云查询的 query_text 取自 normalized_input(已去掉撇号的裸串),云端拿不到音节边界只能自行贪心断句。改用 保留手动撇号的 normalized_segmentation 构造 query_text,与双拼分支一致;也让 to_google_spelling 能逐音节把 nve/lve 换成云端认的 nue/lue 写法。 --- engine/core/input_session_composition.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/engine/core/input_session_composition.cpp b/engine/core/input_session_composition.cpp index efd5d9fe..3aa63c22 100644 --- a/engine/core/input_session_composition.cpp +++ b/engine/core/input_session_composition.cpp @@ -721,8 +721,13 @@ InputSession::CloudQueryState InputSession::get_cloud_query_state() const } state.should_query = !request().normalized_input.empty(); - // 全拼敲出来的 nve / lve 同样要换写法;没有音节边界的裸串匹配不上,原样透传。 - state.query_text = quanpin::to_google_spelling(request().normalized_input); + // 云端要按用户看到的分词来查:手动把 qi'e'huan 分成三段,就该带着音节边界发给云接口, + // 否则云端自行贪心断句会当成 qie'huan(切换)。normalized_segmentation 保留了用户的手动 + // 撇号(cut_pinyin_with_corrections 先按撇号切分),既定住了分词,也让 to_google_spelling + // 能逐音节把 nve / lve 换成云端认的 nue / lue 写法。分词缺失时退回裸串。 + const std::string &segmentation = + request().normalized_segmentation.empty() ? request().normalized_input : request().normalized_segmentation; + state.query_text = quanpin::to_google_spelling(segmentation); return state; }