add simulator duplicate
This commit is contained in:
parent
a63bfd6783
commit
ff0c2017af
@ -386,6 +386,10 @@
|
|||||||
"creating": "Creating...",
|
"creating": "Creating...",
|
||||||
"runSimulation": "Run Simulation",
|
"runSimulation": "Run Simulation",
|
||||||
"createRun": "Create Run",
|
"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",
|
"execute": "Execute",
|
||||||
"executing": "Executing...",
|
"executing": "Executing...",
|
||||||
"generating": "Generating...",
|
"generating": "Generating...",
|
||||||
|
@ -386,6 +386,10 @@
|
|||||||
"creating": "创建中...",
|
"creating": "创建中...",
|
||||||
"runSimulation": "运行模拟",
|
"runSimulation": "运行模拟",
|
||||||
"createRun": "创建运行",
|
"createRun": "创建运行",
|
||||||
|
"duplicateRun": "创建副本",
|
||||||
|
"duplicateRunConfirm": "确定要创建此运行的副本吗?",
|
||||||
|
"duplicateRunSuccess": "副本创建成功",
|
||||||
|
"copyOf": "的副本",
|
||||||
"execute": "执行",
|
"execute": "执行",
|
||||||
"executing": "执行中...",
|
"executing": "执行中...",
|
||||||
"generating": "生成中...",
|
"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 [isExecuting, setIsExecuting] = useState(false)
|
||||||
const [streamOutput, setStreamOutput] = useState('')
|
const [streamOutput, setStreamOutput] = useState('')
|
||||||
const [runId, setRunId] = useState<string | null>(null)
|
const [runId, setRunId] = useState<string | null>(null)
|
||||||
|
const [isDuplicating, setIsDuplicating] = useState(false)
|
||||||
const outputRef = useRef<HTMLDivElement>(null)
|
const outputRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
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) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
@ -272,21 +298,41 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{run.status === 'pending' && (
|
<div className="flex items-center space-x-2">
|
||||||
<Button onClick={executeRun} disabled={isExecuting}>
|
<Button
|
||||||
{isExecuting ? (
|
onClick={handleDuplicateRun}
|
||||||
|
disabled={isDuplicating}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
{isDuplicating ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
<LoadingSpinner size="sm" />
|
||||||
{t('executing')}
|
<span className="ml-2">{t('creating')}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Play className="h-4 w-4 mr-2" />
|
<Copy className="h-4 w-4 mr-2" />
|
||||||
{t('execute')}
|
{t('duplicateRun')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Zap,
|
Zap,
|
||||||
DollarSign
|
DollarSign,
|
||||||
|
Copy
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { formatDistanceToNow } from 'date-fns'
|
import { formatDistanceToNow } from 'date-fns'
|
||||||
@ -72,6 +73,7 @@ export default function SimulatorPage() {
|
|||||||
})
|
})
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [statusFilter, setStatusFilter] = useState<string>('')
|
const [statusFilter, setStatusFilter] = useState<string>('')
|
||||||
|
const [duplicatingRunId, setDuplicatingRunId] = useState<string | null>(null)
|
||||||
|
|
||||||
const fetchRuns = useCallback(async () => {
|
const fetchRuns = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -101,6 +103,32 @@ export default function SimulatorPage() {
|
|||||||
}
|
}
|
||||||
}, [user, authLoading, pagination.page, statusFilter, fetchRuns])
|
}, [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) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
@ -391,6 +419,19 @@ export default function SimulatorPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 ml-6">
|
<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}`}>
|
<Link href={`/simulator/${run.id}`}>
|
||||||
<Button size="sm" variant="outline">
|
<Button size="sm" variant="outline">
|
||||||
<Eye className="h-4 w-4 mr-1" />
|
<Eye className="h-4 w-4 mr-1" />
|
||||||
|
Loading…
Reference in New Issue
Block a user