mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:51:59 +00:00
e10c4f23fc
来自 2026-09 的 UI/UX 审查(142 条)。本提交是前两波,第三波(记录页接入时间轴、跨页互链、check-ui 更新、全站扫尾)另行提交。 地基 - 设计令牌:全局规则和 .btn/.field/.chip 放进 CSS 层,Tailwind 工具类重新生效,选中态、开启态不再被吞掉;字号阶梯; 辅助文字在所有底色上 >= 4.5:1;状态色专用,节点类型色与之拉开色相;「跟随系统」真的跟随,首帧不闪深色;新字标与 favicon - 基础组件:confirmDialog / promptDialog 取代原生对话框;toast 出错不自动消失、可复制、读屏可播报;离线横幅与断线恢复; 九种剪影的状态徽标;唯一一份状态文案(lib/status)、格式化(lib/format)、术语(lib/terms)、报错人话(lib/errors) - 运行态内核:事件折成航迹(run/trace.ts),可投影到任意时刻;取消 / 失败 / 服务重启后统一收尾,不再永远转圈; run.status 随事件回写;token 事件不再堆进 events - 后端:协作成员逐个完成即发事件,调度者决策有事件;时长分执行 / 墙钟 / 等人;API 时间带时区; 中断的运行也发 stream.end;运行列表可筛选翻页;删除运行 / 工作流有保护;运行默认值接通;可测未保存的配置; 报错说清发生了什么、为什么、怎么办 功能 - 画布:状态四重编码、固定高度遥测槽、按缩放三档、运行胶囊与 HUD、航迹时间轴与回放、执行轨迹边、 等待闸门、跟随执行、正式运行只读、一次性时刻(开场点亮、回扫、阻断级联、出具印章) - 编辑:撤销重做、版本历史、问题面板可定位、三栏可收起、模板与表达式字段区分、变量血缘、快捷键、带搜索的选择器 - 回答流 / 问数据:贴底才跟随滚动、工件下钻、出具横幅列出缺口、答案可复制导出、按会话的忙闲、 答案不再在 2000 字处截断、重启后这一轮会收尾 - 记录页:待审批 / 运行中 / 失败页签、筛选翻页、实时接流、排错路径指到节点、封存凭证常驻 - 管理页:新的「数据」页、写操作失败都有反馈、弹窗里先测连接、统一持久的连接状态 - 外壳:导航改版、⌘K 命令面板、? 快捷键帮助、404、页面标题与 favicon 状态点、后台提醒 验证(沙箱):tsc 干净,pytest 579 passed,14 个检查脚本中 13 个全绿;check-ui 剩 1 项旧断言(按旧日期格式找运行行),第三波更新。
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from langgraph.config import get_stream_writer
|
|
|
|
from app.core.events import EventType
|
|
from app.engine.schema import GraphNode, GraphSpec
|
|
from app.engine.state import GraphState, template_context
|
|
from app.engine.expressions import render_deep, render_template
|
|
|
|
|
|
@dataclass
|
|
class RunContext:
|
|
"""一次运行的全局上下文,编译时闭包进每个节点。"""
|
|
|
|
run_id: str
|
|
thread_id: str
|
|
spec: GraphSpec
|
|
workflow_id: str | None = None
|
|
# 深度 > 0 表示当前在子图里,用来防止子图无限递归
|
|
depth: int = 0
|
|
memory_scope: str = "default"
|
|
collection: str = "default"
|
|
#: 工具审批策略的全局默认(设置里的「危险工具默认需要人工确认」)。节点和图级
|
|
#: defaults 都没配 approval 时才用它;为空表示老运行,按节点各自的缺省走
|
|
approval_default: str | None = None
|
|
extra: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class NodeContext:
|
|
"""单个节点执行时能拿到的东西。"""
|
|
|
|
node: GraphNode
|
|
run: RunContext
|
|
|
|
@property
|
|
def config(self) -> dict[str, Any]:
|
|
return self.node.data.config
|
|
|
|
def emit(self, event_type: EventType | str, **data: Any) -> None:
|
|
"""往 LangGraph 的 custom 流里发一条事件。
|
|
|
|
runner 在另一端消费,转成落库的 RunEvent 并广播给前端 —— 画布上的节点高亮、
|
|
token 流、工具调用卡片全都来自这里。
|
|
"""
|
|
try:
|
|
writer = get_stream_writer()
|
|
except Exception: # noqa: BLE001 - 不在图执行上下文里(比如单元测试)
|
|
return
|
|
writer(
|
|
{
|
|
"__agentlab__": True,
|
|
"type": str(event_type),
|
|
"node_id": self.node.id,
|
|
"ts": time.time(),
|
|
"data": data,
|
|
}
|
|
)
|
|
|
|
def render(self, value: Any, state: GraphState) -> Any:
|
|
"""用当前状态渲染配置里的模板。"""
|
|
return render_deep(value, template_context(state))
|
|
|
|
def render_str(self, value: str | None, state: GraphState) -> str:
|
|
return render_template(value or "", template_context(state))
|
|
|
|
def cfg(self, key: str, default: Any = None) -> Any:
|
|
"""读节点配置,没有就回退到图级 defaults。"""
|
|
value = self.config.get(key)
|
|
if value in (None, ""):
|
|
return self.run.spec.defaults.get(key, default)
|
|
return value
|
|
|
|
def approval_mode(self, fallback: str = "dangerous") -> str:
|
|
"""工具审批策略:节点 > 图级 defaults > 全局设置 > 节点类型自己的缺省。
|
|
|
|
审批恢复时节点会整个重放、再算一遍要不要问人,所以这里只能取决于
|
|
这次运行固定下来的东西——全局默认在发起时就定死在 RunContext 上了。
|
|
"""
|
|
return self.cfg("approval") or self.run.approval_default or fallback
|
|
|
|
def actor(self) -> str | None:
|
|
"""回复这个节点审批的人(设置里的署名)。没署名、或者不是恢复出来的,就是 None。"""
|
|
return (self.run.extra.get("actors") or {}).get(self.node.id)
|
|
|
|
|
|
class NodeError(RuntimeError):
|
|
"""节点执行失败。带上 node_id 方便前端定位到具体的卡片。"""
|
|
|
|
def __init__(self, node_id: str, message: str, *, retryable: bool = False) -> None:
|
|
super().__init__(message)
|
|
self.node_id = node_id
|
|
self.retryable = retryable
|