原始代码

This commit is contained in:
xxm
2025-12-05 07:11:25 +00:00
parent 045e777a11
commit dd5339de32
46 changed files with 5848 additions and 0 deletions

164
frontend/react-app.html Normal file
View File

@@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OPRO React 界面</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/immer@10.0.3/dist/immer.umd.production.min.js"></script>
<script crossorigin src="https://unpkg.com/zustand@4.5.2/umd/zustand.min.js"></script>
<style>
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; }
.layout { display: grid; grid-template-columns: 260px 1fr 360px; height: 100vh; }
.sidebar { border-right: 1px solid #eee; padding: 12px; overflow: auto; }
.center { border-right: 1px solid #eee; padding: 12px; overflow: auto; }
.right { padding: 12px; overflow: auto; }
.session-item { padding: 8px; border-radius: 6px; cursor: pointer; }
.session-item:hover { background: #f5f5f5; }
.chat-msg { margin: 8px 0; }
.chat-role { font-size: 12px; color: #888; }
.cand { border: 1px solid #ddd; border-radius: 8px; padding: 8px; margin-bottom: 8px; }
.cand-actions { display: flex; gap: 8px; margin-top: 8px; }
.topbar { display: flex; gap: 8px; padding: 8px; border-bottom: 1px solid #eee; }
textarea { width: 100%; min-height: 80px; }
</style>
</head>
<body>
<div id="root"></div>
<script>
const { useState, useEffect } = React;
const createZustand = zustand.default;
const useStore = createZustand((set, get) => ({
sessions: [],
currentSessionId: null,
detail: null,
candidatesPage: 0,
setSessions: (data) => set({ sessions: data }),
setCurrentSession: (sid) => set({ currentSessionId: sid }),
setDetail: (d) => set({ detail: d, candidatesPage: 0 }),
nextCandidatesPage: () => set({ candidatesPage: get().candidatesPage + 1 }),
prevCandidatesPage: () => set({ candidatesPage: Math.max(0, get().candidatesPage - 1) }),
}));
async function api(path, method = 'GET', body) {
const opts = { method, headers: { 'Content-Type': 'application/json' } };
if (body) opts.body = JSON.stringify(body);
const r = await fetch(path, opts);
if (!r.ok) throw new Error('HTTP ' + r.status);
return await r.json();
}
function SidebarSessionList() {
const sessions = useStore(s => s.sessions);
const setCurrentSession = useStore(s => s.setCurrentSession);
const setDetail = useStore(s => s.setDetail);
const [error, setError] = useState('');
async function loadSessions() { try {
const data = await api('/sessions');
useStore.getState().setSessions(data.sessions);
} catch(e) { setError(e.message); } }
async function openSession(sid) { try {
const data = await api('/session/' + sid);
setCurrentSession(sid);
setDetail(data);
} catch(e) { setError(e.message); } }
useEffect(() => { loadSessions(); }, []);
return React.createElement('div', { className: 'sidebar' },
React.createElement('div', { className: 'topbar' },
React.createElement('button', { onClick: loadSessions }, '刷新'),
),
error && React.createElement('div', null, error),
sessions.map(s => React.createElement('div', { key: s.session_id, className: 'session-item', onClick: () => openSession(s.session_id) },
React.createElement('div', null, s.original_query || '(空)'),
React.createElement('div', { className: 'chat-role' }, `轮次 ${s.round}`)
))
);
}
function ChatHistoryPanel() {
const detail = useStore(s => s.detail);
const [msg, setMsg] = useState('');
const [error, setError] = useState('');
async function send() {
try {
const sid = useStore.getState().currentSessionId;
const data = await api('/message', 'POST', { session_id: sid, message: msg });
setMsg('');
const d = await api('/session/' + sid);
useStore.getState().setDetail(d);
} catch(e) { setError(e.message); }
}
if (!detail) return React.createElement('div', { className: 'center' }, '请选择左侧会话');
return React.createElement('div', { className: 'center' },
(detail.history || []).map((h, i) => React.createElement('div', { key: i, className: 'chat-msg' },
React.createElement('div', { className: 'chat-role' }, h.role),
React.createElement('div', null, h.content)
)),
React.createElement('div', { className: 'topbar' },
React.createElement('textarea', { value: msg, onChange: e => setMsg(e.target.value), placeholder: '继续提问...' }),
React.createElement('button', { onClick: send }, '发送')
),
error && React.createElement('div', null, error)
);
}
function CandidateSelectorPanel() {
const detail = useStore(s => s.detail);
const page = useStore(s => s.candidatesPage);
const nextPage = useStore(s => s.nextCandidatesPage);
const prevPage = useStore(s => s.prevCandidatesPage);
const [error, setError] = useState('');
async function selectCand(c) {
try {
const sid = useStore.getState().currentSessionId;
await api('/select', 'POST', { session_id: sid, choice: c });
const d = await api('/session/' + sid);
useStore.getState().setDetail(d);
} catch(e) { setError(e.message); }
}
async function rejectCand(c) {
try {
const sid = useStore.getState().currentSessionId;
await api('/reject', 'POST', { session_id: sid, candidate: c });
const d = await api('/session/' + sid);
useStore.getState().setDetail(d);
} catch(e) { setError(e.message); }
}
if (!detail) return React.createElement('div', { className: 'right' }, '无会话');
const cands = detail.candidates || [];
const pageSize = 5;
const start = page * pageSize;
const group = cands.slice(start, start + pageSize);
return React.createElement('div', { className: 'right' },
React.createElement('div', { className: 'topbar' },
React.createElement('button', { onClick: prevPage }, '上一组'),
React.createElement('button', { onClick: nextPage }, '下一组')
),
group.map((c, i) => React.createElement('div', { key: start + i, className: 'cand' },
React.createElement('div', null, c),
React.createElement('div', { className: 'cand-actions' },
React.createElement('button', { onClick: () => selectCand(c) }, '✅ 选择'),
React.createElement('button', { onClick: () => rejectCand(c) }, '❌ 拒绝')
)
)),
error && React.createElement('div', null, error)
);
}
function App() {
return React.createElement('div', { className: 'layout' },
React.createElement(SidebarSessionList),
React.createElement(ChatHistoryPanel),
React.createElement(CandidateSelectorPanel),
);
}
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
</script>
</body>
</html>