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

@ -148,7 +148,7 @@ export async function POST(
// 准备AI API请求 // 准备AI API请求
const promptContent = getPromptContent(run); const promptContent = getPromptContent(run);
const finalPrompt = `${promptContent}\n\nUser Input: ${run.userInput}`; const finalPrompt = `${promptContent}\n\nUser Input: ${run.userInput}`;
let apiResponse: Response; let apiResponse: Response;
let debugRequest: Record<string, unknown> | null = null; let debugRequest: Record<string, unknown> | null = null;
let debugResponse: Record<string, unknown> | null = null; let debugResponse: Record<string, unknown> | null = null;

View File

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