fix simulator
This commit is contained in:
parent
e3e37fe515
commit
5c569056e2
@ -196,19 +196,19 @@ export default function SimulatorPage() {
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 py-8 max-w-7xl">
|
||||
<div className="container mx-auto px-4 sm:px-6 py-6 sm:py-8 max-w-7xl">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">{t('title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 sm:gap-0">
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-2xl sm:text-3xl font-bold text-foreground mb-2">{t('title')}</h1>
|
||||
<p className="text-muted-foreground text-sm sm:text-base">
|
||||
{t('description')}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/simulator/new">
|
||||
<Button className="flex items-center space-x-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
<Link href="/simulator/new" className="self-start sm:self-auto">
|
||||
<Button className="flex items-center space-x-2 w-full sm:w-auto">
|
||||
<Plus className="h-4 w-4 flex-shrink-0" />
|
||||
<span>{t('newRun')}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
@ -216,58 +216,58 @@ export default function SimulatorPage() {
|
||||
|
||||
{/* Stats */}
|
||||
{runs.length > 0 && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mt-6">
|
||||
<Card className="p-4">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3 sm:gap-4 mt-6">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.total}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('allRuns')}</div>
|
||||
<Database className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold">{stats.total}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('allRuns')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.completed}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('completed')}</div>
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold">{stats.completed}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('completed')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<RefreshCw className="h-4 w-4 text-blue-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.running}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('running')}</div>
|
||||
<RefreshCw className="h-4 w-4 text-blue-500 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold">{stats.running}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('running')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<XCircle className="h-4 w-4 text-red-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.failed}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('failed')}</div>
|
||||
<XCircle className="h-4 w-4 text-red-500 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold">{stats.failed}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('failed')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<BarChart3 className="h-4 w-4 text-purple-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.totalTokens.toLocaleString()}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('totalTokens')}</div>
|
||||
<BarChart3 className="h-4 w-4 text-purple-500 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold truncate">{stats.totalTokens.toLocaleString()}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('totalTokens')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<Card className="p-3 sm:p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<DollarSign className="h-4 w-4 text-green-600" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold">{stats.totalCost.toFixed(4)}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('totalCost')}</div>
|
||||
<DollarSign className="h-4 w-4 text-green-600 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-lg sm:text-2xl font-bold truncate">${stats.totalCost.toFixed(4)}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{t('totalCost')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@ -276,12 +276,13 @@ export default function SimulatorPage() {
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 mb-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Card className="p-4 sm:p-6 mb-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0 mb-4">
|
||||
<h2 className="text-lg font-semibold">Filter Runs</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="self-start sm:self-auto"
|
||||
onClick={() => {
|
||||
setStatusFilter('')
|
||||
setPagination(prev => ({ ...prev, page: 1 }))
|
||||
@ -335,8 +336,8 @@ export default function SimulatorPage() {
|
||||
</Card>
|
||||
|
||||
{/* Runs List */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<Card className="p-4 sm:p-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0 mb-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-foreground">
|
||||
Simulation Runs
|
||||
@ -371,97 +372,104 @@ export default function SimulatorPage() {
|
||||
{runs.map(run => (
|
||||
<div
|
||||
key={run.id}
|
||||
className="border border-border rounded-lg p-6 hover:shadow-md transition-shadow"
|
||||
className="border border-border rounded-lg p-4 sm:p-6 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<h3 className="font-semibold text-foreground">{run.name}</h3>
|
||||
<Badge className={`border ${getStatusColor(run.status)}`}>
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 lg:gap-6">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-3">
|
||||
<h3 className="font-semibold text-foreground truncate">{run.name}</h3>
|
||||
<Badge className={`border ${getStatusColor(run.status)} self-start sm:self-auto`}>
|
||||
<div className="flex items-center space-x-1">
|
||||
{getStatusIcon(run.status)}
|
||||
<span>{t(`status.${run.status}`)}</span>
|
||||
<span className="whitespace-nowrap">{t(`status.${run.status}`)}</span>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 text-sm text-muted-foreground mb-3">
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground mb-3">
|
||||
<span className="flex items-center">
|
||||
<Zap className="h-3 w-3 mr-1" />
|
||||
{run.model.provider} {run.model.name}
|
||||
<Zap className="h-3 w-3 mr-1 flex-shrink-0" />
|
||||
<span className="truncate">{run.model.provider} {run.model.name}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="truncate">
|
||||
{formatDistanceToNow(new Date(run.createdAt), {
|
||||
addSuffix: true,
|
||||
locale: locale === 'zh' ? zhCN : enUS
|
||||
})}
|
||||
</span>
|
||||
{run.inputTokens && run.outputTokens && (
|
||||
<span>
|
||||
{run.inputTokens + run.outputTokens} tokens
|
||||
<span className="whitespace-nowrap">
|
||||
{(run.inputTokens + run.outputTokens).toLocaleString()} tokens
|
||||
</span>
|
||||
)}
|
||||
{run.totalCost && (
|
||||
<span className="flex items-center">
|
||||
<DollarSign className="h-3 w-3 mr-1" />
|
||||
{run.totalCost.toFixed(4)}
|
||||
<span className="flex items-center whitespace-nowrap">
|
||||
<DollarSign className="h-3 w-3 mr-1 flex-shrink-0" />
|
||||
${run.totalCost.toFixed(4)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="min-w-0">
|
||||
<h4 className="text-sm font-medium text-foreground mb-1">
|
||||
{t('userInput')}
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{run.userInput}
|
||||
</p>
|
||||
<div className="text-sm text-muted-foreground bg-muted/50 rounded-md p-2 border">
|
||||
<p className="line-clamp-2 break-words overflow-hidden">
|
||||
{run.userInput.length > 100 ? `${run.userInput.substring(0, 100)}...` : run.userInput}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{run.output && (
|
||||
<div>
|
||||
<div className="min-w-0">
|
||||
<h4 className="text-sm font-medium text-foreground mb-1">
|
||||
{t('output')}
|
||||
</h4>
|
||||
<p className="text-sm text-muted-foreground line-clamp-3">
|
||||
{run.output}
|
||||
</p>
|
||||
<div className="text-sm text-muted-foreground bg-muted/50 rounded-md p-2 border">
|
||||
<p className="line-clamp-3 break-words overflow-hidden">
|
||||
{run.output.length > 150 ? `${run.output.substring(0, 150)}...` : run.output}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{run.error && (
|
||||
<div>
|
||||
<div className="min-w-0">
|
||||
<h4 className="text-sm font-medium text-red-600 dark:text-red-400 mb-1 flex items-center">
|
||||
<AlertCircle className="h-4 w-4 mr-1" />
|
||||
<AlertCircle className="h-4 w-4 mr-1 flex-shrink-0" />
|
||||
{t('error')}
|
||||
</h4>
|
||||
<p className="text-sm text-red-600 dark:text-red-400 line-clamp-2">
|
||||
{run.error}
|
||||
</p>
|
||||
<div className="text-sm text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-950/30 rounded-md p-2 border border-red-200 dark:border-red-800">
|
||||
<p className="line-clamp-2 break-words overflow-hidden">
|
||||
{run.error.length > 120 ? `${run.error.substring(0, 120)}...` : run.error}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 ml-6">
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-2 lg:ml-6 mt-4 lg:mt-0">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto"
|
||||
onClick={() => handleDuplicateRun(run.id)}
|
||||
disabled={duplicatingRunId === run.id}
|
||||
>
|
||||
{duplicatingRunId === run.id ? (
|
||||
<LoadingSpinner size="sm" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
<Copy className="h-4 w-4 mr-1 flex-shrink-0" />
|
||||
)}
|
||||
{t('duplicateRun')}
|
||||
<span className="truncate">{t('duplicateRun')}</span>
|
||||
</Button>
|
||||
<Link href={`/simulator/${run.id}`}>
|
||||
<Button size="sm" variant="outline">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
{t('viewDetails')}
|
||||
<Link href={`/simulator/${run.id}`} className="w-full sm:w-auto">
|
||||
<Button size="sm" variant="outline" className="w-full">
|
||||
<Eye className="h-4 w-4 mr-1 flex-shrink-0" />
|
||||
<span className="truncate">{t('viewDetails')}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
@ -474,35 +482,45 @@ export default function SimulatorPage() {
|
||||
|
||||
{/* Pagination */}
|
||||
{pagination.totalPages > 1 && (
|
||||
<div className="flex items-center justify-center space-x-2 mt-8">
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 mt-8 px-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={pagination.page <= 1}
|
||||
onClick={() => setPagination(prev => ({ ...prev, page: prev.page - 1 }))}
|
||||
className="min-w-0"
|
||||
>
|
||||
{t('previous')}
|
||||
<span className="hidden sm:inline">{t('previous')}</span>
|
||||
<span className="sm:hidden">‹</span>
|
||||
</Button>
|
||||
|
||||
{[...Array(Math.min(5, pagination.totalPages))].map((_, i) => {
|
||||
const page = Math.max(1, Math.min(pagination.totalPages - 4, pagination.page - 2)) + i
|
||||
if (page > pagination.totalPages) return null
|
||||
return (
|
||||
<Button
|
||||
key={page}
|
||||
variant={pagination.page === page ? 'default' : 'outline'}
|
||||
onClick={() => setPagination(prev => ({ ...prev, page }))}
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
<div className="flex items-center gap-1">
|
||||
{[...Array(Math.min(5, pagination.totalPages))].map((_, i) => {
|
||||
const page = Math.max(1, Math.min(pagination.totalPages - 4, pagination.page - 2)) + i
|
||||
if (page > pagination.totalPages) return null
|
||||
return (
|
||||
<Button
|
||||
key={page}
|
||||
size="sm"
|
||||
variant={pagination.page === page ? 'default' : 'outline'}
|
||||
onClick={() => setPagination(prev => ({ ...prev, page }))}
|
||||
className="min-w-[2.5rem] px-2"
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={pagination.page >= pagination.totalPages}
|
||||
onClick={() => setPagination(prev => ({ ...prev, page: prev.page + 1 }))}
|
||||
className="min-w-0"
|
||||
>
|
||||
{t('next')}
|
||||
<span className="hidden sm:inline">{t('next')}</span>
|
||||
<span className="sm:hidden">›</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
Loading…
Reference in New Issue
Block a user