feat: 增加一个查找语音文件

This commit is contained in:
Wxw-Gu
2026-08-28 15:04:37 +08:00
parent a007e110ac
commit 698251796f
4 changed files with 60 additions and 0 deletions
+45
View File
@@ -1336,6 +1336,51 @@ app.whenReady().then(async () => {
}
)
ipcMain.handle(
'voice:showSourceContextMenu',
(
event,
reference: { sessionId: string; localId: number; createTime: number; svrId?: string | number }
) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (!window || !voiceService) return { success: false, error: '语音服务未初始化' }
const revealSource = async (): Promise<void> => {
try {
const result = await voiceService!.resolveSource(
reference.sessionId,
reference.localId,
reference.createTime,
reference.svrId
)
if (!result.success) throw new Error(result.error)
const directory = join(app.getPath('userData'), 'voice-sources')
await fsPromises.mkdir(directory, { recursive: true })
const fileName = `voice_${reference.localId}_${reference.createTime}_${result.source.sourceHash.slice(0, 12)}.silk`
const filePath = join(directory, fileName)
await fsPromises.writeFile(filePath, result.source.data)
shell.showItemInFolder(filePath)
} catch (error) {
await dialog.showMessageBox(window, {
type: 'error',
title: '查找语音源文件失败',
message: '无法提取这条语音的源文件',
detail: error instanceof Error ? error.message : String(error)
})
}
}
Menu.buildFromTemplate([
{
label: '查找语音源文件',
click: () => void revealSource()
}
]).popup({ window })
return { success: true }
}
)
ipcMain.handle('voice:getModelStatus', async () => {
if (!voiceRecognition) throw new Error('Voice recognition is not initialized')
return voiceRecognition.getModelStatus()
+3
View File
@@ -307,6 +307,9 @@ declare global {
createTime: number,
svrId?: string | number
) => Promise<{ success: boolean; data?: string; error?: string }>
showVoiceSourceContextMenu: (
reference: VoiceMessageReference
) => Promise<{ success: boolean; error?: string }>
getVoiceModelStatus: () => Promise<VoiceModelStatus>
downloadVoiceModel: () => Promise<VoiceModelDownloadResult>
cancelVoiceModelDownload: () => Promise<{ success: boolean }>
+2
View File
@@ -178,6 +178,8 @@ const api = {
copyImage: (base64String) => ipcRenderer.invoke('copy-image', base64String),
getVoiceData: (sessionId: string, localId: number, createTime: number, svrId?: string | number) =>
ipcRenderer.invoke('db:getVoiceData', sessionId, localId, createTime, svrId),
showVoiceSourceContextMenu: (reference: VoiceMessageReference) =>
ipcRenderer.invoke('voice:showSourceContextMenu', reference),
getVoiceModelStatus: (): Promise<VoiceModelStatus> => ipcRenderer.invoke('voice:getModelStatus'),
downloadVoiceModel: (): Promise<VoiceModelDownloadResult> =>
ipcRenderer.invoke('voice:downloadModel'),
@@ -193,6 +193,15 @@ export function VoicePlayer({
window.dispatchEvent(new Event('wxe:open-voice-recognition-settings'))
}, [])
const handleContextMenu = useCallback(
(event: ReactMouseEvent<HTMLDivElement>) => {
event.preventDefault()
event.stopPropagation()
void window.api.showVoiceSourceContextMenu(voiceReference)
},
[voiceReference]
)
const formatDuration = (seconds: number | undefined): string => {
if (!seconds || !isFinite(seconds)) return '0:00'
const mins = Math.floor(seconds / 60)
@@ -205,6 +214,7 @@ export function VoicePlayer({
<div
className={`voice-message ${loading ? 'voice-loading' : ''} ${error && !audioUrl ? 'voice-error' : ''}`}
onClick={handlePlayPause}
onContextMenu={handleContextMenu}
>
<span className={`voice-icon ${isPlaying ? 'playing' : ''}`}>{isPlaying ? '⏸' : '▶'}</span>
{loading ? (