feat: enrich task record details

This commit is contained in:
2026-05-10 22:33:58 +08:00
parent 205a4b625e
commit 53f8edfb67
19 changed files with 1781 additions and 165 deletions
+23 -4
View File
@@ -2,7 +2,6 @@ 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';
@@ -22,10 +21,10 @@ export function WorkspacePage(props: {
onApiKeyFormChange: (value: ApiKeyForm) => void;
onSectionChange: (value: WorkspaceSection) => void;
onSubmitApiKey: (event: FormEvent<HTMLFormElement>) => void;
onUseApiKeyForPlayground: (apiKeyId?: string) => 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">
@@ -106,7 +105,9 @@ function ApiKeyPanel(props: {
state: LoadState;
onApiKeyFormChange: (value: ApiKeyForm) => void;
onSubmitApiKey: (event: FormEvent<HTMLFormElement>) => void;
onUseApiKeyForPlayground: (apiKeyId?: string) => void;
}) {
const latestUsableKey = props.apiKeySecret ? props.data.apiKeys[0] : undefined;
return (
<section className="contentGrid two">
<Card>
@@ -123,7 +124,14 @@ function ApiKeyPanel(props: {
<KeyRound size={15} />
</Button>
{props.apiKeySecret && <code className="secretBox">{props.apiKeySecret}</code>}
{props.apiKeySecret && (
<div className="createdApiKeyBox">
<code className="secretBox">{props.apiKeySecret}</code>
<Button type="button" variant="secondary" onClick={() => props.onUseApiKeyForPlayground(latestUsableKey?.id)}>
使
</Button>
</div>
)}
</form>
</CardContent>
</Card>
@@ -145,6 +153,9 @@ function ApiKeyPanel(props: {
function TaskPanel(props: { data: ConsoleData }) {
const task = props.data.taskResult;
const usage = task?.usage ?? {};
const tokenText = usage.totalTokens ? `${usage.totalTokens}` : '-';
const chargeText = task?.finalChargeAmount ? `${task.finalChargeAmount}` : '-';
return (
<Card>
<CardHeader>
@@ -156,7 +167,15 @@ function TaskPanel(props: { data: ConsoleData }) {
<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 className="infoGrid compact">
<InfoItem label="API Key" value={task.apiKeyName || task.apiKeyId || '-'} />
<InfoItem label="RequestID" value={task.requestId || '-'} />
<InfoItem label="实际模型" value={task.resolvedModel || task.model} />
<InfoItem label="Token" value={tokenText} />
<InfoItem label="扣费" value={chargeText} />
<InfoItem label="响应耗时" value={task.responseDurationMs ? `${task.responseDurationMs}ms` : '-'} />
</div>
<pre>{JSON.stringify({ result: task.result, usage: task.usage, billings: task.billings, billingSummary: task.billingSummary, metrics: task.metrics }, null, 2)}</pre>
</div>
) : (
<div className="emptyState">
+10
View File
@@ -407,6 +407,16 @@ strong {
gap: 10px;
}
.infoGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 10px;
}
.infoGrid.compact .infoItem {
padding: 10px;
}
.spanTwo {
grid-column: 1 / -1;
}