easyai-ai-gateway/apps/web/src/pages/WorkspacePage.tsx

179 lines
5.9 KiB
TypeScript

import type { FormEvent, ReactNode } from 'react';
import { CreditCard, KeyRound, ListChecks, UserRound } from 'lucide-react';
import type { ConsoleData } from '../app-state';
import { EntityTable } from '../components/EntityTable';
import { PageHeader } from '../components/PageHeader';
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Tabs } from '../components/ui';
import type { ApiKeyForm, LoadState, WorkspaceSection } from '../types';
const tabs = [
{ value: 'overview', label: '个人总览', icon: <UserRound size={15} /> },
{ value: 'billing', label: '余额充值', icon: <CreditCard size={15} /> },
{ value: 'apiKeys', label: 'API Key', icon: <KeyRound size={15} /> },
{ value: 'tasks', label: '任务记录', icon: <ListChecks size={15} /> },
] satisfies Array<{ value: WorkspaceSection; label: string; icon: ReactNode }>;
export function WorkspacePage(props: {
apiKeyForm: ApiKeyForm;
apiKeySecret: string;
data: ConsoleData;
section: WorkspaceSection;
state: LoadState;
onApiKeyFormChange: (value: ApiKeyForm) => void;
onSectionChange: (value: WorkspaceSection) => void;
onSubmitApiKey: (event: FormEvent<HTMLFormElement>) => void;
}) {
return (
<div className="pageStack">
<PageHeader eyebrow="Workspace" title="用户工作台" description="个人资产、API Key 和任务记录。" />
<div className="subPageLayout">
<Tabs className="sideTabs" value={props.section} tabs={tabs} onValueChange={props.onSectionChange} />
<div className="subPageContent">
{props.section === 'overview' && <WorkspaceOverview data={props.data} />}
{props.section === 'billing' && <BillingPanel />}
{props.section === 'apiKeys' && <ApiKeyPanel {...props} />}
{props.section === 'tasks' && <TaskPanel data={props.data} />}
</div>
</div>
</div>
);
}
function WorkspaceOverview(props: { data: ConsoleData }) {
const owner = props.data.users[0];
return (
<section className="contentGrid two">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="profileGrid">
<InfoItem label="账号" value={owner?.username ?? '-'} />
<InfoItem label="租户" value={owner?.tenantKey ?? 'default'} />
<InfoItem label="身份源" value={owner?.source ?? 'gateway'} />
<InfoItem label="API Key" value={String(props.data.apiKeys.length)} />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<EntityTable
columns={['用户组', '优先级', '状态', '来源']}
empty="暂无用户组"
rows={props.data.userGroups.map((item) => [item.groupKey, item.priority, item.status, item.source])}
/>
</CardContent>
</Card>
</section>
);
}
function BillingPanel() {
return (
<section className="contentGrid two">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="balanceCard">
<span>resource</span>
<strong>0.00</strong>
<Badge variant="secondary">local</Badge>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="formGrid">
<Label>
<Input value="100" readOnly />
</Label>
<Button type="button" variant="secondary"></Button>
</CardContent>
</Card>
</section>
);
}
function ApiKeyPanel(props: {
apiKeyForm: ApiKeyForm;
apiKeySecret: string;
data: ConsoleData;
state: LoadState;
onApiKeyFormChange: (value: ApiKeyForm) => void;
onSubmitApiKey: (event: FormEvent<HTMLFormElement>) => void;
}) {
return (
<section className="contentGrid two">
<Card>
<CardHeader>
<CardTitle> API Key</CardTitle>
</CardHeader>
<CardContent>
<form className="formGrid" onSubmit={props.onSubmitApiKey}>
<Label>
<Input value={props.apiKeyForm.name} onChange={(event) => props.onApiKeyFormChange({ name: event.target.value })} />
</Label>
<Button type="submit" disabled={props.state === 'loading'}>
<KeyRound size={15} />
</Button>
{props.apiKeySecret && <code className="secretBox">{props.apiKeySecret}</code>}
</form>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>API Key </CardTitle>
</CardHeader>
<CardContent>
<EntityTable
columns={['名称', '前缀', '状态', '创建时间']}
empty="暂无 API Key"
rows={props.data.apiKeys.map((item) => [item.name, item.keyPrefix, item.status, new Date(item.createdAt).toLocaleString()])}
/>
</CardContent>
</Card>
</section>
);
}
function TaskPanel(props: { data: ConsoleData }) {
const task = props.data.taskResult;
return (
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
{task ? (
<div className="taskPreview">
<Badge variant={task.status === 'succeeded' ? 'success' : 'secondary'}>{task.status}</Badge>
<strong>{task.kind}</strong>
<span>{task.model}</span>
<pre>{JSON.stringify(task.result ?? {}, null, 2)}</pre>
</div>
) : (
<div className="emptyState">
<strong></strong>
</div>
)}
</CardContent>
</Card>
);
}
function InfoItem(props: { label: string; value: string }) {
return (
<div className="infoItem">
<span>{props.label}</span>
<strong>{props.value}</strong>
</div>
);
}