auto-sync: 2026-05-21 09:08:20
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user