语音合成与视觉模型api接入实现

语音合成与视觉模型api接入实现

读完这篇,你应能按步骤复现 本仓库里的两条能力:火山豆包语音 TTS (文本 → 音频)与 Moonshot 视觉理解 (图 + 文 → 描述)。代码已在仓库中落地,本文侧重为什么要这样拆、先写哪一层、再拼哪一层,方便你搬到自己的项目里。

多模态业务里,「语音」和「看图说话」常来自不同厂商、不同鉴权方式;浏览器又不能随便塞 Secret。做法是:单页只负责表单与展示 ,本机 server.js 当 BFF 读 .env.local、带齐 Header、转发到官方域名。下面按推荐实现顺序写。


你将得到什么

能力 页面 代理路径 上游
语音合成 index-volc-tts.html POST /tts/api/v1/tts openspeech.bytedance.com/api/v1/tts
视觉理解 index-moonshot-vision.html POST /moonshot/v1/chat/completions api.moonshot.cn/v1/chat/completions

效果图:


第 0 步:先画清楚「三层」

无论做哪一条链路,都可以抽象成同一副骨架:

  1. 浏览器 :只认识 http://127.0.0.1:3000(或你的代理地址),用 fetch 发 JSON,不出现厂商密钥。
  2. server.js :读 .env.local,补鉴权(Header 或 body 里的 app),fetch 到真实上游,把状态码和 body 原样或略加工返回给浏览器。
  3. 厂商 API :校验通过后返回 JSON(TTS 里是 base64 音频;Chat 里是 choices[0].message.content)。
flowchart LR subgraph browser["浏览器单页"] A[表单 / 选图] B[fetch 本地路径] end subgraph bff["server.js"] C[读环境变量] D[拼上游 URL + Header] end subgraph upstream["厂商 HTTPS"] E[(openspeech / Moonshot)] end A --> B --> C --> D --> E E --> D --> B

实现顺序建议 :先能用 curl 或最小脚本 从本机打到上游(验证密钥与路径),再写 BFF 路由 ,最后写 Vue 单页 把体验补齐。本仓库把后两步都写好了,你可以对照 server.js 里的 proxyVolcengineTts 与 proxyMoonshotRequest 逆序读回去。


第 1 步:准备账号与密钥(两条线各自一次)

火山(语音)

去 火山引擎语音活动/实名 与 控制台创建应用,拿到 AppID 、Access Token (测试 Token 常有有效期 ,过期要重新复制)。HTTP 一次性合成文档见 豆包语音 · HTTP 非流式。请求体里的 app.cluster 在常见在线 TTS 场景下为 volcano_tts(若你开通的是其它产品线,以控制台绑定的文档为准)。

Moonshot(视觉)

去 Moonshot 开放平台 注册,在 API Keys 创建 sk-... 。视觉走 OpenAI 兼容的 /v1/chat/completions ,messages 里 content 可为数组:type: image_url(url 可用 Data URL )+ type: text。model 须选支持视觉的型号(如 moonshot-v1-8k-vision-preview ),以 官方 Chat 文档 为准。


第 2 步:在 server.js 接通路(先后端,再前端)

2.1 为什么要单独路由,而不是让浏览器直连?

  • 密钥:火山 Token、Moonshot API Key 不能进前端仓库与打包产物。
  • Header 细节 :火山 TTS 要求 Authorization: Bearer;token(分号) ;Moonshot 是常见的 Bearer <空格>token。写进 BFF 可避免前端写错格式。
  • 路径前缀 :本仓库用 /tts/... 、/moonshot/v1/... 作为「入口命名空间」,与仓库里可灵 /kling 并列,便于在 createServer 里分支维护。

2.2 火山:proxyVolcengineTts 在做什么(实现要点)

  1. 只处理 POST /tts/api/v1/tts(与前端约定死,避免误打到别的服务)。
  2. JSON.parse 请求体后,强制写入 parsed.app = { appid, token, cluster }(来自环境变量),这样即使浏览器带了假 app 也会被覆盖。
  3. 若缺 request.reqid ,服务端用 crypto.randomUUID() 补上,满足「每次合成唯一」。
  4. 向上游 POST https://openspeech.bytedance.com/api/v1/tts (可用 VOLCENGINE_TTS_ORIGIN 覆盖域名),带上 Bearer; 头,把上游响应 原样 写回(状态码 + Content-Type + body)。

环境变量示例(.env.local,勿提交):

env 复制代码
VOLCENGINE_TTS_APP_ID=你的AppId
VOLCENGINE_TTS_ACCESS_TOKEN=你的AccessToken
VOLCENGINE_TTS_CLUSTER_ID=volcano_tts

也支持 VITE_APP_ID / VITE_ACCESS_TOKEN / VITE_CLUSTER_ID。启动后看控制台 [Volc TTS] 已配置...。

2.3 Moonshot:proxyMoonshotRequest 在做什么

  1. 匹配 /moonshot 前缀 ,剥掉后剩余路径必须以 /v1/ 开头且无 ..,防止开放代理被滥用。
  2. 拼 MOONSHOT_API_ORIGIN + restPath ,默认 https://api.moonshot.cn。
  3. 请求头 Authorization: Bearer ${MOONSHOT_API_KEY} ,body 透传(密钥不在 body 里)。
env 复制代码
MOONSHOT_API_KEY=sk-你的密钥

也可用 VITE_API_KEY / API_KEY。日志里 [Moonshot] 已配置... 表示就绪。


第 3 步:写 index-volc-tts.html(从文本到能播的音频)

