fixed bugs
This commit is contained in:
@@ -139,6 +139,6 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script src="/static/admin.js?v=admin-sentence-batch-v1" defer></script>
|
||||
<script src="/static/admin.js?v=admin-sentence-batch-v3" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,6 +11,7 @@ const state = {
|
||||
sentenceBaseMeta: "",
|
||||
sentencePending: false,
|
||||
sentenceSaving: false,
|
||||
sentenceNeedsProcessing: false,
|
||||
};
|
||||
|
||||
const elements = {
|
||||
@@ -335,6 +336,7 @@ async function openSentences(video) {
|
||||
}));
|
||||
state.sentencePending = false;
|
||||
state.sentenceSaving = false;
|
||||
state.sentenceNeedsProcessing = false;
|
||||
const durationMs = detail.video.duration_ms || 0;
|
||||
const sentences = state.sentenceDraft;
|
||||
const gaps = collectSentenceGaps(sentences, durationMs);
|
||||
@@ -357,6 +359,7 @@ function renderSentenceDraft(video) {
|
||||
elements.sentenceMeta.textContent = [
|
||||
state.sentenceBaseMeta,
|
||||
state.sentencePending ? "有暂存修改" : "",
|
||||
state.sentenceNeedsProcessing ? "已入库待处理" : "",
|
||||
state.sentenceSaving ? "正在处理" : "",
|
||||
].filter(Boolean).join(" / ");
|
||||
elements.repairGaps.disabled = gaps.length === 0 || state.sentencePending;
|
||||
@@ -379,12 +382,19 @@ function renderSentenceDraft(video) {
|
||||
|
||||
async function handleSentenceDialogClose() {
|
||||
if (!state.sentenceDraft || state.sentenceSaving) return;
|
||||
if (!state.sentencePending) {
|
||||
if (!state.sentencePending && !state.sentenceNeedsProcessing) {
|
||||
resetSentenceDraft();
|
||||
return;
|
||||
}
|
||||
const saved = await flushSentenceDraft();
|
||||
if (!saved) {
|
||||
if (state.sentencePending) {
|
||||
const persisted = await saveSentenceDraft({ processTimings: false });
|
||||
if (!persisted) {
|
||||
if (state.sentenceVideo && !elements.dialog.open) elements.dialog.showModal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
const processed = await saveSentenceDraft({ processTimings: true });
|
||||
if (!processed) {
|
||||
if (state.sentenceVideo && !elements.dialog.open) elements.dialog.showModal();
|
||||
return;
|
||||
}
|
||||
@@ -392,15 +402,23 @@ async function handleSentenceDialogClose() {
|
||||
loadVideos();
|
||||
}
|
||||
|
||||
async function flushSentenceDraft() {
|
||||
async function saveSentenceDraft({ processTimings = false } = {}) {
|
||||
const video = state.sentenceVideo;
|
||||
if (!video || !state.sentencePending) return true;
|
||||
if (!video) return false;
|
||||
if (!processTimings && !state.sentencePending) return true;
|
||||
if (processTimings && !state.sentencePending && !state.sentenceNeedsProcessing) return true;
|
||||
materializeSentenceDraftSplits(video);
|
||||
state.sentenceSaving = true;
|
||||
renderSentenceDraft(video);
|
||||
try {
|
||||
const body = {
|
||||
sentences: state.sentenceDraft.map(({ index, zh_text, ...sentence }) => sentence),
|
||||
process_timings: processTimings,
|
||||
sentences: state.sentenceDraft.map(({
|
||||
index,
|
||||
originStartMs,
|
||||
originEndMs,
|
||||
...sentence
|
||||
}) => sentence),
|
||||
};
|
||||
const saved = await api(
|
||||
`/api/v1/admin/videos/${video.video_hash}/sentences/batch`,
|
||||
@@ -410,11 +428,26 @@ async function flushSentenceDraft() {
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
state.sentenceDraft = saved.map((sentence) => ({ ...sentence }));
|
||||
const previousByRange = new Map(
|
||||
state.sentenceDraft.map((sentence) => [`${sentence.start_ms}:${sentence.end_ms}`, sentence]),
|
||||
);
|
||||
state.sentenceDraft = saved.map((sentence) => {
|
||||
const previous = previousByRange.get(`${sentence.start_ms}:${sentence.end_ms}`);
|
||||
return {
|
||||
...sentence,
|
||||
originStartMs: previous?.originStartMs ?? sentence.start_ms,
|
||||
originEndMs: previous?.originEndMs ?? sentence.end_ms,
|
||||
sourceStartMs: previous?.sourceStartMs,
|
||||
sourceEndMs: previous?.sourceEndMs,
|
||||
};
|
||||
});
|
||||
state.sentencePending = false;
|
||||
state.sentenceNeedsProcessing = !processTimings;
|
||||
return true;
|
||||
} catch (error) {
|
||||
elements.serviceState.textContent = `句子批量处理失败: ${error.message}`;
|
||||
elements.serviceState.textContent = processTimings
|
||||
? `句子批量处理失败: ${error.message}`
|
||||
: `句子入库失败: ${error.message}`;
|
||||
return false;
|
||||
} finally {
|
||||
state.sentenceSaving = false;
|
||||
@@ -434,6 +467,7 @@ function resetSentenceDraft() {
|
||||
state.sentenceDraft = null;
|
||||
state.sentencePending = false;
|
||||
state.sentenceSaving = false;
|
||||
state.sentenceNeedsProcessing = false;
|
||||
state.sentenceVideo = null;
|
||||
state.sentenceBaseMeta = "";
|
||||
}
|
||||
@@ -592,39 +626,64 @@ function sentenceRow(video, sentence, totalCount) {
|
||||
const languageContainer = document.createElement("td");
|
||||
languageContainer.append(language);
|
||||
const saveContainer = document.createElement("td");
|
||||
const save = button("保存", () => {
|
||||
if (/\r?\n/.test(sentence.text || "")) {
|
||||
splitDraftSentence(video, sentence, sentence.text, language.value || null);
|
||||
return;
|
||||
const save = button("保存", async () => {
|
||||
save.disabled = true;
|
||||
save.textContent = "保存中";
|
||||
try {
|
||||
if (/\r?\n/.test(sentence.text || "")) {
|
||||
splitDraftSentence(video, sentence, sentence.text, language.value || null);
|
||||
} else {
|
||||
sentence.text = textArea.value;
|
||||
sentence.language = language.value || null;
|
||||
sentence.zh_text = null;
|
||||
markSentenceDraftChanged(video);
|
||||
}
|
||||
await saveSentenceDraft({ processTimings: false });
|
||||
} finally {
|
||||
save.disabled = false;
|
||||
save.textContent = "保存";
|
||||
}
|
||||
markSentenceDraftChanged(video);
|
||||
});
|
||||
saveContainer.append(save);
|
||||
if (sentence.index < totalCount - 1) {
|
||||
const merge = button("合并下句", () => {
|
||||
const preview = `${(textArea.value || sentence.text || "").trim()} + 下一句`;
|
||||
const merge = button("合并下句", async () => {
|
||||
const preview = `${(sentence.text || "").trim()} + 下一句`;
|
||||
const confirmed = window.confirm(`确定将第 ${sentence.index + 1} 句与下一句合并成一句吗?\n${preview}`);
|
||||
if (!confirmed) return;
|
||||
const next = state.sentenceDraft[sentence.index + 1];
|
||||
const mergedLanguage = sentence.language || next.language;
|
||||
const merged = {
|
||||
index: sentence.index,
|
||||
start_ms: sentence.start_ms,
|
||||
end_ms: next.end_ms,
|
||||
text: joinDraftSentenceText(sentence.text, next.text, mergedLanguage),
|
||||
language: mergedLanguage,
|
||||
zh_text: null,
|
||||
reference_speech_duration_ms: Math.max(
|
||||
1,
|
||||
(sentence.reference_speech_duration_ms || 1) +
|
||||
(next.reference_speech_duration_ms || 1),
|
||||
),
|
||||
sourceStartMs: undefined,
|
||||
sourceEndMs: undefined,
|
||||
};
|
||||
state.sentenceDraft.splice(sentence.index, 2, merged);
|
||||
renumberSentenceDraft();
|
||||
markSentenceDraftChanged(video);
|
||||
const sourceStart = sentence.start_ms;
|
||||
const sourceEnd = sentence.end_ms;
|
||||
merge.disabled = true;
|
||||
merge.textContent = "入库中";
|
||||
try {
|
||||
if (state.sentencePending) {
|
||||
const persisted = await saveSentenceDraft({ processTimings: false });
|
||||
if (!persisted) return;
|
||||
}
|
||||
const targetIndex = state.sentenceDraft.findIndex(
|
||||
(item) => item.start_ms === sourceStart && item.end_ms === sourceEnd,
|
||||
);
|
||||
if (targetIndex < 0) throw new Error("找不到要合并的句子");
|
||||
const merged = await api(
|
||||
`/api/v1/admin/videos/${video.video_hash}/sentences/${targetIndex}/merge`,
|
||||
{ method: "PUT" },
|
||||
);
|
||||
state.sentenceDraft.splice(targetIndex, 2, {
|
||||
...merged,
|
||||
originStartMs: merged.start_ms,
|
||||
originEndMs: merged.end_ms,
|
||||
sourceStartMs: undefined,
|
||||
sourceEndMs: undefined,
|
||||
});
|
||||
renumberSentenceDraft();
|
||||
state.sentencePending = false;
|
||||
state.sentenceNeedsProcessing = true;
|
||||
} catch (error) {
|
||||
elements.serviceState.textContent = `句子合并失败: ${error.message}`;
|
||||
} finally {
|
||||
merge.disabled = false;
|
||||
merge.textContent = "合并下句";
|
||||
renderSentenceDraft(video);
|
||||
}
|
||||
});
|
||||
saveContainer.append(merge);
|
||||
}
|
||||
@@ -651,7 +710,7 @@ function boundaryControl(video, sentence) {
|
||||
const status = document.createElement("span");
|
||||
status.className = "boundary-status";
|
||||
|
||||
function adjust(deltaMs) {
|
||||
async function adjust(deltaMs) {
|
||||
const next = state.sentenceDraft[sentence.index + 1];
|
||||
if (!next) return;
|
||||
const nextBoundary = sentence.end_ms + deltaMs;
|
||||
@@ -669,6 +728,7 @@ function boundaryControl(video, sentence) {
|
||||
next.sourceStartMs = undefined;
|
||||
next.sourceEndMs = undefined;
|
||||
markSentenceDraftChanged(video);
|
||||
await saveSentenceDraft({ processTimings: false });
|
||||
}
|
||||
|
||||
const minus = button("−0.1", (event) => adjust(event.shiftKey ? -10 : -100));
|
||||
|
||||
Reference in New Issue
Block a user