53 lines
2.3 KiB
TypeScript
53 lines
2.3 KiB
TypeScript
import { WORKS_SQUARE_CONFIG } from '../api/works-config';
|
|
import { proxyAwareFetch } from '../utils/proxy-fetch';
|
|
import { getValidWorksSquareAccessToken } from './works-square-session';
|
|
|
|
const MAX_AUDIO_BYTES = 26_214_400;
|
|
|
|
export type LearningSpeechRequest = {
|
|
audio: Uint8Array;
|
|
fileName: string;
|
|
mimeType: string;
|
|
language?: string;
|
|
};
|
|
|
|
type Dependencies = {
|
|
fetchImpl?: typeof fetch;
|
|
getAccessToken?: typeof getValidWorksSquareAccessToken;
|
|
apiBaseUrl?: string;
|
|
};
|
|
|
|
export function createLearningSpeechClient(dependencies: Dependencies = {}) {
|
|
const fetchImpl = dependencies.fetchImpl ?? proxyAwareFetch;
|
|
const getAccessToken = dependencies.getAccessToken ?? getValidWorksSquareAccessToken;
|
|
const apiBaseUrl = (dependencies.apiBaseUrl ?? WORKS_SQUARE_CONFIG.apiBaseUrl).replace(/\/+$/, '');
|
|
|
|
async function transcribe(input: LearningSpeechRequest): Promise<{ text: string }> {
|
|
const audio = input.audio instanceof Uint8Array ? input.audio : new Uint8Array(input.audio);
|
|
if (!audio.byteLength || audio.byteLength > MAX_AUDIO_BYTES) throw new Error('语音长度不符合要求');
|
|
const token = await getAccessToken({ fetchImpl });
|
|
if (!token) throw new Error('请先登录');
|
|
const form = new FormData();
|
|
form.set('audio', new Blob([audio], { type: input.mimeType || 'audio/webm' }), input.fileName || 'voice.webm');
|
|
if (input.language?.trim()) form.set('language', input.language.trim());
|
|
const request = (accessToken: string) => fetchImpl(`${apiBaseUrl}/api/speech/transcriptions`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
body: form,
|
|
});
|
|
let response = await request(token);
|
|
if (response.status === 401) {
|
|
await response.body?.cancel().catch(() => undefined);
|
|
const refreshed = await getAccessToken({ fetchImpl, forceRefresh: true });
|
|
if (refreshed) response = await request(refreshed);
|
|
}
|
|
const payload = await response.json().catch(() => null) as { text?: unknown; detail?: unknown } | null;
|
|
if (!response.ok || typeof payload?.text !== 'string' || !payload.text.trim()) {
|
|
throw new Error(typeof payload?.detail === 'string' ? payload.detail : '语音识别失败');
|
|
}
|
|
return { text: payload.text.trim() };
|
|
}
|
|
|
|
return { transcribe };
|
|
}
|