feat: implement session-based architecture for OPRO

- Add session layer above runs to group related optimization tasks
- Sessions use first task description as name instead of 'Session 1'
- Simplified sidebar: show sessions without expansion
- Add '+ 新建任务' button in header to create runs within session
- Fix: reload sessions after creating new run
- Add debugging logs for candidate generation
- Backend: auto-update session name with first task description
This commit is contained in:
2025-12-06 21:26:24 +08:00
parent 1376d60ed5
commit da30a0999c
4 changed files with 380 additions and 42 deletions

View File

@@ -50,7 +50,9 @@
// Main App Component
function App() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [runs, setRuns] = useState([]);
const [sessions, setSessions] = useState([]);
const [currentSessionId, setCurrentSessionId] = useState(null);
const [currentSessionRuns, setCurrentSessionRuns] = useState([]);
const [currentRunId, setCurrentRunId] = useState(null);
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
@@ -59,9 +61,9 @@
const [selectedModel, setSelectedModel] = useState('');
const chatEndRef = useRef(null);
// Load runs and models on mount
// Load sessions and models on mount
useEffect(() => {
loadRuns();
loadSessions();
loadModels();
}, []);
@@ -79,56 +81,106 @@
console.error('Failed to load models:', err);
}
}
// Auto-scroll chat
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
async function loadRuns() {
async function loadSessions() {
try {
const res = await fetch(`${API_BASE}/opro/runs`);
const res = await fetch(`${API_BASE}/opro/sessions`);
const data = await res.json();
if (data.success) {
setRuns(data.data.runs || []);
setSessions(data.data.sessions || []);
}
} catch (err) {
console.error('Failed to load runs:', err);
console.error('Failed to load sessions:', err);
}
}
async function loadSessionRuns(sessionId) {
try {
const res = await fetch(`${API_BASE}/opro/session/${sessionId}`);
const data = await res.json();
if (data.success) {
setCurrentSessionRuns(data.data.runs || []);
}
} catch (err) {
console.error('Failed to load session runs:', err);
}
}
async function createNewSession() {
try {
const res = await fetch(`${API_BASE}/opro/session/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to create session');
}
const sessionId = data.data.session_id;
setCurrentSessionId(sessionId);
setCurrentSessionRuns([]);
setCurrentRunId(null);
setMessages([]);
// Reload sessions list
await loadSessions();
return sessionId;
} catch (err) {
alert('创建会话失败: ' + err.message);
return null;
}
}
async function createNewRun(taskDescription) {
setLoading(true);
try {
// Create run
// Ensure we have a session
let sessionId = currentSessionId;
if (!sessionId) {
sessionId = await createNewSession();
if (!sessionId) return;
}
// Create run within session
const res = await fetch(`${API_BASE}/opro/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
task_description: taskDescription,
test_cases: [],
model_name: selectedModel || undefined
model_name: selectedModel || undefined,
session_id: sessionId
})
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to create run');
}
const runId = data.data.run_id;
setCurrentRunId(runId);
// Add user message
setMessages([{ role: 'user', content: taskDescription }]);
// Generate and evaluate candidates
await generateCandidates(runId);
// Reload runs list
await loadRuns();
// Reload sessions and session runs
await loadSessions();
await loadSessionRuns(sessionId);
} catch (err) {
alert('创建任务失败: ' + err.message);
console.error('Error creating run:', err);
} finally {
setLoading(false);
}
@@ -137,6 +189,7 @@
async function generateCandidates(runId) {
setLoading(true);
try {
console.log('Generating candidates for run:', runId);
const res = await fetch(`${API_BASE}/opro/generate_and_evaluate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -147,11 +200,13 @@
})
});
const data = await res.json();
console.log('Generate candidates response:', data);
if (!data.success) {
throw new Error(data.error || 'Failed to generate candidates');
}
// Add assistant message with candidates
setMessages(prev => [...prev, {
role: 'assistant',
@@ -161,6 +216,7 @@
}]);
} catch (err) {
alert('生成候选指令失败: ' + err.message);
console.error('Error generating candidates:', err);
} finally {
setLoading(false);
}
@@ -221,8 +277,7 @@
function handleExecute(instruction) {
if (loading) return;
const userInput = prompt('请输入要处理的内容(可选):');
executeInstruction(instruction, userInput);
executeInstruction(instruction, '');
}
function handleCopyInstruction(instruction) {
@@ -235,11 +290,31 @@
}
function handleNewTask() {
// Create new run within current session
setCurrentRunId(null);
setMessages([]);
setInputValue('');
}
async function handleNewSession() {
// Create completely new session
const sessionId = await createNewSession();
if (sessionId) {
setCurrentSessionId(sessionId);
setCurrentSessionRuns([]);
setCurrentRunId(null);
setMessages([]);
setInputValue('');
}
}
async function handleSelectSession(sessionId) {
setCurrentSessionId(sessionId);
setCurrentRunId(null);
setMessages([]);
await loadSessionRuns(sessionId);
}
async function loadRun(runId) {
setLoading(true);
try {
@@ -301,22 +376,22 @@
// Content area
React.createElement('div', { className: 'flex-1 overflow-y-auto scrollbar-hide p-2 flex flex-col' },
sidebarOpen ? React.createElement(React.Fragment, null,
// New task button (expanded)
// New session button (expanded)
React.createElement('button', {
onClick: handleNewTask,
onClick: handleNewSession,
className: 'mb-3 px-4 py-2.5 bg-white border border-gray-300 hover:bg-gray-50 rounded-lg transition-colors flex items-center justify-center gap-2 text-gray-700 font-medium'
},
React.createElement('span', { className: 'text-lg' }, '+'),
React.createElement('span', null, '新建会话')
),
// Sessions list
runs.length > 0 && React.createElement('div', { className: 'text-xs text-gray-500 mb-2 px-2' }, '会话列表'),
runs.map(run =>
sessions.length > 0 && React.createElement('div', { className: 'text-xs text-gray-500 mb-2 px-2' }, '会话列表'),
sessions.map(session =>
React.createElement('div', {
key: run.run_id,
onClick: () => loadRun(run.run_id),
key: session.session_id,
onClick: () => handleSelectSession(session.session_id),
className: `p-3 mb-1 rounded-lg cursor-pointer transition-colors flex items-center gap-2 ${
currentRunId === run.run_id ? 'bg-gray-100' : 'hover:bg-gray-50'
currentSessionId === session.session_id ? 'bg-gray-100' : 'hover:bg-gray-50'
}`
},
React.createElement('svg', {
@@ -331,12 +406,12 @@
React.createElement('path', { d: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z' })
),
React.createElement('div', { className: 'text-sm text-gray-800 truncate flex-1' },
run.task_description
session.session_name
)
)
)
) : React.createElement('button', {
onClick: handleNewTask,
onClick: handleNewSession,
className: 'p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors flex items-center justify-center',
title: '新建会话'
},
@@ -350,10 +425,19 @@
// 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 gap-3' },
React.createElement('h1', { className: 'text-lg font-normal text-gray-800' },
'OPRO'
)
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 || '当前会话'
)
),
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'
}, '+ 新建任务')
),
// Chat Messages
@@ -490,7 +574,9 @@
)
),
!currentRunId && React.createElement('div', { className: 'text-xs text-gray-500 mt-3 px-4' },
'输入任务描述后AI 将为你生成优化的系统指令'
currentSessionId
? '输入任务描述后AI 将为你生成优化的系统指令'
: '点击左侧"新建会话"开始,或输入任务描述自动创建会话'
)
)
)