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