news 2026/9/2 10:02:17

SwiftUI实现Mac菜单栏LLM用量实时监控与账单提醒

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SwiftUI实现Mac菜单栏LLM用量实时监控与账单提醒

在实际项目里,API 账单和 token 消耗通常是月底才知道,但真正需要优化成本时,已经晚了。一个常驻在 Mac 菜单栏上的扩展,让 LLM usage 数据随时可见,是很多开发者的刚需。所谓 panel / pill / nub,分别对应展开后的详情面板、菜单栏里的胶囊指示器、以及可以拖到屏幕边缘的小圆点。这篇文章从零实现一个 macOS 菜单栏扩展:用 SwiftUI 的 MenuBarExtra 搭建入口,用一个可替换的 UsageProvider 拉取用量数据,通过定时刷新把总 token、花费和剩余额度展示在菜单栏胶囊里。

读完这篇文章,你会知道 LLM usage 数据模型怎么设计,Provider 怎么抽象,SwiftUI 菜单栏怎么接,常见坑怎么排查,以及如何用 NSStatusItem 或 NSPanel 升级成更原生的形态。

1. 先梳理需求:LLM Usage 和 Mac 上的三种呈现形态

1.1 用量数据到底指哪些字段

LLM usage 不是单纯一个 token 数字。不同供应商返回的字段差别很大,但在做展示端时,可以统一成几个核心概念:

概念英文常见字段说明
输入 token 数prompt_tokens / input_tokens请求里发送给模型的文本 token 数
输出 token 数completion_tokens / output_tokens模型生成的文本 token 数
总 token 数total_tokens输入和输出的总和
费用cost / amount按 token 量和模型单价计算出的金额
币种currencyUSD、CNY 等
剩余额度remaining_credit / balance账户剩余可用余额
统计周期period_start / period_end当前账单周期或查询时间范围
按模型拆分models / model_usage每个模型各自的请求数、token 数和费用
请求次数requests / n_requests一段时间内调用次数,做限额观察时需要

如果只是显示一个“总 token”,对日常开发不够。最实用的菜单栏形态是:胶囊显示费用或总 token,点击展开后,面板里展示完整的模型拆解和周期信息。

1.2 panel / pill / nub 的差异和适用场景

这三种叫法来自不同 UI 形态:

  • Panel:展开后的详情面板,通常是一个 popover 或 NSPanel。适合展示表格、统计图、模型列表。
  • Pill:菜单栏里的胶囊状小标签,紧凑显示一个核心数字,比如$12.34
  • Nub:悬浮在屏幕边缘的小圆点或小把手,可以拖动,适合做常驻监控浮窗。

这篇文章先实现 pill + panel 的组合:菜单栏显示胶囊,点击后弹出详情面板。nub 作为进阶方向,会在后面用 NSPanel 的思路说明。

1.3 技术选型:MenuBarExtra、NSStatusItem、NSPanel

macOS 上实现菜单栏扩展,主要有三条路:

方案最低系统优点缺点适用场景
SwiftUI MenuBarExtramacOS 13声明式、写法简单、自带 popover自定义菜单栏 View 的能力受限快速实现菜单栏入口和详情面板
AppKit NSStatusItemmacOS 10.0完全控制按钮、图标、事件代码多,需要处理生命周期需要自定义胶囊背景、点击手势的菜单栏工具
NSPanel 悬浮窗口macOS 10.0可以实现屏幕任意位置悬浮要处理拖拽、层级、焦点nub 形态的常驻监控浮窗

对一个不需要复杂交互的用量展示工具,先用 MenuBarExtra 足够。

2. 环境准备与工程骨架

2.1 环境要求

项目要求
macOS13.0 或更高版本
Xcode14.3 或更高版本
Swift5.8 或更高
外部依赖
网络可以访问你的 LLM Usage 查询接口

示例代码全部使用系统自带框架,不需要引入 CocoaPods 或 Swift Package。

2.2 创建 macOS App 项目

在 Xcode 中按以下步骤操作:

  1. 选择 File > New > Project。
  2. 选择 macOS > App。
  3. Interface 选 SwiftUI。
  4. Lifecycle 选 SwiftUI App。
  5. Language 选 Swift。
  6. 取消勾选 Use Core Data、Include Tests。
  7. Deployment Target 设置为 macOS 13.0 或更高。

创建完成后,项目里默认会有一个ContentView.swift。这个项目只需要菜单栏,不需要主窗口,所以后面可以把默认窗口删掉。

2.3 App Sandbox 和网络权限

macOS App 默认开启 App Sandbox。如果使用 URLSession 发送网络请求,必须开启 outgoing network 权限。

打开 target 的 Signing & Capabilities,点击 App Sandbox,确保 Network 下面的 Outgoing Connections 已勾选。也可以直接修改 entitlement 文件:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.network.client</key> <true/> </dict> </plist>

如果漏掉com.apple.security.network.client,运行时会看到类似The network connection was lost或请求立即失败的错误。这个权限是网络请求最常见的坑。

2.4 工程文件与职责

