Files
apes-sidebar/src/client/SubagentView.module.css
T
Menghuan1918 8c521be26e chore: v0.8.0 — 任务管理页(子代理拓扑 + 后台命令底部输出 Dock、两击确认终止)
- tab 更名为任务管理:同页显示当前树全部后台命令(bash/pwsh 徽标、状态、时长、属主、退出码)
- 点击任务行在侧边栏底部共享 Dock 显示输出(sticky 终端式、自动跟随、单一轮询实例)
- 运行中任务两击确认强制终止;大量任务时列表保持紧凑、排序变动不跳动
- 新增 /sidebar/api tasks.output(非消费 peek,不偷模型 task_output 字节)与 tasks.kill 路由
- 修复任务镜像清空时的 React #300 hook 顺序崩溃(useCallback 移到提前 return 之前)+ 回归测试
2026-08-12 23:04:30 +08:00

548 lines
11 KiB
CSS

/* Subagent page: main-agent topology tree. Row/card chrome mirrors the
official ui-subagent catalog row (min-height 50, 7/8/11 padding, radius 8,
flex-start alignment, tree connector lines via border-left hairlines). */
.subagent {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.subagentHeader {
flex: none;
display: flex;
align-items: center;
gap: 8px;
height: 36px;
padding: 0 8px 0 12px;
}
.subagentTitle {
flex: 1;
min-width: 0;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* The descendant count badge ("3 · 1 运行中") beside the title. */
.subagentCount {
flex: none;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.subagentRefresh {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--dsw-alias-label-secondary);
cursor: pointer;
}
.subagentRefresh:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.subagentBody {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 2px 6px 8px;
}
/* One node card: state dot + label + secondary + (running) live lines. */
.subagentRow {
position: relative;
display: flex;
align-items: flex-start;
gap: 8px;
box-sizing: border-box;
width: 100%;
min-height: 50px;
padding: 7px 8px 7px 11px;
border: none;
border-radius: 8px;
background: transparent;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-primary);
text-align: left;
cursor: pointer;
outline: none;
}
.subagentRow:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
/* Keyboard focus (mirror of the official catalog row): a visible fill only
when focused via keyboard; the selected state below still wins on top. */
.subagentRow:focus-visible {
background: var(--dsw-alias-interactive-bg-hover);
}
/* The currently-open session, highlighted in place (mirror of the workspace
`.sessionRow.selected` — declared after the hover/focus rules so it wins). */
.subagentRowActive,
.subagentRowActive:hover,
.subagentRowActive:focus-visible {
background: var(--dsw-alias-interactive-bg-active);
}
.subagentRowDisabled {
color: var(--dsw-alias-label-dimmed);
cursor: not-allowed;
}
.subagentRowDisabled:hover {
background: transparent;
}
/* Loading placeholders are disabled but keep the default cursor (official
`.loadingRow` distinction from `.disabled`). */
.subagentRowLoading {
cursor: default;
}
/* The state dot aligns to the first text line (official catalog detail). */
.subagentDot {
margin-top: 4px;
}
.subagentContent {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.subagentLabel,
.subagentSecondary {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subagentLabel {
color: inherit;
font-weight: 400;
}
.subagentSecondary {
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
/* ── Live lines (running cards only) ──────────────────────────────────── */
.subagentLive {
display: flex;
align-items: baseline;
gap: 4px;
min-width: 0;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
overflow: hidden;
}
.subagentLiveTool {
flex: none;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-label-secondary);
}
.subagentLiveArgs {
min-width: 0;
font-family: var(--ds-font-family-code);
font-size: var(--dsw-font-xxxs-11-font-size);
line-height: var(--dsw-font-xxxs-11-line-height);
color: var(--dsw-alias-label-tertiary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subagentLiveText {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-secondary);
}
/* ── Tree connector lines (official catalog recipe) ────────────────────── */
.subagentNode {
position: relative;
min-width: 0;
}
.subagentChildren {
position: relative;
margin-left: 18px;
padding-left: 4px;
}
.subagentChildren::before {
content: '';
position: absolute;
left: 0;
top: -26px;
height: 26px;
border-left: 1px solid var(--dsw-alias-border-l2);
}
.subagentChildren[aria-busy='true']::before {
content: none;
}
.subagentChildren > .subagentNode::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: -4px;
border-left: 1px solid var(--dsw-alias-border-l2);
}
.subagentChildren > .subagentNode:last-child::before {
bottom: auto;
height: 17px;
}
.subagentChildren > .subagentNode > .subagentRow::before {
content: '';
position: absolute;
top: 16px;
left: -4px;
width: 14px;
border-top: 1px solid var(--dsw-alias-border-l2);
}
.subagentEmpty {
display: flex;
flex-direction: column;
gap: 2px;
padding: 16px;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-label-tertiary);
text-align: center;
}
.subagentEmptyHint {
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-dimmed);
}
.subagentError {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
font: var(--dsw-font-xxs-12);
color: var(--dsw-alias-state-error-primary);
}
.subagentErrorRetry {
flex: none;
display: inline-flex;
align-items: center;
gap: 4px;
height: 24px;
padding: 0 8px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--dsw-alias-label-secondary);
font: var(--dsw-font-xxxs-strong-11);
cursor: pointer;
}
.subagentErrorRetry:hover {
background: var(--dsw-alias-interactive-bg-hover);
color: var(--dsw-alias-label-primary);
}
/* ── Background-task section (below the topology tree) ────────────────── */
.tasks {
margin-top: 10px;
border-top: 1px solid var(--dsw-alias-border-l2);
padding-top: 8px;
}
.tasksHeader {
display: flex;
align-items: center;
gap: 8px;
height: 26px;
padding: 0 2px;
}
.tasksTitle {
flex: 1;
min-width: 0;
font: var(--dsw-font-xxxs-strong-11);
color: var(--dsw-alias-label-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tasksCount {
flex: none;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.tasksList {
display: flex;
flex-direction: column;
gap: 2px;
margin: 0;
padding: 0;
list-style: none;
}
/* One task row; the whole row lights up on hover, the expandable target
stays transparent (the kill button rides on the row's right edge). */
.tasksRow {
display: flex;
align-items: center;
gap: 4px;
border-radius: 8px;
}
.tasksRow:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.tasksRowSettled {
opacity: 0.8;
}
/* The row whose output is shown in the bottom dock. */
.tasksRowSelected,
.tasksRowSelected:hover {
background: var(--dsw-alias-interactive-bg-active);
}
.tasksRowMain {
flex: 1;
display: flex;
align-items: flex-start;
gap: 8px;
min-width: 0;
padding: 6px 8px 6px 11px;
border: none;
border-radius: 8px;
background: transparent;
font: var(--dsw-font-s-14);
color: var(--dsw-alias-label-primary);
text-align: left;
cursor: pointer;
outline: none;
}
.tasksRowMain:focus-visible {
background: var(--dsw-alias-interactive-bg-hover);
}
.tasksDot {
margin-top: 5px;
}
.tasksContent {
display: flex;
flex-direction: column;
min-width: 0;
gap: 1px;
}
/* The command line: a kind badge followed by the command itself in the code
face, like the terminal's live lines. */
.tasksLabelLine {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.tasksKind {
flex: none;
max-width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 5px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 4px;
font: var(--dsw-font-xxxs-strong-11);
line-height: 14px;
color: var(--dsw-alias-label-tertiary);
}
.tasksLabel {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--ds-font-family-code);
font-size: var(--dsw-font-xxxs-11-font-size);
line-height: var(--dsw-font-xxxs-11-line-height);
color: var(--dsw-alias-label-primary);
}
.tasksSecondary {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.tasksKill {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
margin-right: 4px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--dsw-alias-label-secondary);
cursor: pointer;
}
.tasksKill:hover {
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);
color: var(--dsw-alias-state-error-primary);
}
/* The armed (first-click) state: a filled confirm chip instead of the icon. */
.tasksKillArmed,
.tasksKillArmed:hover {
width: auto;
height: 20px;
padding: 0 8px;
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);
color: var(--dsw-alias-state-error-primary);
font: var(--dsw-font-xxxs-strong-11);
white-space: nowrap;
}
.tasksKill:disabled {
opacity: 0.5;
cursor: default;
}
.tasksKillError {
flex: none;
margin-right: 4px;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-state-error-primary);
}
/* ── The shared output dock (bottom of the page, sticky like a terminal) ── */
/* Direct child of the scrolling body, so it docks at the scrollport's bottom
edge while the task list scrolls above it; a solid background keeps the
tree from showing through. */
.tasksPane {
position: sticky;
bottom: 0;
z-index: 1;
margin-top: 4px;
border: 1px solid var(--dsw-alias-border-l2);
border-radius: 8px;
background: var(--dsw-alias-bg-base);
box-shadow: 0 -6px 12px -8px rgba(0, 0, 0, 0.35);
overflow: hidden;
}
.tasksPaneHeader {
display: flex;
align-items: center;
gap: 6px;
height: 28px;
padding: 0 4px 0 10px;
border-bottom: 1px solid var(--dsw-alias-border-l1);
}
.tasksPaneDot {
flex: none;
}
.tasksPaneLabel {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--ds-font-family-code);
font-size: var(--dsw-font-xxxs-11-font-size);
line-height: var(--dsw-font-xxxs-11-line-height);
color: var(--dsw-alias-label-primary);
}
.tasksPaneStatus {
flex: none;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.tasksPaneClose {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border: none;
border-radius: 5px;
background: transparent;
color: var(--dsw-alias-label-secondary);
cursor: pointer;
}
.tasksPaneClose:hover {
background: var(--dsw-alias-interactive-bg-hover);
}
.tasksPanePre {
margin: 0;
max-height: 200px;
overflow: auto;
padding: 6px 10px;
font-family: var(--ds-font-family-code);
font-size: var(--dsw-font-xxxs-11-font-size);
line-height: 1.5;
color: var(--dsw-alias-label-primary);
white-space: pre-wrap;
word-break: break-word;
}
.tasksPaneHint {
padding: 8px 10px;
font: var(--dsw-font-xxxs-11);
color: var(--dsw-alias-label-tertiary);
}
.tasksPaneError {
color: var(--dsw-alias-state-error-primary);
}