目标:用户输入文案 → 点按钮 → 听到合成声。

  1. 技术栈 :一个 HTML 里 <script type="module">,从 unpkg 引入 Vue 3 ESM ;需要 npx serve . 这类静态服务,避免 file:// 下模块加载失败。
  2. 代理根 proxyBase :所有请求都是 base + '/tts/api/v1/tts',与第 2 步里服务端路由一致;成功请求前写入 localStorage,下次打开少输一次地址。
  3. 请求体 :只组 user / audio / request ,不要 在浏览器写 app(交给服务端合并)。
  4. reqid:在浏览器生成 UUID,减少与服务端补全的竞态,也符合文档习惯。
  5. 解析返回 :JSON 里 data 是纯 base64;用 atob → Uint8Array → Blob ,MIME 用 mimeForEncoding(encoding) (mp3 ↔ audio/mpeg,ogg_opus ↔ audio/ogg),否则容易「有数据但播不出」。
  6. 播放与内存 :URL.createObjectURL 赋给 <audio>;每次合成前 revokeObjectURL 旧地址,组件卸载时再清一次,避免泄漏。

跑通检查清单:node server.js → npx serve . → 打开 index-volc-tts.html → 代理填 http://127.0.0.1:3000 → 点 Generate & Play → 听到声音。更多字段以火山文档为准;仓库交叉索引:README.md。


第 4 步:写 index-moonshot-vision.html(从本地图片到模型回复)

目标:选一张图 + 一句问法 → 看到模型对图的描述。

  1. 读文件 :<input type="file"> + FileReader.readAsDataURL ,得到 data:image/...;base64,... ,同时用于 <img :src> 预览 和 请求体(一份数据两处用,避免双份状态)。
  2. isValid :用 computed 判断是否已选图,禁用「提交」,减少空请求。
  3. 请求 URL :base + '/moonshot/v1/chat/completions',对应第 2.3 步的代理前缀。
  4. 请求体 :stream: false;messages[0].content 为数组,先图后文 (与多模态习惯一致);model 可做成输入框便于换型号线。
  5. 解析 :取 choices[0].message.content ;HTTP 错误或结构不对时,把截断的 JSON 塞进 Error 文案,便于对照上游 error 字段排错。
  6. 大图:Data URL 会线性撑大 POST body,易触发超时或网关限制------产品上要引导压图或走对象存储 URL,本示例先文档化约束。

跑通检查清单:.env.local 配好 Moonshot Key → node server.js → 打开 index-moonshot-vision.html → 选图 → 提交 → 看到文字回复。


第 5 步:单页内部的共通套路(和 server.js 怎么分工)

环节 单页负责 server.js 负责
密钥 不出现 读 .env.local,向上游带正确 Header / 火山 app
URL proxyBase + 固定相对路径 拼官方 origin + /api/v1/tts 或 /v1/...
业务 JSON 文案、音色、encoding、图片 Data URL、model 等 火山:覆盖 app;Moonshot:透传 body
错误展示 try/catch、HTTP 状态、关键字段校验 上游非 2xx 时透传 status + body

一句话 :单页只做 「表单 → JSON → fetch → 解析 → UI」 ;BFF 只做 「鉴权 + 合法路径 + 转发」。

index-volc-tts.html 再抠两点

  • status / error:把「进行中 / 成功 / 失败」从控制台搬到页面上,demo 才像产品。
  • await audio.play() :若浏览器拦截自动播放,用户仍可通过 controls 手动点播放。

index-moonshot-vision.html 再抠两点

  • OpenAI 兼容 :content 为数组是多模态与纯文本混排的关键形状,顺序影响模型「先看到什么」。
  • 与火山的 Header 差异 :火山是 Bearer; ,Moonshot 是 Bearer ------写博客或接其它厂商时务必按文档逐字核对,不要想当然混用。

排错与扩展

  • 火山 401 / 鉴权失败 :核对 Token 是否过期、Authorization 是否为 Bearer; 、cluster 是否为 volcano_tts(或文档要求值)。
  • Moonshot 4xx:Key 是否有效、模型是否支持视觉、图片是否过大。
  • 改 .env.local 后 :务必重启 node server.js。
  • 扩展:TTS 若改流式要换协议;视觉若改流式要解析 SSE / chunk,单页复杂度会明显高于本文的非流式示例。

相关文件(按阅读顺序)

  1. server.js --- 搜 proxyVolcengineTts、proxyMoonshotRequest
  2. index-volc-tts.html、index-moonshot-vision.html
  3. README.md --- 环境变量与接口总表

若你还想对照「文生图 + 异步轮询」的另一条 BFF 链路,可继续读 image-model.md 里的 index-keling.html 思路(与本篇的「一次 POST 即返回」形态不同)。

相关推荐
小狼154544 小时前
浏览器扩展脚本为什么有时候不生效:注入时机、iframe 和单页路由,多多开票助手
前端·chrome
东方芷兰4 小时前
Agent 技术摘要 04 —— AI4S、VLM、VLA、VLN、WM、AI Infra
人工智能
catchadmin4 小时前
用 Jev 与 Laravel AI SDK 检测垃圾邮件和自动回复
数据库·人工智能·laravel
じòぴé南冸じょうげん4 小时前
油猴脚本突然发现变成灰色了,无法使用?页面不生效?刷新页面没反应?
前端
一 铭4 小时前
Pi实战 05:本地模型 · MCP · 安全沙箱篇
人工智能·ai·agent·harness
198******126344 小时前
2026企业AI办公工具选型指南:从评估框架到场景适配
大数据·运维·人工智能
OxYGC4 小时前
[AI工程]Jev 决策模型第二篇:三原语、一次多问与置信度路由,从 Playground 到能上线的代码
人工智能
楚来客4 小时前
AI基础概念之十四:时空Transformer架构
人工智能·深度学习·transformer
u1301305 小时前
AI 日报(2026年10月2日)
人工智能
Csvn6 小时前
组合式 API(Composition API)
前端