Fix iPhone audio upload fallback

This commit is contained in:
daixiawu 2026-06-29 18:23:10 +08:00
parent 2ab6b63368
commit 1742d0a495
2 changed files with 21 additions and 12 deletions

View File

@ -843,10 +843,10 @@
<div class="codex-runner">
<textarea id="codex-prompt" class="codex-prompt" rows="5" placeholder="输入要交给本机 Codex 执行的任务..."></textarea>
<input type="file" id="codex-audio-file" class="codex-audio-file" accept="audio/*" capture>
<input type="file" id="codex-audio-file" class="codex-audio-file" accept="audio/*,.m4a,.mp3,.wav,.webm,.ogg,.aac,.flac">
<div class="codex-runner-actions">
<button class="bug-btn codex-voice-btn" id="codex-voice-toggle" type="button">语音输入</button>
<button class="bug-btn" id="codex-audio-pick" type="button">音频文件</button>
<button class="bug-btn" id="codex-audio-pick" type="button">上传音频</button>
<button class="bug-btn" id="codex-run-new" type="button">新会话执行</button>
<button class="bug-btn bug-btn-primary" id="codex-run-resume" type="button" disabled>续接所选会话执行</button>
<span id="codex-voice-status" class="codex-voice-status"></span>

View File

@ -40,13 +40,21 @@ function codexPreferredAudioMimeType() {
function codexAudioExtension(mimeType) {
const lower = (mimeType || '').toLowerCase();
if (lower.includes('mp4')) return 'm4a';
if (lower.includes('m4a') || lower.includes('mp4')) return 'm4a';
if (lower.includes('wav')) return 'wav';
if (lower.includes('mpeg') || lower.includes('mp3')) return 'mp3';
if (lower.includes('ogg')) return 'ogg';
return 'webm';
}
function codexLooksLikeAudioFile(file) {
const type = (file?.type || '').toLowerCase();
if (type.startsWith('audio/')) return true;
if (type.startsWith('video/')) return false;
const name = (file?.name || '').toLowerCase();
return /\.(m4a|mp3|wav|webm|ogg|aac|flac)$/i.test(name);
}
function codexIsSecureVoiceContext() {
return window.isSecureContext || ['localhost', '127.0.0.1', '::1'].includes(window.location.hostname);
}
@ -104,11 +112,11 @@ function codexOpenAudioFilePicker(message) {
async function codexStartVoiceInput() {
if (!codexIsSecureVoiceContext()) {
codexOpenAudioFilePicker('手机 Chrome 需要 HTTPS 才能直接录音,请改用系统录音/音频文件');
codexSetVoiceStatus('手机局域网 HTTP 页面不能直接录音;请先用语音备忘录录音,再点“上传音频”。');
return;
}
if (!navigator.mediaDevices?.getUserMedia || !window.MediaRecorder) {
codexOpenAudioFilePicker('当前浏览器不能直接录音,请改用系统录音/音频文件');
codexSetVoiceStatus('当前浏览器不能直接录音;请点“上传音频”选择录音文件。');
return;
}
try {
@ -158,21 +166,22 @@ async function codexToggleVoiceInput() {
}
}
async function codexFinishVoiceInput(blob) {
async function codexFinishVoiceInput(blob, filename = '') {
if (!blob || blob.size === 0) {
codexSetVoiceStatus('没有录到声音');
return;
}
codexSetVoiceStatus('正在转写...');
try {
const mimeType = blob.type || (filename ? '' : 'audio/webm');
const audioBase64 = await codexBlobToBase64(blob);
const resp = await fetch('/api/codex/transcribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
audioBase64,
mimeType: blob.type || 'audio/webm',
filename: `codex-voice-${Date.now()}.${codexAudioExtension(blob.type)}`,
mimeType,
filename: filename || `codex-voice-${Date.now()}.${codexAudioExtension(mimeType)}`,
language: 'zh',
}),
});
@ -188,11 +197,11 @@ async function codexFinishVoiceInput(blob) {
async function codexHandleAudioFilePicked(event) {
const file = event.target?.files?.[0];
if (!file) return;
if (!file.type.startsWith('audio/')) {
codexSetVoiceStatus('请选择音频文件');
if (!codexLooksLikeAudioFile(file)) {
codexSetVoiceStatus('请选择音频文件,不支持视频');
return;
}
await codexFinishVoiceInput(file);
await codexFinishVoiceInput(file, file.name || '');
}
async function codexLoadSessions(force = false) {
@ -416,7 +425,7 @@ function codexBind() {
const audioPick = document.getElementById('codex-audio-pick');
if (audioPick && !audioPick.dataset.bound) {
audioPick.dataset.bound = '1';
audioPick.addEventListener('click', () => codexOpenAudioFilePicker('选择或录制一段音频后自动转写'));
audioPick.addEventListener('click', () => codexOpenAudioFilePicker('请选择语音备忘录/录音文件,选完会自动转写'));
}
const audioFile = document.getElementById('codex-audio-file');
if (audioFile && !audioFile.dataset.bound) {