Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

自造詞進入用戶字典 #947

Open
Magician62011 opened this issue Oct 25, 2024 · 10 comments
Open

自造詞進入用戶字典 #947

Magician62011 opened this issue Oct 25, 2024 · 10 comments
Labels

Comments

@Magician62011
Copy link

請教個問題,我嘗試通過lua實現自造詞進入用戶字典功能,寫了一個測試例子

local function translator(input, seg, env )
	if input == 'ceshi' then 
		local dict_entry = DictEntry()
		dict_entry.text = '芝麻不开门'
		dict_entry.custom_code = 'zhi ma kai men'
		dict_entry.comment = '测试✍️📙'
		
		--Memory(engine, schema, name_space)
		env.mem = Memory(env.engine, env.engine.schema, 'translator') -- get dictionary name under name_space
		--Phrase(memory, type, start, end, entry)
		local phrase = Phrase(env.mem, 'phrase', seg.start, seg._end, dict_entry)
		phrase.quality = 99
		yield(phrase:toCandidate())  -- choose the phrase candidate will update dictionary
	end
end
return translator

大致途徑是通過構造Phrase,然後轉換成Candidate,然後yield到候選列表裏。
利用Phrase的特性,實現選中候選時,自動保存到用戶字典。

我測試發現,我通過這種方式能寫進英文用戶字典,但始終無法寫進中文字典,想請教下問題可能出自哪裏?是否是table_translator和script_translator在commit時有什麼差異?

@lotem
Copy link
Member

lotem commented Oct 25, 2024

如果詞組的編碼種類與詞典不兼容(Language「語言」屬性不同)就不記錄。

@Magician62011
Copy link
Author

