feat: 更新推斷模組以使用 search token 進行格式推斷,並新增格式搜尋字詞詞庫

This commit is contained in:
Your Name 2026-01-26 14:14:38 +08:00
parent 6d948b0873
commit fcc9e36220
7 changed files with 515 additions and 48 deletions

View file

@ -1,16 +1,19 @@
/**
* 自動格式推斷前端模組
* 智慧搜尋代理 - 前端模組
*
* 在使用者上傳檔案後自動推斷最可能的目標格式並填入搜尋欄
* 在使用者上傳檔案後自動推斷最可能的目標格式
* 並模擬使用者在搜尋欄輸入 token (prefix matching)
*
* UI 行為完全等同真人輸入
*/
// @ts-check
/**
* @typedef {Object} FormatPrediction
* @property {string} search_format - 預測的搜尋格式
* @property {string} search_token - 預測的搜尋 token (用於 prefix matching)
* @property {number} confidence - 預測信心度 (0-1)
* @property {Array<{format: string, score: number}>} top_k - Top-K 候選格式
* @property {Array<{token: string, score: number}>} top_k - Top-K 候選 token
* @property {string[]} reason_codes - 預測原因碼
*/
@ -39,7 +42,7 @@ const inferenceWebroot = inferenceWebrootMeta
// 狀態追蹤
let inferenceEnabled = true;
/** @type {string|null} */
let lastInferredFormat = null;
let lastInferredToken = null;
/** @type {string|null} */
let lastInferredEngine = null;
let isInferredValue = false;
@ -119,10 +122,11 @@ async function cancelWarmup() {
/**
* 自動填入推斷的格式
* @param {string} format - 推斷的格式
* UI 行為完全等同使用者手動輸入
* @param {string} token - 推斷的 search token
* @param {string} [engine] - 推斷的引擎
*/
function autoFillInferredFormat(format, engine) {
function autoFillInferredFormat(token, engine) {
/** @type {HTMLInputElement|null} */
const searchInput = document.querySelector("input[name='convert_to_search']");
const convertToPopup = document.querySelector(".convert_to_popup");
@ -133,30 +137,20 @@ function autoFillInferredFormat(format, engine) {
}
// 儲存推斷值
lastInferredFormat = format;
lastInferredToken = token;
lastInferredEngine = engine || null;
isInferredValue = true;
// 填入搜尋欄
searchInput.value = format;
// 填入搜尋欄 - UI 行為完全等同使用者輸入
searchInput.value = token;
// 觸發 input 事件以過濾結果
const inputEvent = new Event("input", { bubbles: true });
searchInput.dispatchEvent(inputEvent);
// 添加視覺提示 (使用淡色邊框)
searchInput.style.borderColor = "#22c55e";
searchInput.style.borderWidth = "2px";
// 不修改任何 UI 樣式 - 純粹模擬使用者輸入
// 3秒後恢復原樣
setTimeout(() => {
if (isInferredValue && searchInput.value === format) {
searchInput.style.borderColor = "";
searchInput.style.borderWidth = "";
}
}, 3000);
console.log(`🎯 Auto-filled format: ${format}${engine ? ` (engine: ${engine})` : ""}`);
console.log(`🎯 Auto-filled search token: ${token}${engine ? ` (engine: ${engine})` : ""}`);
}
/**
@ -164,19 +158,19 @@ function autoFillInferredFormat(format, engine) {
* @param {string} inputExt - 輸入副檔名
*/
function handleSearchClear(inputExt) {
if (isInferredValue && lastInferredFormat) {
if (isInferredValue && lastInferredToken) {
// 記錄為負樣本
logDismissEvent(inputExt, lastInferredFormat, lastInferredEngine || undefined);
logDismissEvent(inputExt, lastInferredToken, lastInferredEngine || undefined);
// 取消預調用
cancelWarmup();
console.log(`❌ User dismissed inference: ${lastInferredFormat}`);
console.log(`❌ User dismissed inference: ${lastInferredToken}`);
}
// 重置狀態
isInferredValue = false;
lastInferredFormat = null;
lastInferredToken = null;
lastInferredEngine = null;
}
@ -188,14 +182,6 @@ function handleManualInput() {
// 使用者手動修改,取消預調用
cancelWarmup();
isInferredValue = false;
// 恢復邊框樣式
/** @type {HTMLInputElement|null} */
const searchInput = document.querySelector("input[name='convert_to_search']");
if (searchInput) {
searchInput.style.borderColor = "";
searchInput.style.borderWidth = "";
}
}
}
@ -221,7 +207,7 @@ function initInferenceModule() {
// 如果是程式設定的值,不處理
if (e.isTrusted && isInferredValue) {
const currentValue = searchInput.value;
if (currentValue !== lastInferredFormat) {
if (currentValue !== lastInferredToken) {
handleManualInput();
}
}

View file

@ -439,15 +439,15 @@ async function triggerFormatInference(ext, fileSize) {
const result = await window.inferenceModule.requestFormatInference(ext, fileSizeKb);
if (result && result.should_auto_fill && result.format) {
// 自動填入推斷的格式
// 自動填入推斷的 search token (模擬使用者輸入)
window.inferenceModule.autoFillInferredFormat(
result.format.search_format,
result.format.search_token,
result.engine?.engine,
);
// 嘗試自動選擇對應的引擎選項
if (result.engine) {
autoSelectEngine(result.format.search_format, result.engine.engine);
autoSelectEngine(result.format.search_token, result.engine.engine);
}
}
} catch (error) {