Completed fine-tuning for several models and prepared the foundational components for an upcoming refactor.
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
'use strict'; class SubtitleDatabase {
|
||||
constructor() { this.db = null; this.isLoading = false; this.isLoaded = false; this.loadPromise = null; this.progressCallback = null; this.version = "1.0.1" } async clearCache() { try { await dbStorage.removeItem("databases", "subtitleDB"); await dbStorage.setItem("databases", "version", this.version); this.db = null; this.isLoaded = false; this.isLoading = false; this.loadPromise = null; return true } catch (error) { throw error; } } getDebugInfo() {
|
||||
return {
|
||||
version: this.version, isLoaded: this.isLoaded, isLoading: this.isLoading,
|
||||
recordCount: this.db ? this.db.length : 0, memoryUsage: this.db ? JSON.stringify(this.db).length : 0, cacheStatus: this.isLoaded ? "\u5df2\u52a0\u8f7d" : this.isLoading ? "\u52a0\u8f7d\u4e2d" : "\u672a\u52a0\u8f7d"
|
||||
}
|
||||
} printDebugInfo() { const info = this.getDebugInfo() } async checkVersion() {
|
||||
try {
|
||||
const storedVersion = await dbStorage.getItem("databases", "version"); if (!storedVersion) { await dbStorage.setItem("databases", "version", this.version); return false } if (storedVersion !== this.version) {
|
||||
await this.clearCache(); await dbStorage.setItem("databases",
|
||||
"version", this.version); return true
|
||||
} return false
|
||||
} catch (error) { return false }
|
||||
} async load(progressCallback = null) {
|
||||
if (this.isLoaded && this.db && this.db.length > 0) return true;
|
||||
if (this.isLoading) return this.loadPromise;
|
||||
this.isLoading = true;
|
||||
this.progressCallback = progressCallback;
|
||||
await this.checkVersion();
|
||||
|
||||
try {
|
||||
const cachedDB = await dbStorage.getItem("databases", "subtitleDB");
|
||||
if (cachedDB) {
|
||||
if (Array.isArray(cachedDB) && cachedDB.length > 0) {
|
||||
this.db = cachedDB;
|
||||
this.isLoaded = true;
|
||||
this.isLoading = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
this.loadPromise = new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const checkResponse = await fetch("https://vvdb.cicada000.work/subtitle_db", {
|
||||
method: "HEAD",
|
||||
referrerPolicy: 'no-referrer',
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
});
|
||||
|
||||
if (!checkResponse.ok) throw new Error(`Database file not found: ${checkResponse.status}`);
|
||||
|
||||
const response = await fetch("https://vvdb.cicada000.work/subtitle_db", {
|
||||
referrerPolicy: 'no-referrer',
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to load database: ${response.status}`);
|
||||
|
||||
const contentLength = response.headers.get("Content-Length");
|
||||
const reader = response.body.getReader();
|
||||
let receivedLength = 0;
|
||||
const chunks = [];
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
receivedLength += value.length;
|
||||
}
|
||||
|
||||
const chunksAll = new Uint8Array(receivedLength);
|
||||
let position = 0;
|
||||
for (const chunk of chunks) {
|
||||
chunksAll.set(chunk, position);
|
||||
position += chunk.length
|
||||
}
|
||||
const ds =
|
||||
new DecompressionStream("gzip");
|
||||
const decompressedStream = (new Response(chunksAll)).body.pipeThrough(ds);
|
||||
const decompressedData = await (new Response(decompressedStream)).text();
|
||||
const parsedData = JSON.parse(decompressedData);
|
||||
if (!parsedData || !Array.isArray(parsedData) || parsedData.length === 0) throw new Error("Invalid or empty database format");
|
||||
this.db = parsedData;
|
||||
this.isLoaded = true;
|
||||
this.isLoading = false;
|
||||
try { await dbStorage.setItem("databases", "subtitleDB", this.db) } catch (e) { }
|
||||
if (window.subtitleDB !==
|
||||
this) window.subtitleDB = this;
|
||||
resolve(true)
|
||||
} catch (error) {
|
||||
this.isLoading = false;
|
||||
this.isLoaded = false;
|
||||
this.db = null;
|
||||
reject(error)
|
||||
}
|
||||
});
|
||||
return this.loadPromise
|
||||
} lcsRatio(str1, str2) { str1 = str1.toLowerCase(); str2 = str2.toLowerCase(); if (!str1 || !str2) return 0; const m = str1.length; const n = str2.length; const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0)); for (let i = 1; i <= m; i++)for (let j = 1; j <= n; j++)if (str1[i - 1] === str2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); return dp[m][n] / m * 100 } multiWordLcsRatio(queryWords,
|
||||
text) {
|
||||
text = text.toLowerCase(); const totalQueryLength = queryWords.reduce((sum, word) => sum + word.length, 0); const usedChars = (new Array(text.length)).fill(false); let totalMatched = 0; for (const word of queryWords) {
|
||||
const wordLower = word.toLowerCase(); let foundMatch = false; let startPos = 0; while (true) {
|
||||
const pos = text.indexOf(wordLower, startPos); if (pos === -1) break; const endPos = pos + wordLower.length; let canUse = true; for (let i = pos; i < endPos; i++)if (usedChars[i]) { canUse = false; break } if (canUse) {
|
||||
for (let i = pos; i < endPos; i++)usedChars[i] =
|
||||
true; totalMatched += wordLower.length; foundMatch = true; break
|
||||
} startPos = pos + 1
|
||||
}
|
||||
} return totalMatched / totalQueryLength * 100
|
||||
} search(query, minRatio = 50, minSimilarity = 0) {
|
||||
if (!this.isLoaded || !this.db || this.db.length === 0) {
|
||||
return {
|
||||
status: "success",
|
||||
data: [],
|
||||
count: 0,
|
||||
message: "数据库未加载或为空",
|
||||
suggestions: [
|
||||
"请等待数据库加载完成",
|
||||
"如果问题持续,请刷新页面"
|
||||
]
|
||||
};
|
||||
}
|
||||
const startTime = performance.now();
|
||||
query = query.toLowerCase();
|
||||
const hasSpaces = query.includes(" ") || query.includes("%20");
|
||||
const queryWords = hasSpaces ? query.replace(/%20/g, " ").split(/\s+/).filter(Boolean) : [query];
|
||||
const filteredBySimiliarity = this.db.filter(item => item.s >= minSimilarity);
|
||||
|
||||
if (window.Worker && queryWords.length > 1) return new Promise(resolve => {
|
||||
const worker = new Worker("search_worker.js");
|
||||
worker.onmessage = e => {
|
||||
const workerResults = e.data;
|
||||
worker.terminate();
|
||||
|
||||
if (workerResults.status === "success") {
|
||||
if (workerResults.data.length === 0) {
|
||||
resolve({
|
||||
status: "success",
|
||||
data: [],
|
||||
count: 0,
|
||||
message: `未找到与 '${query}' 匹配的结果`,
|
||||
suggestions: [
|
||||
"检查输入是否正确",
|
||||
`尝试降低最小匹配率(当前:${minRatio}%)`,
|
||||
`尝试降低最小相似度(当前:${minSimilarity})`,
|
||||
"尝试使用更简短的关键词"
|
||||
]
|
||||
});
|
||||
} else {
|
||||
resolve(workerResults);
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
status: "error",
|
||||
data: [],
|
||||
count: 0,
|
||||
message: workerResults.message || "搜索失败",
|
||||
suggestions: ["请重试"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (error) => {
|
||||
console.error("Worker error:", error);
|
||||
resolve({
|
||||
status: "error",
|
||||
message: "Search failed",
|
||||
data: [],
|
||||
count: 0
|
||||
});
|
||||
};
|
||||
|
||||
worker.postMessage({ db: filteredBySimiliarity, queryWords, minRatio });
|
||||
});
|
||||
|
||||
const results = filteredBySimiliarity.map(item => {
|
||||
const matchRatio = hasSpaces ? this.multiWordLcsRatio(queryWords, item.x) : this.lcsRatio(query, item.x);
|
||||
return {
|
||||
matchRatio,
|
||||
item,
|
||||
exactMatch: queryWords.every(word => item.x.toLowerCase().includes(word))
|
||||
};
|
||||
}).filter(result => result.matchRatio >= minRatio);
|
||||
|
||||
results.sort((a, b) => {
|
||||
if (b.matchRatio !== a.matchRatio) return b.matchRatio - a.matchRatio;
|
||||
return b.exactMatch - a.exactMatch;
|
||||
});
|
||||
|
||||
const apiResults = results.map(result => ({
|
||||
filename: result.item.f,
|
||||
timestamp: result.item.t,
|
||||
similarity: result.item.s,
|
||||
text: result.item.x,
|
||||
match_ratio: result.matchRatio,
|
||||
exact_match: result.exactMatch
|
||||
}));
|
||||
|
||||
if (apiResults.length === 0) {
|
||||
return {
|
||||
status: "success",
|
||||
data: [{ // 包装在数组中
|
||||
status: "success",
|
||||
data: [],
|
||||
count: 0,
|
||||
folder: "subtitle",
|
||||
max_results: "unlimited",
|
||||
message: `未找到与 '${query}' 匹配的结果`,
|
||||
suggestions: [
|
||||
"检查输入是否正确",
|
||||
`尝试降低最小匹配率(当前:${minRatio}%)`,
|
||||
`尝试降低最小相似度(当前:${minSimilarity})`,
|
||||
"尝试使用更简短的关键词"
|
||||
]
|
||||
}],
|
||||
count: 1 // 数组长度为1
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
data: apiResults,
|
||||
count: apiResults.length
|
||||
};
|
||||
}
|
||||
}
|
||||
window.dbDebug = { clearCache: async () => { try { await window.subtitleDB.clearCache() } catch (error) { } }, info: () => { window.subtitleDB.printDebugInfo() }, reload: async () => { try { await window.subtitleDB.load() } catch (error) { } }, help: () => { } }; window.subtitleDB = new SubtitleDatabase; fetch("https://vvdb.cicada000.work/subtitle_db", {
|
||||
method: "HEAD",
|
||||
referrerPolicy: 'no-referrer',
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
}).then(response => {
|
||||
if (response.ok) window.subtitleDB.load().catch(error => { })
|
||||
}).catch(error => { });
|
||||
Reference in New Issue
Block a user