Add audio playback to API test
This commit is contained in:
1 parent
a85eb362b2
commit
466511b399
3 files changed
+81
-9
No files matched your search
@@ -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)
|
||||
},
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user