add simulator edit
This commit is contained in:
parent
ff0c2017af
commit
559f8fc878
@ -390,6 +390,13 @@
|
|||||||
"duplicateRunConfirm": "Are you sure you want to create a copy of this run?",
|
"duplicateRunConfirm": "Are you sure you want to create a copy of this run?",
|
||||||
"duplicateRunSuccess": "Duplicate created successfully",
|
"duplicateRunSuccess": "Duplicate created successfully",
|
||||||
"copyOf": " Copy",
|
"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",
|
"execute": "Execute",
|
||||||
"executing": "Executing...",
|
"executing": "Executing...",
|
||||||
"generating": "Generating...",
|
"generating": "Generating...",
|
||||||
|
@ -390,6 +390,13 @@
|
|||||||
"duplicateRunConfirm": "确定要创建此运行的副本吗?",
|
"duplicateRunConfirm": "确定要创建此运行的副本吗?",
|
||||||
"duplicateRunSuccess": "副本创建成功",
|
"duplicateRunSuccess": "副本创建成功",
|
||||||
"copyOf": "的副本",
|
"copyOf": "的副本",
|
||||||
|
"editRun": "编辑运行",
|
||||||
|
"saveChanges": "保存更改",
|
||||||
|
"cancelEdit": "取消编辑",
|
||||||
|
"runName": "运行名称",
|
||||||
|
"cannotEditExecutedRun": "已运行的记录无法编辑",
|
||||||
|
"runUpdated": "运行记录更新成功",
|
||||||
|
"selectModel": "选择模型",
|
||||||
"execute": "执行",
|
"execute": "执行",
|
||||||
"executing": "执行中...",
|
"executing": "执行中...",
|
||||||
"generating": "生成中...",
|
"generating": "生成中...",
|
||||||
|
@ -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);
|
return NextResponse.json(updatedRun);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating simulator run:", error);
|
console.error("Error updating simulator run:", error);
|
||||||
|
@ -10,6 +10,9 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { Card } from '@/components/ui/card'
|
import { Card } from '@/components/ui/card'
|
||||||
import { LoadingSpinner } from '@/components/ui/loading-spinner'
|
import { LoadingSpinner } from '@/components/ui/loading-spinner'
|
||||||
import { Badge } from '@/components/ui/badge'
|
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 {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Play,
|
Play,
|
||||||
@ -23,7 +26,10 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
Settings,
|
Settings,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
DollarSign
|
DollarSign,
|
||||||
|
Edit,
|
||||||
|
Save,
|
||||||
|
X
|
||||||
} 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'
|
||||||
@ -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 }> }) {
|
export default function SimulatorRunPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { user, loading: authLoading } = useAuthUser()
|
const { user, loading: authLoading } = useAuthUser()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -77,6 +94,20 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
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 [isDuplicating, setIsDuplicating] = useState(false)
|
||||||
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const [models, setModels] = useState<Model[]>([])
|
||||||
|
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<HTMLDivElement>(null)
|
const outputRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -106,6 +137,94 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
}
|
}
|
||||||
}, [runId, router])
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!authLoading && user && runId) {
|
if (!authLoading && user && runId) {
|
||||||
fetchRunCallback()
|
fetchRunCallback()
|
||||||
@ -278,9 +397,22 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
|
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-foreground mb-2">
|
{isEditing ? (
|
||||||
{run.name}
|
<div className="mb-2">
|
||||||
</h1>
|
<Label htmlFor="runName" className="text-sm font-medium">{t('runName')}</Label>
|
||||||
|
<Input
|
||||||
|
id="runName"
|
||||||
|
value={editForm.name}
|
||||||
|
onChange={(e) => setEditForm({...editForm, name: e.target.value})}
|
||||||
|
className="text-2xl font-bold border-2 border-primary"
|
||||||
|
placeholder={t('runName')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<h1 className="text-3xl font-bold text-foreground mb-2">
|
||||||
|
{run.name}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
<div className="flex items-center space-x-4 text-sm text-muted-foreground">
|
<div className="flex items-center space-x-4 text-sm text-muted-foreground">
|
||||||
<Badge className={`border ${getStatusColor(run.status)}`}>
|
<Badge className={`border ${getStatusColor(run.status)}`}>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
@ -317,7 +449,47 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{run.status === 'pending' && (
|
{/* 编辑按钮 - 只有pending状态才显示 */}
|
||||||
|
{run.status === 'pending' && !isEditing && (
|
||||||
|
<Button
|
||||||
|
onClick={handleStartEdit}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4 mr-2" />
|
||||||
|
{t('editRun')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 编辑模式下的保存和取消按钮 */}
|
||||||
|
{isEditing && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 mr-2" />
|
||||||
|
{t('cancelEdit')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSaveEdit}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
{isSaving ? (
|
||||||
|
<>
|
||||||
|
<LoadingSpinner size="sm" />
|
||||||
|
<span className="ml-2">{t('creating')}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="h-4 w-4 mr-2" />
|
||||||
|
{t('saveChanges')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{run.status === 'pending' && !isEditing && (
|
||||||
<Button onClick={executeRun} disabled={isExecuting}>
|
<Button onClick={executeRun} disabled={isExecuting}>
|
||||||
{isExecuting ? (
|
{isExecuting ? (
|
||||||
<>
|
<>
|
||||||
@ -346,38 +518,60 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
<FileText className="h-5 w-5 mr-2" />
|
<FileText className="h-5 w-5 mr-2" />
|
||||||
{t('promptContent')}
|
{t('promptContent')}
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
{!isEditing && (
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={() => copyToClipboard(promptContent)}
|
size="sm"
|
||||||
>
|
onClick={() => copyToClipboard(promptContent)}
|
||||||
<Copy className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Copy className="h-4 w-4" />
|
||||||
</div>
|
</Button>
|
||||||
<div className="bg-muted rounded-md p-4 border max-h-64 overflow-y-auto">
|
)}
|
||||||
<pre className="text-sm text-foreground whitespace-pre-wrap font-mono">
|
|
||||||
{promptContent}
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
|
{isEditing ? (
|
||||||
|
<Textarea
|
||||||
|
value={editForm.promptContent}
|
||||||
|
onChange={(e) => setEditForm({...editForm, promptContent: e.target.value})}
|
||||||
|
className="min-h-32 font-mono text-sm"
|
||||||
|
placeholder={t('promptContentPlaceholder')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="bg-muted rounded-md p-4 border max-h-64 overflow-y-auto">
|
||||||
|
<pre className="text-sm text-foreground whitespace-pre-wrap font-mono">
|
||||||
|
{promptContent}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* User Input */}
|
{/* User Input */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-xl font-semibold">{t('userInput')}</h2>
|
<h2 className="text-xl font-semibold">{t('userInput')}</h2>
|
||||||
<Button
|
{!isEditing && (
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={() => copyToClipboard(run.userInput)}
|
size="sm"
|
||||||
>
|
onClick={() => copyToClipboard(run.userInput)}
|
||||||
<Copy className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Copy className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-muted rounded-md p-4 border">
|
{isEditing ? (
|
||||||
<div className="text-sm text-foreground whitespace-pre-wrap">
|
<Textarea
|
||||||
{run.userInput}
|
value={editForm.userInput}
|
||||||
|
onChange={(e) => setEditForm({...editForm, userInput: e.target.value})}
|
||||||
|
className="min-h-32"
|
||||||
|
placeholder={t('userInputPlaceholder')}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="bg-muted rounded-md p-4 border">
|
||||||
|
<div className="text-sm text-foreground whitespace-pre-wrap">
|
||||||
|
{run.userInput}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Configuration */}
|
{/* Configuration */}
|
||||||
@ -386,56 +580,159 @@ export default function SimulatorRunPage({ params }: { params: Promise<{ id: str
|
|||||||
<Settings className="h-5 w-5 mr-2" />
|
<Settings className="h-5 w-5 mr-2" />
|
||||||
{t('configuration')}
|
{t('configuration')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
{isEditing ? (
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<span className="font-medium text-foreground">
|
{/* Model Selection */}
|
||||||
{t('temperature')}:
|
<div>
|
||||||
</span>
|
<Label htmlFor="modelSelect" className="text-sm font-medium">{t('selectModel')}</Label>
|
||||||
<span className="ml-2 text-muted-foreground">
|
<select
|
||||||
{run.temperature || 0.7}
|
id="modelSelect"
|
||||||
</span>
|
value={editForm.modelId}
|
||||||
|
onChange={(e) => setEditForm({...editForm, modelId: e.target.value})}
|
||||||
|
className="w-full mt-1 px-3 py-2 border border-input bg-background rounded-md text-sm"
|
||||||
|
>
|
||||||
|
{models.map((model) => (
|
||||||
|
<option key={model.id} value={model.id}>
|
||||||
|
{model.provider} - {model.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Temperature */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="temperature" className="text-sm font-medium">{t('temperature')}</Label>
|
||||||
|
<Input
|
||||||
|
id="temperature"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="2"
|
||||||
|
step="0.1"
|
||||||
|
value={editForm.temperature}
|
||||||
|
onChange={(e) => setEditForm({...editForm, temperature: parseFloat(e.target.value) || 0.7})}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Max Tokens */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="maxTokens" className="text-sm font-medium">{t('maxTokens')}</Label>
|
||||||
|
<Input
|
||||||
|
id="maxTokens"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={editForm.maxTokens || ''}
|
||||||
|
onChange={(e) => setEditForm({...editForm, maxTokens: e.target.value ? parseInt(e.target.value) : undefined})}
|
||||||
|
placeholder="Leave empty for model default"
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top P */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="topP" className="text-sm font-medium">{t('topP')}</Label>
|
||||||
|
<Input
|
||||||
|
id="topP"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.01"
|
||||||
|
value={editForm.topP}
|
||||||
|
onChange={(e) => setEditForm({...editForm, topP: parseFloat(e.target.value) || 1})}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Frequency Penalty */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="frequencyPenalty" className="text-sm font-medium">{t('frequencyPenalty')}</Label>
|
||||||
|
<Input
|
||||||
|
id="frequencyPenalty"
|
||||||
|
type="number"
|
||||||
|
min="-2"
|
||||||
|
max="2"
|
||||||
|
step="0.1"
|
||||||
|
value={editForm.frequencyPenalty}
|
||||||
|
onChange={(e) => setEditForm({...editForm, frequencyPenalty: parseFloat(e.target.value) || 0})}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Presence Penalty */}
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="presencePenalty" className="text-sm font-medium">{t('presencePenalty')}</Label>
|
||||||
|
<Input
|
||||||
|
id="presencePenalty"
|
||||||
|
type="number"
|
||||||
|
min="-2"
|
||||||
|
max="2"
|
||||||
|
step="0.1"
|
||||||
|
value={editForm.presencePenalty}
|
||||||
|
onChange={(e) => setEditForm({...editForm, presencePenalty: parseFloat(e.target.value) || 0})}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{run.maxTokens && (
|
) : (
|
||||||
<div>
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div className="col-span-2">
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-foreground">
|
||||||
{t('maxTokens')}:
|
{t('selectModel')}:
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-2 text-muted-foreground">
|
<span className="ml-2 text-muted-foreground">
|
||||||
{run.maxTokens.toLocaleString()}
|
{run.model.provider} - {run.model.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{run.topP && (
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-foreground">
|
||||||
{t('topP')}:
|
{t('temperature')}:
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-2 text-muted-foreground">
|
<span className="ml-2 text-muted-foreground">
|
||||||
{run.topP}
|
{run.temperature || 0.7}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{run.maxTokens && (
|
||||||
{run.frequencyPenalty !== undefined && (
|
<div>
|
||||||
<div>
|
<span className="font-medium text-foreground">
|
||||||
<span className="font-medium text-foreground">
|
{t('maxTokens')}:
|
||||||
{t('frequencyPenalty')}:
|
</span>
|
||||||
</span>
|
<span className="ml-2 text-muted-foreground">
|
||||||
<span className="ml-2 text-muted-foreground">
|
{run.maxTokens.toLocaleString()}
|
||||||
{run.frequencyPenalty}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{run.topP && (
|
||||||
{run.presencePenalty !== undefined && (
|
<div>
|
||||||
<div>
|
<span className="font-medium text-foreground">
|
||||||
<span className="font-medium text-foreground">
|
{t('topP')}:
|
||||||
{t('presencePenalty')}:
|
</span>
|
||||||
</span>
|
<span className="ml-2 text-muted-foreground">
|
||||||
<span className="ml-2 text-muted-foreground">
|
{run.topP}
|
||||||
{run.presencePenalty}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{run.frequencyPenalty !== undefined && (
|
||||||
</div>
|
<div>
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{t('frequencyPenalty')}:
|
||||||
|
</span>
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{run.frequencyPenalty}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{run.presencePenalty !== undefined && (
|
||||||
|
<div>
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{t('presencePenalty')}:
|
||||||
|
</span>
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{run.presencePenalty}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Statistics */}
|
{/* Statistics */}
|
||||||
|
Loading…
Reference in New Issue
Block a user