用户词库日志存的是调频写下的绝对权重。词库升级换了权重量级后(msime-dictionary 把拼音词频对齐万象,「了 le」从 348 万变成 5200 万),安装时的回放仍把旧词库上 调出来的 650 万写回去,打 l 时「了」排到「来」「里」后面。 回放现在用数据目录里 dictionary-manifest.json 记的 msime.db SHA-256 认出厂词库, 并在日志新增的 dictionary_state 表记下回放到哪份。同一份词库照旧原样写回;换了 词库(或日志还没有记录)时,自动调频的行只在高过新出厂权重时写回,否则保留新 权重并把日志改记成新权重。用户手动加的词和删除不变。 设置页改权重改走新的 record_weight_edit,记在新增的 weight_edited 列里,换词库时 照写不让位;之后的自动调频覆盖同一行时清掉这个标记。
151 lines
11 KiB
C++
151 lines
11 KiB
C++
#pragma once
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <functional>
|
||
#include <optional>
|
||
#include "../core/word_item.h"
|
||
|
||
namespace user_dictionary
|
||
{
|
||
enum class DictionaryKind
|
||
{
|
||
Pinyin,
|
||
Wubi,
|
||
QuickPhrase,
|
||
English,
|
||
};
|
||
|
||
std::string default_user_db_path();
|
||
// Compatibility no-op; journal connections are operation-scoped.
|
||
void close_default_user_database();
|
||
|
||
bool record_upsert(const std::string &user_db_path, DictionaryKind kind, const std::string &key,
|
||
const std::string &value, std::int64_t weight, const std::string &display = {});
|
||
// 用户在设置页给词库里已有的词改了权重。和 record_upsert 一样写一条 upsert,但记下这是用户
|
||
// 亲手定的值:出厂词库换过时回放照写这个权重,不像自动调频那样让位给新的出厂权重(见 replay)。
|
||
bool record_weight_edit(const std::string &user_db_path, DictionaryKind kind, const std::string &key,
|
||
const std::string &value, std::int64_t weight, const std::string &display = {});
|
||
bool record_user_insert(const std::string &user_db_path, DictionaryKind kind, const std::string &key,
|
||
const std::string &value, std::int64_t weight, const std::string &display = {});
|
||
bool record_delete(const std::string &user_db_path, DictionaryKind kind, const std::string &key,
|
||
const std::string &value);
|
||
bool is_user_inserted(const std::string &user_db_path, DictionaryKind kind, const std::string &key,
|
||
const std::string &value);
|
||
// 该键下是否有用户留下的权重(调频或造词的 upsert,不看具体词)。删除记录不算:
|
||
// 它表达的是「不要这个词」,不是对这组读音的偏好。
|
||
bool has_user_upsert_for_key(const std::string &user_db_path, DictionaryKind kind, const std::string &key);
|
||
bool ensure_user_database(const std::string &user_db_path);
|
||
// Switch an existing dictionary database (msime.db) to WAL so the key thread's
|
||
// queries never wait for a learning write's commit. The user journal does this
|
||
// on every connection it opens. Returns whether the database is now in WAL mode.
|
||
bool enable_write_ahead_log(const std::string &db_path);
|
||
bool record_pinyin_upsert_from_database(const std::string &main_db_path, const std::string &key,
|
||
const std::string &value,
|
||
const std::string &user_db_path = default_user_db_path());
|
||
// Raise the selected wubi candidate to the top of its own code group (max weight + 1, same
|
||
// semantics as the quanpin side) and persist the new weight plus a journal upsert in one
|
||
// attached-database transaction. Existing rows only: a missing (key,value) pair returns false
|
||
// without inserting. Row-missing behaviour matches update_wubi_weight.
|
||
bool bump_wubi_weight(const std::string &main_db_path, const std::string &user_db_path, const std::string &key,
|
||
const std::string &value);
|
||
|
||
// 快捷短语混排的调频状态。快捷短语与拼音候选的权重不在同一个量纲上,不能互相比较,所以
|
||
// 按编码记一个槽位:同码快捷短语组前面排几个普通候选(见
|
||
// metasequoia::local_modes::counts_toward_quick_phrase_slot)。没有记录即 0,组在首位。
|
||
int quick_phrase_slot(const std::string &user_db_path, const std::string &code);
|
||
// 用户选中了组里的一条快捷短语。累计到 trigger_count 次后按调频模式把组往前挪(槽位按
|
||
// ranking_target 缩小),选中的不是组内第一条时再把它的权重升到组内最高 + 1 并记日志。
|
||
// mode 为 "disabled" 时什么都不做。
|
||
bool learn_quick_phrase_selection(const std::string &main_db_path, const std::string &user_db_path,
|
||
const std::string &code, const std::string &value, bool first_in_group,
|
||
const std::string &mode, int linear_step, int trigger_count);
|
||
// 用户越过快捷短语组选了排在组后面的普通候选,ordinary_rank 是它在普通候选里的名次。
|
||
// 把整组当成它前面的一个位置,它按调频模式的目标位置越过这一组时,累计到 trigger_count
|
||
// 次后槽位 + 1,最多到 max_slot(首页最后一位)。
|
||
bool learn_quick_phrase_bypass(const std::string &user_db_path, const std::string &code, int ordinary_rank,
|
||
const std::string &mode, int linear_step, int trigger_count, int max_slot);
|
||
|
||
// 中英混输里英文候选的调频状态,思路同快捷短语:英文词库和中文词库的权重不在同一个量纲上,
|
||
// 不能比大小,所以按输入(小写)记一个槽位:首个英文候选在整个候选列表里的下标,0 是首位。
|
||
// 没有记录时由调用方决定默认位置(精确匹配紧跟第一个中文候选,补全词在首页末位)。
|
||
std::optional<int> english_slot(const std::string &user_db_path, const std::string &code);
|
||
// 用户显式置顶英文候选:直接写槽位,不走计数。
|
||
bool set_english_slot(const std::string &user_db_path, const std::string &code, int slot);
|
||
// 用户选中了占着槽位的英文候选,english_index 是它此刻在列表里的下标。累计到 trigger_count
|
||
// 次后,按调频模式从这个名次算出目标位置写成槽位,和中文调频一样可以一路升到首位。
|
||
bool learn_english_slot_selection(const std::string &user_db_path, const std::string &code, int english_index,
|
||
const std::string &mode, int linear_step, int trigger_count);
|
||
// 用户越过英文候选选了排在它后面的候选。english_index / selected_index 是两者在列表里的下标,
|
||
// 选中的候选按调频模式的目标位置越过英文时,累计到 trigger_count 次后英文后退一位,最多到 max_slot。
|
||
bool learn_english_slot_bypass(const std::string &user_db_path, const std::string &code, int english_index,
|
||
int selected_index, const std::string &mode, int linear_step, int trigger_count,
|
||
int max_slot);
|
||
|
||
// 日期时间格式(rq / sj / xq 那几组)的调频状态。日期文本每天都变,不能按文字记,所以按格式 ID
|
||
// (WordItem::pinyin,见 local_modes::query_date_time)记一份整组的顺序,category 是
|
||
// local_modes::date_time_category 的分组名。Shift+T 模式和混输共用这一份:混输取的就是排第一的格式。
|
||
// default_ids 是这组格式的出厂顺序(local_modes::date_time_format_ids),后来新增的格式按它插回去。
|
||
//
|
||
// 按学到的顺序重排一组日期时间候选(learned_order 为 false 时跳过,调频关闭时用),再按固定位置摆放
|
||
// (fixed_position 置为固定的位置)。
|
||
void apply_date_time_order(const std::string &user_db_path, const std::string &category,
|
||
std::vector<WordItem> &candidates, bool learned_order = true);
|
||
// 用户选中了 format_id。累计到 trigger_count 次后按调频模式把它往前挪,mode 为 "disabled" 时什么都不做。
|
||
bool learn_date_time_selection(const std::string &user_db_path, const std::string &category,
|
||
const std::vector<std::string> &default_ids, const std::string &format_id,
|
||
const std::string &mode, int linear_step, int trigger_count);
|
||
// 用户显式置顶:直接挪到首位,不走计数。
|
||
bool pin_date_time_format(const std::string &user_db_path, const std::string &category,
|
||
const std::vector<std::string> &default_ids, const std::string &format_id);
|
||
// 固定位置写进 fixed_candidate_positions 用的 context_key,entry_key 与 value 都写格式 ID。
|
||
std::string date_time_fixed_position_context(const std::string &category);
|
||
|
||
struct ReplayResult
|
||
{
|
||
int applied = 0;
|
||
int skipped = 0;
|
||
int failed = 0;
|
||
// 出厂词库换过时,日志里低于新出厂权重、因而保留新权重并改记进日志的自动调频行数。
|
||
int rebased = 0;
|
||
std::string error;
|
||
};
|
||
|
||
ReplayResult replay(const std::string &user_db_path, const std::string &main_db_path,
|
||
const std::string &english_db_path);
|
||
|
||
bool adjust_candidate_ranking(const std::string &main_db_path, const std::string &user_db_path,
|
||
const std::string &context_key, const std::vector<WordItem> &ordered_candidates,
|
||
const std::string &entry_key, const std::string &value, const std::string &mode,
|
||
int linear_step, int trigger_count, bool force_top, bool *ranking_changed = nullptr,
|
||
DictionaryKind kind = DictionaryKind::Pinyin);
|
||
bool adjust_english_candidate_ranking(const std::string &english_db_path, const std::string &user_db_path,
|
||
const std::string &context_key, const std::vector<WordItem> &ordered_candidates,
|
||
const std::string &entry_key, const std::string &value, const std::string &mode,
|
||
int linear_step, int trigger_count, bool force_top,
|
||
bool *ranking_changed = nullptr);
|
||
// Delete an exact dictionary key and persist its tombstone in one attached-database
|
||
// transaction. Supports Pinyin, Wubi and English; false preserves candidate rows and journal operations
|
||
// on statement/commit failures (not a cross-file power-loss guarantee in WAL mode).
|
||
bool delete_dictionary_candidate(const std::string &dictionary_db_path, const std::string &user_db_path,
|
||
DictionaryKind kind, const std::string &entry_key, const std::string &value);
|
||
bool delete_english_candidate(const std::string &english_db_path, const std::string &user_db_path,
|
||
const std::string &entry_key, const std::string &value);
|
||
bool learn_entered_english_word(const std::string &english_db_path, const std::string &user_db_path,
|
||
const std::string &display, std::int64_t weight = 10);
|
||
bool set_fixed_position(const std::string &user_db_path, const std::string &context_key, const std::string &entry_key,
|
||
const std::string &value, int position);
|
||
bool clear_fixed_position(const std::string &user_db_path, const std::string &context_key, const std::string &entry_key,
|
||
const std::string &value);
|
||
bool is_fixed(const std::string &user_db_path, const std::string &context_key, const std::string &entry_key,
|
||
const std::string &value);
|
||
// Cloud/AI suggestions are normally hoisted back to their fixed slots (cloud at
|
||
// index 1, AI at index 2). Set keep_dynamic_candidate_positions when the caller
|
||
// already decided where they belong, e.g. after helpcode filtering.
|
||
void apply_fixed_positions(
|
||
const std::string &user_db_path, const std::string &context_key, std::vector<WordItem> &candidates,
|
||
bool include_missing,
|
||
const std::function<std::optional<WordItem>(const std::string &, const std::string &)> &find_candidate = {},
|
||
bool keep_dynamic_candidate_positions = false);
|
||
} // namespace user_dictionary
|