add simulator duplicate
This commit is contained in:
parent
a63bfd6783
commit
ff0c2017af
@ -386,6 +386,10 @@
|
||||
"creating": "Creating...",
|
||||
"runSimulation": "Run Simulation",
|
||||
"createRun": "Create Run",
|
||||
"duplicateRun": "Duplicate Run",
|
||||
"duplicateRunConfirm": "Are you sure you want to create a copy of this run?",
|
||||
"duplicateRunSuccess": "Duplicate created successfully",
|
||||
"copyOf": " Copy",
|
||||
"execute": "Execute",
|
||||
"executing": "Executing...",
|
||||
"generating": "Generating...",
|
||||
|
@ -386,6 +386,10 @@
|
||||
"creating": "创建中...",
|
||||
"runSimulation": "运行模拟",
|
||||
"createRun": "创建运行",
|
||||
"duplicateRun": "创建副本",
|
||||
"duplicateRunConfirm": "确定要创建此运行的副本吗?",
|
||||
"duplicateRunSuccess": "副本创建成功",
|
||||
"copyOf": "的副本",
|
||||
"execute": "执行",
|
||||
"executing": "执行中...",
|
||||
"generating": "生成中...",
|
||||
|
79
src/app/api/simulator/[id]/duplicate/route.ts
Normal file
79
src/app/api/simulator/[id]/duplicate/route.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase-server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const supabase = await createServerSupabaseClient();
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
||||
|
||||
if (authError || !user) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// 获取原始运行记录
|
||||
const originalRun = await prisma.simulatorRun.findFirst({
|
||||
where: {
|
||||
id,
|
||||
userId: user.id,
|
||||
},
|
||||
include: {
|
||||
prompt: true,
|
||||
promptVersion: true,
|
||||
model: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!originalRun) {
|
||||
return NextResponse.json({ error: "Run not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 创建副本名称
|
||||
const copyName = `${originalRun.name}${originalRun.name.includes('Copy') || originalRun.name.includes('副本') ? '' : ' Copy'}`;
|
||||
|
||||
// 创建新的运行记录(副本)
|
||||
const duplicateRun = await prisma.simulatorRun.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
name: copyName,
|
||||
promptId: originalRun.promptId,
|
||||
promptVersionId: originalRun.promptVersionId,
|
||||
modelId: originalRun.modelId,
|
||||
userInput: originalRun.userInput,
|
||||
promptContent: originalRun.promptContent,
|
||||
temperature: originalRun.temperature,
|
||||
maxTokens: originalRun.maxTokens,
|
||||
topP: originalRun.topP,
|
||||
frequencyPenalty: originalRun.frequencyPenalty,
|
||||
presencePenalty: originalRun.presencePenalty,
|
||||
status: "pending", // 副本状态为pending,可以重新运行
|
||||
// 不复制输出相关字段:output, error, inputTokens, outputTokens, totalCost, duration, completedAt
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
userInput: true,
|
||||
createdAt: true,
|
||||
prompt: {
|
||||
select: { id: true, name: true }
|
||||
},
|
||||
model: {
|
||||
select: { id: true, name: true, provider: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json(duplicateRun, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error("Error duplicating simulator run:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
const [streamOutput, setStreamOutput] = useState('')
|
||||
const [runId, setRunId] = useState<string | null>(null)
|
||||
const [isDuplicating, setIsDuplicating] = useState(false)
|
||||
const outputRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@ -186,6 +187,31 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
||||
}
|
||||
}
|
||||
|
||||
const handleDuplicateRun = async () => {
|
||||
if (!run || !confirm(t('duplicateRunConfirm'))) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsDuplicating(true)
|
||||
try {
|
||||
const response = await fetch(`/api/simulator/${run.id}/duplicate`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const newRun = await response.json()
|
||||
// 跳转到新创建的运行页面
|
||||
router.push(`/simulator/${newRun.id}`)
|
||||
} else {
|
||||
console.error('Error duplicating run:', await response.text())
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error duplicating run:', error)
|
||||
} finally {
|
||||
setIsDuplicating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
@ -272,21 +298,41 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{run.status === 'pending' && (
|
||||
<Button onClick={executeRun} disabled={isExecuting}>
|
||||
{isExecuting ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
onClick={handleDuplicateRun}
|
||||
disabled={isDuplicating}
|
||||
variant="outline"
|
||||
>
|
||||
{isDuplicating ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||
{t('executing')}
|
||||
<LoadingSpinner size="sm" />
|
||||
<span className="ml-2">{t('creating')}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
{t('execute')}
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
{t('duplicateRun')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{run.status === 'pending' && (
|
||||
<Button onClick={executeRun} disabled={isExecuting}>
|
||||
{isExecuting ? (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||
{t('executing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
{t('execute')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -20,7 +20,8 @@ import {
|
||||
Eye,
|
||||
RefreshCw,
|
||||
Zap,
|
||||
DollarSign
|
||||
DollarSign,
|
||||
Copy
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
@ -72,6 +73,7 @@ export default function SimulatorPage() {
|
||||
})
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [statusFilter, setStatusFilter] = useState<string>('')
|
||||
const [duplicatingRunId, setDuplicatingRunId] = useState<string | null>(null)
|
||||
|
||||
const fetchRuns = useCallback(async () => {
|
||||
try {
|
||||
@ -101,6 +103,32 @@ export default function SimulatorPage() {
|
||||
}
|
||||
}, [user, authLoading, pagination.page, statusFilter, fetchRuns])
|
||||
|
||||
const handleDuplicateRun = async (runId: string) => {
|
||||
if (!confirm(t('duplicateRunConfirm'))) {
|
||||
return
|
||||
}
|
||||
|
||||
setDuplicatingRunId(runId)
|
||||
try {
|
||||
const response = await fetch(`/api/simulator/${runId}/duplicate`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
// 刷新列表
|
||||
await fetchRuns()
|
||||
// 可以添加一个成功提示
|
||||
console.log(t('duplicateRunSuccess'))
|
||||
} else {
|
||||
console.error('Error duplicating run:', await response.text())
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error duplicating run:', error)
|
||||
} finally {
|
||||
setDuplicatingRunId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
@ -391,6 +419,19 @@ export default function SimulatorPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 ml-6">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDuplicateRun(run.id)}
|
||||
disabled={duplicatingRunId === run.id}
|
||||
>
|
||||
{duplicatingRunId === run.id ? (
|
||||
<LoadingSpinner size="sm" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
)}
|
||||
{t('duplicateRun')}
|
||||
</Button>
|
||||
<Link href={`/simulator/${run.id}`}>
|
||||
<Button size="sm" variant="outline">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
|
Loading…
Reference in New Issue
Block a user