diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 501cb24..62ddaad 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,4 +1,4 @@ -import { ApiResponse } from '@/types' +import { ApiResponse, AudioTestResponse, TestResponse } from '@/types' const BASE_URL = import.meta.env.VITE_API_BASE_URL || '' @@ -115,6 +115,12 @@ async function responseInterceptor(response: Response): Promise { return response.text() } +function buildAudioFilename(endpoint: string, contentType: string): string { + const extension = contentType.split('/')[1]?.split(';')[0] || 'bin' + const suffix = endpoint.split('/').filter(Boolean).join('-') || 'response' + return `${suffix}.${extension}` +} + // 统一请求方法 async function request(endpoint: string, options: RequestInit = {}): Promise { const url = `${BASE_URL}${endpoint}` @@ -186,7 +192,7 @@ export const apiClient = { initDatabase: () => request('/api/admin/db_initialize', { method: 'POST' }), // API Test - 使用自定义 token,不走通用拦截器 - testApi: async (endpoint: string, token: string, body: unknown) => { + testApi: async (endpoint: string, token: string, body: unknown): Promise => { const url = `${BASE_URL}${endpoint}` const response = await fetch(url, { method: 'POST', @@ -196,6 +202,22 @@ export const apiClient = { }, body: JSON.stringify(body), }) + + if (response.ok) { + const contentType = response.headers.get('content-type') || '' + if (contentType.startsWith('audio/')) { + const blob = await response.blob() + const audioResponse: AudioTestResponse = { + object: 'audio', + contentType, + size: blob.size, + url: URL.createObjectURL(blob), + filename: buildAudioFilename(endpoint, contentType), + } + return audioResponse + } + } + return responseInterceptor(response) }, diff --git a/frontend/src/pages/ApiTest.tsx b/frontend/src/pages/ApiTest.tsx index a57ce72..3a33f11 100644 --- a/frontend/src/pages/ApiTest.tsx +++ b/frontend/src/pages/ApiTest.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -8,7 +8,8 @@ import { Select } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { useToast } from '@/components/ui/use-toast' import { apiClient } from '@/api/client' -import { Send, Clock, CheckCircle, XCircle, Copy } from 'lucide-react' +import { AudioTestResponse, TestResponse } from '@/types' +import { Send, Clock, CheckCircle, XCircle, Copy, Download } from 'lucide-react' import { PageContainer } from '@/components/ui/page-container' import { cn, copyToClipboard } from '@/lib/utils' @@ -64,6 +65,18 @@ export function ApiTest() { const { addToast } = useToast() + const isAudioResponse = (value: TestResponse | { error: string } | null): value is AudioTestResponse => { + return !!value && value.object === 'audio' && typeof value.url === 'string' + } + + useEffect(() => { + return () => { + if (isAudioResponse(response)) { + URL.revokeObjectURL(response.url) + } + } + }, [response]) + const handleEndpointChange = (newEndpoint: string) => { setEndpoint(newEndpoint) setRequestBody(JSON.stringify(requestTemplates[newEndpoint], null, 2)) @@ -72,7 +85,15 @@ export function ApiTest() { const handleCopyResponse = async () => { if (response) { try { - await copyToClipboard(JSON.stringify(response, null, 2)) + const payload = isAudioResponse(response) + ? { + object: response.object, + contentType: response.contentType, + size: response.size, + filename: response.filename, + } + : response + await copyToClipboard(JSON.stringify(payload, null, 2)) addToast('已复制到剪贴板', 'success') } catch { addToast('复制失败', 'error') @@ -226,9 +247,27 @@ export function ApiTest() { {response ? ( -
-                {JSON.stringify(response, null, 2)}
-              
+ isAudioResponse(response) ? ( +
+
+
音频返回成功
+
+ {response.contentType} · {response.size} bytes +
+
+
+ ) : ( +
+                  {JSON.stringify(response, null, 2)}
+                
+ ) ) : (
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 8bb4852..52bf55a 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -53,7 +53,15 @@ export interface TestRequest { stream?: boolean } -export interface TestResponse { +export interface AudioTestResponse { + object: 'audio' + contentType: string + size: number + url: string + filename: string +} + +export interface JsonTestResponse { id?: string model?: string choices?: Array<{ @@ -68,4 +76,7 @@ export interface TestResponse { completion_tokens?: number total_tokens?: number } + [key: string]: any } + +export type TestResponse = JsonTestResponse | AudioTestResponse