代码按职责拆成几个文件,不要全部塞进 App 入口:

文件职责
LLMUsageApp.swiftApp 入口,创建菜单栏入口和 Provider
UsageModels.swift用量数据模型
UsageProvider.swift数据源协议、HTTP 实现、Mock 实现
UsageStore.swift状态管理、自动刷新、错误状态
UsagePanelView.swift菜单栏胶囊和详情面板 UI

3. 定义用量模型:先让数据层稳定

3.1 领域模型

在写网络请求之前,先定义 UI 真正需要的领域模型。这样 Provider 返回什么、界面展示什么,都会被统一约束,不会因为上游字段变化导致 UI 到处改。

import Foundation struct LLMUsage: Codable, Equatable { var totalTokens: Int var inputTokens: Int var outputTokens: Int var cost: Double var currency: String var remainingCredit: Double? var periodStart: String var periodEnd: String var models: [String: ModelUsage] } struct ModelUsage: Codable, Equatable { var requests: Int var tokens: Int var cost: Double }

这里把periodStartperiodEnd直接定义为字符串,而不是Date。原因很简单:不同供应商返回的日期格式差异很大,iOS 17 和 macOS 14 之后虽然有ISO8601FormatStyle,但为了兼容较多接口,先保留字符串,展示时再格式化最稳妥。

3.2 上游返回结构示例

下面是一个兼容多数“网关聚合统计接口”的 JSON 示例。字段名按 snake_case 给出,因为很多供应商实际接口也使用 snake_case:

{ "data": { "total_tokens": 1234567, "input_tokens": 800000, "output_tokens": 434567, "cost": 12.34, "currency": "USD", "remaining_credit": 87.66, "period_start": "2026-05-01T00:00:00Z", "period_end": "2026-05-31T23:59:59Z", "models": [ { "name": "gpt-4o-mini", "requests": 1200, "tokens": 900000, "cost": 5.20 }, { "name": "gpt-4o", "requests": 300, "tokens": 334567, "cost": 7.14 } ] } }

这里的数据是示例,不是某个供应商的真实接口。实际项目中,以你使用的服务商文档为准。

3.3 用 Decodable 适配上游字段

为兼容 snake_case,可以定义RemoteUsageResponse,并通过CodingKeys做字段映射。

struct RemoteUsageResponse: Decodable { let data: RemoteUsageData } struct RemoteUsageData: Decodable { let totalTokens: Int let inputTokens: Int let outputTokens: Int let cost: Double let currency: String let remainingCredit: Double? let periodStart: String let periodEnd: String let models: [RemoteModelUsage]? enum CodingKeys: String, CodingKey { case totalTokens = "total_tokens" case inputTokens = "input_tokens" case outputTokens = "output_tokens" case cost, currency case remainingCredit = "remaining_credit" case periodStart = "period_start" case periodEnd = "period_end" case models } } struct RemoteModelUsage: Decodable { let name: String let requests: Int let tokens: Int let cost: Double }

然后写一个toDomain()方法,把远程结构转换成 UI 使用的LLMUsage

extension RemoteUsageResponse { func toDomain() -> LLMUsage { var modelMap: [String: ModelUsage] = [:] for item in data.models ?? [] { modelMap[item.name] = ModelUsage( requests: item.requests, tokens: item.tokens, cost: item.cost ) } return LLMUsage
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 10:01:25

单片机毕设项目:基于 STM32 的酒精超标声光报警及模拟熄火硬件实现 基于 STM32 单片机车载安全监测报警系统设计与开发(010206)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/2 10:00:19

5 分钟把 Mac 微信记录导出成可搜索的网页:WeChatMsg 完整指南

5 分钟把 Mac 微信记录导出成可搜索的网页&#xff1a;WeChatMsg 完整指南 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华
网站建设 2026/9/2 10:00:15

3步跑通 shadPS4:PS4模拟器新手避坑指南

3步跑通 shadPS4&#xff1a;PS4模拟器新手避坑指南 【免费下载链接】shadPS4 PlayStation 4 emulator for Windows, Linux, macOS and FreeBSD written in C 项目地址: https://gitcode.com/GitHub_Trending/sh/shadPS4 shadPS4 是一个用 C 写的 PlayStation 4 模拟器&…

作者头像 李华
网站建设 2026/9/2 9:59:50

美赛MATLAB解题工具链:题型-方法-验证三维映射

简介&#xff1a;本资源是面向美国大学生数学建模竞赛&#xff08;MCM/ICM&#xff09;参赛者的MATLAB实战代码合集&#xff0c;聚焦优化建模、时间序列预测、图论算法、统计分析、微分方程仿真及图像处理等六大高频题型&#xff0c;为初学者提供可直接复用的编程范式&#xff…

作者头像 李华
网站建设 2026/9/2 9:58:37

vue-vben-admin 数据可视化仪表盘实战:3 步搭出专业 ECharts 看板

vue-vben-admin 数据可视化仪表盘实战&#xff1a;3 步搭出专业 ECharts 看板 【免费下载链接】vue-vben-admin A modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast! 项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vb…

作者头像 李华