diff --git a/server/MetasequoiaImeEngine b/server/MetasequoiaImeEngine index 452dd531..626b0507 160000 --- a/server/MetasequoiaImeEngine +++ b/server/MetasequoiaImeEngine @@ -1 +1 @@ -Subproject commit 452dd53125430195a06509eb0e9786f1c10657eb +Subproject commit 626b05079fa0d3a35eafc4a9a52ecaac1a04654c diff --git a/server/src/ai/ai_assistant.cpp b/server/src/ai/ai_assistant.cpp index bb9794b5..28990189 100644 --- a/server/src/ai/ai_assistant.cpp +++ b/server/src/ai/ai_assistant.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace { @@ -21,6 +22,14 @@ std::atomic g_generation{0}; AiAssistant::Request g_latest; std::chrono::steady_clock::time_point g_last_input; AiAssistant::ApplyCallback g_callback; +std::unordered_map g_candidate_cache; + +std::string BuildCacheKey(const AiAssistant::Request &request) +{ + nlohmann::json segments = request.pinyin_segments; + return request.config.provider + "\n" + request.config.endpoint + "\n" + request.config.model + "\n" + + segments.dump(); +} size_t WriteResponse(char *data, size_t size, size_t count, void *user) { @@ -139,6 +148,19 @@ void WorkerLoop() if (!g_running) break; observed = g_generation.load(); auto request = g_latest; + const auto cached = g_candidate_cache.find(BuildCacheKey(request)); + if (!request.identity.empty() && cached != g_candidate_cache.end()) + { + const std::string candidate = cached->second; + const uint64_t cached_generation = observed; + lock.unlock(); + OutputDebugStringA(fmt::format("[ai-assistant] cache hit: generation={}, pinyin={}, candidate={}\n", + cached_generation, + nlohmann::json(request.pinyin_segments).dump(), candidate).c_str()); + if (g_running && g_generation.load() == cached_generation && g_callback) + g_callback(candidate, request.identity, cached_generation); + continue; + } auto target = g_last_input + kIdleDelay; while (g_running && g_cv.wait_until(lock, target, [&] { return !g_running || g_generation.load() != observed; })) { @@ -151,7 +173,16 @@ void WorkerLoop() if (request.identity.empty()) continue; const std::string candidate = Fetch(request, observed); if (!candidate.empty() && g_generation.load() == observed && g_callback) + { + { + std::lock_guard cache_lock(g_mutex); + g_candidate_cache[BuildCacheKey(request)] = candidate; + } + OutputDebugStringA(fmt::format("[ai-assistant] cache stored: generation={}, pinyin={}, candidate={}\n", + observed, nlohmann::json(request.pinyin_segments).dump(), + candidate).c_str()); g_callback(candidate, request.identity, observed); + } } } } // namespace @@ -172,6 +203,7 @@ void Stop() g_running = false; g_cv.notify_all(); if (g_worker.joinable()) g_worker.join(); + { std::lock_guard lock(g_mutex); g_candidate_cache.clear(); } curl_global_cleanup(); } void OnInputChanged(Request request) diff --git a/server/src/ipc/event_listener.cpp b/server/src/ipc/event_listener.cpp index 496d9152..8b5653ae 100644 --- a/server/src/ipc/event_listener.cpp +++ b/server/src/ipc/event_listener.cpp @@ -1828,7 +1828,8 @@ void ApplyCloudCandidate(const std::string &candidate, const std::string &pinyin Global::candidate_ui.items.insert(Global::candidate_ui.items.begin() + insert_index, WordItem(pinyin, candidate, 1, CandidateSource::CloudSuggestion)); // 还需要更新一下 dictionary 中的 cache - g_inputSession->cache_dynamic_candidate(cloud_query_state.cache_key, candidate); + g_inputSession->cache_dynamic_candidate(cloud_query_state.cache_key, candidate, + CandidateSource::CloudSuggestion); // 标记一下,云候选已经被加进来了 Global::cloud_candidate.added = true; Global::cloud_candidate.word = candidate; @@ -1873,6 +1874,7 @@ void ApplyAiCandidate(const std::string &candidate, const std::string &identity, const auto query = g_inputSession->get_cloud_query_state(); const size_t insert_index = std::min(2, items.size()); items.insert(items.begin() + insert_index, WordItem(identity, candidate, 1, CandidateSource::AiSuggestion)); + g_inputSession->cache_dynamic_candidate(query.cache_key, candidate, CandidateSource::AiSuggestion); OutputDebugStringA(fmt::format("[ai-assistant] candidate inserted: generation={}, index={}, candidate={}, " "identity={}\n", generation, insert_index + 1, candidate, identity).c_str()); Global::ai_candidate = {true, candidate, query.committed_pinyin}; diff --git a/server/src/session/engine_input_session.cpp b/server/src/session/engine_input_session.cpp index b70988ee..00dbe4cf 100644 --- a/server/src/session/engine_input_session.cpp +++ b/server/src/session/engine_input_session.cpp @@ -279,10 +279,11 @@ int EngineInputSession::remove_candidate(std::string pinyin, std::string word) return session_.delete_by_pinyin_and_word(std::move(pinyin), std::move(word)); } -int EngineInputSession::cache_dynamic_candidate(const std::string &pinyin, const std::string &word) +int EngineInputSession::cache_dynamic_candidate(const std::string &pinyin, const std::string &word, + CandidateSource source) { - const int cache_result = session_.cache_dynamic_candidate(pinyin, word); - (void)session_.cache_dynamic_candidate_for_current_request(word); + const int cache_result = session_.cache_dynamic_candidate(pinyin, word, source); + (void)session_.cache_dynamic_candidate_for_current_request(word, source); return cache_result; } diff --git a/server/src/session/engine_input_session.h b/server/src/session/engine_input_session.h index f56ef24c..08ab937b 100644 --- a/server/src/session/engine_input_session.h +++ b/server/src/session/engine_input_session.h @@ -34,7 +34,8 @@ class EngineInputSession : public IInputSession int store_user_phrase(std::string pinyin, std::string word) override; int pin_candidate(std::string pinyin, std::string word) override; int remove_candidate(std::string pinyin, std::string word) override; - int cache_dynamic_candidate(const std::string &pinyin, const std::string &word) override; + int cache_dynamic_candidate(const std::string &pinyin, const std::string &word, + CandidateSource source) override; SelectionTransition advance_composition_after_selection(const std::string &selected_pinyin, const std::string &selected_word) override; CloudQueryState get_cloud_query_state() const override; diff --git a/server/src/session/input_session.h b/server/src/session/input_session.h index df67293a..b473a3b4 100644 --- a/server/src/session/input_session.h +++ b/server/src/session/input_session.h @@ -61,7 +61,8 @@ class IInputSession virtual int store_user_phrase(std::string pinyin, std::string word) = 0; virtual int pin_candidate(std::string pinyin, std::string word) = 0; virtual int remove_candidate(std::string pinyin, std::string word) = 0; - virtual int cache_dynamic_candidate(const std::string &pinyin, const std::string &word) = 0; + virtual int cache_dynamic_candidate(const std::string &pinyin, const std::string &word, + CandidateSource source) = 0; virtual SelectionTransition advance_composition_after_selection(const std::string &selected_pinyin, const std::string &selected_word) = 0; virtual CloudQueryState get_cloud_query_state() const = 0; diff --git a/server/src/session/shuangpin_input_session.cpp b/server/src/session/shuangpin_input_session.cpp index 9de7cbd7..db4f3b93 100644 --- a/server/src/session/shuangpin_input_session.cpp +++ b/server/src/session/shuangpin_input_session.cpp @@ -140,9 +140,10 @@ int ShuangpinInputSession::remove_candidate(std::string pinyin, std::string word return dictionary_->delete_by_pinyin_and_word(std::move(pinyin), std::move(word)); } -int ShuangpinInputSession::cache_dynamic_candidate(const std::string &pinyin, const std::string &word) +int ShuangpinInputSession::cache_dynamic_candidate(const std::string &pinyin, const std::string &word, + CandidateSource source) { - return dictionary_->insert_word_to_cached_buffer_series(pinyin, word); + return dictionary_->insert_word_to_cached_buffer_series(pinyin, word, source); } IInputSession::SelectionTransition diff --git a/server/src/session/shuangpin_input_session.h b/server/src/session/shuangpin_input_session.h index a23aaccc..e64dec5c 100644 --- a/server/src/session/shuangpin_input_session.h +++ b/server/src/session/shuangpin_input_session.h @@ -34,7 +34,8 @@ class ShuangpinInputSession : public IInputSession int store_user_phrase(std::string pinyin, std::string word) override; int pin_candidate(std::string pinyin, std::string word) override; int remove_candidate(std::string pinyin, std::string word) override; - int cache_dynamic_candidate(const std::string &pinyin, const std::string &word) override; + int cache_dynamic_candidate(const std::string &pinyin, const std::string &word, + CandidateSource source) override; SelectionTransition advance_composition_after_selection(const std::string &selected_pinyin, const std::string &selected_word) override; CloudQueryState get_cloud_query_state() const override;