feat: 后端代理层(Jev 透传 + ASR 预留端点),前端支持代理模式
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* ASR 路由:POST /api/asr(multipart/form-data,文件字段名 "audio")。
|
||||
* 为移动端 ASR 预留(Tauri WKWebView 不支持 Web Speech API 的环境走这里)。
|
||||
*
|
||||
* ADR-0004 约束:音频处理完即弃——
|
||||
* 只在内存中传给 provider,不写临时文件、不进任何存储;
|
||||
* 响应只含 { transcript, prosody },不含音频或可还原声音的内容。
|
||||
*/
|
||||
import { Hono } from 'hono';
|
||||
import { MockAsrProvider, type AsrProvider } from '../asr/provider.js';
|
||||
|
||||
// 接入真实 provider 时改为 new AliyunNlsProvider() / new WhisperProvider(...) 等,
|
||||
// 构造参数(appkey、token、endpoint)从 process.env 读取。
|
||||
const provider: AsrProvider = new MockAsrProvider();
|
||||
|
||||
export const asrRoute = new Hono().post('/', async (c) => {
|
||||
const form = await c.req.parseBody();
|
||||
const file = form['audio'];
|
||||
if (!(file instanceof File)) {
|
||||
return c.json({ error: '缺少 multipart 文件字段 "audio"' }, 400);
|
||||
}
|
||||
const data = new Uint8Array(await file.arrayBuffer());
|
||||
// data 仅在本请求作用域内存活,函数返回后由 GC 回收;不落盘(ADR-0004)。
|
||||
const result = await provider.transcribe({
|
||||
data,
|
||||
contentType: file.type,
|
||||
filename: file.name,
|
||||
});
|
||||
return c.json(result);
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Jev 代理路由:POST /api/jev
|
||||
* 透传 systemone 请求到 https://api.typesafe.ai/v1/systemone,
|
||||
* key 从服务端环境变量 TYPESAFE_API_KEY 读取(不下发到前端)。
|
||||
* 上游状态码(401/422/429/529 等)与响应体原样透传回前端。
|
||||
* key 永不写入日志或响应。
|
||||
*/
|
||||
import { Hono } from 'hono';
|
||||
|
||||
const UPSTREAM_URL = 'https://api.typesafe.ai/v1/systemone';
|
||||
|
||||
export const jevRoute = new Hono().post('/', async (c) => {
|
||||
const apiKey = process.env.TYPESAFE_API_KEY;
|
||||
if (!apiKey) {
|
||||
return c.json({ error: 'server 未配置 TYPESAFE_API_KEY(见 server/README.md)' }, 503);
|
||||
}
|
||||
const body = await c.req.text(); // 请求体原样透传,不解析不修改
|
||||
const upstream = await fetch(UPSTREAM_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const text = await upstream.text();
|
||||
return new Response(text, {
|
||||
status: upstream.status,
|
||||
headers: { 'Content-Type': upstream.headers.get('content-type') ?? 'application/json' },
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user