merge: integrate remote learning module safely
This commit is contained in:
commit
f7171a471a
125 files changed
+11451
-12132
No files matched your search
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
const MAX_TRANSCRIPT_LENGTH = 16_000;
|
||||
|
||||
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,
|
||||
assertCurrentAccount: () => void = () => undefined,
|
||||
): 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('语音长度不符合要求');
|
||||
assertCurrentAccount();
|
||||
const token = await getAccessToken({ fetchImpl }).catch(() => { throw new Error('语音识别失败'); });
|
||||
assertCurrentAccount();
|
||||
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) => {
|
||||
assertCurrentAccount();
|
||||
return fetchImpl(`${apiBaseUrl}/api/speech/transcriptions`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body: form,
|
||||
});
|
||||
};
|
||||
let response = await request(token).catch(() => { throw new Error('语音识别失败'); });
|
||||
assertCurrentAccount();
|
||||
if (response.status === 401) {
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
assertCurrentAccount();
|
||||
const refreshed = await getAccessToken({ fetchImpl, forceRefresh: true }).catch(() => null);
|
||||
assertCurrentAccount();
|
||||
if (refreshed) {
|
||||
response = await request(refreshed).catch(() => { throw new Error('语音识别失败'); });
|
||||
assertCurrentAccount();
|
||||
}
|
||||
}
|
||||
const payload = await response.json().catch(() => null) as { text?: unknown; detail?: unknown } | null;
|
||||
const text = typeof payload?.text === 'string' ? payload.text.trim() : '';
|
||||
if (!response.ok || !text || text.length > MAX_TRANSCRIPT_LENGTH) throw new Error('语音识别失败');
|
||||
return { text };
|
||||
}
|
||||
|
||||
return { transcribe };
|
||||
}
|
||||
Reference in new issue
Block a user