mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-24 10:58:13 +00:00
feat(status+webui): 运行态图形化 —— 排队/四级中断队列/中断栈/驻留子/通道拓扑
需求:首页不该只有文字,要能一眼看出内核在忙什么——排队消息数、各级中断
排队与中断栈、驻留子 agent 数量;这些要向**内部 SDK 暴露接口**,供 WebUI 等应用
展示;通道划分也要能画出来。
## 一、内核状态面(internal/sdk,内部 SDK,不受公开 SDK 冻结约束)
* SchedulerStatus 补:
- interrupt_queues[5]:**四级中断队列各自的深度**(下标即级别 1..4,下标 0 恒 0,
这样 level 能直接当数组下标用)。此前只有 pending_interrupts 总数,
看不出"堵在 L1 还是 L4"——四级是抢占优先级,堵在哪级是完全不同的运行状态。
- immediate:刚抢占成功、下一个安全点立即运行的那个中断(此前完全不可见)。
- suspend_frames:中断栈的帧(栈底→栈顶,只给任务标识),depth 之外还能看出
"谁被谁打断"。
- interrupts_by_level / preempts_by_level:各级累计登记数与抢占成功数。
* KernelStatus 补 residents(驻留子运行时视图:状态/轮次/上下文已满/输入通道/允许输出)。
刻意**不带**每个驻留子的 inputch 登记明细——状态面会被反复轮询,明细会让
每次 /status 背上几十 KB;只给表大小,要明细走专门接口。
* ChannelInfo 补 direction(in/out/io)、description、tools、output_caps、caps_text。
此前 collectKernelStatus 只透传 Name/Type,把描述/工具/能力**全丢了**,
前端只能画出一排光秃秃的名字。
## 二、WebUI
* 新增只读 `/api/v1/runtime`:只回运行态三件事(scheduler/residents/channels),
实测 **1.0KB**(/kernel 是 30KB 级)——所以能 3 秒轮询做"实时"感,
而不必反复拉全量状态。
* 首页新增「运行态」面板(纯 CSS + 内联 SVG,前端仍无构建链):
- 四个数字块:排队任务 / 待处理中断 / 中断栈(深度/上限) / 驻留子 Agent,带占比条;
- 四级中断队列条形图:每级"深度 · 登记/抢占",四级语义**照抄内核**
(L4 内核独占 / L3 交互 / L2 消息 / L1 后台),不自己起名字;
- 中断栈层叠图(栈顶在上)+ ⚡立即运行项;
- 通道拓扑:输入通道 → 内核 → 输出通道,双向通道两侧都出现,能力以胶囊标签显示。
* 3 秒轮询只在总览页可见时才发请求;切回总览时 renderAll 会立刻补一次。
## 验证
* 新增 TestSchedulerStatusExposesLevelsAndStack(四级队列/立即项/栈帧/各级计数映射,
并断言"未使用的级别必须为 0"与"下标 0 恒 0")、TestChannelInfoCarriesTopology、
TestRuntimeEndpoint(形状 + 不携带 tools/plugins + 无状态源时 503)。
* go build / vet / agent+core+sdk+plugin+webui 全量测试绿。
* 真实浏览器实测(CDP 驱动,注入运行态样本走真实渲染路径):
数字块 [3, 5, 2/4, 2];四级条 L4=1/L3=2/L2=1/L1=1 与数据一致;
栈帧按"栈底→栈顶"渲染且标出栈顶;通道左右分列、io 通道两侧都出现。
This commit is contained in:
@ -2097,3 +2097,190 @@
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 127, 172, 0.35) transparent;
|
||||
}
|
||||
|
||||
/* ===== 运行态面板(图形化总览)=====
|
||||
目标是"一眼看懂内核现在在干什么":排队/中断/中断栈/驻留子四个数字,
|
||||
四级中断队列的条形,中断栈的层叠图,以及通道拓扑。全部用纯 CSS + 内联 SVG,
|
||||
不引入任何依赖(前端刻意没有构建链)。 */
|
||||
.rt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
||||
gap: 10px;
|
||||
margin: 4px 0 14px;
|
||||
}
|
||||
.rt-tile {
|
||||
position: relative;
|
||||
padding: 12px 14px 10px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
.rt-tile .rt-num {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.rt-tile .rt-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.rt-tile .rt-sub {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.85;
|
||||
}
|
||||
.rt-tile.rt-active {
|
||||
border-color: var(--sakura-400);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
.rt-tile.rt-warn .rt-num {
|
||||
color: #ffb86b;
|
||||
}
|
||||
/* 底部细条:把"占了多少"用长度表达,比纯数字更快读懂 */
|
||||
.rt-bar {
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rt-bar > i {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--sakura-400), var(--frost-300));
|
||||
transition: width 0.35s var(--ease-out);
|
||||
}
|
||||
.rt-levels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.rt-level {
|
||||
display: grid;
|
||||
grid-template-columns: 74px 1fr 96px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.rt-level .rt-lv-name {
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.rt-level .rt-lv-track {
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.rt-level .rt-lv-track > i {
|
||||
display: block;
|
||||
height: 100%;
|
||||
transition: width 0.35s var(--ease-out);
|
||||
}
|
||||
.rt-level .rt-lv-meta {
|
||||
text-align: right;
|
||||
color: var(--text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.rt-lv-4 > i { background: #ff5c7a; }
|
||||
.rt-lv-3 > i { background: #ffa657; }
|
||||
.rt-lv-2 > i { background: var(--sakura-400); }
|
||||
.rt-lv-1 > i { background: var(--frost-300); }
|
||||
/* 中断栈:栈顶在上,做出"层叠现场"的观感 */
|
||||
.rt-stack {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
gap: 4px;
|
||||
margin: 2px 0 6px;
|
||||
}
|
||||
.rt-frame {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 9px;
|
||||
border-radius: 8px;
|
||||
font-size: 11px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-left: 3px solid var(--sakura-400);
|
||||
}
|
||||
.rt-frame .rt-frame-top {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.rt-empty {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
padding: 4px 0;
|
||||
}
|
||||
/* 通道拓扑:输入 → 内核 → 输出 */
|
||||
.rt-topo {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.rt-topo-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.rt-topo-col.rt-right {
|
||||
text-align: right;
|
||||
}
|
||||
.rt-chan {
|
||||
padding: 6px 9px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border-color);
|
||||
font-size: 11px;
|
||||
cursor: default;
|
||||
}
|
||||
.rt-chan .rt-chan-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
.rt-chan .rt-chan-sub {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.rt-chan .rt-caps {
|
||||
display: inline-flex;
|
||||
gap: 3px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.rt-cap {
|
||||
font-size: 9px;
|
||||
padding: 1px 5px;
|
||||
border-radius: 999px;
|
||||
background: rgba(136, 192, 208, 0.16);
|
||||
color: var(--frost-300);
|
||||
}
|
||||
.rt-core {
|
||||
width: 74px;
|
||||
height: 74px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
background: radial-gradient(circle at 50% 40%, rgba(243, 59, 124, 0.35), rgba(24, 24, 38, 0.9));
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
.rt-section-title {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.4px;
|
||||
margin: 12px 0 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user