fff
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
const GAP_REPAIR_MIN_MS = 8000;
|
||||
const CJK_RE = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/;
|
||||
|
||||
const state = {
|
||||
adminKey: localStorage.getItem("oralTrainerAdminKey") || "",
|
||||
@@ -27,11 +28,13 @@ const elements = {
|
||||
sentenceRows: document.querySelector("#sentence-rows"),
|
||||
closeDialog: document.querySelector("#close-dialog"),
|
||||
repairGaps: document.querySelector("#repair-gaps"),
|
||||
repairMissingZh: document.querySelector("#repair-missing-zh"),
|
||||
retranslateDialog: document.querySelector("#retranslate-dialog"),
|
||||
retranslateTitle: document.querySelector("#retranslate-title"),
|
||||
retranslateMeta: document.querySelector("#retranslate-meta"),
|
||||
retranslateForm: document.querySelector("#retranslate-form"),
|
||||
retranslateTerms: document.querySelector("#retranslate-terms"),
|
||||
repairTranslations: document.querySelector("#repair-translations"),
|
||||
closeRetranslateDialog: document.querySelector("#close-retranslate-dialog"),
|
||||
userRows: document.querySelector("#user-rows"),
|
||||
userCount: document.querySelector("#user-count"),
|
||||
@@ -52,7 +55,13 @@ elements.closeDialog.addEventListener("click", () => elements.dialog.close());
|
||||
elements.repairGaps.addEventListener("click", () => {
|
||||
if (state.sentenceVideo) repairGaps(state.sentenceVideo, { reloadSentences: true });
|
||||
});
|
||||
elements.repairMissingZh.addEventListener("click", () => {
|
||||
if (state.sentenceVideo) repairMissingTranslations(state.sentenceVideo, { reloadSentences: true });
|
||||
});
|
||||
elements.closeRetranslateDialog.addEventListener("click", () => elements.retranslateDialog.close());
|
||||
elements.repairTranslations.addEventListener("click", () => {
|
||||
if (state.retranslateVideo) repairMissingTranslations(state.retranslateVideo, { closeRetranslate: true });
|
||||
});
|
||||
elements.retranslateForm.addEventListener("submit", submitRetranslate);
|
||||
elements.uploadForm.addEventListener("submit", uploadVideo);
|
||||
elements.videoFile.addEventListener("change", () => {
|
||||
@@ -295,9 +304,12 @@ async function openSentences(video) {
|
||||
const sentences = detail.boundaries?.sentences || [];
|
||||
const durationMs = detail.video.duration_ms || 0;
|
||||
const gaps = collectSentenceGaps(sentences, durationMs);
|
||||
const missingZh = sentences.filter(translationNeedsRepair).length;
|
||||
const gapLabel = gaps.length ? ` / ${gaps.length} 段空白待修复` : "";
|
||||
elements.sentenceMeta.textContent = `${sentences.length} 句${gapLabel} / ${detail.video.video_hash}`;
|
||||
const zhLabel = missingZh ? ` / ${missingZh} 句未译` : "";
|
||||
elements.sentenceMeta.textContent = `${sentences.length} 句${gapLabel}${zhLabel} / ${detail.video.video_hash}`;
|
||||
elements.repairGaps.disabled = gaps.length === 0;
|
||||
elements.repairMissingZh.disabled = missingZh === 0;
|
||||
elements.sentenceRows.replaceChildren();
|
||||
let gapIndex = 0;
|
||||
for (const sentence of sentences) {
|
||||
@@ -345,8 +357,18 @@ function gapRow(gap) {
|
||||
return row;
|
||||
}
|
||||
|
||||
function translationNeedsRepair(sentence) {
|
||||
const source = (sentence.text || "").trim();
|
||||
const zh = (sentence.zh_text || "").trim();
|
||||
if (!source) return false;
|
||||
if (!zh) return true;
|
||||
if (CJK_RE.test(zh)) return false;
|
||||
return !CJK_RE.test(source);
|
||||
}
|
||||
|
||||
function sentenceRow(video, sentence, totalCount) {
|
||||
const row = document.createElement("tr");
|
||||
if (translationNeedsRepair(sentence)) row.className = "missing-zh-row";
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = sentence.text || "";
|
||||
const language = document.createElement("select");
|
||||
@@ -360,7 +382,9 @@ function sentenceRow(video, sentence, totalCount) {
|
||||
const textContainer = document.createElement("td");
|
||||
const zhNote = document.createElement("div");
|
||||
zhNote.className = "zh-text";
|
||||
zhNote.textContent = sentence.zh_text || "暂无中文翻译";
|
||||
zhNote.textContent = translationNeedsRepair(sentence)
|
||||
? (sentence.zh_text ? `仍是英文:${sentence.zh_text}` : "暂无中文翻译")
|
||||
: sentence.zh_text;
|
||||
textContainer.append(textArea, zhNote);
|
||||
const languageContainer = document.createElement("td");
|
||||
languageContainer.append(language);
|
||||
@@ -479,8 +503,12 @@ async function openRetranslate(video) {
|
||||
try {
|
||||
const payload = await api(`/api/v1/admin/videos/${video.video_hash}/subtitle-translation`);
|
||||
elements.retranslateTerms.value = formatTranslationTerms(payload.terms || []);
|
||||
const status = payload.complete ? "中文字幕已就绪" : (payload.status === "running" ? "正在翻译" : "尚未翻译完成");
|
||||
const missing = payload.missing_count || 0;
|
||||
const status = missing
|
||||
? `${missing} 句未译`
|
||||
: (payload.complete ? "中文字幕已就绪" : (payload.status === "running" ? "正在翻译" : "尚未翻译完成"));
|
||||
elements.retranslateMeta.textContent = `${status} / ${video.video_hash}`;
|
||||
elements.repairTranslations.disabled = missing === 0;
|
||||
} catch (error) {
|
||||
elements.serviceState.textContent = `加载翻译信息失败: ${error.message}`;
|
||||
}
|
||||
@@ -540,6 +568,39 @@ async function submitRetranslate(event) {
|
||||
}
|
||||
}
|
||||
|
||||
async function repairMissingTranslations(video, options = {}) {
|
||||
const confirmed = window.confirm(
|
||||
`将只补译“${video.title}”里缺失或仍是英文的中文,已有中文会保留。继续?`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
const buttons = [elements.repairMissingZh, elements.repairTranslations].filter(Boolean);
|
||||
buttons.forEach((button) => { button.disabled = true; });
|
||||
elements.serviceState.textContent = "正在补译缺失的中文…";
|
||||
try {
|
||||
const payload = await api(`/api/v1/admin/videos/${video.video_hash}/subtitle-translation/repair`, {
|
||||
method: "POST",
|
||||
});
|
||||
const attempted = payload.attempted_count || 0;
|
||||
if (!attempted) {
|
||||
elements.serviceState.textContent = "没有需要补译的句子";
|
||||
} else if (payload.complete) {
|
||||
elements.serviceState.textContent = `补译完成:处理了 ${attempted} 句`;
|
||||
} else {
|
||||
const remaining = payload.missing_count ? `,还剩 ${payload.missing_count} 句` : "";
|
||||
elements.serviceState.textContent = `已开始补译 ${attempted} 句${remaining},请稍后刷新`;
|
||||
}
|
||||
if (options.closeRetranslate) elements.retranslateDialog.close();
|
||||
await loadVideos();
|
||||
if (options.reloadSentences) await openSentences(video);
|
||||
} catch (error) {
|
||||
elements.serviceState.textContent = `补译失败: ${error.message}`;
|
||||
} finally {
|
||||
if (!options.reloadSentences) {
|
||||
buttons.forEach((button) => { button.disabled = false; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reprocess(videoHash) {
|
||||
try {
|
||||
await api(`/api/v1/admin/videos/${videoHash}/process`, { method: "POST" });
|
||||
|
||||
Reference in New Issue
Block a user