From 559f8fc878e95f682ac63e2338a9d6b40e88ad90 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Sat, 9 Aug 2025 13:00:51 +0800 Subject: [PATCH] add simulator edit --- messages/en.json | 7 + messages/zh.json | 7 + src/app/api/simulator/[id]/route.ts | 106 +++++++ src/app/simulator/[id]/page.tsx | 429 +++++++++++++++++++++++----- 4 files changed, 483 insertions(+), 66 deletions(-) diff --git a/messages/en.json b/messages/en.json index 96bd178..c2a38ff 100644 --- a/messages/en.json +++ b/messages/en.json @@ -390,6 +390,13 @@ "duplicateRunConfirm": "Are you sure you want to create a copy of this run?", "duplicateRunSuccess": "Duplicate created successfully", "copyOf": " Copy", + "editRun": "Edit Run", + "saveChanges": "Save Changes", + "cancelEdit": "Cancel Edit", + "runName": "Run Name", + "cannotEditExecutedRun": "Cannot edit executed runs", + "runUpdated": "Run updated successfully", + "selectModel": "Select Model", "execute": "Execute", "executing": "Executing...", "generating": "Generating...", diff --git a/messages/zh.json b/messages/zh.json index 9beb658..b435ecd 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -390,6 +390,13 @@ "duplicateRunConfirm": "确定要创建此运行的副本吗?", "duplicateRunSuccess": "副本创建成功", "copyOf": "的副本", + "editRun": "编辑运行", + "saveChanges": "保存更改", + "cancelEdit": "取消编辑", + "runName": "运行名称", + "cannotEditExecutedRun": "已运行的记录无法编辑", + "runUpdated": "运行记录更新成功", + "selectModel": "选择模型", "execute": "执行", "executing": "执行中...", "generating": "生成中...", diff --git a/src/app/api/simulator/[id]/route.ts b/src/app/api/simulator/[id]/route.ts index b5f119b..8d30fea 100644 --- a/src/app/api/simulator/[id]/route.ts +++ b/src/app/api/simulator/[id]/route.ts @@ -128,6 +128,112 @@ export async function PATCH( } }); + return NextResponse.json(updatedRun); + } catch (error) { + console.error("Error updating simulator run:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} + +export async function PUT( + 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 body = await request.json(); + const { + name, + userInput, + promptContent, + modelId, + temperature, + maxTokens, + topP, + frequencyPenalty, + presencePenalty, + } = body; + + // 检查运行是否存在且属于用户 + const existingRun = await prisma.simulatorRun.findFirst({ + where: { + id, + userId: user.id, + }, + }); + + if (!existingRun) { + return NextResponse.json({ error: "Run not found" }, { status: 404 }); + } + + // 只允许编辑pending状态的运行 + if (existingRun.status !== "pending") { + return NextResponse.json({ error: "Cannot edit executed runs" }, { status: 400 }); + } + + // 如果更改了模型,验证新模型是否可用 + if (modelId && modelId !== existingRun.modelId) { + const model = await prisma.model.findUnique({ + where: { id: modelId }, + }); + + if (!model || !model.isActive) { + return NextResponse.json({ error: "Model not available" }, { status: 400 }); + } + } + + // 更新运行记录 + const updatedRun = await prisma.simulatorRun.update({ + where: { id }, + data: { + ...(name && { name }), + ...(userInput && { userInput }), + ...(promptContent !== undefined && { promptContent }), + ...(modelId && { modelId }), + ...(temperature !== undefined && { temperature }), + ...(maxTokens !== undefined && { maxTokens }), + ...(topP !== undefined && { topP }), + ...(frequencyPenalty !== undefined && { frequencyPenalty }), + ...(presencePenalty !== undefined && { presencePenalty }), + }, + select: { + id: true, + name: true, + status: true, + userInput: true, + promptContent: true, + createdAt: true, + temperature: true, + maxTokens: true, + topP: true, + frequencyPenalty: true, + presencePenalty: true, + prompt: { + select: { id: true, name: true, content: true } + }, + model: { + select: { + id: true, + name: true, + provider: true, + modelId: true, + description: true, + maxTokens: true + } + } + } + }); + return NextResponse.json(updatedRun); } catch (error) { console.error("Error updating simulator run:", error); diff --git a/src/app/simulator/[id]/page.tsx b/src/app/simulator/[id]/page.tsx index 9471391..3927d20 100644 --- a/src/app/simulator/[id]/page.tsx +++ b/src/app/simulator/[id]/page.tsx @@ -10,6 +10,9 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { LoadingSpinner } from '@/components/ui/loading-spinner' import { Badge } from '@/components/ui/badge' +import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' +import { Label } from '@/components/ui/label' import { ArrowLeft, Play, @@ -23,7 +26,10 @@ import { FileText, Settings, BarChart3, - DollarSign + DollarSign, + Edit, + Save, + X } from 'lucide-react' import Link from 'next/link' import { formatDistanceToNow } from 'date-fns' @@ -65,6 +71,17 @@ interface SimulatorRun { } } +interface Model { + id: string + modelId: string + name: string + provider: string + description?: string + maxTokens?: number + inputCostPer1k?: number + outputCostPer1k?: number +} + export default function SimulatorRunPage({ params }: { params: Promise<{ id: string }> }) { const { user, loading: authLoading } = useAuthUser() const router = useRouter() @@ -77,6 +94,20 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str const [streamOutput, setStreamOutput] = useState('') const [runId, setRunId] = useState(null) const [isDuplicating, setIsDuplicating] = useState(false) + const [isEditing, setIsEditing] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [models, setModels] = useState([]) + const [editForm, setEditForm] = useState({ + name: '', + userInput: '', + promptContent: '', + modelId: '', + temperature: 0.7, + maxTokens: undefined as number | undefined, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + }) const outputRef = useRef(null) useEffect(() => { @@ -106,6 +137,94 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str } }, [runId, router]) + const fetchModels = useCallback(async () => { + try { + const response = await fetch('/api/simulator/models') + if (response.ok) { + const data = await response.json() + setModels(data.models || []) + } + } catch (error) { + console.error('Error fetching models:', error) + } + }, []) + + const handleStartEdit = () => { + if (!run || run.status !== 'pending') { + return + } + + const promptContent = getPromptContent(run) + setEditForm({ + name: run.name, + userInput: run.userInput, + promptContent, + modelId: run.model.id, + temperature: run.temperature || 0.7, + maxTokens: run.maxTokens, + topP: run.topP || 1, + frequencyPenalty: run.frequencyPenalty || 0, + presencePenalty: run.presencePenalty || 0, + }) + setIsEditing(true) + + // 获取模型列表 + fetchModels() + } + + const handleCancelEdit = () => { + setIsEditing(false) + setEditForm({ + name: '', + userInput: '', + promptContent: '', + modelId: '', + temperature: 0.7, + maxTokens: undefined, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + }) + } + + const handleSaveEdit = async () => { + if (!run) return + + setIsSaving(true) + try { + const response = await fetch(`/api/simulator/${run.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: editForm.name, + userInput: editForm.userInput, + promptContent: editForm.promptContent, + modelId: editForm.modelId, + temperature: editForm.temperature, + maxTokens: editForm.maxTokens, + topP: editForm.topP, + frequencyPenalty: editForm.frequencyPenalty, + presencePenalty: editForm.presencePenalty, + }), + }) + + if (response.ok) { + const updatedRun = await response.json() + setRun(updatedRun) + setIsEditing(false) + console.log(t('runUpdated')) + } else { + console.error('Error updating run:', await response.text()) + } + } catch (error) { + console.error('Error updating run:', error) + } finally { + setIsSaving(false) + } + } + useEffect(() => { if (!authLoading && user && runId) { fetchRunCallback() @@ -278,9 +397,22 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
-

- {run.name} -

+ {isEditing ? ( +
+ + setEditForm({...editForm, name: e.target.value})} + className="text-2xl font-bold border-2 border-primary" + placeholder={t('runName')} + /> +
+ ) : ( +

+ {run.name} +

+ )}
@@ -317,7 +449,47 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str )} - {run.status === 'pending' && ( + {/* 编辑按钮 - 只有pending状态才显示 */} + {run.status === 'pending' && !isEditing && ( + + )} + + {/* 编辑模式下的保存和取消按钮 */} + {isEditing && ( + <> + + + + )} + + {run.status === 'pending' && !isEditing && ( -
-
-
-                {promptContent}
-              
+ {!isEditing && ( + + )}
+ {isEditing ? ( +