diff --git a/messages/en.json b/messages/en.json index 3f7f18b..96bd178 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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...", diff --git a/messages/zh.json b/messages/zh.json index a305ab6..9beb658 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -386,6 +386,10 @@ "creating": "创建中...", "runSimulation": "运行模拟", "createRun": "创建运行", + "duplicateRun": "创建副本", + "duplicateRunConfirm": "确定要创建此运行的副本吗?", + "duplicateRunSuccess": "副本创建成功", + "copyOf": "的副本", "execute": "执行", "executing": "执行中...", "generating": "生成中...", diff --git a/src/app/api/simulator/[id]/duplicate/route.ts b/src/app/api/simulator/[id]/duplicate/route.ts new file mode 100644 index 0000000..41d558e --- /dev/null +++ b/src/app/api/simulator/[id]/duplicate/route.ts @@ -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 } + ); + } +} \ No newline at end of file diff --git a/src/app/simulator/[id]/page.tsx b/src/app/simulator/[id]/page.tsx index 7d6f5bf..9471391 100644 --- a/src/app/simulator/[id]/page.tsx +++ b/src/app/simulator/[id]/page.tsx @@ -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(null) + const [isDuplicating, setIsDuplicating] = useState(false) const outputRef = useRef(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 - {run.status === 'pending' && ( - - )} + + {run.status === 'pending' && ( + + )} + diff --git a/src/app/simulator/page.tsx b/src/app/simulator/page.tsx index 83ac2ae..e77f139 100644 --- a/src/app/simulator/page.tsx +++ b/src/app/simulator/page.tsx @@ -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('') + const [duplicatingRunId, setDuplicatingRunId] = useState(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() {
+