76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { NextRequest } from 'next/server';
|
|
import { transcribeAudio } from '@/lib/audio/asr-providers';
|
|
import {
|
|
isServerConfiguredProvider,
|
|
resolveASRApiKey,
|
|
resolveASRBaseUrl,
|
|
} from '@/lib/server/provider-config';
|
|
import type { ASRProviderId } from '@/lib/audio/types';
|
|
import { createLogger } from '@/lib/logger';
|
|
import { apiError, apiSuccess } from '@/lib/server/api-response';
|
|
import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
|
|
import { providerAccessApiError } from '@/lib/server/provider-access-response';
|
|
const log = createLogger('Transcription');
|
|
|
|
export const maxDuration = 60;
|
|
|
|
export async function POST(req: NextRequest) {
|
|
let resolvedProviderId: string | undefined;
|
|
let resolvedModelId: string | undefined;
|
|
try {
|
|
const formData = await req.formData();
|
|
const audioFile = formData.get('audio') as File;
|
|
const providerId = formData.get('providerId') as ASRProviderId | null;
|
|
const modelId = formData.get('modelId') as string | null;
|
|
const language = formData.get('language') as string | null;
|
|
const apiKey = formData.get('apiKey') as string | null;
|
|
const baseUrl = formData.get('baseUrl') as string | null;
|
|
|
|
if (!audioFile) {
|
|
return apiError('MISSING_REQUIRED_FIELD', 400, 'Audio file is required');
|
|
}
|
|
|
|
// providerId is required from the client — no server-side store to fall back to
|
|
const effectiveProviderId = providerId || ('openai-whisper' as ASRProviderId);
|
|
resolvedProviderId = effectiveProviderId;
|
|
resolvedModelId = modelId ?? undefined;
|
|
|
|
// Managed providers are admin-owned: ignore any client-sent key/baseUrl.
|
|
const managed = isServerConfiguredProvider('asr', effectiveProviderId);
|
|
const clientBaseUrl = managed ? undefined : baseUrl || undefined;
|
|
if (clientBaseUrl && process.env.NODE_ENV === 'production') {
|
|
const ssrfError = await validateUrlForSSRF(clientBaseUrl);
|
|
if (ssrfError) {
|
|
return apiError('INVALID_URL', 403, ssrfError);
|
|
}
|
|
}
|
|
|
|
const config = {
|
|
providerId: effectiveProviderId,
|
|
modelId: modelId || undefined,
|
|
language: language || 'auto',
|
|
apiKey: resolveASRApiKey(effectiveProviderId, managed ? undefined : apiKey || undefined),
|
|
baseUrl: resolveASRBaseUrl(effectiveProviderId, clientBaseUrl),
|
|
};
|
|
|
|
// Transcribe using the provider system
|
|
const result = await transcribeAudio(config, audioFile);
|
|
|
|
return apiSuccess({ text: result.text });
|
|
} catch (error) {
|
|
log.error(
|
|
`Transcription failed [provider=${resolvedProviderId ?? 'unknown'}, model=${resolvedModelId ?? 'default'}]:`,
|
|
error,
|
|
);
|
|
const providerAccessError = providerAccessApiError(error);
|
|
if (providerAccessError) return providerAccessError;
|
|
|
|
return apiError(
|
|
'TRANSCRIPTION_FAILED',
|
|
500,
|
|
'Transcription failed',
|
|
error instanceof Error ? error.message : 'Unknown error',
|
|
);
|
|
}
|
|
}
|