|
|
|
|
@ -308,16 +308,16 @@ const ApiTester = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className='min-h-full bg-slate-50 pb-16 text-slate-900 dark:bg-neutral-950 dark:text-white'>
|
|
|
|
|
<div className='mx-auto w-full max-w-[1400px] px-4 sm:px-6 lg:px-10'>
|
|
|
|
|
<div className='space-y-6 pt-8 pb-8'>
|
|
|
|
|
<div className='min-h-full pb-16 text-slate-900 dark:text-white'>
|
|
|
|
|
<div className='mx-auto w-full max-w-[1400px] px-4 pt-20 sm:px-6 lg:px-10'>
|
|
|
|
|
<div className='space-y-8 pb-8'>
|
|
|
|
|
{/* 页面标题区 */}
|
|
|
|
|
<div className='flex flex-wrap items-end justify-between gap-4'>
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className='text-3xl font-bold tracking-tight text-slate-900 md:text-4xl dark:text-white'>
|
|
|
|
|
API 快速测试
|
|
|
|
|
</h1>
|
|
|
|
|
<p className='mt-2 text-sm text-slate-500 dark:text-white/50'>
|
|
|
|
|
<p className='mt-2 text-xs text-slate-500 dark:text-white/50'>
|
|
|
|
|
当前协议:{format === 'anthropic' ? 'Anthropic Messages' : 'OpenAI Chat Completions'} · 请求地址 {finalEndpoint}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
@ -330,14 +330,14 @@ const ApiTester = () => {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 第一行:API Key / 测试目标 / 模型 */}
|
|
|
|
|
<div className='grid grid-cols-1 gap-5 lg:grid-cols-3'>
|
|
|
|
|
<div className='grid grid-cols-1 gap-6 lg:grid-cols-3'>
|
|
|
|
|
{/* API Key 输入卡片 */}
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-5 transition-shadow hover:shadow-md dark:border-white/5 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-6 transition-shadow hover:shadow-md dark:border-white/10 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='mb-3 flex items-center gap-2'>
|
|
|
|
|
<div className='flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'>
|
|
|
|
|
<KeyRound size={15} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className='text-sm font-semibold text-slate-900 dark:text-white'>API Key</div>
|
|
|
|
|
<div className='text-xs font-semibold text-slate-900 dark:text-white'>API Key</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className='relative'>
|
|
|
|
|
<input
|
|
|
|
|
@ -354,25 +354,25 @@ const ApiTester = () => {
|
|
|
|
|
{showKey ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className='mt-2 text-[11px] text-slate-400 dark:text-white/40'>
|
|
|
|
|
<div className='mt-2 text-xs text-slate-400 dark:text-white/40'>
|
|
|
|
|
{user?.token ? '已自动填充当前登录用户的 Token' : '请填写你的 API Key'}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 测试目标选择卡片 */}
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-5 transition-shadow hover:shadow-md dark:border-white/5 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-6 transition-shadow hover:shadow-md dark:border-white/10 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='mb-3 flex items-center justify-between'>
|
|
|
|
|
<div className='flex items-center gap-2'>
|
|
|
|
|
<div className='flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'>
|
|
|
|
|
<Globe size={15} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className='text-sm font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
<div className='text-xs font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
测试目标
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setShowCustomBase((v) => !v)}
|
|
|
|
|
className='inline-flex items-center gap-1 text-[11px] text-slate-400 transition-colors hover:text-slate-700 dark:text-white/40 dark:hover:text-white'
|
|
|
|
|
className='inline-flex items-center gap-1 text-xs text-slate-400 transition-colors hover:text-slate-700 dark:text-white/40 dark:hover:text-white'
|
|
|
|
|
title='切换自定义域名'
|
|
|
|
|
>
|
|
|
|
|
{showCustomBase ? '收起域名' : '换个平台'}
|
|
|
|
|
@ -386,10 +386,10 @@ const ApiTester = () => {
|
|
|
|
|
return acc;
|
|
|
|
|
}, {});
|
|
|
|
|
return (
|
|
|
|
|
<div className='space-y-2.5'>
|
|
|
|
|
<div className='space-y-3'>
|
|
|
|
|
{Object.entries(grouped).map(([group, items]) => (
|
|
|
|
|
<div key={group}>
|
|
|
|
|
<div className='mb-1.5 text-[10px] font-medium uppercase tracking-wider text-slate-400 dark:text-white/40'>
|
|
|
|
|
<div className='mb-1.5 text-[11px] font-medium uppercase tracking-wider text-slate-400 dark:text-white/40'>
|
|
|
|
|
{group}
|
|
|
|
|
</div>
|
|
|
|
|
<div className='flex flex-wrap gap-1.5'>
|
|
|
|
|
@ -397,7 +397,7 @@ const ApiTester = () => {
|
|
|
|
|
<button
|
|
|
|
|
key={s.id}
|
|
|
|
|
onClick={() => applyScene(s)}
|
|
|
|
|
className='inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3 py-1.5 text-[11px] font-medium text-slate-600 transition hover:border-blue-400 hover:bg-blue-50 hover:text-blue-600 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:hover:border-blue-400 dark:hover:bg-blue-500/10 dark:hover:text-blue-400'
|
|
|
|
|
className='inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs font-medium text-slate-600 transition hover:border-blue-400 hover:bg-blue-50 hover:text-blue-600 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:hover:border-blue-400 dark:hover:bg-blue-500/10 dark:hover:text-blue-400'
|
|
|
|
|
>
|
|
|
|
|
<s.Icon size={11} /> {s.label}
|
|
|
|
|
</button>
|
|
|
|
|
@ -408,18 +408,18 @@ const ApiTester = () => {
|
|
|
|
|
|
|
|
|
|
<div className='border-t border-slate-100 pt-2.5 dark:border-white/5'>
|
|
|
|
|
<div className='mb-1.5 flex items-center justify-between'>
|
|
|
|
|
<div className='text-[10px] font-medium uppercase tracking-wider text-slate-400 dark:text-white/40'>
|
|
|
|
|
<div className='text-[11px] font-medium uppercase tracking-wider text-slate-400 dark:text-white/40'>
|
|
|
|
|
我的目标
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={saveCurrentAsCustom}
|
|
|
|
|
className='inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2.5 py-1 text-[10px] font-medium text-slate-600 transition hover:border-blue-400 hover:text-blue-600 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:hover:text-blue-400'
|
|
|
|
|
className='inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2.5 py-1 text-xs font-medium text-slate-600 transition hover:border-blue-400 hover:text-blue-600 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:hover:text-blue-400'
|
|
|
|
|
>
|
|
|
|
|
<Plus size={10} /> 保存当前
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{customScenes.length === 0 ? (
|
|
|
|
|
<div className='text-[11px] text-slate-400 dark:text-white/40'>
|
|
|
|
|
<div className='text-xs text-slate-400 dark:text-white/40'>
|
|
|
|
|
调整完地址后,点右上「保存当前」即可收藏到本地
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
@ -431,7 +431,7 @@ const ApiTester = () => {
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => applyScene(s)}
|
|
|
|
|
className='inline-flex items-center gap-1.5 px-3 py-1.5 text-[11px] font-medium text-slate-600 transition hover:bg-blue-50 hover:text-blue-600 dark:text-white/70 dark:hover:bg-blue-500/10 dark:hover:text-blue-400'
|
|
|
|
|
className='inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-slate-600 transition hover:bg-blue-50 hover:text-blue-600 dark:text-white/70 dark:hover:bg-blue-500/10 dark:hover:text-blue-400'
|
|
|
|
|
>
|
|
|
|
|
<Bookmark size={11} /> {s.label}
|
|
|
|
|
</button>
|
|
|
|
|
@ -452,14 +452,14 @@ const ApiTester = () => {
|
|
|
|
|
})()}
|
|
|
|
|
|
|
|
|
|
<div className='mt-3'>
|
|
|
|
|
<label className='mb-1.5 block text-[11px] font-medium text-slate-500 dark:text-white/50'>
|
|
|
|
|
<label className='mb-1.5 block text-xs font-medium text-slate-500 dark:text-white/50'>
|
|
|
|
|
完整请求地址
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
value={isAbsoluteEndpoint ? endpointPath : `${endpointBase}${endpointPath}`}
|
|
|
|
|
onChange={(e) => applyFullUrl(e.target.value)}
|
|
|
|
|
placeholder='https://你的平台域名/v1/chat/completions'
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-[11px] text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@ -480,29 +480,29 @@ const ApiTester = () => {
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className='mt-3 flex items-center gap-1.5 truncate font-mono text-[11px] text-slate-400 dark:text-white/40'>
|
|
|
|
|
<div className='mt-3 flex items-center gap-1.5 truncate font-mono text-xs text-slate-400 dark:text-white/40'>
|
|
|
|
|
<span className='shrink-0'>实际请求:</span>
|
|
|
|
|
<span className='truncate'>{finalEndpoint}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 模型选择卡片 */}
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-5 transition-shadow hover:shadow-md dark:border-white/5 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-6 transition-shadow hover:shadow-md dark:border-white/10 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='mb-3 flex items-center gap-2'>
|
|
|
|
|
<div className='flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'>
|
|
|
|
|
<Cpu size={15} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className='text-sm font-semibold text-slate-900 dark:text-white'>模型</div>
|
|
|
|
|
<div className='text-xs font-semibold text-slate-900 dark:text-white'>模型</div>
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
value={model}
|
|
|
|
|
onChange={(e) => setModel(e.target.value)}
|
|
|
|
|
placeholder='例如 gpt-4o-mini / claude-3-5-sonnet ...'
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 font-mono text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
/>
|
|
|
|
|
{format === 'anthropic' && (
|
|
|
|
|
<div className='mt-3'>
|
|
|
|
|
<label className='mb-1.5 block text-[11px] font-medium text-slate-500 dark:text-white/50'>
|
|
|
|
|
<label className='mb-1.5 block text-xs font-medium text-slate-500 dark:text-white/50'>
|
|
|
|
|
max_tokens(Anthropic 必填)
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
@ -511,7 +511,7 @@ const ApiTester = () => {
|
|
|
|
|
max={8192}
|
|
|
|
|
value={maxTokens}
|
|
|
|
|
onChange={(e) => setMaxTokens(Number(e.target.value) || 1)}
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
className='w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 font-mono text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
@ -519,7 +519,7 @@ const ApiTester = () => {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 第二行:请求参数 + 响应结果 */}
|
|
|
|
|
<div className='grid grid-cols-1 gap-5 lg:grid-cols-2'>
|
|
|
|
|
<div className='grid grid-cols-1 gap-6 lg:grid-cols-2'>
|
|
|
|
|
{/* 请求参数卡片 */}
|
|
|
|
|
<div className='rounded-2xl border border-slate-200 bg-white p-6 transition-shadow hover:shadow-md dark:border-white/5 dark:bg-white/[0.02] dark:hover:shadow-black/20'>
|
|
|
|
|
<div className='mb-4 flex items-center justify-between'>
|
|
|
|
|
@ -527,7 +527,7 @@ const ApiTester = () => {
|
|
|
|
|
<div className='flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'>
|
|
|
|
|
<MessageSquare size={15} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className='text-sm font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
<div className='text-xs font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
请求参数
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -547,7 +547,7 @@ const ApiTester = () => {
|
|
|
|
|
|
|
|
|
|
<div className='space-y-4'>
|
|
|
|
|
<div>
|
|
|
|
|
<label className='mb-1.5 block text-[11px] font-medium uppercase tracking-wider text-slate-500 dark:text-white/50'>
|
|
|
|
|
<label className='mb-1.5 block text-xs font-medium uppercase tracking-wider text-slate-500 dark:text-white/50'>
|
|
|
|
|
{format === 'anthropic' ? 'system(顶层字段)' : '系统提示'}
|
|
|
|
|
</label>
|
|
|
|
|
<textarea
|
|
|
|
|
@ -555,12 +555,12 @@ const ApiTester = () => {
|
|
|
|
|
onChange={(e) => setSystemPrompt(e.target.value)}
|
|
|
|
|
rows={2}
|
|
|
|
|
placeholder={format === 'anthropic' ? '(可选)Anthropic 顶层 system 字段' : '(可选)设置模型角色'}
|
|
|
|
|
className='w-full resize-none rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
className='w-full resize-none rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className='mb-1.5 block text-[11px] font-medium uppercase tracking-wider text-slate-500 dark:text-white/50'>
|
|
|
|
|
<label className='mb-1.5 block text-xs font-medium uppercase tracking-wider text-slate-500 dark:text-white/50'>
|
|
|
|
|
用户提示 <span className='text-red-500'>*</span>
|
|
|
|
|
</label>
|
|
|
|
|
<textarea
|
|
|
|
|
@ -568,7 +568,7 @@ const ApiTester = () => {
|
|
|
|
|
onChange={(e) => setPrompt(e.target.value)}
|
|
|
|
|
rows={5}
|
|
|
|
|
placeholder='输入你想让模型回答的内容'
|
|
|
|
|
className='w-full resize-none rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
className='w-full resize-none rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs text-slate-900 outline-none transition focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-500/20 dark:border-white/10 dark:bg-white/[0.04] dark:text-white dark:focus:border-blue-400 dark:focus:bg-white/[0.06]'
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -581,12 +581,12 @@ const ApiTester = () => {
|
|
|
|
|
<div className='flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400'>
|
|
|
|
|
<Zap size={15} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className='text-sm font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
<div className='text-xs font-semibold text-slate-900 dark:text-white'>
|
|
|
|
|
响应结果
|
|
|
|
|
</div>
|
|
|
|
|
{status && (
|
|
|
|
|
<span
|
|
|
|
|
className={`ml-2 inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-[11px] font-medium ${
|
|
|
|
|
className={`ml-2 inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
|
|
|
|
status.ok
|
|
|
|
|
? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
|
|
|
|
: 'bg-red-50 text-red-600 dark:bg-red-500/10 dark:text-red-400'
|
|
|
|
|
@ -607,7 +607,7 @@ const ApiTester = () => {
|
|
|
|
|
navigator.clipboard?.writeText(response);
|
|
|
|
|
Toast.success('已复制响应');
|
|
|
|
|
}}
|
|
|
|
|
className='inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3 py-1.5 text-[11px] font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/60 dark:hover:border-white/20 dark:hover:text-white'
|
|
|
|
|
className='inline-flex items-center gap-1.5 rounded-full border border-slate-200 bg-white px-3 py-1.5 text-xs font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/60 dark:hover:border-white/20 dark:hover:text-white'
|
|
|
|
|
>
|
|
|
|
|
<Copy size={11} /> 复制
|
|
|
|
|
</button>
|
|
|
|
|
@ -621,7 +621,7 @@ const ApiTester = () => {
|
|
|
|
|
请求中...
|
|
|
|
|
</div>
|
|
|
|
|
) : response ? (
|
|
|
|
|
<pre className='max-h-[480px] overflow-auto p-4 font-mono text-[11px] leading-relaxed text-slate-800 dark:text-white/80'>
|
|
|
|
|
<pre className='max-h-[480px] overflow-auto p-4 font-mono text-xs leading-relaxed text-slate-800 dark:text-white/80'>
|
|
|
|
|
{response}
|
|
|
|
|
</pre>
|
|
|
|
|
) : (
|
|
|
|
|
|