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:
@@ -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 将为你生成优化的系统指令'
|
||||
: '点击左侧"新建会话"开始,或输入任务描述自动创建会话'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user