当前位置: 首页 > news >正文

用 SwiftUI 构建一个 Agent 可视化工作台

  • 每一步执行都实时显示在时间线上——文本输出、工具调用、执行结果、错误信息,全部可视化

这不是一个终端里的 CLI 工具,也不是一个网页应用。它是用 SwiftUI 写的原生 macOS 应用,用@Observable做状态管理,用 SwiftData 做数据持久化,用 Apple 原生的渲染管线做 Markdown 和代码高亮。

为什么做 SwiftWork

做 SwiftWork 有两个动机。

第一,SDK 需要一个"展示应用"。SDK 的 31 个示例项目覆盖了各种用法——流式输出、自定义工具、MCP 集成——但都是命令行工具。SDK 的能力需要一个 GUI 来完整展示,尤其是工具调用的可视化、事件流的实时渲染这些 CLI 做不好的事情。

第二,Agent 应用的可视化是一个被低估的问题。当前的 Agent 应用(包括 Claude Code 自己)在终端里跑,用户看到的是滚动的文字流。但 Agent 在执行一个复杂任务时,可能调用十几次工具、读写多个文件、执行多条命令。终端里的线性输出很难让用户理解全局进展。SwiftWork 试图用事件时间线和工具卡片来改善这个问题。

架构总览

SwiftWork 采用事件驱动架构。整条数据流是一条单向管线:

SDK Agent Loop │ │ AsyncStream<SDKMessage> ▼ AgentBridge (@Observable) │ │ EventMapper.map() → AgentEvent ▼ AgentBridge.events: [AgentEvent] │ │ SwiftUI 自动响应 @Observable 变化 ▼ TimelineView → 各 EventView

四个角色:

组件职责
Agent LoopSDK 提供,跑 Agent 的推理循环,产出SDKMessage
AgentBridge消费AsyncStream<SDKMessage>,映射成AgentEvent,管理生命周期
EventMapper纯函数,SDKMessage → AgentEvent的类型映射
TimelineViewSwiftUI 视图,消费AgentEvent数组,渲染时间线

核心设计决策:视图不直接接触 SDK 类型。AgentEvent是 SwiftWork 自己定义的 UI 模型,跟 SDK 的SDKMessage完全解耦。视图只认AgentEvent,不知道也不关心事件来自哪个 SDK 版本。

核心数据流

用户发一条消息,整条管线的运转过程:

1. 用户输入 → Agent 启动

// AgentBridge.swift func sendMessage(_ text: String) { // 用户消息直接追加到事件列表 let userEvent = AgentEvent(type: .userMessage, content: text, timestamp: .now) appendAndPersist(userEvent) isRunning = true // 在后台 Task 中消费 stream currentTask = Task { [weak self] in let stream = agent.stream(text) for await message in stream { let event = EventMapper.map(message) self.appendAndPersist(event) } self.isRunning = false } }

用户消息先追加到事件列表(即时显示),然后启动一个Task来消费 SDK 的AsyncStream

2. SDKMessage → AgentEvent

EventMapper是一个纯函数,把 SDK 的 18 种SDKMessage映射成 SwiftWork 的AgentEventType

// EventMapper.swift static func map(_ message: SDKMessage) -> AgentEvent { switch message { case .assistant(let data): return AgentEvent(type: .assistant, content: data.text, metadata: ["model": data.model, "stopReason": data.stopReason], timestamp: .now) case .toolUse(let data): return AgentEvent(type: .toolUse, content: data.toolName, metadata: ["toolName": data.toolName, "toolUseId": data.toolUseId, "input": data.input], timestamp: .now) case .toolResult(let data): return AgentEvent(type: .toolResult, content: data.content, metadata: ["toolUseId": data.toolUseId, "isError": data.isError], timestamp: .now) // ... 18 种消息类型 } }

为什么要有这一层映射?因为 SDK 的类型是给 Agent 运行时用的,它包含很多 UI 不需要的细节。AgentEvent只保留 UI 渲染需要的字段:类型、内容、元数据、时间戳。视图不需要知道SDKMessage的枚举定义,只需要处理AgentEventType

3. 事件追加 + 持久化

每条事件都经过appendAndPersist,同时更新内存数组和 SwiftData 数据库:

private func appendAndPersist(_ event: AgentEvent) { events.append(event) processToolContentMap(for: event) guard event.type != .partialMessage, let eventStore, let currentSession else { return } try eventStore.persist(event, session: currentSession, order: eventOrder) eventOrder += 1 trimOldEvents() }

注意partialMessage不持久化——它是流式文本的中间片段,累积完成后会生成一条完整的.assistant事件。

4. SwiftUI 自动渲染

AgentBridge标记了@Observable。当events数组变化时,TimelineView自动重新渲染:

// TimelineView.swift ForEach(virtualizedEvents) { event in eventView(for: event) }

eventView根据event.type分派到不同的视图组件——UserMessageViewAssistantMessageViewToolCardViewSystemEventView等。

项目结构