bool recognized = Language::intelligible(phrase, this);
if (recognized) {
commit_entry.AppendPhrase(phrase);

感謝回覆~您說的是這段邏輯麼,判斷Phrase和Memory的language是否一致。
我學習了下代碼,Memory的language_是構造時取到的,lua裏構造Phrase時從Memory取的language_,
https://github.com/hchunhui/librime-lua/blob/fa6563cf7b40f3bfbf09e856420bff8de6820558/src/types.cc#L2041-L2047
所以兩者的屬性應該是一致的。我也通過log打印查看了兩者的lang_name屬性,也的確是我的字典名。一時不知道摸排方向了。

另外想請教下,在windows上如何能用VS去debug librime,查找了很多issue,憾水平有限,目前只能參考md編譯成功。

@Magician62011
Copy link
Author

弄了虛擬機,debug出原因所在了。

void CommitEntry::AppendPhrase(const an<Phrase>& phrase) {
text += phrase->text();
code.insert(code.end(), phrase->code().begin(), phrase->code().end());
if (auto sentence = As<Sentence>(phrase)) {
for (const DictEntry& e : sentence->components()) {
elements.push_back(&e);
}
} else {
elements.push_back(&phrase->entry());
}

在AppendPhrase環節,commit_entry 從Phrase接收了text和code。 帶有custom_code的phrase.entry都傳給了commit_entry.elements。

而在接下來的Save環節,調用了Memorize(commit_entry)

bool ScriptTranslator::Memorize(const CommitEntry& commit_entry) {
bool update_elements = false;
// avoid updating single character entries within a phrase which is
// composed with single characters only
if (commit_entry.elements.size() > 1) {
for (const DictEntry* e : commit_entry.elements) {
if (e->code.size() > 1) {
update_elements = true;
break;
}
}
}
if (update_elements) {
for (const DictEntry* e : commit_entry.elements) {
user_dict_->UpdateEntry(*e, 0);
}
}
user_dict_->UpdateEntry(commit_entry, 1);
return true;
}

ScriptTranslator的Memorize會判斷 elements size,
僅>1的時候,給後續 UpdateEntry傳參的是 含有 custom_code的commit_entry.elements
而當爲1的時候,給後續 UpdateEntry傳參的卻是 commit_entry。L261(不知此處這麼處理是不是爲了規避什麼問題)
bool UserDictionary::UpdateEntry(const DictEntry& entry,
int commits,
const string& new_entry_prefix) {
string code_str(entry.custom_code);
if (code_str.empty() && !TranslateCodeToString(entry.code, &code_str))
return false;
string key(code_str + '\t' + entry.text);

由於我的 commit_entry 既沒有 custom_code,code也爲空(無法像普通模式一樣通過TranslateCodeToString得到code_str) ,導致最終傳入詞典的key沒有編碼部分。

反觀如果是英文詞典,會跳到 TableTranslator的Memorize

bool TableTranslator::Memorize(const CommitEntry& commit_entry) {
if (!user_dict_)
return false;
for (const DictEntry* e : commit_entry.elements) {
if (is_constructed(e)) {
DictEntry blessed(*e);
UnityTableEncoder::RemovePrefix(&blessed.custom_code);
user_dict_->UpdateEntry(blessed, 1);
} else {
user_dict_->UpdateEntry(*e, 1);

它則沒有 size的判斷
會給後續 UpdateEntry都傳入 commit_entry.elements。 會包含custom_code,傳給code_str並最終完整的寫入用戶詞典。

看來如果我想按照現行的方式走下去,就只能預先給phrase構造code。但我目前測code的構造,只能靠lua往裏push int值。繼續嘗試看看有無更好的辦法吧。

@shewer
Copy link
Contributor

shewer commented Oct 29, 2024

librime-lua 可以查一下 MemoryReg & DictEntryReg & UserDictionaryReg
我印象中 user_dict:update_entry() 不會檢查 language
mem.user_dict
user_dict:update_entry( ...)

entry = DictEntry()

---- 以下是我在 user_dict 中取出的entry  
I20241030 01:49:20.688678 138200073850432 user_dictionary.cc:376] custom_code : abb     明月, value: c=2 d=1.53826 t=134
I20241030 01:49:20.688799 138200073850432 user_dictionary.cc:544] text = '明月', code_len = 0, weight = -4.73331, commit_count = 2, present_tick = 168

[0][./lua/selector.lua:33] → entry.text
"明月"
[0][./lua/selector.lua:33] → entry.custom_code
"abb "
[0][./lua/selector.lua:33] → 

@Magician62011
Copy link
Author

感謝回覆~
我嘗試構造code,沒有尋找到好的方法。code好像存儲的是通過prism裏獲取的音節位置數。沒找到input string變成code的具體邏輯位置,lua裏目前好像也無方法實現。
我最終也是只能選擇了在lua裏自行update_dict的方式。還是將Phrase換成普通的Candidate去yield。(因爲自建Phrase(中文拼音型)會因爲code的缺失,造成一條髒數據同時進入到 ldb。如圖)
image

然後須要配合commit_notifier,當commit candidate之後,自行update詞條進入到用戶字典。
就是感覺在lua裏利用notifier的方式,在滿足當前需求場景後,如果未來有其他類似場景需求,處理邏輯這塊還是要多考慮一下。
以下是更新後的功能測試代碼,看各位有無更好建議

local function init(env)
	local ctx = env.engine.context
	ctx.commit_notifier:connect(function()
		log.error("commit notifier detected! ")
		if env.custom_code ~= nil then 
			-- Memory(engine, schema, name_space)
			env.mem = Memory(env.engine, env.engine.schema, 'translator') -- get dictionary name under name_space for language
			local commit_record = ctx.commit_history:back() -- last commit record
			log.error("commit_record.text: "..tostring(commit_record.text))
			log.error("env.custom_code: "..tostring(env.custom_code))
			-- construct a commit entry
			local commit_entry = DictEntry()
			commit_entry.text = commit_record.text
			commit_entry.custom_code = env.custom_code..' ' -- a space character for constructing user dict key: e.g. Key: 'zhao meng fu \t赵孟頫'

			env.mem:update_userdict(commit_entry, 1, '') 
			env.custom_code = nil
		end
	end)
end
local function translator(input, seg, env )
	if input == 'ceshi' then 
		local dict_entry = DictEntry()
		dict_entry.text = '芝麻不开门'
		dict_entry.custom_code = 'zhi ma kai men'
		dict_entry.comment = '测试✍️📙'

		local cand = Candidate('', seg.start, seg._end, dict_entry.text, dict_entry.comment) 
		cand.quality = 2
		yield(cand)
		env.custom_code = dict_entry.custom_code
	end
end
return { init = init, func = translator }

主要想實現這個功能的目的,是經常需要輸入一些生詞如外國運動員名稱,想將雲拼音返回的結果,快速加入到用戶詞典中,提高效率。

@shewer
Copy link
Contributor

shewer commented Oct 30, 2024

'zhi ma kai men' 要再加上 ' ' 'zhi ma kai men '
userdict 在 leveldb 上的formate key: 'zhi ma kai men \t芝麻不开门' value: ....

@Magician62011
Copy link
Author

感謝提醒~💗️
我也在構造用戶詞典key的時候發現了這個點。
寫了個完整的功能,與有興趣的好朋友们交流學習😊️
cloud_pinyin.lua

@shewer
Copy link
Contributor

shewer commented Oct 31, 2024

有個嚴重bug
notifier:connect() 解構時 要 disconnect()

local M={}
fuction M.init(env)
   env.notifier= env.engine.context.commit_notifier:connect( func(ctx) .... end) -- << 
end
function M.fini(env)
    env.notifier:disconnect()  -- <<<<<<<
end
function M.func(...)
end

return M

@Magician62011
Copy link
Author

好的,我試一下。
順便請教下,lua這個fini環節會什麼時候運行?然後如果不disconnect notifier 的話有什麼風險? 我之前大略測試觀察過init會在啓動時運行一次,然後我一直打字,它會一直觸發進入func,然後不一定什麼時候,好像init就又被觸發運行一次。是不是我不disconnect的話,它有可能重複設置多個commit_notifier?

@shewer
Copy link
Contributor

shewer commented Nov 2, 2024

lua 記憶體回收 和 C++ 不同步 ,會發生泄露

lua_compose的 lua_function 在C++中實現 ,

class  lua_processor : public processor {
     lua_processor::lua_processor() {
            call init()
     }
     lua_processor::ProcessKeyevent() {
             call func()
      }
     lua_processor::~lua_processor() {
             call fini()
     }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants