import { Asr, type AsrRequest, type AsrResponse } from './auc.ts'; import { getConfig, setConfig } from './config.ts'; import { AudioRecorder, RecordingState, type RecorderOptions } from './recorder.ts'; // 创建页面结构 function createPageStructure() { const html = `

音频转文字工具

`; document.body.innerHTML = html; } // 全局变量存储选中的文件 let selectedFile: File | null = null; // 全局变量存储录制器和录制状态 let audioRecorder: AudioRecorder | null = null; let recordedAudioBlob: Blob | null = null; let isUsingRecordedAudio = false; // 文件上传处理函数 function setupFileUpload() { const fileInput = document.getElementById('audioFile') as HTMLInputElement; const uploadBtn = document.getElementById('uploadBtn') as HTMLButtonElement; const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; const fileInfo = document.getElementById('fileInfo') as HTMLDivElement; // 点击上传按钮,触发文件选择 uploadBtn.addEventListener('click', () => { fileInput.click(); }); // 文件选择处理 fileInput.addEventListener('change', (event) => { const target = event.target as HTMLInputElement; const file = target.files?.[0]; if (file) { // 验证文件类型 if (!file.type.startsWith('audio/')) { alert('请选择音频文件!'); return; } // 验证文件大小 (例如限制为100MB) const maxSize = 100 * 1024 * 1024; // 100MB if (file.size > maxSize) { alert('文件大小不能超过100MB!'); return; } selectedFile = file; // 清除录制状态 recordedAudioBlob = null; isUsingRecordedAudio = false; // 显示文件信息 const fileSizeMB = (file.size / 1024 / 1024).toFixed(2); fileInfo.innerHTML = `

已选择文件: ${file.name}

文件大小: ${fileSizeMB} MB

文件类型: ${file.type}

`; // 启用转换按钮 convertBtn.disabled = false; uploadBtn.textContent = '重新选择文件'; } }); } // 将文件转换为base64 function fileToBase64(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = reader.result as string; // 移除data:audio/xxx;base64,前缀,只保留base64数据 const base64Data = result.split(',')[1]; resolve(base64Data); }; reader.onerror = reject; reader.readAsDataURL(file); }); } // 获取音频格式 function getAudioFormat(file: File): string { const mimeType = file.type; if (mimeType.includes('wav')) return 'wav'; if (mimeType.includes('mp3')) return 'mp3'; if (mimeType.includes('ogg')) return 'ogg'; if (mimeType.includes('pcm')) return 'pcm'; if (mimeType.includes('webm')) return 'webm'; // 默认返回wav return 'wav'; } // 从Blob获取音频格式 function getAudioFormatFromBlob(blob: Blob): string { const mimeType = blob.type; if (mimeType.includes('wav')) return 'wav'; if (mimeType.includes('mp3')) return 'mp3'; if (mimeType.includes('ogg')) return 'ogg'; if (mimeType.includes('pcm')) return 'pcm'; if (mimeType.includes('webm')) return 'webm'; // 默认返回webm (录制的音频通常是webm格式) return 'webm'; } // ASR转换功能 async function convertToText() { if (!selectedFile && !recordedAudioBlob) { alert('请先选择音频文件或录制音频!'); return; } const loadingEl = document.getElementById('loading') as HTMLDivElement; const resultEl = document.getElementById('result') as HTMLDivElement; const resultText = document.getElementById('resultText') as HTMLDivElement; const resultDetails = document.getElementById('resultDetails') as HTMLDivElement; const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; try { // 显示加载状态 loadingEl.style.display = 'flex'; resultEl.style.display = 'none'; convertBtn.disabled = true; convertBtn.textContent = '转换中...'; // 获取音频数据 let base64Data: string; let audioFormat: string; if (isUsingRecordedAudio && recordedAudioBlob) { // 使用录制的音频 const recordedFile = AudioRecorder.blobToFile(recordedAudioBlob, 'recording.webm'); base64Data = await fileToBase64(recordedFile); audioFormat = getAudioFormatFromBlob(recordedAudioBlob); } else if (selectedFile) { // 使用选择的文件 base64Data = await fileToBase64(selectedFile); audioFormat = getAudioFormat(selectedFile); } else { throw new Error('没有可用的音频数据'); } const config = getConfig(); const VOLCENGINE_AUC_APPID = config.VOLCENGINE_AUC_APPID; const VOLCENGINE_AUC_TOKEN = config.VOLCENGINE_AUC_TOKEN; // 配置ASR请求 // 注意:这里需要提供真实的API密钥 const asr = new Asr({ appid: VOLCENGINE_AUC_APPID, // 请替换为真实的APP ID token: VOLCENGINE_AUC_TOKEN, // 请替换为真实的ACCESS TOKEN type: 'flash' // 使用flash模式进行快速识别 }); const asrRequest: AsrRequest = { audio: { data: base64Data, format: audioFormat as any, rate: 16000, channel: 1 }, request: { enable_words: true, enable_sentence_info: true, enable_utterance_info: true, enable_punctuation_prediction: true, enable_inverse_text_normalization: true } }; // 调用ASR API const response: AsrResponse = await asr.getText(asrRequest); // 隐藏加载状态 loadingEl.style.display = 'none'; // 显示结果 if (response.result && response.result.text) { resultText.innerHTML = `

${response.result.text}

`; // 显示详细信息 const duration = response.audio_info?.duration ? (response.audio_info.duration / 1000).toFixed(2) + '秒' : '未知'; let detailsHtml = `

音频信息:

时长: ${duration}

`; // 如果有语句级别的信息,显示时间戳 if (response.result.utterances && response.result.utterances.length > 0) { detailsHtml += `

分句结果:

`; } resultDetails.innerHTML = detailsHtml; resultEl.style.display = 'block'; } else { throw new Error('转换失败:未能获取到文字结果'); } } catch (error) { console.error('转换出错:', error); loadingEl.style.display = 'none'; let errorMessage = '转换失败,请稍后重试。'; if (error instanceof Error) { if (error.message.includes('VOLCENGINE_Asr_APPID') || error.message.includes('VOLCENGINE_Asr_TOKEN')) { errorMessage = '请配置正确的API密钥(APP ID和Access Token)后重试。'; } else { errorMessage = `转换失败: ${error.message}`; } } alert(errorMessage); } finally { convertBtn.disabled = false; convertBtn.textContent = '转换为文字'; } } // 设置转换按钮 function setupConvertButton() { const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; convertBtn.addEventListener('click', convertToText); } // 设置弹窗功能 function setupSettingsModal() { const settingsBtn = document.getElementById('settingsBtn') as HTMLButtonElement; const settingsModal = document.getElementById('settingsModal') as HTMLDivElement; const closeModal = document.getElementById('closeModal') as HTMLButtonElement; const saveSettings = document.getElementById('saveSettings') as HTMLButtonElement; const cancelSettings = document.getElementById('cancelSettings') as HTMLButtonElement; const appIdInput = document.getElementById('appId') as HTMLInputElement; const accessTokenInput = document.getElementById('accessToken') as HTMLInputElement; // 打开设置弹窗 function openSettingsModal() { // 加载当前配置 const config = getConfig(); appIdInput.value = config.VOLCENGINE_AUC_APPID; accessTokenInput.value = config.VOLCENGINE_AUC_TOKEN; settingsModal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // 防止背景滚动 } // 关闭设置弹窗 function closeSettingsModal() { settingsModal.style.display = 'none'; document.body.style.overflow = 'auto'; // 恢复滚动 } // 保存设置 function saveSettingsHandler() { const appId = appIdInput.value.trim(); const accessToken = accessTokenInput.value.trim(); if (!appId || !accessToken) { alert('请填写完整的APP ID和Access Token'); return; } // 保存到localStorage try { setConfig({ VOLCENGINE_AUC_APPID: appId, VOLCENGINE_AUC_TOKEN: accessToken }); alert('设置保存成功!'); closeSettingsModal(); } catch (error) { console.error('保存设置失败:', error); alert('保存设置失败,请重试'); } } // 绑定事件 settingsBtn.addEventListener('click', openSettingsModal); closeModal.addEventListener('click', closeSettingsModal); cancelSettings.addEventListener('click', closeSettingsModal); saveSettings.addEventListener('click', saveSettingsHandler); // 点击弹窗背景关闭 settingsModal.addEventListener('click', (e) => { if (e.target === settingsModal) { closeSettingsModal(); } }); // ESC键关闭弹窗 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && settingsModal.style.display === 'flex') { closeSettingsModal(); } }); } // 录制功能设置 function setupRecording() { const recordBtn = document.getElementById('recordBtn') as HTMLButtonElement; const recordIcon = document.getElementById('recordIcon') as HTMLElement; const recordText = document.getElementById('recordText') as HTMLSpanElement; const recordStatus = document.getElementById('recordStatus') as HTMLDivElement; const recordTime = document.getElementById('recordTime') as HTMLSpanElement; const fileInfo = document.getElementById('fileInfo') as HTMLDivElement; const convertBtn = document.getElementById('convertBtn') as HTMLButtonElement; const uploadBtn = document.getElementById('uploadBtn') as HTMLButtonElement; // 检查浏览器支持 if (!AudioRecorder.isSupported()) { recordBtn.disabled = true; recordBtn.title = '当前浏览器不支持录音功能'; recordText.textContent = '不支持录音'; return; } // 初始化录制器 function initRecorder() { if (audioRecorder) { audioRecorder.destroy(); } const options: RecorderOptions = { audioBitsPerSecond: 128000, mimeType: AudioRecorder.getSupportedMimeTypes()[0] || 'audio/webm' }; audioRecorder = new AudioRecorder(options); // 监听状态变化 audioRecorder.on('stateChange', (state: RecordingState) => { updateRecordUI(state); }); // 监听时间更新 audioRecorder.on('timeUpdate', (time: number) => { const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); recordTime.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }); // 监听录制完成 audioRecorder.on('dataAvailable', (blob: Blob) => { recordedAudioBlob = blob; isUsingRecordedAudio = true; // 清除选择的文件 selectedFile = null; const fileInput = document.getElementById('audioFile') as HTMLInputElement; fileInput.value = ''; // 显示录制信息 const fileSizeMB = (blob.size / 1024 / 1024).toFixed(2); fileInfo.innerHTML = `

录制完成: 新录音

录制时长: ${recordTime.textContent}

文件大小: ${fileSizeMB} MB

文件类型: ${blob.type}

`; // 启用转换按钮 convertBtn.disabled = false; // 更新上传按钮文字 uploadBtn.textContent = '选择音频文件'; }); // 监听错误 audioRecorder.on('error', (error: Error) => { console.error('录制错误:', error); alert(`录制失败: ${error.message}`); updateRecordUI(RecordingState.IDLE); }); } // 更新录制UI function updateRecordUI(state: RecordingState) { switch (state) { case RecordingState.IDLE: recordBtn.classList.remove('recording', 'stopping'); recordBtn.disabled = false; recordIcon.innerHTML = ''; recordText.textContent = '录制音频'; recordStatus.style.display = 'none'; recordTime.textContent = '00:00'; break; case RecordingState.RECORDING: recordBtn.classList.add('recording'); recordBtn.classList.remove('stopping'); recordBtn.disabled = false; recordIcon.innerHTML = ''; recordText.textContent = '停止录制'; recordStatus.style.display = 'flex'; break; case RecordingState.STOPPED: recordBtn.classList.remove('recording'); recordBtn.classList.add('stopping'); recordBtn.disabled = true; recordText.textContent = '处理中...'; recordStatus.style.display = 'none'; break; } } // 录制按钮点击事件 recordBtn.addEventListener('click', async () => { if (!audioRecorder) { initRecorder(); } try { const currentState = audioRecorder!.getState(); if (currentState === RecordingState.IDLE) { // 开始录制 await audioRecorder!.startRecording(); } else if (currentState === RecordingState.RECORDING) { // 停止录制 audioRecorder!.stopRecording(); } } catch (error) { console.error('录制操作失败:', error); alert(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`); } }); // 初始化录制器 initRecorder(); } // 初始化页面 createPageStructure(); setupFileUpload(); setupConvertButton(); setupSettingsModal(); setupRecording();