Completed fine-tuning for several models and prepared the foundational components for an upcoming refactor.

This commit is contained in:
戒酒的李白
2025-08-03 23:08:30 +08:00
parent 67d1888a69
commit 14d8ac6e71
510 changed files with 90517 additions and 404401 deletions
+225
View File
@@ -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 => { });