feat(web): add reusable admin form dialog
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import type { FormEvent } from 'react';
|
||||
import type { GatewayApiKey, GatewayTask } from '@easyai-ai-gateway/contracts';
|
||||
import type { LoadState, TaskForm } from '../types';
|
||||
|
||||
const taskKindOptions = [
|
||||
['chat.completions', 'Chat'],
|
||||
['images.generations', '生图'],
|
||||
['images.edits', '图像编辑'],
|
||||
] as const;
|
||||
|
||||
export function CoreFlowPanel(props: {
|
||||
apiKeyForm: { name: string };
|
||||
apiKeys: GatewayApiKey[];
|
||||
apiKeySecret: string;
|
||||
coreMessage: string;
|
||||
coreState: LoadState;
|
||||
platformForm: { provider: string; platformKey: string; name: string; baseUrl: string };
|
||||
taskForm: TaskForm;
|
||||
taskResult: GatewayTask | null;
|
||||
onAPIKeyFormChange: (value: { name: string }) => void;
|
||||
onPlatformFormChange: (value: { provider: string; platformKey: string; name: string; baseUrl: string }) => void;
|
||||
onSubmitAPIKey: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onSubmitPlatform: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onSubmitTask: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onTaskFormChange: (value: TaskForm) => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="corePanel" aria-label="核心链路验证">
|
||||
<div className="sectionHeader">
|
||||
<div>
|
||||
<p className="eyebrow">Smoke Flow</p>
|
||||
<h2>核心链路验证</h2>
|
||||
</div>
|
||||
<span>{props.coreState === 'loading' ? '运行中' : '本地闭环'}</span>
|
||||
</div>
|
||||
|
||||
<div className="coreGrid">
|
||||
<ApiKeyForm {...props} />
|
||||
<PlatformForm {...props} />
|
||||
<TaskSmokeForm {...props} />
|
||||
</div>
|
||||
{props.coreMessage && (
|
||||
<p className="coreMessage" data-error={props.coreState === 'error'}>
|
||||
{props.coreMessage}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiKeyForm(props: {
|
||||
apiKeyForm: { name: string };
|
||||
apiKeys: GatewayApiKey[];
|
||||
apiKeySecret: string;
|
||||
coreState: LoadState;
|
||||
onAPIKeyFormChange: (value: { name: string }) => void;
|
||||
onSubmitAPIKey: (event: FormEvent<HTMLFormElement>) => void;
|
||||
}) {
|
||||
return (
|
||||
<form className="inlineForm" onSubmit={props.onSubmitAPIKey}>
|
||||
<h3>1. API Key</h3>
|
||||
<label>
|
||||
<span>名称</span>
|
||||
<input value={props.apiKeyForm.name} onChange={(event) => props.onAPIKeyFormChange({ name: event.target.value })} />
|
||||
</label>
|
||||
<button type="submit" disabled={props.coreState === 'loading'}>
|
||||
创建 API Key
|
||||
</button>
|
||||
<p className="formHint">已创建 {props.apiKeys.length} 个 Key</p>
|
||||
{props.apiKeySecret && <code className="secretBox">{props.apiKeySecret}</code>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function PlatformForm(props: {
|
||||
coreState: LoadState;
|
||||
platformForm: { provider: string; platformKey: string; name: string; baseUrl: string };
|
||||
onPlatformFormChange: (value: { provider: string; platformKey: string; name: string; baseUrl: string }) => void;
|
||||
onSubmitPlatform: (event: FormEvent<HTMLFormElement>) => void;
|
||||
}) {
|
||||
return (
|
||||
<form className="inlineForm" onSubmit={props.onSubmitPlatform}>
|
||||
<h3>2. 平台</h3>
|
||||
<label>
|
||||
<span>Provider</span>
|
||||
<input value={props.platformForm.provider} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, provider: event.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>平台 Key</span>
|
||||
<input value={props.platformForm.platformKey} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, platformKey: event.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>名称</span>
|
||||
<input value={props.platformForm.name} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, name: event.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Base URL</span>
|
||||
<input value={props.platformForm.baseUrl} onChange={(event) => props.onPlatformFormChange({ ...props.platformForm, baseUrl: event.target.value })} />
|
||||
</label>
|
||||
<button type="submit" disabled={props.coreState === 'loading'}>
|
||||
创建平台
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskSmokeForm(props: {
|
||||
coreState: LoadState;
|
||||
taskForm: TaskForm;
|
||||
taskResult: GatewayTask | null;
|
||||
onSubmitTask: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onTaskFormChange: (value: TaskForm) => void;
|
||||
}) {
|
||||
return (
|
||||
<form className="inlineForm" onSubmit={props.onSubmitTask}>
|
||||
<h3>3. Phase 1 测试</h3>
|
||||
<label>
|
||||
<span>能力</span>
|
||||
<select value={props.taskForm.kind} onChange={(event) => props.onTaskFormChange(defaultTaskForKind(event.target.value as TaskForm['kind'], props.taskForm))}>
|
||||
{taskKindOptions.map(([value, label]) => (
|
||||
<option value={value} key={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>模型</span>
|
||||
<input value={props.taskForm.model} onChange={(event) => props.onTaskFormChange({ ...props.taskForm, model: event.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Prompt</span>
|
||||
<input value={props.taskForm.prompt} onChange={(event) => props.onTaskFormChange({ ...props.taskForm, prompt: event.target.value })} />
|
||||
</label>
|
||||
{props.taskForm.kind === 'images.edits' && (
|
||||
<>
|
||||
<label>
|
||||
<span>参考图 URL</span>
|
||||
<input value={props.taskForm.image ?? ''} onChange={(event) => props.onTaskFormChange({ ...props.taskForm, image: event.target.value })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Mask URL</span>
|
||||
<input value={props.taskForm.mask ?? ''} onChange={(event) => props.onTaskFormChange({ ...props.taskForm, mask: event.target.value })} />
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
<button type="submit" disabled={props.coreState === 'loading'}>
|
||||
生成测试任务
|
||||
</button>
|
||||
{props.taskResult && (
|
||||
<div className="resultBox">
|
||||
<div>
|
||||
<span className="statusPill">{props.taskResult.status}</span>
|
||||
<strong>{props.taskResult.model}</strong>
|
||||
</div>
|
||||
<pre>{JSON.stringify(props.taskResult.result ?? {}, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function defaultTaskForKind(kind: TaskForm['kind'], current: TaskForm): TaskForm {
|
||||
if (kind === 'chat.completions') {
|
||||
return { ...current, kind, model: 'gpt-4o-mini' };
|
||||
}
|
||||
if (kind === 'images.edits') {
|
||||
return { ...current, kind, model: 'gpt-image-1', image: current.image ?? 'https://example.com/source.png', mask: current.mask ?? 'https://example.com/mask.png' };
|
||||
}
|
||||
return { ...current, kind, model: 'gpt-image-1' };
|
||||
}
|
||||
Reference in New Issue
Block a user