Add audio playback to API test

This commit is contained in:
dreamhunter2333 committed 2026-03-15 18:40:46 +08:00
1 parent a85eb362b2
commit 466511b399
3 files changed
+81 -9

No files matched your search

+24 -2
View File
@@ -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<any> {
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<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
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<TestResponse> => {
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)
},
+45 -6
View File
@@ -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() {
</div>
{response ? (
<pre className="whitespace-pre-wrap break-words text-sm font-mono bg-muted/50 rounded-lg p-4 max-h-[500px] overflow-y-auto scrollbar-thin">
{JSON.stringify(response, null, 2)}
</pre>
isAudioResponse(response) ? (
<div className="space-y-4 rounded-lg bg-muted/50 p-4">
<div className="space-y-1">
<div className="text-sm font-medium">音频返回成功</div>
<div className="text-xs text-muted-foreground font-mono">
{response.contentType} · {response.size} bytes
</div>
</div>
<audio controls src={response.url} className="w-full" />
<Button asChild variant="outline" size="sm">
<a href={response.url} download={response.filename}>
<Download className="h-4 w-4" />
下载音频
</a>
</Button>
</div>
) : (
<pre className="whitespace-pre-wrap break-words text-sm font-mono bg-muted/50 rounded-lg p-4 max-h-[500px] overflow-y-auto scrollbar-thin">
{JSON.stringify(response, null, 2)}
</pre>
)
) : (
<div className="flex flex-col items-center justify-center h-[400px] text-muted-foreground/40">
<Send className="h-10 w-10 mb-3" />
+12 -1
View File
@@ -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