auto-sync: 2026-05-21 09:08:20

This commit is contained in:
cfdaily
2026-05-21 09:08:20 +08:00
parent 9b37a0a6e4
commit dfcbcb0ecf
+81 -33
View File
@@ -98,32 +98,72 @@ function StatusButtons({ status, taskId, onAction }: { status: string; taskId: s
const selectedProjectId = useStore((s) => s.selectedProjectId);
const projects = useStore((s) => s.projects);
const loadV2Tasks = useStore((s) => s.loadV2Tasks);
const transitions = VALID_TRANSITIONS[status] || [];
const [loading, setLoading] = useState<string | null>(null);
const btnMap: Record<string, { label: string; icon: string; bg: string; color: string; border: string }> = {
claimed: { label: '认领任务', icon: '👤', bg: '#a07aff22', color: '#a07aff', border: '#a07aff44' },
working: { label: '开始执行', icon: '⚔️', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
review: { label: '提交审查', icon: '🔍', bg: '#818cf822', color: '#818cf8', border: '#818cf844' },
done: { label: '标记完成', icon: '', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
failed: { label: '标记失败', icon: '❌', bg: '#ff527022', color: '#ff5270', border: '#ff527044' },
blocked: { label: '标记阻塞', icon: '🚧', bg: '#f59e0b22', color: '#f59e0b', border: '#f59e0b44' },
pending: { label: '重置待认领', icon: '🔄', bg: '#7a9aff22', color: '#7a9aff', border: '#7a9aff44' },
paused: { label: '暂停', icon: '⏸', bg: '#818cf822', color: '#818cf8', border: '#818cf844' },
escalated: { label: '升级', icon: '⚠️', bg: '#ff527022', color: '#ff5270', border: '#ff527044' },
cancelled: { label: '取消任务', icon: '🚫', bg: '#6b728022', color: '#6b7280', border: '#6b728044' },
const [showAdvanced, setShowAdvanced] = useState(false);
// AI Native:只显示人需要做的动作
const PRIMARY_ACTIONS: Record<string, Array<{ target: string; label: string; icon: string; bg: string; color: string; border: string }>> = {
working: [{ target: 'paused', label: '暂停任务', icon: '', bg: '#818cf822', color: '#818cf8', border: '#818cf844' }],
waiting_human: [
{ target: 'done', label: '确认完成', icon: '', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
{ target: 'working', label: '拒绝,继续做', icon: '🔄', bg: '#7a9aff22', color: '#7a9aff', border: '#7a9aff44' },
],
escalated: [
{ target: 'working', label: '继续执行', icon: '', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
{ target: 'pending', label: '重新分配', icon: '🔄', bg: '#7a9aff22', color: '#7a9aff', border: '#7a9aff44' },
],
failed: [{ target: 'pending', label: '重试', icon: '🔄', bg: '#7a9aff22', color: '#7a9aff', border: '#7a9aff44' }],
blocked: [{ target: 'pending', label: '解除阻塞', icon: '🔓', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' }],
paused: [
{ target: 'working', label: '继续执行', icon: '▶', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
{ target: 'cancelled', label: '取消任务', icon: '🚫', bg: '#6b728022', color: '#6b7280', border: '#6b728044' },
],
done: [{ target: '__archive', label: '归档', icon: '📦', bg: '#6b728022', color: '#6b7280', border: '#6b728044' }],
cancelled: [{ target: '__archive', label: '归档', icon: '📦', bg: '#6b728022', color: '#6b7280', border: '#6b728044' }],
};
// 高级操作(手动干预用)
const ADVANCED_ACTIONS: Record<string, Array<{ target: string; label: string; icon: string; bg: string; color: string; border: string }>> = {
working: [
{ target: 'review', label: '手动提交审查', icon: '🔍', bg: '#818cf822', color: '#818cf8', border: '#818cf844' },
{ target: 'cancelled', label: '取消', icon: '🚫', bg: '#6b728022', color: '#6b7280', border: '#6b728044' },
],
review: [
{ target: 'done', label: '手动通过', icon: '✅', bg: '#2ecc8a22', color: '#2ecc8a', border: '#2ecc8a44' },
{ target: 'pending', label: '打回重做', icon: '🔄', bg: '#7a9aff22', color: '#7a9aff', border: '#7a9aff44' },
],
failed: [
{ target: 'cancelled', label: '取消', icon: '🚫', bg: '#6b728022', color: '#6b7280', border: '#6b728044' },
],
};
const primary = PRIMARY_ACTIONS[status] || [];
const advanced = ADVANCED_ACTIONS[status] || [];
if (primary.length === 0 && advanced.length === 0) {
// Agent 自动流转的状态,提示用户
const autoMsg: Record<string, string> = {
pending: '⏳ 等待 Agent 自动认领...',
claimed: '⏳ Agent 已认领,即将开始...',
review: '🔍 Agent 审查中,请等待审查结果...',
};
return <div style={{ fontSize: 12, color: 'var(--muted)', padding: '4px 0' }}>{autoMsg[status] || ''}</div>;
}
const handleClick = async (targetStatus: string) => {
setLoading(targetStatus);
try {
const result = await api.taskStatusUpdate(taskId, targetStatus);
if (result.ok) {
toast(`${targetStatus} 操作成功`, 'ok');
loadV2TaskDetail(taskId);
loadAll();
onAction?.();
if (targetStatus === '__archive') {
const pid = selectedProjectId || '';
const res = await fetch(`/api/projects/${pid}/tasks/${taskId}`, {
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ archived: 1 }),
});
if (res.ok) { toast('📦 已归档'); loadV2Tasks(); } else toast('归档失败', 'err');
} else {
toast(result.error || '操作失败', 'err');
const result = await api.taskStatusUpdate(taskId, targetStatus);
if (result.ok) { toast(`${targetStatus} 操作成功`, 'ok'); loadV2TaskDetail(taskId); loadAll(); onAction?.(); }
else toast(result.error || '操作失败', 'err');
}
} catch (e: any) {
toast(e.message || '⚠️ 操作失败', 'err');
@@ -132,22 +172,30 @@ function StatusButtons({ status, taskId, onAction }: { status: string; taskId: s
}
};
if (transitions.length === 0) return null;
const btn = (b: typeof primary[0]) => (
<button key={b.target} onClick={() => handleClick(b.target)} disabled={!!loading} style={{
padding: '5px 12px', borderRadius: 6, fontSize: 11, cursor: loading ? 'not-allowed' : 'pointer',
background: b.bg, color: b.color, border: `1px solid ${b.border}`, fontWeight: 600, opacity: loading === b.target ? 0.6 : 1,
}}>{loading === b.target ? '⏳' : b.icon} {b.label}</button>
);
return (
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{transitions.map(t => {
const b = btnMap[t];
if (!b) return null;
const isLoading = loading === t;
return (
<button key={t} onClick={() => handleClick(t)} disabled={!!loading} style={{
padding: '5px 12px', borderRadius: 6, fontSize: 11, cursor: loading ? 'not-allowed' : 'pointer',
background: b.bg, color: b.color, border: `1px solid ${b.border}`, fontWeight: 600,
opacity: isLoading ? 0.6 : 1,
}}>{isLoading ? '⏳' : b.icon} {b.label}</button>
);
})}
<div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>{primary.map(b => btn(b))}</div>
{advanced.length > 0 && (
<div style={{ marginTop: 6 }}>
{!showAdvanced ? (
<button onClick={() => setShowAdvanced(true)} style={{ fontSize: 10, color: 'var(--muted)', background: 'none', border: 'none', cursor: 'pointer', padding: '2px 0' }}>
</button>
) : (
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', padding: '4px 0', borderTop: '1px dashed var(--line)', marginTop: 4 }}>
{advanced.map(b => btn(b))}
<button onClick={() => setShowAdvanced(false)} style={{ fontSize: 10, color: 'var(--muted)', background: 'none', border: 'none', cursor: 'pointer' }}></button>
</div>
)}
</div>
)}
</div>
);
}