fix simulator

This commit is contained in:
songtianlun 2025-08-31 23:12:55 +08:00
parent e3e37fe515
commit 5c569056e2
2 changed files with 114 additions and 96 deletions

View File

@ -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}
<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}
<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}
<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>
<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>
)}