宿主交互设计
设计 UI 组件与 downcity 主包之间的标准交互边界,而不是直接导入主包内部模块
宿主交互设计
这一页回答一个更关键的问题:
@downcity/ui 里的组件,应该怎样和 downcity 主包交互?
答案不是“组件里直接 import 主包内部模块”,而是把交互拆成三层:
downcity主包负责暴露稳定的网关能力- 宿主应用负责把网关包装成适配器或 hook
@downcity/ui只负责展示和触发交互意图
这样做的原因很直接:
- 不依赖
cli/city/*内部路径,生产打包更稳定 - 组件可以在
console-ui、homepage、其他宿主里复用 - 业务逻辑、运行时控制、视觉组件不会混成一层
不推荐的方式
下面这种方式不应该继续扩散:
import { someInternalRuntimeMethod } from "../../../cli/city/src/console/ui/xxx";这会带来几个问题:
- UI 包直接依赖主包源码路径
- 打包产物无法保证对外可用
- 主包一重构,组件就失效
- 组件无法脱离当前 monorepo 复用
推荐的三层结构
cli/city
└─ 提供 /api/ui/* 与 /api/dashboard/* 网关能力
console-ui
└─ 封装 request / query / mutation / hook
@downcity/ui
└─ 提供 Button / Card / Dialog / Sheet / Tabs 等纯展示原语如果未来需要把“交互适配器”也对外复用,可以单独增加一个包:
@downcity/ui-bridge但它不应该塞进 @downcity/ui。
现有边界已经具备的基础
当前仓库里,其实已经有一条很好的边界:
cli/city/src/console/ui/ConsoleUIGateway.tscli/city/src/types/ConsoleUI.tsproducts/console/src/lib/dashboard-api.tsproducts/console/src/hooks/useConsoleDashboard.ts
这说明正确方向已经不是“组件直连主包源码”,而是:
- 主包提供稳定 API
- 前端通过适配层访问 API
- 组件接收数据和回调
推荐组件原型
如果参考 teamprofile 那种“地图 + 详情 + 操作”的组织方式,在 Downcity 里不应该直接复制它的业务模拟逻辑,而应该抽象成一个适合控制台场景的组件:
AgentWorkbenchCard
这是一个“可选中、可查看状态、可触发操作”的工作区卡片。
它负责展示什么
- agent 名称
- 当前运行状态
- 主模型
- 渠道状态摘要
- 最近更新时间
- 主要操作入口
它不负责什么
- 自己发请求
- 自己决定 API 地址
- 自己维护复杂业务状态机
- 直接访问
downcity主包源码
推荐的数据契约
export interface AgentWorkbenchSnapshot {
id: string;
name: string;
projectRoot?: string;
running: boolean;
primaryModelId?: string;
updatedAt?: string;
baseUrl?: string;
statusText?: string;
channels: Array<{
channel: string;
linkState?: string;
statusText?: string;
}>;
}export interface AgentWorkbenchActions {
onSelect: (agentId: string) => void;
onStart?: (agentId: string) => Promise<void> | void;
onStop?: (agentId: string) => Promise<void> | void;
onRestart?: (agentId: string) => Promise<void> | void;
onOpenDetails?: (agentId: string) => void;
}export interface AgentWorkbenchCardProps
extends AgentWorkbenchActions {
snapshot: AgentWorkbenchSnapshot;
selected?: boolean;
busy?: boolean;
}关键点是:
- 组件拿到的是
snapshot - 组件发出的是
intent - 真正的请求行为由宿主层实现
推荐的事件流
主包网关返回 agent 列表
-> console-ui adapter 转成 snapshot
-> AgentWorkbenchCard 渲染
-> 用户点击 restart
-> adapter 调用 /api/ui/agents/restart
-> hook 刷新 agents / overview / services
-> 组件收到新 snapshot 并更新这和 teamprofile 最值得借鉴的地方一致:
- 组件负责“可视化当前状态”
- 容器负责“编排交互”
- 领域层负责“真正执行命令”
推荐的宿主层实现
在 console-ui 中,应该继续把交互收敛在 query、mutation、hook 里,而不是下沉到 UI 原语。
function AgentWorkbenchCardContainer(props: { agentId: string }) {
const dashboard = useConsoleDashboard();
const snapshot = dashboard.agents.find((item) => item.id === props.agentId);
if (!snapshot) return null;
return (
<AgentWorkbenchCard
snapshot={{
id: snapshot.id,
name: snapshot.name,
projectRoot: snapshot.projectRoot,
running: Boolean(snapshot.running),
primaryModelId: snapshot.primaryModelId,
updatedAt: snapshot.updatedAt,
baseUrl: snapshot.baseUrl,
channels:
snapshot.chatProfiles?.map((item) => ({
channel: item.channel || "unknown",
linkState: item.linkState,
statusText: item.statusText,
})) || [],
}}
selected={dashboard.selectedAgentId === snapshot.id}
onSelect={dashboard.selectAgent}
onRestart={dashboard.restartAgent}
onStop={dashboard.stopAgent}
onOpenDetails={() => {
// 由宿主决定是打开 Sheet、Dialog 还是右侧 Inspector
}}
/>
);
}推荐的页面组合
这个组件适合和以下原语组合:
Card:承载 agent 摘要Badge:展示运行态、连接态Button:主操作,例如启动、停止、重启DropdownMenu:收纳次级动作Sheet:打开详情面板Tabs:在详情面板里切换概览、服务、日志、配置
组合之后,就是 Downcity 版的“状态地图 + 检查器”模式,只是从 teamprofile 的空间地图,变成了控制台里的运行时工作区。
从 teamprofile 应该借鉴什么
应该借鉴:
- 主视图 + Inspector 的双栏关系
- 选中态驱动详情面板
- 操作入口靠近当前对象
- 视觉层只消费结构化状态
不应该借鉴:
- 业务模拟逻辑直接进 UI 包
- pathfinding、动画调度、主题状态全部塞进一个组件
- UI 组件自己持有领域真相
对包边界的建议
@downcity/ui
只放这些内容:
- 基础组件
- 通用布局原语
- 纯展示型复合组件
- 公共样式与公开类型
console-ui
放这些内容:
dashboard-apidashboard-queriesdashboard-mutations- 页面级 hook
- 容器组件
cli/city
放这些内容:
- 网关路由
- runtime 控制能力
- agent 状态聚合
- 类型定义
第一批最值得做的复合组件
如果你要把这套设计真正落到 downcity-ui,我建议先做下面这几类,而不是直接做“业务地图”:
AgentWorkbenchCardAgentChannelStatusListAgentInspectorSheetRuntimeMetricCardCommandActionBar
它们都可以复用 @downcity/ui 原语,但和主包交互时统一通过宿主适配层完成。
结论
对 Downcity 来说,正确方案不是“把某个业务组件搬进 UI 包”,而是:
- 在
@downcity/ui中沉淀可复用视觉组件 - 在
console-ui中承接downcity主包交互 - 通过稳定的
snapshot + intent契约连接两者
如果后面你要继续推进,我建议下一步直接做 AgentWorkbenchCard + AgentInspectorSheet 这一组,这是最贴近当前 downcity 运行时场景的一套组件。