route.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { NextRequest } from 'next/server';
  2. import { transcribeAudio } from '@/lib/audio/asr-providers';
  3. import { resolveASRApiKey, resolveASRBaseUrl } from '@/lib/server/provider-config';
  4. import type { ASRProviderId } from '@/lib/audio/types';
  5. import { createLogger } from '@/lib/logger';
  6. import { apiError, apiSuccess } from '@/lib/server/api-response';
  7. import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
  8. const log = createLogger('Transcription');
  9. export const maxDuration = 60;
  10. export async function POST(req: NextRequest) {
  11. let resolvedProviderId: string | undefined;
  12. let resolvedModelId: string | undefined;
  13. try {
  14. const formData = await req.formData();
  15. const audioFile = formData.get('audio') as File;
  16. const providerId = formData.get('providerId') as ASRProviderId | null;
  17. const modelId = formData.get('modelId') as string | null;
  18. const language = formData.get('language') as string | null;
  19. const apiKey = formData.get('apiKey') as string | null;
  20. const baseUrl = formData.get('baseUrl') as string | null;
  21. if (!audioFile) {
  22. return apiError('MISSING_REQUIRED_FIELD', 400, 'Audio file is required');
  23. }
  24. // providerId is required from the client — no server-side store to fall back to
  25. const effectiveProviderId = providerId || ('openai-whisper' as ASRProviderId);
  26. resolvedProviderId = effectiveProviderId;
  27. resolvedModelId = modelId ?? undefined;
  28. const clientBaseUrl = baseUrl || undefined;
  29. if (clientBaseUrl && process.env.NODE_ENV === 'production') {
  30. const ssrfError = await validateUrlForSSRF(clientBaseUrl);
  31. if (ssrfError) {
  32. return apiError('INVALID_URL', 403, ssrfError);
  33. }
  34. }
  35. const config = {
  36. providerId: effectiveProviderId,
  37. modelId: modelId || undefined,
  38. language: language || 'auto',
  39. apiKey: clientBaseUrl
  40. ? apiKey || ''
  41. : resolveASRApiKey(effectiveProviderId, apiKey || undefined),
  42. baseUrl: clientBaseUrl
  43. ? clientBaseUrl
  44. : resolveASRBaseUrl(effectiveProviderId, baseUrl || undefined),
  45. };
  46. // Convert audio file to buffer
  47. const arrayBuffer = await audioFile.arrayBuffer();
  48. const buffer = Buffer.from(arrayBuffer);
  49. // Transcribe using the provider system
  50. const result = await transcribeAudio(config, buffer);
  51. return apiSuccess({ text: result.text });
  52. } catch (error) {
  53. log.error(
  54. `Transcription failed [provider=${resolvedProviderId ?? 'unknown'}, model=${resolvedModelId ?? 'default'}]:`,
  55. error,
  56. );
  57. return apiError(
  58. 'TRANSCRIPTION_FAILED',
  59. 500,
  60. 'Transcription failed',
  61. error instanceof Error ? error.message : 'Unknown error',
  62. );
  63. }
  64. }