auto-sync: 2026-05-19 13:57:55
This commit is contained in:
@@ -45,15 +45,9 @@ const AGENT_EMOJI: Record<string, string> = {
|
||||
// ── Decision Checkpoint ──
|
||||
|
||||
function DecisionCheckpoint({
|
||||
taskId,
|
||||
info,
|
||||
onDone,
|
||||
}: {
|
||||
taskId: string;
|
||||
info: CheckpointInfo;
|
||||
onDone: () => void;
|
||||
}) {
|
||||
const cp = info.checkpoint;
|
||||
taskId, cp, onDone,
|
||||
}: { taskId: string; cp: Checkpoint; onDone: () => void }) {
|
||||
const payload: DecisionPayload = JSON.parse(cp.payload || '{}');
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [note, setNote] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -63,12 +57,11 @@ function DecisionCheckpoint({
|
||||
if (!selected) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const r = await api.humanInputRespond(taskId, {
|
||||
node_id: info.node_id,
|
||||
action: 'decide',
|
||||
selected_option: selected,
|
||||
reason: note,
|
||||
const res = await fetch(`/api/projects/${api._getProjectId()}/tasks/${taskId}/checkpoints/${cp.id}/approve`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ resolved_by: 'user', note: `选择: ${selected}${note ? '\n' + note : ''}` }),
|
||||
});
|
||||
const r = await res.json();
|
||||
if (r.ok) { toast('✅ 已御批', 'ok'); onDone(); }
|
||||
else toast(r.error || '操作失败', 'err');
|
||||
} catch { toast('服务器连接失败', 'err'); }
|
||||
@@ -77,27 +70,14 @@ function DecisionCheckpoint({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.6, marginBottom: 14, padding: 12, background: 'var(--panel2)', borderRadius: 8 }}>
|
||||
{cp.description}
|
||||
</div>
|
||||
{/* 关联产出物 */}
|
||||
{cp.artifacts?.length ? (
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 4 }}>📦 关联产出物</div>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{cp.artifacts.map((a, i) => (
|
||||
<a key={i} href={api.artifactDownloadUrl(taskId, a.path)} target="_blank" rel="noreferrer"
|
||||
style={{ fontSize: 10, padding: '2px 8px', borderRadius: 4, border: '1px solid var(--line)', color: 'var(--fg)', textDecoration: 'none' }}>
|
||||
📄 {a.name} ↗
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{cp.description && (
|
||||
<div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.6, marginBottom: 14, padding: 12, background: 'var(--panel2)', borderRadius: 8 }}>
|
||||
{cp.description}
|
||||
</div>
|
||||
) : null}
|
||||
{/* 方案 */}
|
||||
)}
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--muted)', marginBottom: 8 }}>📋 备选方案</div>
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 14, flexWrap: 'wrap' }}>
|
||||
{cp.options?.map((opt) => (
|
||||
{(payload.options || []).map((opt) => (
|
||||
<div key={opt.id}
|
||||
onClick={() => setSelected(opt.id)}
|
||||
style={{
|
||||
@@ -110,28 +90,19 @@ function DecisionCheckpoint({
|
||||
<div style={{ float: 'right', background: '#22c55e', color: '#fff', fontSize: 10, fontWeight: 700, padding: '1px 6px', borderRadius: 3 }}>荐</div>
|
||||
)}
|
||||
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 4 }}>{opt.label}</div>
|
||||
{opt.description && <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6 }}>{opt.description}</div>}
|
||||
{opt.desc && <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6 }}>{opt.desc}</div>}
|
||||
{opt.pros?.map((p, i) => <div key={i} style={{ fontSize: 11, color: '#22c55e' }}>👍 {p}</div>)}
|
||||
{opt.cons?.map((c, i) => <div key={i} style={{ fontSize: 11, color: '#ef4444' }}>👎 {c}</div>)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 兜底自由输入 */}
|
||||
{(!cp.options || cp.options.length === 0) && (
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 4 }}>无预定义方案,请直接输入决策:</div>
|
||||
<textarea style={{ width: '100%', height: 60, background: 'var(--panel2)', border: '1px solid var(--line)', borderRadius: 6, padding: 8, color: 'var(--fg)', fontSize: 12 }}
|
||||
value={note} onChange={(e) => setNote(e.target.value)} placeholder="输入你的决策..." />
|
||||
</div>
|
||||
)}
|
||||
{/* 御批备注 */}
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 4 }}>📝 御批备注(可选)</div>
|
||||
<textarea style={{ width: '100%', height: 48, background: 'var(--panel2)', border: '1px solid var(--line)', borderRadius: 6, padding: 8, color: 'var(--fg)', fontSize: 12 }}
|
||||
value={note} onChange={(e) => setNote(e.target.value)} placeholder="记录决策理由..." />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
||||
<button className="btn-primary" disabled={!selected || loading} onClick={handleSubmit}>
|
||||
<button disabled={!selected || loading} onClick={handleSubmit} style={{ padding: '6px 16px', borderRadius: 6, border: 'none', background: selected ? '#4a90d9' : '#555', color: '#fff', cursor: selected ? 'pointer' : 'not-allowed', fontSize: 13, fontWeight: 600 }}>
|
||||
🎯 依此方案施行
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user