import { useEffect, useState } from 'react'; import { Bot, Settings2, Lock } from 'lucide-react'; import ModelValidator from '../components/validators/ModelValidator'; import { LM_MODELS } from '../constants/models'; import type { JobConfig } from '../types'; type Extras = { datasetLimit: number; }; type Props = { onRun: (cfg: JobConfig, extras: Extras) => void; }; export default function ModelConfigPage({ onRun }: Props) { // 從 localStorage 載入 Dataset 頁的草稿 const [cfg, setCfg] = useState(() => { try { const draft = localStorage.getItem('cfgDraft'); if (draft) return JSON.parse(draft); } catch {} // fallback 預設 return { dataset: '', languageModel: '', scorerModel: '', k: 5, numCounterfactuals: 3, metrictarget: 0.5, tau: 0.1, iterations: 1000, seed: 42, enableFineTuning: false, counterfactual: false, }; }); const [datasetLimit, setDatasetLimit] = useState(() => { try { const extrasDraft = JSON.parse(localStorage.getItem('extrasDraft') || '{}'); return typeof extrasDraft.datasetLimit === 'number' ? extrasDraft.datasetLimit : 10; } catch { return 10; } }); const [customLM, setCustomLM] = useState(''); const [showCustomLanguageInput, setShowCustomLanguageInput] = useState(false); const [classificationTask, setClassificationTask] = useState< 'sentiment' | 'regard' | 'stereotype' | 'personality' | 'toxicity' >('sentiment'); const [toxicityModelChoice, setToxicityModelChoice] = useState<'detoxify' | 'junglelee'>('detoxify'); const setField = (k: K, v: JobConfig[K]) => setCfg((prev) => ({ ...prev, [k]: v })); // 若有草稿中的 model 設定也載回 useEffect(() => { try { const draft = localStorage.getItem('cfgDraft'); if (!draft) return; const parsed = JSON.parse(draft); setClassificationTask(parsed.classificationTask ?? 'sentiment'); setToxicityModelChoice(parsed.toxicityModelChoice ?? 'detoxify'); if (parsed.languageModel) setField('languageModel', parsed.languageModel); if (parsed.k) setField('k', parsed.k); if (typeof parsed.metrictarget === 'number') setField('metrictarget', parsed.metrictarget); } catch {} }, []); // 🔒 Example 鎖死規則:偵測到 example 就強制設定並關閉自定義輸入 const isExample = cfg.dataset === 'example'; useEffect(() => { if (!isExample) return; setField('languageModel', 'openai-community/gpt2'); // 後端預設也用這個 setShowCustomLanguageInput(false); setCustomLM(''); setField('k', 10); setClassificationTask('sentiment'); setField('metrictarget', 0.5); }, [isExample]); // 當 dataset 改為/離開 example 時觸發 const card = 'group relative rounded-2xl p-8 border border-white/30 bg-white/60 backdrop-blur-xl ' + 'shadow-[0_15px_40px_-20px_rgba(30,41,59,0.35)] transition-all duration-300 ' + 'hover:shadow-[0_20px_50px_-20px_rgba(79,70,229,0.45)] hover:-translate-y-0.5'; const sectionTitle = 'text-xl font-bold tracking-tight text-slate-900'; const fieldInput = 'w-full rounded-xl border-2 border-slate-200/70 bg-white/70 px-4 py-3 ' + 'focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-500/20 transition-all'; const selectInput = 'w-full rounded-xl border-2 border-slate-200/70 bg-white/70 px-3 py-2.5 ' + 'focus:outline-none focus:border-indigo-500 focus:ring-4 focus:ring-indigo-500/20 transition-all'; const canRun = !!(cfg.dataset && (cfg.languageModel || customLM)); return (
{/* 卡片 1:Language Generation Model */}

Language Generation Model

{isExample && ( Locked by Example )}
{!isExample && showCustomLanguageInput && ( { setCustomLM(e.target.value); setField('languageModel', e.target.value); }} className={`${fieldInput} mt-3`} /> )} {(isExample || customLM || cfg.languageModel) && (
)}
{/* K 與 datasetLimit */}
{ if (isExample) return; setField('k', parseInt(e.target.value || '0', 10)); }} disabled={isExample} className={fieldInput + (isExample ? ' cursor-not-allowed opacity-80' : '')} /> {isExample && (
Locked to 10 for the Example preset.
)}
setDatasetLimit(parseInt(e.target.value || '0', 10))} className={fieldInput} />
{/* 卡片 2:Feature Extraction / Classification */}

Feature Extraction Model

{isExample && ( Locked by Example )}
{/* 只有 toxicity 才需要,example 強制 sentiment 就不顯示這塊 */} {classificationTask === 'toxicity' && !isExample && (
)}
{ if (isExample) return; setField('metrictarget', parseFloat(e.target.value || '0')); }} disabled={isExample} className={fieldInput + (isExample ? ' cursor-not-allowed opacity-80' : '')} /> {isExample && (
Locked to 0.5 for the Example preset.
)}
{/* Run */}
); }