Files
openmaic/OpenMAIC/app/api/azure-voices/route.ts
2026-08-16 14:58:47 +08:00

89 lines
2.6 KiB
TypeScript

import { NextRequest } from 'next/server';
import { createLogger } from '@/lib/logger';
import { apiError, apiSuccess } from '@/lib/server/api-response';
import { fetchPinnedPublicUrl, PublicUrlFetchError } from '@/lib/server/public-url-fetch';
const log = createLogger('Azure Voices');
export const maxDuration = 30;
export const runtime = 'nodejs';
function publicFetchError(error: unknown) {
if (
error instanceof PublicUrlFetchError &&
(error.code === 'INVALID_URL' ||
error.code === 'BLOCKED_TARGET' ||
error.code === 'DNS_FAILURE')
) {
return apiError('INVALID_URL', 403, 'Azure endpoint is not an allowed public URL');
}
return apiError('UPSTREAM_ERROR', 502, 'Unable to connect to Azure Speech Services');
}
/**
* Azure TTS Voice List API
* Fetches available voices from Azure Speech Services
*/
export async function POST(req: NextRequest) {
let baseUrl: string | undefined;
try {
const body = await req.json();
const { apiKey } = body;
baseUrl = body.baseUrl;
if (typeof apiKey !== 'string' || !apiKey) {
return apiError('MISSING_API_KEY', 400, 'API Key is required');
}
if (typeof baseUrl !== 'string' || !baseUrl) {
return apiError('MISSING_REQUIRED_FIELD', 400, 'Base URL is required');
}
let voicesUrl: string;
try {
voicesUrl = new URL(
'cognitiveservices/voices/list',
baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`,
).href;
} catch {
return apiError('INVALID_URL', 403, 'Azure endpoint is not an allowed public URL');
}
let pinnedResponse;
try {
pinnedResponse = await fetchPinnedPublicUrl(voicesUrl, {
headers: { 'Ocp-Apim-Subscription-Key': apiKey },
signal: AbortSignal.timeout(25_000),
maxResponseBytes: 8 * 1024 * 1024,
});
} catch (error) {
return publicFetchError(error);
}
const { response } = pinnedResponse;
try {
if (response.status >= 300 && response.status < 400) {
return apiError('REDIRECT_NOT_ALLOWED', 403, 'Redirects are not allowed');
}
if (!response.ok) {
const errorText = await response.text();
return apiError(
'UPSTREAM_ERROR',
response.status,
'Failed to fetch voices from Azure',
errorText || response.statusText,
);
}
const voices = await response.json();
return apiSuccess({ voices });
} finally {
await pinnedResponse.dispose();
}
} catch (error) {
log.error(`Azure voices fetch failed [baseUrl="${baseUrl ?? 'unknown'}"]:`, error);
return apiError('INTERNAL_ERROR', 500, 'Failed to fetch voices');
}
}