@@ -39,6 +39,13 @@ ipcMain.handle('transcribe-local', async (_event, { audioData, modelPath, langua
3939 }
4040} ) ;
4141
42+ // IPC Handler: Abort Local Whisper
43+ ipcMain . handle ( 'local-whisper-abort' , async ( ) => {
44+ console . log ( '[Main] Aborting all local whisper processes' ) ;
45+ localWhisperService . abort ( ) ;
46+ return { success : true } ;
47+ } ) ;
48+
4249// IPC Handler: Select Whisper Model
4350ipcMain . handle ( 'select-whisper-model' , async ( ) => {
4451 try {
@@ -70,6 +77,32 @@ ipcMain.handle('select-whisper-model', async () => {
7077 }
7178} ) ;
7279
80+ // IPC Handler: Save Subtitle Dialog
81+ ipcMain . handle ( 'save-subtitle-dialog' , async ( _event , defaultName : string , content : string , format : 'srt' | 'ass' ) => {
82+ try {
83+ const result = await dialog . showSaveDialog ( {
84+ title : '保存字幕文件' ,
85+ defaultPath : defaultName ,
86+ filters : [
87+ { name : format . toUpperCase ( ) + ' 字幕' , extensions : [ format ] } ,
88+ { name : '所有文件' , extensions : [ '*' ] }
89+ ]
90+ } ) ;
91+
92+ if ( ! result . canceled && result . filePath ) {
93+ // Ensure Windows line endings
94+ const windowsContent = content . replace ( / \r \n / g, '\n' ) . replace ( / \n / g, '\r\n' ) ;
95+ const bom = '\uFEFF' ; // UTF-8 BOM
96+ await fs . promises . writeFile ( result . filePath , bom + windowsContent , 'utf-8' ) ;
97+ return { success : true , path : result . filePath } ;
98+ }
99+ return { success : false , canceled : true } ;
100+ } catch ( error : any ) {
101+ console . error ( '[Main] Save subtitle failed:' , error ) ;
102+ return { success : false , error : error . message } ;
103+ }
104+ } ) ;
105+
73106// IPC Handler: Save Logs Dialog
74107ipcMain . handle ( 'save-logs-dialog' , async ( _event , content : string ) => {
75108 try {
@@ -129,6 +162,17 @@ ipcMain.handle('read-extracted-audio', async (_event, audioPath: string) => {
129162 }
130163} ) ;
131164
165+ // IPC Handler: 读取音频文件 (Fallback)
166+ ipcMain . handle ( 'read-audio-file' , async ( _event , filePath : string ) => {
167+ try {
168+ const buffer = await fs . promises . readFile ( filePath ) ;
169+ return buffer . buffer ;
170+ } catch ( error : any ) {
171+ console . error ( '[Main] Failed to read audio file:' , error ) ;
172+ return { success : false , error : error . message } ;
173+ }
174+ } ) ;
175+
132176// IPC Handler: 清理临时音频文件
133177ipcMain . handle ( 'cleanup-temp-audio' , async ( _event , audioPath : string ) => {
134178 try {
@@ -178,8 +222,39 @@ const originalConsoleError = console.error;
178222
179223function addLog ( message : string ) {
180224 const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
181- const logLine = `[${ timestamp } ] ${ message } ` ;
182- originalConsoleLog ( logLine ) ; // Use original console log to avoid recursion
225+
226+ // Parse log level from message prefix
227+ let level : 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' = 'INFO' ;
228+ let cleanMessage = message ;
229+
230+ if ( message . startsWith ( '[DEBUG]' ) ) {
231+ level = 'DEBUG' ;
232+ cleanMessage = message . substring ( 7 ) . trim ( ) ; // Remove '[DEBUG]'
233+ } else if ( message . startsWith ( '[INFO]' ) ) {
234+ level = 'INFO' ;
235+ cleanMessage = message . substring ( 6 ) . trim ( ) ; // Remove '[INFO]'
236+ } else if ( message . startsWith ( '[WARN]' ) ) {
237+ level = 'WARN' ;
238+ cleanMessage = message . substring ( 6 ) . trim ( ) ; // Remove '[WARN]'
239+ } else if ( message . startsWith ( '[ERROR]' ) ) {
240+ level = 'ERROR' ;
241+ cleanMessage = message . substring ( 7 ) . trim ( ) ; // Remove '[ERROR]'
242+ }
243+
244+ // Format with level prefix for UI
245+ const logLine = `[${ timestamp } ] [${ level } ] ${ cleanMessage } ` ;
246+
247+ // Use original console to avoid recursion, but use appropriate level
248+ if ( level === 'DEBUG' ) {
249+ originalConsoleLog ( logLine ) ;
250+ } else if ( level === 'INFO' ) {
251+ originalConsoleLog ( logLine ) ;
252+ } else if ( level === 'WARN' ) {
253+ originalConsoleWarn ( logLine ) ;
254+ } else if ( level === 'ERROR' ) {
255+ originalConsoleError ( logLine ) ;
256+ }
257+
183258 BrowserWindow . getAllWindows ( ) . forEach ( ( win ) => {
184259 win . webContents . send ( 'new-log' , logLine ) ;
185260 } ) ;
@@ -208,6 +283,13 @@ const createWindow = () => {
208283 width : 1200 ,
209284 height : 800 ,
210285 icon : path . join ( __dirname , '../resources/icon.png' ) ,
286+ backgroundColor : '#1a0f2e' , // 深紫色背景,与主界面协调
287+ titleBarStyle : 'hidden' , // 必须设置为hidden才能使用titleBarOverlay
288+ titleBarOverlay : {
289+ color : '#1a0f2e' , // 深紫色标题栏
290+ symbolColor : '#ffffff' , // 白色控制按钮
291+ height : 33
292+ } ,
211293 webPreferences : {
212294 preload : path . join ( __dirname , '../dist-electron/preload.cjs' ) ,
213295 nodeIntegration : false ,
@@ -286,7 +368,12 @@ const createMenu = () => {
286368 }
287369
288370 const menu = Menu . buildFromTemplate ( template ) ;
289- Menu . setApplicationMenu ( menu ) ;
371+ // 只在开发环境显示菜单栏,生产环境隐藏以避免白色菜单栏的视觉违和感
372+ if ( ! app . isPackaged ) {
373+ Menu . setApplicationMenu ( menu ) ;
374+ } else {
375+ Menu . setApplicationMenu ( null ) ;
376+ }
290377} ;
291378
292379app . on ( 'ready' , async ( ) => {
0 commit comments