feat(server): 整句联想来源开关接入配置与设置页

新增 [association] 段的五个开关(词格、Google、神经 desktop / keyboard、去重补位),
同步写入出厂模板、工作配置和两个设置窗口宿主的读写映射,设置页输入分区加对应选项。
This commit is contained in:
fanlusky committed 2026-09-24 14:03:43 +08:00
1 parent e7f7addd4f
commit 16877cb2d0
16 files changed
+402 -6

No files matched your search

@@ -54,6 +54,23 @@ cloud_candidates = true
# TODO: 极简模式。极简模式下,候选窗口使用 d2d 绘制,界面只作展示候选项,无其他功能。
clean_mode = false
#
# 整句联想:多音节输入时把整串拼音解码成一句,四个来源各一个开关,另有去重补位开关,默认全关,
# 打开后才会在候选里出现对应整句。词格/Google 是传统 ngram 来源;神经两档是
# chinese-ime-lm 的 Transformer,还需模型文件在数据目录里,desktop 更准也更慢。
#
[association]
# 词格整句联想(kenlm 三元模型打分)
sentence_wordlattice = false
# Google 解码器整句联想
sentence_google = false
# 神经网络模型整句(效果优先 / desktop):内部生成最多 12 条 Trigram 备选并挑选,不依赖 sentence_wordlattice 开关
sentence_neural_desktop = false
# 神经网络模型整句(速度优先 / keyboard):同上;与 desktop 同时开启时各自独立给出结果
sentence_neural_keyboard = false
# 某个模型的首选与已有候选重复时,改为显示该模型排名最高的下一条不同结果
sentence_show_next_on_duplicate = false
#
# 腾讯云机器翻译(竖排候选项本地词库未命中时的补全)
#
+17
View File
@@ -54,6 +54,23 @@ cloud_candidates = true
# TODO: 极简模式。极简模式下,候选窗口使用 d2d 绘制,界面只作展示候选项,无其他功能。
clean_mode = false
#
# 整句联想:多音节输入时把整串拼音解码成一句,四个来源各一个开关,另有去重补位开关,默认全关,
# 打开后才会在候选里出现对应整句。词格/Google 是传统 ngram 来源;神经两档是
# chinese-ime-lm 的 Transformer,还需模型文件在数据目录里,desktop 更准也更慢。
#
[association]
# 词格整句联想(kenlm 三元模型打分)
sentence_wordlattice = false
# Google 解码器整句联想
sentence_google = false
# 神经网络模型整句(效果优先 / desktop):内部生成最多 12 条 Trigram 备选并挑选,不依赖 sentence_wordlattice 开关
sentence_neural_desktop = false
# 神经网络模型整句(速度优先 / keyboard):同上;与 desktop 同时开启时各自独立给出结果
sentence_neural_keyboard = false
# 某个模型的首选与已有候选重复时,改为显示该模型排名最高的下一条不同结果
sentence_show_next_on_duplicate = false
#
# 腾讯云机器翻译(候选项本地词库未命中时的补全)
#
+82
View File
@@ -122,6 +122,12 @@ bool g_english_candidates_enabled = false;
bool g_candidate_translations_enabled = true;
int g_english_mixed_input_min_chars = kEnglishMixedInputMinCharsDefault;
bool g_cloud_candidates_enabled = true;
// 整句候选来源与去重补位开关,默认全关。
bool g_assoc_sentence_wordlattice = false;
bool g_assoc_sentence_google = false;
bool g_assoc_sentence_neural_desktop = false;
bool g_assoc_sentence_neural_keyboard = false;
bool g_assoc_sentence_show_next_on_duplicate = false;
bool g_emoji_mixed_input_enabled = false;
bool g_kaomoji_mixed_input_enabled = false;
bool g_unicode_mode_enabled = true;
@@ -1057,6 +1063,11 @@ bool LoadImeConfig()
: kEnglishMixedInputMinCharsDefault;
}
g_cloud_candidates_enabled = tbl["general"]["cloud_candidates"].value_or(true);
g_assoc_sentence_wordlattice = tbl["association"]["sentence_wordlattice"].value_or(false);
g_assoc_sentence_google = tbl["association"]["sentence_google"].value_or(false);
g_assoc_sentence_neural_desktop = tbl["association"]["sentence_neural_desktop"].value_or(false);
g_assoc_sentence_neural_keyboard = tbl["association"]["sentence_neural_keyboard"].value_or(false);
g_assoc_sentence_show_next_on_duplicate = tbl["association"]["sentence_show_next_on_duplicate"].value_or(false);
g_emoji_mixed_input_enabled = tbl["general"]["emoji_mixed_input"].value_or(false);
g_kaomoji_mixed_input_enabled = tbl["general"]["kaomoji_mixed_input"].value_or(false);
g_unicode_mode_enabled = tbl["utility"]["unicode_mode"].value_or(true);
@@ -3387,6 +3398,77 @@ bool SetConfiguredCloudCandidatesEnabled(bool enabled)
return true;
}
// 整句候选来源与去重补位开关的 getter / setter。写入 [association] 段,值域为布尔。
bool GetConfiguredAssocSentenceWordLattice()
{
return g_assoc_sentence_wordlattice;
}
bool SetConfiguredAssocSentenceWordLattice(bool enabled)
{
if (!WriteConfiguredValue("association", "sentence_wordlattice", enabled ? "true" : "false"))
{
return false;
}
g_assoc_sentence_wordlattice = enabled;
return true;
}
bool GetConfiguredAssocSentenceGoogle()
{
return g_assoc_sentence_google;
}
bool SetConfiguredAssocSentenceGoogle(bool enabled)
{
if (!WriteConfiguredValue("association", "sentence_google", enabled ? "true" : "false"))
{
return false;
}
g_assoc_sentence_google = enabled;
return true;
}
bool GetConfiguredAssocSentenceNeuralDesktop()
{
return g_assoc_sentence_neural_desktop;
}
bool SetConfiguredAssocSentenceNeuralDesktop(bool enabled)
{
if (!WriteConfiguredValue("association", "sentence_neural_desktop", enabled ? "true" : "false"))
{
return false;
}
g_assoc_sentence_neural_desktop = enabled;
return true;
}
bool GetConfiguredAssocSentenceNeuralKeyboard()
{
return g_assoc_sentence_neural_keyboard;
}
bool SetConfiguredAssocSentenceNeuralKeyboard(bool enabled)
{
if (!WriteConfiguredValue("association", "sentence_neural_keyboard", enabled ? "true" : "false"))
{
return false;
}
g_assoc_sentence_neural_keyboard = enabled;
return true;
}
bool GetConfiguredAssocSentenceShowNextOnDuplicate()
{
return g_assoc_sentence_show_next_on_duplicate;
}
bool SetConfiguredAssocSentenceShowNextOnDuplicate(bool enabled)
{
if (!WriteConfiguredValue("association", "sentence_show_next_on_duplicate", enabled ? "true" : "false"))
{
return false;
}
g_assoc_sentence_show_next_on_duplicate = enabled;
return true;
}
bool GetConfiguredUnicodeModeEnabled()
{
return g_unicode_mode_enabled;
+11
View File
@@ -277,6 +277,17 @@ bool GetConfiguredKaomojiMixedInputEnabled();
bool SetConfiguredKaomojiMixedInputEnabled(bool enabled);
bool GetConfiguredCloudCandidatesEnabled();
bool SetConfiguredCloudCandidatesEnabled(bool enabled);
// 整句候选来源与去重补位开关([association] 段),默认全关。
bool GetConfiguredAssocSentenceWordLattice();
bool SetConfiguredAssocSentenceWordLattice(bool enabled);
bool GetConfiguredAssocSentenceGoogle();
bool SetConfiguredAssocSentenceGoogle(bool enabled);
bool GetConfiguredAssocSentenceNeuralDesktop();
bool SetConfiguredAssocSentenceNeuralDesktop(bool enabled);
bool GetConfiguredAssocSentenceNeuralKeyboard();
bool SetConfiguredAssocSentenceNeuralKeyboard(bool enabled);
bool GetConfiguredAssocSentenceShowNextOnDuplicate();
bool SetConfiguredAssocSentenceShowNextOnDuplicate(bool enabled);
bool GetConfiguredUnicodeModeEnabled();
bool SetConfiguredUnicodeModeEnabled(bool enabled);
bool GetConfiguredQuickPhraseEnabled();
+17 -6
View File
@@ -31,14 +31,21 @@ inline size_t CountCanonicalSyllables(const std::string &canonical_pinyin) noexc
return 1 + static_cast<size_t>(std::count(canonical_pinyin.begin(), canonical_pinyin.end(), '\''));
}
// 整句候选(词格 Generated / Google 解码器 Fallback)独立上屏——不接在造词前缀
// 后面、自己就是整条输入——时是否该落库。该落:整句是猜出来的,词库里没有它那一行,
// 调频改的是已有的行,改不到它。不落库的话用户选多少次,下次它仍然要靠猜,也仍然
// 排在词库里那条同音短语后面。
// 猜出来的整句来源:词格 Generated、Google 解码器 Fallback,以及神经整句
// NeuralDesktop / NeuralKeyboard。它们都不是词库里已有的行,落库/学习判定同类处理。
constexpr bool IsGuessedSentenceSource(CandidateSource source) noexcept
{
return source == CandidateSource::Generated || source == CandidateSource::Fallback ||
source == CandidateSource::NeuralDesktop || source == CandidateSource::NeuralKeyboard;
}
// 整句候选独立上屏——不接在造词前缀后面、自己就是整条输入——时是否该落库。该落:整句是
// 猜出来的,词库里没有它那一行,调频改的是已有的行,改不到它。不落库的话用户选多少次,
// 下次它仍然要靠猜,也仍然排在词库里那条同音短语后面。
inline bool ShouldStoreStandaloneSentence(CandidateSource source,
const std::string &candidate_canonical_pinyin) noexcept
{
if (source != CandidateSource::Generated && source != CandidateSource::Fallback)
if (!IsGuessedSentenceSource(source))
{
return false;
}
@@ -57,7 +64,11 @@ inline bool ShouldStoreEarlyReturnPhrase(CandidateSource source, bool creating_w
const std::string &prefix_canonical_pinyin,
const std::string &candidate_canonical_pinyin) noexcept
{
if (source != CandidateSource::Generated || candidate_canonical_pinyin.empty())
// 与词格 Generated 同一支:神经整句同样带 canonical quanpin,可以结束一段造词或独立
// 上屏。Google Fallback 沿用旧语义仍不走这条造词落库路径。
const bool early_return_source = source == CandidateSource::Generated || source == CandidateSource::NeuralDesktop ||
source == CandidateSource::NeuralKeyboard;
if (!early_return_source || candidate_canonical_pinyin.empty())
{
return false;
}
+82
View File
@@ -717,6 +717,10 @@ void AppendAiContext(const std::string &committed_word)
++cut;
g_ai_context.erase(0, cut);
}
// 同一份上屏历史也是神经整句重排的前文(青简那边叫 InputHistory):下一次查询会随
// QueryRequest 下发到词典层。词典层自己按 RerankOptions::context_chars 取末尾若干字。
if (g_inputSession)
g_inputSession->set_rescoring_context(g_ai_context);
}
std::wstring BuildCreateWordPipePayload(const std::string &remaining_raw_input_with_cases,
@@ -1207,6 +1211,16 @@ std::string BuildCurrentCandidatePage()
view.badge = " ☁️";
else if (item.source == CandidateSource::AiSuggestion)
view.badge = " 🤖";
// 整句来源标签与设置页名称保持一致,方便同时比较四个来源。两家选中同一句时只剩一行,
// 标签归先保留下来的来源;开启去重补位后,其余来源会改为显示自己的下一条不同结果。
else if (item.source == CandidateSource::Generated)
view.badge = " 〔Trigram〕";
else if (item.source == CandidateSource::Fallback)
view.badge = " 〔Unigram〕";
else if (item.source == CandidateSource::NeuralDesktop)
view.badge = " 〔神经D〕";
else if (item.source == CandidateSource::NeuralKeyboard)
view.badge = " 〔神经K〕";
view.fixed_position = item.fixed_position > 0;
EnglishIme::TranslationQuery translation_query;
if (!translation_page && BuildTranslationQuery(item, translation_query))
@@ -1600,6 +1614,8 @@ enum class TaskType
RefreshCandidatePage,
ResetInputSessionCache,
ExitEnglishInputMode,
// 神经整句重排在后台线程里算完了,候选顺序需要就地更新一次。
ApplyRescoredOrder,
};
struct Task
@@ -1830,6 +1846,7 @@ void ProcessSelectionKey(UINT keycode, uint64_t client_id, uint64_t activation_e
void WaitForCandidateRenderSync(UINT keycode);
void ApplyCloudCandidate(const std::string &candidate, const std::string &pinyin, uint64_t generation,
const std::optional<metasequoia::OnlineQuery> &query);
void ApplyRescoredOrder();
void ApplyAiCandidate(const std::string &candidate, const std::string &identity, uint64_t generation,
const std::optional<metasequoia::OnlineQuery> &query);
void ApplyEnglishCandidates(std::vector<WordItem> candidates, const std::string &input, uint64_t generation);
@@ -2408,6 +2425,11 @@ void WorkerThread()
}
break;
}
case TaskType::ApplyRescoredOrder: {
ApplyRescoredOrder();
break;
}
}
}
@@ -2467,6 +2489,19 @@ void EnqueueCloudCandidate(const std::string &candidate, const std::string &piny
pipe_queueCv.notify_one();
}
// 后台线程算完了一批整句重排。它不知道自己算的是不是当前这次输入——那要在任务线程上、拿着
// g_inputSession 才判断得了——所以这里只负责把「去看一眼」排进队列,判断留给 ApplyRescoredOrder。
void EnqueueRescoredCandidates()
{
{
std::lock_guard lock(queueMutex);
Task task;
task.type = TaskType::ApplyRescoredOrder;
taskQueue.push(std::move(task));
}
pipe_queueCv.notify_one();
}
void EnqueueAiCandidate(const std::string &candidate, const std::string &identity, uint64_t generation)
{
const AsyncRequestOrigin origin = FindAiRequestOrigin(identity, generation);
@@ -3866,6 +3901,53 @@ void ApplyCloudCandidate(const std::string &candidate, const std::string &pinyin
RefreshCandidatePageUi(true);
}
// 候选列表当初是按词格的静态顺序发出去的,因为打分那会儿还没算完。现在算完了:把引擎的候选缓存
// 丢掉重查一次,这一次 quanpin::make_neural_reranker 能在结果表里查到顺序,整句就落到它该在的位
// 置上。重查本身不碰模型,走的还是词格那条快路。
//
// 后台线程算的可能已经是上一次输入的了(用户没停手),所以这里不认「哪一批」,只看重查出来的词
// 序有没有真的变:没变就一个字节都不动 UI。这既挡掉了过期结果,也挡掉了模型弃权的情况。
void ApplyRescoredOrder()
{
if (!g_inputSession || g_translation_candidates_active)
return;
// 造词界面和译文页各自占着 items,重排不该去动它们。
if (GlobalIme::composition.creating_word.active)
return;
if (Global::candidate_ui.items.empty())
return;
const FanyImeIpc::CandidateUiOwner owner = SnapshotCandidateUiOwner();
if (!owner || !IsPipeActivationCurrent(owner.client_id, owner.activation_epoch))
return;
const std::vector<WordItem> before = g_inputSession->get_candidates();
// 云/AI 候选只活在 series cache 里,reset_cache 会把它们一起清掉,而它们的请求早已回来、不会
// 再发一次——不补回来,重排一落地它们就从列表里消失了。重查不推进在线请求的 generation,
// 所以用当前的 online_query 原样回填即可;回填时若同一句已被整句候选占了,引擎自己会拒绝。
std::vector<WordItem> online_items;
std::copy_if(before.begin(), before.end(), std::back_inserter(online_items), [](const WordItem &item) {
return item.source == CandidateSource::CloudSuggestion || item.source == CandidateSource::AiSuggestion;
});
g_inputSession->reset_cache();
g_inputSession->recompute_candidates();
if (!online_items.empty())
{
if (const auto query = g_inputSession->online_query())
{
for (const WordItem &item : online_items)
g_inputSession->apply_online_candidate(*query, item.word, item.source);
}
}
const std::vector<WordItem> &after = g_inputSession->get_candidates();
if (after.size() == before.size() &&
std::equal(before.begin(), before.end(), after.begin(),
[](const WordItem &a, const WordItem &b) { return a.word == b.word; }))
return;
PrepareCandidateList(owner.client_id, owner.activation_epoch);
RequestShowCandidateWindow();
}
void ApplyAiCandidate(const std::string &candidate, const std::string &identity, uint64_t generation,
const std::optional<metasequoia::OnlineQuery> &engine_query)
{
+2
View File
@@ -49,6 +49,8 @@ void ClearState();
void RegisterStatusSnapshotWindow(HWND toolbar_window);
void EnqueueCloudCandidate(const std::string &candidate, const std::string &pinyin, uint64_t generation);
void EnqueueAiCandidate(const std::string &candidate, const std::string &identity, uint64_t generation);
// 神经整句重排的后台线程算完一批后调这个;见 event_listener.cpp 的 ApplyRescoredOrder。
void EnqueueRescoredCandidates();
void CancelCloudCandidateRequest();
void EnqueueEnglishCandidates(std::vector<WordItem> candidates, const std::string &input, uint64_t generation);
void EnqueueCandidateTranslations(std::vector<EnglishIme::TranslationResult> results, uint64_t generation,
+4
View File
@@ -18,6 +18,7 @@
#include "english/english_ime.h"
#include "emoji/emoji_ime.h"
#include "kaomoji/kaomoji_ime.h"
#include "engine/neural/rescore_worker.h"
#include "utils/common_utils.h"
#include "utils/single_instance.h"
#include "session/session_factory.h"
@@ -176,6 +177,9 @@ int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE /*hPrevInstance*/,
AiAssistant::Start([](const std::string &candidate, const std::string &identity, uint64_t generation) {
FanyNamedPipe::EnqueueAiCandidate(candidate, identity, generation);
});
// 神经整句重排:打分在引擎自己的后台线程上跑,算完通过这个回调回到任务队列,由队列去重查候选。
// 不 Start 什么线程——没开这个开关的用户不会平白多一条。
neural::RescoreWorker::instance().set_ready_callback([] { FanyNamedPipe::EnqueueRescoredCandidates(); });
EnglishIme::Start(
CommonUtils::get_ime_data_path() + "\\english.db",
[](std::vector<WordItem> candidates, const std::string &input, uint64_t generation) {
@@ -1,6 +1,7 @@
#include "engine_input_session.h"
#include "config/ime_config.h"
#include "engine/common/helpcode_utils.h"
#include "engine/core/sentence_association_options.h"
#include "engine/quanpin/quanpin_utils.h"
EngineInputSession::EngineInputSession(SchemeType scheme, const ShuangpinProfile &profile)
@@ -38,6 +39,14 @@ void EngineInputSession::ApplyConfiguration()
// converted quanpin syllables for shuangpin. Re-read on every key so setting
// changes take effect immediately.
session_.set_fuzzy_pinyin_options(GetConfiguredFuzzyPinyinOptions());
// 整句候选来源与去重补位选项,每次击键重读,改设置立即生效。
SentenceAssociationOptions association;
association.word_lattice = GetConfiguredAssocSentenceWordLattice();
association.google = GetConfiguredAssocSentenceGoogle();
association.neural_desktop = GetConfiguredAssocSentenceNeuralDesktop();
association.neural_keyboard = GetConfiguredAssocSentenceNeuralKeyboard();
association.show_next_on_duplicate = GetConfiguredAssocSentenceShowNextOnDuplicate();
session_.set_sentence_association(association);
session_.set_shuangpin_preedit_uses_raw(GetConfiguredShuangpinPreeditMode() == "shuangpin");
}
@@ -144,6 +153,11 @@ bool EngineInputSession::has_active_helpcode() const
return session_.has_active_helpcode();
}
void EngineInputSession::set_rescoring_context(std::string context)
{
return session_.set_rescoring_context(std::move(context));
}
void EngineInputSession::set_pinyin_sequence(const std::string &pinyin_sequence)
{
return session_.set_pinyin_sequence(pinyin_sequence);
@@ -35,6 +35,7 @@ class EngineInputSession : public IInputSession
bool wubi_four_code_is_complete() const override;
bool has_active_helpcode() const override;
void set_rescoring_context(std::string context) override;
void set_pinyin_sequence(const std::string &pinyin_sequence) override;
void set_pinyin_sequence_with_cases(const std::string &pinyin_sequence) override;
+5
View File
@@ -53,6 +53,11 @@ class IInputSession
virtual bool wubi_four_code_is_complete() const = 0;
virtual bool has_active_helpcode() const = 0;
// 本会话最近上屏的文本,给神经整句重排当前文。空实现:除引擎会话外没人需要前文。
virtual void set_rescoring_context(std::string)
{
}
virtual void set_pinyin_sequence(const std::string &pinyin_sequence) = 0;
virtual void set_pinyin_sequence_with_cases(const std::string &pinyin_sequence) = 0;
+16
View File
@@ -374,6 +374,12 @@ std::wstring BuildConfigMessage(bool refresh_skin_catalog)
{"paging_page_up_down", GetConfiguredPagingPageUpDownEnabled()},
{"paging_mouse_wheel", GetConfiguredPagingMouseWheelEnabled()},
{"candidate_arrow_navigation", GetConfiguredCandidateArrowNavigationEnabled()}}},
{"association",
{{"sentence_wordlattice", GetConfiguredAssocSentenceWordLattice()},
{"sentence_google", GetConfiguredAssocSentenceGoogle()},
{"sentence_neural_desktop", GetConfiguredAssocSentenceNeuralDesktop()},
{"sentence_neural_keyboard", GetConfiguredAssocSentenceNeuralKeyboard()},
{"sentence_show_next_on_duplicate", GetConfiguredAssocSentenceShowNextOnDuplicate()}}},
{"keybindings",
{{"switch_language_shift", GetConfiguredSwitchLanguageShiftEnabled()},
{"switch_language_ctrl", GetConfiguredSwitchLanguageCtrlEnabled()},
@@ -681,6 +687,16 @@ bool ApplyConfigUpdate(const json::object &data)
return SetConfiguredKaomojiMixedInputEnabled(json::value_to<bool>(data.at("value")));
if (path == "general.cloud_candidates")
return SetConfiguredCloudCandidatesEnabled(json::value_to<bool>(data.at("value")));
if (path == "association.sentence_wordlattice")
return SetConfiguredAssocSentenceWordLattice(json::value_to<bool>(data.at("value")));
if (path == "association.sentence_google")
return SetConfiguredAssocSentenceGoogle(json::value_to<bool>(data.at("value")));
if (path == "association.sentence_neural_desktop")
return SetConfiguredAssocSentenceNeuralDesktop(json::value_to<bool>(data.at("value")));
if (path == "association.sentence_neural_keyboard")
return SetConfiguredAssocSentenceNeuralKeyboard(json::value_to<bool>(data.at("value")));
if (path == "association.sentence_show_next_on_duplicate")
return SetConfiguredAssocSentenceShowNextOnDuplicate(json::value_to<bool>(data.at("value")));
if (path == "utility.unicode_mode")
return SetConfiguredUnicodeModeEnabled(json::value_to<bool>(data.at("value")));
if (path == "utility.quick_phrase")
+46
View File
@@ -4267,6 +4267,46 @@ HRESULT OnControllerCreatedSettingsWnd( //
PostSettingsConfig();
}
}
else if (path == "association.sentence_wordlattice")
{
const bool value = json::value_to<bool>(data.at("value"));
if (SetConfiguredAssocSentenceWordLattice(value))
{
PostSettingsConfig();
}
}
else if (path == "association.sentence_google")
{
const bool value = json::value_to<bool>(data.at("value"));
if (SetConfiguredAssocSentenceGoogle(value))
{
PostSettingsConfig();
}
}
else if (path == "association.sentence_neural_desktop")
{
const bool value = json::value_to<bool>(data.at("value"));
if (SetConfiguredAssocSentenceNeuralDesktop(value))
{
PostSettingsConfig();
}
}
else if (path == "association.sentence_neural_keyboard")
{
const bool value = json::value_to<bool>(data.at("value"));
if (SetConfiguredAssocSentenceNeuralKeyboard(value))
{
PostSettingsConfig();
}
}
else if (path == "association.sentence_show_next_on_duplicate")
{
const bool value = json::value_to<bool>(data.at("value"));
if (SetConfiguredAssocSentenceShowNextOnDuplicate(value))
{
PostSettingsConfig();
}
}
else if (path == "utility.unicode_mode")
{
const bool value = json::value_to<bool>(data.at("value"));
@@ -4636,6 +4676,12 @@ void PostSettingsConfig()
{"paging_page_up_down", GetConfiguredPagingPageUpDownEnabled()},
{"paging_mouse_wheel", GetConfiguredPagingMouseWheelEnabled()},
{"candidate_arrow_navigation", GetConfiguredCandidateArrowNavigationEnabled()}}},
{"association",
{{"sentence_wordlattice", GetConfiguredAssocSentenceWordLattice()},
{"sentence_google", GetConfiguredAssocSentenceGoogle()},
{"sentence_neural_desktop", GetConfiguredAssocSentenceNeuralDesktop()},
{"sentence_neural_keyboard", GetConfiguredAssocSentenceNeuralKeyboard()},
{"sentence_show_next_on_duplicate", GetConfiguredAssocSentenceShowNextOnDuplicate()}}},
{"keybindings",
{{"switch_language_shift", GetConfiguredSwitchLanguageShiftEnabled()},
{"switch_language_ctrl", GetConfiguredSwitchLanguageCtrlEnabled()},
@@ -181,6 +181,21 @@ function applyConfigData(data: Record<string, any>, target?: string): void {
if (typeof data?.general?.cloud_candidates === 'boolean') {
applyToggleState('cloudCandidatesToggleBtn', data.general.cloud_candidates);
}
if (typeof data?.association?.sentence_wordlattice === 'boolean') {
applyToggleState('sentenceWordLatticeToggleBtn', data.association.sentence_wordlattice);
}
if (typeof data?.association?.sentence_google === 'boolean') {
applyToggleState('sentenceGoogleToggleBtn', data.association.sentence_google);
}
if (typeof data?.association?.sentence_neural_desktop === 'boolean') {
applyToggleState('sentenceNeuralDesktopToggleBtn', data.association.sentence_neural_desktop);
}
if (typeof data?.association?.sentence_neural_keyboard === 'boolean') {
applyToggleState('sentenceNeuralKeyboardToggleBtn', data.association.sentence_neural_keyboard);
}
if (typeof data?.association?.sentence_show_next_on_duplicate === 'boolean') {
applyToggleState('sentenceShowNextOnDuplicateToggleBtn', data.association.sentence_show_next_on_duplicate);
}
if (typeof data?.utility?.unicode_mode === 'boolean') {
applyToggleState('unicodeModeToggleBtn', data.utility.unicode_mode);
}
@@ -345,6 +345,23 @@ export function setupInput(): void {
setupToggleButton('cloudCandidatesToggleBtn', (active) => {
updateConfig('general.cloud_candidates', active);
});
// 四个整句来源和一个去重补位开关,默认全关。两个神经来源会自行在词格中生成最多 12 条
// 内部备选;补位打开后,某来源的首选重复时沿该来源自己的排名寻找下一条不同结果。
setupToggleButton('sentenceWordLatticeToggleBtn', (active) => {
updateConfig('association.sentence_wordlattice', active);
});
setupToggleButton('sentenceGoogleToggleBtn', (active) => {
updateConfig('association.sentence_google', active);
});
setupToggleButton('sentenceNeuralDesktopToggleBtn', (active) => {
updateConfig('association.sentence_neural_desktop', active);
});
setupToggleButton('sentenceNeuralKeyboardToggleBtn', (active) => {
updateConfig('association.sentence_neural_keyboard', active);
});
setupToggleButton('sentenceShowNextOnDuplicateToggleBtn', (active) => {
updateConfig('association.sentence_show_next_on_duplicate', active);
});
}
function setupFrequencyOptions(): void {
@@ -558,6 +558,62 @@
</div>
</div>
<div class="section">
<div class="section-header">
<div>
<div class="section-title">整句候选</div>
<div class="input-setting-description">输入多个拼音音节时,提供整句候选</div>
</div>
</div>
<div class="frequency-option-content">
<div class="section-header frequency-option-row">
<div>
<div class="section-title">Trigram 模型整句</div>
<div class="input-setting-description">使用三元语言模型生成整句候选</div>
</div>
<div class="ftb-toggle-btn" id="sentenceWordLatticeToggleBtn" role="switch" aria-checked="false" tabindex="0">
<div class="ftb-toggle-knob"></div>
</div>
</div>
<div class="section-header frequency-option-row">
<div>
<div class="section-title">Unigram 模型整句</div>
<div class="input-setting-description">使用 Google 拼音的一元语言模型生成整句候选</div>
</div>
<div class="ftb-toggle-btn" id="sentenceGoogleToggleBtn" role="switch" aria-checked="false" tabindex="0">
<div class="ftb-toggle-knob"></div>
</div>
</div>
<div class="section-header frequency-option-row">
<div>
<div class="section-title">神经网络模型整句(效果优先)</div>
<div class="input-setting-description">结合已上屏的前文,从内部生成的最多 12 个备选句中选择,处理时间较长</div>
</div>
<div class="ftb-toggle-btn" id="sentenceNeuralDesktopToggleBtn" role="switch" aria-checked="false" tabindex="0">
<div class="ftb-toggle-knob"></div>
</div>
</div>
<div class="section-header frequency-option-row">
<div>
<div class="section-title">神经网络模型整句(速度优先)</div>
<div class="input-setting-description">使用轻量模型从最多 12 个备选句中选择;两种模式可同时独立给出结果</div>
</div>
<div class="ftb-toggle-btn" id="sentenceNeuralKeyboardToggleBtn" role="switch" aria-checked="false" tabindex="0">
<div class="ftb-toggle-knob"></div>
</div>
</div>
<div class="section-header frequency-option-row">
<div>
<div class="section-title">去重后补充次选</div>
<div class="input-setting-description">某个模型的首选与已有候选重复时,显示该模型排名最高的下一条不同结果</div>
</div>
<div class="ftb-toggle-btn" id="sentenceShowNextOnDuplicateToggleBtn" role="switch" aria-checked="false" tabindex="0">
<div class="ftb-toggle-knob"></div>
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-header"><div class="section-title">拼音方案调频</div></div>
<div class="frequency-option-content">