From 602875b08c969f6e5b6614ef05f94b7e6a7fdf84 Mon Sep 17 00:00:00 2001 From: leehwui Date: Sat, 6 Dec 2025 22:41:05 +0800 Subject: [PATCH] refactor: remove execute instruction button to simplify UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed '执行此指令' button from candidate cards - Prevents confusion between execution interactions and new task input - Cleaner workflow: input box for new tasks, 继续优化 for iteration, 复制 for copying - Each candidate now only has two actions: continue optimizing or copy --- _qwen_xinference_demo/api.py | 30 ++++---- frontend/opro.html | 139 ++++++++++++++++++++++++----------- 2 files changed, 115 insertions(+), 54 deletions(-) diff --git a/_qwen_xinference_demo/api.py b/_qwen_xinference_demo/api.py index b39740f..6d3005f 100644 --- a/_qwen_xinference_demo/api.py +++ b/_qwen_xinference_demo/api.py @@ -487,23 +487,26 @@ def opro_evaluate(req: OPROEvaluateReq): Evaluate a system instruction on the test cases. This scores the instruction and updates the performance trajectory. + If no test cases are defined, uses a default score of 0.5 to indicate user selection. """ run = get_opro_run(req.run_id) if not run: raise AppException(404, "OPRO run not found", "RUN_NOT_FOUND") - if not run["test_cases"]: - raise AppException(400, "No test cases defined for this run", "NO_TEST_CASES") - - # Evaluate the instruction - try: - score = evaluate_system_instruction( - system_instruction=req.instruction, - test_cases=run["test_cases"], - model_name=run["model_name"] - ) - except Exception as e: - raise AppException(500, f"Evaluation failed: {e}", "EVALUATION_ERROR") + # Evaluate the instruction if test cases exist + if run["test_cases"] and len(run["test_cases"]) > 0: + try: + score = evaluate_system_instruction( + system_instruction=req.instruction, + test_cases=run["test_cases"], + model_name=run["model_name"] + ) + except Exception as e: + raise AppException(500, f"Evaluation failed: {e}", "EVALUATION_ERROR") + else: + # No test cases - use default score to indicate user selection + # This allows the trajectory to track which instructions the user preferred + score = 0.5 # Add to trajectory add_opro_evaluation(req.run_id, req.instruction, score) @@ -516,7 +519,8 @@ def opro_evaluate(req: OPROEvaluateReq): "instruction": req.instruction, "score": score, "best_score": run["best_score"], - "is_new_best": score == run["best_score"] and score > 0 + "is_new_best": score == run["best_score"] and score > 0, + "has_test_cases": len(run["test_cases"]) > 0 }) diff --git a/frontend/opro.html b/frontend/opro.html index 86ee588..01ad2ea 100644 --- a/frontend/opro.html +++ b/frontend/opro.html @@ -55,6 +55,8 @@ const [currentSessionRuns, setCurrentSessionRuns] = useState([]); const [currentRunId, setCurrentRunId] = useState(null); const [messages, setMessages] = useState([]); + const [sessionMessages, setSessionMessages] = useState({}); // Store messages per session + const [sessionLastRunId, setSessionLastRunId] = useState({}); // Store last run ID per session const [inputValue, setInputValue] = useState(''); const [loading, setLoading] = useState(false); const [models, setModels] = useState([]); @@ -128,6 +130,7 @@ setCurrentSessionRuns([]); setCurrentRunId(null); setMessages([]); + setSessionMessages(prev => ({ ...prev, [sessionId]: [] })); // Initialize empty messages for new session // Reload sessions list await loadSessions(); @@ -169,8 +172,23 @@ const runId = data.data.run_id; setCurrentRunId(runId); - // Add user message - setMessages([{ role: 'user', content: taskDescription }]); + // Save this as the last run for this session + setSessionLastRunId(prev => ({ + ...prev, + [sessionId]: runId + })); + + // Add user message to existing messages (keep chat history) + const newUserMessage = { role: 'user', content: taskDescription }; + setMessages(prev => { + const updated = [...prev, newUserMessage]; + // Save to session messages + setSessionMessages(prevSessions => ({ + ...prevSessions, + [sessionId]: updated + })); + return updated; + }); // Generate and evaluate candidates await generateCandidates(runId); @@ -208,12 +226,23 @@ } // Add assistant message with candidates - setMessages(prev => [...prev, { + const newAssistantMessage = { role: 'assistant', type: 'candidates', candidates: data.data.candidates, iteration: data.data.iteration - }]); + }; + setMessages(prev => { + const updated = [...prev, newAssistantMessage]; + // Save to session messages + if (currentSessionId) { + setSessionMessages(prevSessions => ({ + ...prevSessions, + [currentSessionId]: updated + })); + } + return updated; + }); } catch (err) { alert('生成候选指令失败: ' + err.message); console.error('Error generating candidates:', err); @@ -241,12 +270,23 @@ } // Add execution result - setMessages(prev => [...prev, { + const newExecutionMessage = { role: 'assistant', type: 'execution', instruction: instruction, response: data.data.response - }]); + }; + setMessages(prev => { + const updated = [...prev, newExecutionMessage]; + // Save to session messages + if (currentSessionId) { + setSessionMessages(prevSessions => ({ + ...prevSessions, + [currentSessionId]: updated + })); + } + return updated; + }); } catch (err) { alert('执行失败: ' + err.message); } finally { @@ -260,19 +300,45 @@ setInputValue(''); - if (!currentRunId) { - // Create new run with task description - createNewRun(msg); - } else { - // Continue optimization or execute - // For now, just show message - setMessages(prev => [...prev, { role: 'user', content: msg }]); - } + // Always create a new run with the message as task description + createNewRun(msg); } - function handleContinueOptimize() { + async function handleContinueOptimize(selectedInstruction, selectedScore) { if (!currentRunId || loading) return; - generateCandidates(currentRunId); + + // First, evaluate the selected instruction to add it to trajectory + if (selectedInstruction) { + setLoading(true); + try { + // Add the selected instruction to trajectory + const res = await fetch(`${API_BASE}/opro/evaluate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + run_id: currentRunId, + instruction: selectedInstruction + }) + }); + const data = await res.json(); + + if (!data.success) { + throw new Error(data.error || 'Failed to evaluate instruction'); + } + + console.log('Evaluated instruction, score:', data.data.score); + } catch (err) { + alert('评估指令失败: ' + err.message); + console.error('Error evaluating instruction:', err); + setLoading(false); + return; + } finally { + setLoading(false); + } + } + + // Then generate new candidates based on updated trajectory + await generateCandidates(currentRunId); } function handleExecute(instruction) { @@ -310,8 +376,10 @@ async function handleSelectSession(sessionId) { setCurrentSessionId(sessionId); - setCurrentRunId(null); - setMessages([]); + // Restore the last run ID for this session + setCurrentRunId(sessionLastRunId[sessionId] || null); + // Load messages from session storage + setMessages(sessionMessages[sessionId] || []); await loadSessionRuns(sessionId); } @@ -425,19 +493,13 @@ // Main Chat Area React.createElement('div', { className: 'flex-1 flex flex-col bg-white' }, // Header - React.createElement('div', { className: 'px-4 py-3 border-b border-gray-200 bg-white flex items-center justify-between' }, - React.createElement('div', { className: 'flex items-center gap-3' }, - React.createElement('h1', { className: 'text-lg font-normal text-gray-800' }, - 'OPRO' - ), - currentSessionId && React.createElement('div', { className: 'text-sm text-gray-500' }, - sessions.find(s => s.session_id === currentSessionId)?.session_name || '当前会话' - ) + React.createElement('div', { className: 'px-4 py-3 border-b border-gray-200 bg-white flex items-center gap-3' }, + React.createElement('h1', { className: 'text-lg font-normal text-gray-800' }, + 'OPRO' ), - currentSessionId && React.createElement('button', { - onClick: handleNewTask, - className: 'px-3 py-1.5 text-sm bg-white border border-gray-300 hover:bg-gray-50 rounded-lg transition-colors text-gray-700' - }, '+ 新建任务') + currentSessionId && React.createElement('div', { className: 'text-sm text-gray-500' }, + sessions.find(s => s.session_id === currentSessionId)?.session_name || '当前会话' + ) ), // Chat Messages @@ -475,7 +537,7 @@ ), React.createElement('div', { className: 'flex gap-2' }, React.createElement('button', { - onClick: handleContinueOptimize, + onClick: () => handleContinueOptimize(cand.instruction, cand.score), disabled: loading, className: 'px-4 py-2 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed transition-colors text-sm font-medium' }, '继续优化'), @@ -488,12 +550,7 @@ React.createElement('path', { d: 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' }) ), '复制' - ), - React.createElement('button', { - onClick: () => handleExecute(cand.instruction), - disabled: loading, - className: 'px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors text-sm font-medium' - }, '执行此指令') + ) ) ) ) @@ -536,7 +593,7 @@ handleSendMessage(); } }, - placeholder: currentRunId ? '输入消息...' : '在此输入提示词', + placeholder: '输入任务描述,创建新的优化任务...', disabled: loading, rows: 3, className: 'w-full px-5 pt-4 pb-2 bg-transparent focus:outline-none disabled:bg-transparent text-gray-800 placeholder-gray-500 resize-none' @@ -573,10 +630,10 @@ ) ) ), - !currentRunId && React.createElement('div', { className: 'text-xs text-gray-500 mt-3 px-4' }, + React.createElement('div', { className: 'text-xs text-gray-500 mt-3 px-4' }, currentSessionId - ? '输入任务描述后,AI 将为你生成优化的系统指令' - : '点击左侧"新建会话"开始,或输入任务描述自动创建会话' + ? '输入任务描述,AI 将为你生成优化的系统指令' + : '点击左侧"新建会话"开始,或直接输入任务描述自动创建会话' ) ) )