Skip to content

Commit

Permalink
feat: 开发字幕文件翻译后端(3/3)
Browse files Browse the repository at this point in the history
  • Loading branch information
QiuYeDx committed Feb 16, 2025
1 parent 71eeaed commit 5e22504
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 23 deletions.
2 changes: 1 addition & 1 deletion electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async function createWindow() {
// remove the default titlebar
titleBarStyle: "hidden",
// expose window controlls in Windows/Linux
...(process.platform !== "darwin" ? { titleBarOverlay: true } : {}),
// ...(process.platform !== "darwin" ? { titleBarOverlay: true } : {}), // TODO: 临时关闭 Windows 上的右上角操作按钮
webPreferences: {
preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
Expand Down
23 changes: 18 additions & 5 deletions electron/main/translation/class/base-translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export abstract class BaseTranslator {
): string;

// 新增重试配置(子类可覆盖)
protected maxRetries = 3;
// TODO: 未来做成可配置的
protected maxRetries = 5;
protected retryDelay = 1000;

async translate(task: SubtitleTranslatorTask, signal?: AbortSignal) {
Expand Down Expand Up @@ -62,6 +63,18 @@ export abstract class BaseTranslator {
this.updateProgress(task, fragments.length, fragments.length);

} catch (error) {
// 获取主窗口并发送消息
const mainWindow = BrowserWindow.getAllWindows()[0];
if (mainWindow) {
mainWindow.webContents.send("task-failed", {
fileName: task.fileName,
error: error instanceof Error ? error.message : '未知错误',
message: '请求接口失败' // 显示 toast 的信息
});
} else {
console.error("未找到主窗口,无法发送进度更新");
}

console.error("翻译过程出错:", error);
throw error;
}
Expand Down Expand Up @@ -166,7 +179,7 @@ export abstract class BaseTranslator {
}
],
max_tokens: 3500,
temperature: 0.3
// temperature: 0.3
},
{
headers: {
Expand All @@ -184,9 +197,9 @@ export abstract class BaseTranslator {
return this.parseResponse(response.data);

} catch (error) {
if (signal?.aborted) {
throw new DOMException("Aborted", "AbortError");
}
// if (signal?.aborted) {
// throw new DOMException("Aborted", "AbortError");
// }

console.error(`第 ${attempt} 次翻译尝试失败:`, error);

Expand Down
4 changes: 2 additions & 2 deletions electron/main/translation/translation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export class TranslationService {

try {
const translator = this.getTranslator(
task.fileName.split(".")[1].toUpperCase() as SubtitleFileType,
task.fileName.split(".").at(-1)?.toUpperCase() as SubtitleFileType,
{
apiKey: task.apiKey,
apiModel: task.apiModel,
endpoint: task.endPoint,
}
);
console.info(">>> [processTask] task: ", task);
console.info(">>> [processTask] task: ", task, translator);
await translator.translate(task, controller.signal);
return { status: "completed" };
} catch (error) {
Expand Down
14 changes: 7 additions & 7 deletions src/pages/Setting/components/ModelConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function ModelConfig() {
</div>

{/* 模型 apiKey 输入 */}
<label className="form-control w-full max-w-xs">
<label className="form-control w-full max-w-2xl">
<div className="label mt-1 -mb-1">
<span className="label-text">{t("setting:fields.apikey")}</span>
<span className="label-text-alt">
Expand All @@ -77,13 +77,13 @@ function ModelConfig() {
placeholder={t("setting:placeholder.apikey")}
value={apiKeyMap[model]}
onChange={handleApiKeyChange}
className="input input-sm input-bordered box-border w-full max-w-xs"
className="input input-sm input-bordered box-border w-full max-w-2xl"
/>
</label>

{/* 自定义模型 URL 输入 */}
{model === Model.Other && (
<label className="form-control w-full max-w-xs">
<label className="form-control w-full max-w-2xl">
<div className="label mt-1 -mb-1 shrink-0">
<span className="label-text">{t("setting:fields.model_url")}</span>
<span className="label-text-alt">
Expand All @@ -92,18 +92,18 @@ function ModelConfig() {
</div>
<input
type="text"
placeholder={t("setting:placeholder.model_url")}
placeholder={t("setting:placeholder.model_url") + '(https://.../v1/chat/completions)'}
value={modelUrlMap[model]}
disabled={[Model.DeepSeek, Model.OpenAI].includes(model)}
onChange={handleModelUrlChange}
className="input input-sm input-bordered box-border w-full max-w-xs shrink-0"
className="input input-sm input-bordered box-border w-full max-w-2xl shrink-0"
/>
</label>
)}

{/* 自定义模型 Key 输入 */}
{model === Model.Other && (
<label className="form-control w-full max-w-xs">
<label className="form-control w-full max-w-2xl">
<div className="label mt-1 -mb-1 shrink-0">
<span className="label-text">{t("setting:fields.model_key")}</span>
<span className="label-text-alt">
Expand All @@ -116,7 +116,7 @@ function ModelConfig() {
value={modelKeyMap[model]}
disabled={[Model.DeepSeek, Model.OpenAI].includes(model)}
onChange={handleModelKeyChange}
className="input input-sm input-bordered box-border w-full max-w-xs shrink-0"
className="input input-sm input-bordered box-border w-full max-w-2xl shrink-0"
/>
</label>
)}
Expand Down
8 changes: 4 additions & 4 deletions src/pages/components/BottomNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ const BottomNavigation: React.FC = () => {
});

return (
<div className="fixed bottom-0 left-1/2 -translate-x-1/2 w-full flex justify-center items-center">
<div className="fixed bottom-0 left-1/2 -translate-x-1/2 w-full flex justify-center items-center pointer-events-none">
{/* 底部导航栏 */}
{isMainMenu ? (
// 一级菜单
<animated.ul
style={mainMenuFadeUpProps}
className="my-2 glass mx-2 menu bg-base-200 menu-horizontal rounded-box ring ring-base-100 gap-1 justify-center flex-nowrap"
className="my-2 glass mx-2 menu bg-base-200 menu-horizontal rounded-box ring ring-base-100 gap-1 justify-center flex-nowrap pointer-events-auto"
>
<li>
<a
Expand Down Expand Up @@ -155,7 +155,7 @@ const BottomNavigation: React.FC = () => {
// 二级菜单
<animated.ul
style={subMenuFadeUpProps}
className="my-2 glass mx-2 menu bg-base-200 menu-horizontal rounded-box ring ring-base-100 gap-1 justify-center flex-nowrap"
className="my-2 glass mx-2 menu bg-base-200 menu-horizontal rounded-box ring ring-base-100 gap-1 justify-center flex-nowrap pointer-events-auto"
>
<li>
<a
Expand All @@ -176,7 +176,7 @@ const BottomNavigation: React.FC = () => {
)}

{/* Dark Mode 快捷切换 */}
<div className="absolute right-6">
<div className="absolute right-6 pointer-events-auto">
<label
className={`swap swap-rotate ${isDark ? "" : "swap-active"}`}
onClick={handleToggleDarkMode}
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/subtitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ window.ipcRenderer.on("update-progress", (_, progressData) => {
progressData.progress
);
});

window.ipcRenderer.on("task-failed", (_, errorData) => {
console.info('>>> 收到 task-failed', errorData);
const store = useSubtitleTranslatorStore.getState();
store.addFailedTask(errorData);
});
61 changes: 57 additions & 4 deletions src/store/tools/subtitle/useSubtitleTranslatorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SubtitleTranslatorTask,
TaskStatus,
} from "@/type/subtitle";
import { showToast } from "@/utils/toast";

// 最大并发数
const MAX_CONCURRENCY = 5;
Expand All @@ -32,6 +33,7 @@ interface SubtitleTranslatorStore {
retryTask: (fileName: string) => void;
removeAllResolvedTask: () => void;
startAllTasks: () => void;
addFailedTask: (errorData: { fileName: string, error: string, message: string }) => void;

// TODO: 任务取消
// cancelTask: (fileName: string) => void;
Expand Down Expand Up @@ -169,16 +171,19 @@ const useSubtitleTranslatorStore = create<SubtitleTranslatorStore>((set) => ({
status: TaskStatus.PENDING,
}));

const updatedWaitingTasks = tasksToWait.map((task) => ({
...task,
status: TaskStatus.WAITING,
}));

// 任务启动
updatedTasks.forEach((task) => {
window.ipcRenderer.invoke("translate-subtitle", task);
});

return {
notStartedTaskQueue: state.notStartedTaskQueue.slice(
MAX_CONCURRENCY - pendingTasks
),
waitingTaskQueue: [...state.waitingTaskQueue, ...tasksToWait],
notStartedTaskQueue: [],
waitingTaskQueue: [...state.waitingTaskQueue, ...updatedWaitingTasks],
pendingTaskQueue: [...state.pendingTaskQueue, ...updatedTasks],
};
});
Expand Down Expand Up @@ -216,6 +221,10 @@ const useSubtitleTranslatorStore = create<SubtitleTranslatorStore>((set) => ({
...waitingTask,
status: TaskStatus.PENDING,
};

// 任务启动
window.ipcRenderer.invoke("translate-subtitle", updatedWaitingTask);

return {
waitingTaskQueue: state.waitingTaskQueue.slice(1),
pendingTaskQueue: [...state.pendingTaskQueue, updatedWaitingTask],
Expand Down Expand Up @@ -246,6 +255,50 @@ const useSubtitleTranslatorStore = create<SubtitleTranslatorStore>((set) => ({
};
});
},

addFailedTask: (errorData: { fileName: string, error: string, message: string }) => {
set((state) => {
const task = state.pendingTaskQueue.find((t) => t.fileName === errorData.fileName);
if (!task) return state;

const updatedTask = {
...task,
status: TaskStatus.FAILED,
extraInfo: {
error: errorData.error,
message: errorData.message,
}
};

showToast(errorData.message, "error");

// 如果等待队列中有任务且并发数未满
if (
state.waitingTaskQueue.length > 0 &&
state.pendingTaskQueue.length < MAX_CONCURRENCY
) {
const waitingTask = state.waitingTaskQueue[0];
const updatedWaitingTask = {
...waitingTask,
status: TaskStatus.PENDING,
};

// 任务启动
window.ipcRenderer.invoke("translate-subtitle", updatedWaitingTask);

return {
waitingTaskQueue: state.waitingTaskQueue.slice(1),
pendingTaskQueue: [...state.pendingTaskQueue.filter((t) => t.fileName !== errorData.fileName), updatedWaitingTask],
failedTaskQueue: [...state.failedTaskQueue, updatedTask],
};
}

return {
pendingTaskQueue: state.pendingTaskQueue.filter((t) => t.fileName !== errorData.fileName),
failedTaskQueue: [...state.failedTaskQueue, updatedTask],
};
});
},
}));

export default useSubtitleTranslatorStore;

0 comments on commit 5e22504

Please sign in to comment.