diff --git a/.local.config.js b/.local.config.js index 1cf68b8..dad5c42 100644 --- a/.local.config.js +++ b/.local.config.js @@ -218,6 +218,10 @@ const localDevConfig = ({ uniacid: 317, domain: 'https://5g.aigc-quickapp.com/', }, + '5g-312': { // 电磁兼容,模板显示不全商品 + uniacid: 312, + domain: 'https://5g.aigc-quickapp.com/', + }, // })['5g-237']; // 商品分类,没有立即咨询文字 // })['5g-97']; // 大宗商品贸易,支持自定义字体大小、颜色 // })['5g-50']; // 燃气安全产品机器人,购物车无反应 diff --git a/common/js/media-upload.js b/common/js/media-upload.js index e65c0d4..d1b9a34 100644 --- a/common/js/media-upload.js +++ b/common/js/media-upload.js @@ -38,7 +38,12 @@ const EXT_MIME_MAP = { bmp: 'image/bmp', tif: 'image/tiff', tiff: 'image/tiff', mp3: 'audio/mpeg', wav: 'audio/wav', aac: 'audio/aac', m4a: 'audio/mp4', flac: 'audio/flac', ogg: 'audio/ogg', + oga: 'audio/ogg', opus: 'audio/opus', amr: 'audio/amr', wma: 'audio/x-ms-wma', + aiff: 'audio/aiff', aif: 'audio/aiff', ape: 'audio/x-ape', mid: 'audio/midi', mp4: 'video/mp4', mov: 'video/quicktime', avi: 'video/x-msvideo', + mkv: 'video/x-matroska', webm: 'video/webm', flv: 'video/x-flv', + m4v: 'video/x-m4v', wmv: 'video/x-ms-wmv', '3gp': 'video/3gpp', + mpeg: 'video/mpeg', mpg: 'video/mpeg', ts: 'video/mp2t', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ppt: 'application/vnd.ms-powerpoint', @@ -117,17 +122,55 @@ export async function uploadImage(filePath, opts = {}) { } /** - * 上传 audio / video / file (一律 STS, 因为体积大概率 >2MB) + * 上传 audio / video / file + * 策略: 商家配了云存储 (oss_sts/auto) → STS 直传, 失败回退 PHP 中转; + * 未配云存储 (backend) → 直接 PHP 中转 * @param {string} filePath * @param {'audio'|'video'|'file'} kind + * @param {{name?:string, mime?:string, size?:number}} [opts] + * PR-FRONTEND-MEDIA-ACCEPT (2026-09-17): H5 传进来的是 blob URL (无扩展名), + * 必须由调用方把原始 File 的 name/mime/size 带进来, 否则 STS key 会以 .bin 结尾, + * Content-Type 退化成 application/octet-stream, 音频/视频播放器取不到正确类型。 */ -export async function uploadMedia(filePath, kind) { +export async function uploadMedia(filePath, kind, opts = {}) { if (!['audio', 'video', 'file'].includes(kind)) { throw new Error(`invalid kind: ${kind}`) } const addon = aiService.getAddonName() const config = await aiService.getServiceConfig() - return await uploadViaSts(filePath, kind, addon, aiService.getAgentId(), config) + const agentId = aiService.getAgentId() + const hints = { name: opts.name || '', mime: opts.mime || '' } + const size = opts.size || 0 + const strategy = (config && config.upload_strategy) || 'backend' + + // PHP 中转 (kind 以调用方传入的音视频分类为准, 后端只区分 image|file) + const doPhpUpload = async (fallbackErr) => { + let phpRes = null + try { + phpRes = await uploadViaPhp(filePath, kind, addon, agentId, size) + } catch (err) { + throw new Error((err && err.message) || (fallbackErr && fallbackErr.message) || '上传失败') + } + if (!phpRes || !phpRes.url) { + throw new Error((fallbackErr && fallbackErr.message) || '上传失败: 当前平台不支持该上传方式') + } + return Object.assign({}, phpRes, { kind }) + } + + // 商家未开启云存储 (strategy=backend): 与图片保持一致走 PHP 中转, + // 避免没配 STS 时"视频/音频"按钮一选就报 "STS 凭据获取失败" 完全不可用 + if (strategy === 'backend') { + return await doPhpUpload() + } + + try { + return await uploadViaSts(filePath, kind, addon, agentId, config, size, hints) + } catch (e) { + // PR-FRONTEND-MEDIA-ACCEPT (2026-09-17): STS 不可用 (未配置云存储 / 凭证接口失败) + // 时回退 PHP 中转, 保证音频/视频文件仍能上传 + console.warn('[media-upload] STS 上传失败, 回退 PHP 中转:', e && e.message) + return await doPhpUpload(e) + } } /** @@ -241,7 +284,7 @@ async function uploadViaPhp(filePath, kind, addon, agentId, knownSize = 0) { * PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX (2026-09-16): 提到模块顶层, * 之前只在 uploadViaSts 里, pathToFile 引用不到 → "extractExt is not defined" */ -function extractExt(filePath, mimeHint = '') { +function extractExt(filePath, mimeHint = '', nameHint = '') { let ext = 'bin' if (typeof filePath === 'string') { const lastSeg = filePath.split('?')[0].split('/').pop() || '' @@ -252,6 +295,11 @@ function extractExt(filePath, mimeHint = '') { const m2 = String(filePath.name).match(/\.([a-z0-9]+)$/i) if (m2) ext = m2[1].toLowerCase() } + // PR-FRONTEND-MEDIA-ACCEPT (2026-09-17): H5 blob URL + 调用方带下来的原始文件名 + if (ext === 'bin' && nameHint) { + const m3 = String(nameHint).match(/\.([a-z0-9]+)$/i) + if (m3) ext = m3[1].toLowerCase() + } // 3) mime→ext 反查 (PR-FRONTEND-MEDIA-UPLOAD-FIX-HOTFIX): blob URL 无扩展名但 // blob.type 是 image/png 这种, 反查 EXT_MIME_MAP → png if (ext === 'bin' && mimeHint) { @@ -263,11 +311,13 @@ function extractExt(filePath, mimeHint = '') { return ext } -async function uploadViaSts(filePath, kind, addon, agentId, config, knownSize = 0) { +async function uploadViaSts(filePath, kind, addon, agentId, config, knownSize = 0, hints = {}) { // PR-FRONTEND-MEDIA-UPLOAD-FIX (2026-09-16): 用更稳的扩展名提取 // H5 blob URL (blob:https://xxx/uuid) 无扩展名 → split('.').pop() 会拿整条 URL // 优先 file.name (H5 file / APP tempFile.name), 都没有就 blob URL → 'bin' - const ext = extractExt(filePath) + // PR-FRONTEND-MEDIA-ACCEPT (2026-09-17): H5 走 blob URL 时 file.name 拿不到, + // 由 uploadMedia 的 opts.name/mime 兜底 (音频 .mp3 / 视频 .mp4 才不会被写成 .bin) + const ext = extractExt(filePath, hints.mime || '', hints.name || '') // 1. 拿 STS 凭证 (addon 模式 URL) const stsUrl = (addon === 'aikefu' ? '/aikefu/api/upload/' : '/aiagent/api/upload/') + 'ossSts' diff --git a/lang/en-us/ai-chat/index.js b/lang/en-us/ai-chat/index.js index 62b76c3..e880b56 100644 --- a/lang/en-us/ai-chat/index.js +++ b/lang/en-us/ai-chat/index.js @@ -118,6 +118,8 @@ export const lang = { voice: 'Voice', recordVoice: 'Record Voice', uploadAudio: 'Upload Audio', + pickVideo: 'Choose Video', + pickAudio: 'Choose Audio', chooseAudioFail: 'Failed to select audio', chooseVideoFail: 'Failed to select video', audioNotReady: 'Audio not ready', diff --git a/lang/zh-cn/ai-chat/index.js b/lang/zh-cn/ai-chat/index.js index b547740..f5bda06 100644 --- a/lang/zh-cn/ai-chat/index.js +++ b/lang/zh-cn/ai-chat/index.js @@ -118,6 +118,8 @@ export const lang = { voice: '语音', recordVoice: '录制语音', uploadAudio: '上传音频', + pickVideo: '选择视频', + pickAudio: '选择音频', chooseAudioFail: '选择音频失败', chooseVideoFail: '选择视频失败', audioNotReady: '音频尚未就绪', diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index 03deab5..5aa1e77 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -355,7 +355,7 @@ - + @@ -368,17 +368,35 @@ × - - - - + + + + {{ voiceHolding ? '松开发送' : '按住说话' }} + + + + + + - - + +