SwiftWork/ ├── App/ │ ├── SwiftWorkApp.swift # @main 入口,注册 SwiftData 模型 │ └── ContentView.swift # NavigationSplitView 根视图 ├── Models/ │ ├── UI/ # UI 模型层 │ │ ├── AgentEvent.swift # 事件模型(SwiftUI 渲染用) │ │ ├── AgentEventType.swift # 18 种事件类型枚举 │ │ ├── ToolContent.swift # 工具内容(配对 toolUse + toolResult) │ │ ├── PermissionDecision.swift # 权限决策 │ │ └── AppError.swift # 错误模型 │ └── SwiftData/ # 持久化模型层 │ ├── Session.swift # 会话 │ ├── Event.swift # 持久化事件 │ ├── AppConfiguration.swift # 应用配置 │ └── PermissionRule.swift # 权限规则 ├── ViewModels/ │ ├── SessionViewModel.swift # 会话 CRUD │ └── SettingsViewModel.swift # 设置管理 ├── Views/ │ ├── Sidebar/ # 会话列表 │ ├── Workspace/ │ │ ├── Timeline/ │ │ │ ├── TimelineView.swift # 时间线主视图 + 虚拟化 │ │ │ ├── EventViews/ # 各事件类型视图 + ToolCardView │ │ │ │ ├── ToolRenderers/ # 5 个内置工具渲染器 │ │ │ │ ├── StreamingTextView.swift │ │ │ │ ├── MarkdownContentView.swift │ │ │ │ └── ... │ │ │ └── Inspector/ # 事件详情面板 │ │ └── InputBar/ # 消息输入框 │ ├── Settings/ # 设置界面 │ ├── Onboarding/ # 首次启动引导 │ └── Permission/ # 权限审批弹窗 ├── SDKIntegration/ │ ├── AgentBridge.swift # SDK ↔ ViewModel 桥接 │ ├── AgentBridge+ToolContentMap.swift # 工具内容配对逻辑 │ ├── EventMapper.swift # SDKMessage → AgentEvent │ ├── ToolRenderable.swift # 工具渲染协议 │ └── ToolRendererRegistry.swift # 工具渲染注册表 ├── Services/ │ ├── CodeHighlighter.swift # Splash 代码高亮 │ ├── MarkdownRenderer.swift # swift-markdown 渲染 │ ├── KeychainManager.swift # API Key 安全存储 │ ├── EventStore.swift # 事件持久化接口 │ ├── AppStateManager.swift # 应用状态保存/恢复 │ └── TitleGenerator.swift # 自动生成会话标题 └── Utils/ └── Extensions/ # 颜色、日期格式化等

结构上的核心分层:

  • Models/UI/Models/SwiftData/是两套独立的模型层。UI 模型(AgentEvent)是给 SwiftUI 渲染用的,SwiftData 模型(Event)是给持久化用的。两者之间有转换逻辑。
  • SDKIntegration/是 SDK 和 UI 之间的桥梁层。视图和 ViewModel 不直接 importOpenAgentSDK
  • Views/按功能分区,每个事件类型一个视图文件,工具渲染器有独立的子目录。

技术选型

组件选择原因
语言Swift 6.1 严格并发Agent SDK 要求,Sendable保证线程安全
UISwiftUI +@ObservablemacOS 14+ 支持,跟 Swift 并发配合好
持久化SwiftData跟 SwiftUI 深度集成,比 Core Data 简洁
Markdownswift-markdown (Apple)原生 Apple 库,CommonMark 兼容
代码高亮Splash (John Sundell)轻量、支持 Swift/Python/JS/Bash
自动更新Sparkle 2.xmacOS 应用更新的标准方案
Agent SDKOpen Agent SDK自己写的 SDK,当然用自己的
http://www.jsqmd.com/news/1145798/

相关文章:

  • 15分钟搞定黑苹果EFI配置:OpCore-Simplify自动化工具终极指南
  • 业务上线前三类医疗器械经营许可证办理要点及四家机构盘点
  • 闲鱼鱼小铺开通教程,新老号通用
  • 还在用传统SWAT?新一代SWAT+模型水文水资源模拟技术:从数据制备到BMPs评估:SWAT+模型水文与面源污染模拟,附香溪河完整案例
  • 循古拓新:糖尿病中医科学化调理的务实发展之道
  • 商用大模型安全策略失控:为何LLM平台不能照搬C端审核逻辑
  • 企业全媒体云客服系统架构解析与落地选型避坑指南
  • 【技术向】北斗三号(BDS-3)实现对 GPS 全面超越:架构、性能与独有功能解析
  • 零依赖高性能跨平台 Web 胶水库 PicoServer 价值挖掘与选型推荐
  • 户用光伏规模化运营中的电费自动划转:从人工对账到系统化结算
  • 广东大巴“奇迹般复活“——一场由“水鱼号“逼出的客运逆袭
  • 强化学习入门-从斯金纳箱到大模型推理
  • 六自由度船舶模拟平台:重构海事训练与船舶测试的技术底座
  • GPT-4o 与 GPT-3.5-Turbo API 成本对比:128K 与 16K 上下文窗口的 3 倍价差分析
  • 数字人本地部署实战:从环境配置到生产级优化指南
  • MifareOneTool实战指南:三步掌握Windows平台智能卡管理
  • 黑苹果配置革命:OpCore-Simplify让你15分钟搞定复杂EFI设置
  • 【小白也能轻松玩转龙虾】虾壳云一键部署稳定优化设置,长时间运行 OpenClaw v2.7.9 无闪退(附最新安装包)
  • Apache Camel:老牌集成框架,350 个连接器打通所有系统
  • 代码检查平台怎么选?2026企业级代码治理闭环构建指南
  • 【小白也能轻松玩转龙虾】虾壳云一键部署零基础全解,从零搭建 OpenClaw v2.7.9 本地 AI(附最新安装包)
  • trackerjacker:像 nmap 一样扫描附近的 WiFi
  • Spark MLlib 3.0+ 实战:9大算法模块核心API与避坑3要点
  • 2026年实践反思:统计报表驱动决策工具解决的是统一,不是判断
  • 能烧录但是程序跑不起来
  • 从 SEO 工程到 GEO 工程:技术团队需要补上的五项能力
  • 2026 DTSS评估咨询机构怎么选才靠谱?
  • 5分钟掌握Luyten:Java反编译工具的最佳图形化解决方案
  • 三维凹凸地形图渲染实战:课程简介
  • Luyten:5分钟掌握Java字节码的“时光倒流“魔法