feat: 人工接管任务(takeover)+ 任务详情展示附件
B-③ takeover(⑧ ★ POST /api/tasks/:id/takeover):
后端:
- POST /api/tasks/:id/takeover:准备(或复用 result.worktree)任务 worktree,
返回 {taskId, worktree, branch, console}——console 为用户在自己终端起交互式
claude 的命令(daemon 无 TTY,不在进程内起交互会话);在途 run 时拒绝
前端:
- api.takeover;TakeoverModal(展示命令 + 一键复制 + 分支提示)
- TaskDetail 加「⌨ 人工接管」按钮(onTakeover 经 TaskSection→TaskRow 线程下来)
- 顺带:TaskDetail 展示任务附件(📎 文件名,读 task._raw.attachments)
验证:typecheck 干净;206 测试通过;前端 build + dev 渲染 0 错误。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -100,7 +100,7 @@ function FilterBar({ t, kw, setKw, cplxSet, toggleCplx, statusSet, toggleGroup,
|
||||
);
|
||||
}
|
||||
|
||||
function TaskRow({ task, depth, ctx, expandedId, setExpandedId, openIds, toggleOpen, t, byId, cplxOf, onChangeCplx, flashId, onJump, visibleSet }) {
|
||||
function TaskRow({ task, depth, ctx, expandedId, setExpandedId, openIds, toggleOpen, t, byId, cplxOf, onChangeCplx, flashId, onJump, visibleSet, onTakeover }) {
|
||||
const { StatusChip } = window.MaestroDesignSystem_a6a290;
|
||||
const kids = (task.children || []).filter((k) => !visibleSet || visibleSet.has(k.id));
|
||||
const open = openIds.has(task.id) || !!visibleSet; // 筛选时自动展开可见节点
|
||||
@@ -140,13 +140,13 @@ function TaskRow({ task, depth, ctx, expandedId, setExpandedId, openIds, toggleO
|
||||
<CplxPicker task={task} cplx={cplxOf(task)} onChange={onChangeCplx} />
|
||||
<StatusChip status={task.status} label={t.status[task.status]} />
|
||||
</div>
|
||||
{expanded ? <TaskDetail task={task} t={t} byId={byId} onJump={onJump} /> : null}
|
||||
{expanded ? <TaskDetail task={task} t={t} byId={byId} onJump={onJump} onTakeover={onTakeover} /> : null}
|
||||
{kids.length && open ? (
|
||||
<div style={{ marginLeft: 22 }}>
|
||||
{kids.map((k) => (
|
||||
<TaskRow key={k.id} task={k} depth={depth + 1} ctx={visibleSet ? visibleSet.ctx.has(k.id) : false}
|
||||
expandedId={expandedId} setExpandedId={setExpandedId} openIds={openIds} toggleOpen={toggleOpen} t={t}
|
||||
byId={byId} cplxOf={cplxOf} onChangeCplx={onChangeCplx} flashId={flashId} onJump={onJump} visibleSet={visibleSet} />
|
||||
byId={byId} cplxOf={cplxOf} onChangeCplx={onChangeCplx} flashId={flashId} onJump={onJump} visibleSet={visibleSet} onTakeover={onTakeover} />
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -154,11 +154,25 @@ function TaskRow({ task, depth, ctx, expandedId, setExpandedId, openIds, toggleO
|
||||
);
|
||||
}
|
||||
|
||||
function TaskDetail({ task, t, byId, onJump }) {
|
||||
function TaskDetail({ task, t, byId, onJump, onTakeover }) {
|
||||
const label = task.doc ? t.specLabel : task.ops ? t.opsLabel : null;
|
||||
const text = task.doc || task.ops;
|
||||
const attachments = (task._raw && task._raw.attachments) || [];
|
||||
return (
|
||||
<div style={{ background: 'var(--panel)', borderBottom: '1px solid var(--line)', borderLeft: '2px solid var(--green-dim)', padding: '14px 16px', animation: 'maestro-rise .2s ease both', display: 'grid', gap: 14 }}>
|
||||
{onTakeover || attachments.length ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
{onTakeover ? (
|
||||
<button onClick={(e) => { e.stopPropagation(); onTakeover(task); }}
|
||||
style={{ fontFamily: 'var(--mono)', fontSize: 12, cursor: 'pointer', background: 'transparent', color: 'var(--cyan)', border: '1px solid var(--cyan-dim)', borderRadius: 'var(--radius-sm,4px)', padding: '3px 10px' }}>
|
||||
⌨ 人工接管
|
||||
</button>
|
||||
) : null}
|
||||
{attachments.length ? (
|
||||
<span style={{ fontSize: 11, color: 'var(--faint)' }}>📎 {attachments.map((a) => a.name).join(' · ')}</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{label ? (
|
||||
<div>
|
||||
<div style={{ fontSize: 10.5, color: 'var(--muted)', letterSpacing: '.18em', marginBottom: 4 }}>{label}</div>
|
||||
@@ -253,7 +267,7 @@ function NewTaskPanel({ tasks, onCancel, onCreate, t }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TaskSection({ tasks, onToast, onCreate, t }) {
|
||||
function TaskSection({ tasks, onToast, onCreate, onTakeover, t }) {
|
||||
const { Button, SectionHead } = window.MaestroDesignSystem_a6a290;
|
||||
const [expandedId, setExpandedId] = React.useState(null);
|
||||
const [openIds, setOpenIds] = React.useState(() => new Set(['t1']));
|
||||
@@ -333,7 +347,7 @@ function TaskSection({ tasks, onToast, onCreate, t }) {
|
||||
{roots.map((tk) => (
|
||||
<TaskRow key={tk.id} task={tk} depth={0} ctx={visibleSet ? visibleSet.ctx.has(tk.id) : false}
|
||||
expandedId={expandedId} setExpandedId={setExpandedId} openIds={openIds} toggleOpen={toggleOpen} t={t}
|
||||
byId={byId} cplxOf={cplxOf} onChangeCplx={onChangeCplx} flashId={flashId} onJump={onJump} visibleSet={visibleSet} />
|
||||
byId={byId} cplxOf={cplxOf} onChangeCplx={onChangeCplx} flashId={flashId} onJump={onJump} visibleSet={visibleSet} onTakeover={onTakeover} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user