GenOffice 是一款开源、AI 原生的办公套件,基于 Electron 构建:文字处理器、表格编辑器、演示文稿制作工具和 PDF 阅读器,全部整合在同一个外壳下。五个应用共享一个底层承诺:文件格式保真。打开一份 .docx、.xlsx 或 .pptx,完成编辑后,凡是你没碰过的内容都会原样返回,逐字节一致。GenOffice 会解析原始文件,只追踪你改动过的区块,保存时把窄幅补丁拼回源 XML;归档中的其余部分则原样复制,不做改动。
每个应用都带有同样的 AI 面板:Docs 里是带版本历史的区块级编辑,Sheets 里是对实时工作表进行工具调用的智能体,Slides 里则是一个受限的布局脚本智能体,只通过一组固定的、经过校验的操作原语来编辑演示文稿,而不是自由形式的代码。三者底层共用两个包:负责工具调用循环的 agent-core,以及负责对接所配置模型后端的 ai-provider。
这正是本文的切入点:ai-provider 本身就说标准的 OpenAI 兼容 HTTP 协议。开箱即用时,GenOffice 把它指向 Genspark。把它改指向 Token Station,各个应用不会有任何变化:变的只是 token 从哪里来。
为什么要用 Token Station 来跑
登录后 Genspark 立刻就能用,这也是它作为默认选项的合理之处。但它是单一账户、单一模型列表,信用额度也要按 Genspark 的条款充值。Token Station 在两个方面改变了这层关系,而这两点对每天都要用的桌面应用来说很关键。
| Genspark(默认) | Token Station | |
|---|---|---|
| 账户 / 额度 | 单一账户,单一信用池 | 按量付费,没有月度或年度合同 |
| 模型 | 由集成方固定模型列表 | 25+ 家供应商、250+ 个模型,按任务挑选 |
| 定价 | 额度绑定 Genspark 套餐 | 一把密钥,供应商原价,零加价 |
按量付费,不是签合同。Token Station 没有订阅档位。免费注册,无需信用卡,注册后立即到账 1 美元额度。之后你只按实际调用的模型付供应商原价,没有周期性费用,也没有需要取消的东西。像 NVIDIA NIM 这样的部分模型甚至完全免费。
模型选择的自由。网关账户不会被锁定在某一家供应商的产品线上。GenOffice Docs 用 Claude 做长文编辑,Sheets 用更便宜的模型处理日常公式工作,Slides 则挑选适合当前演示稿的图像模型,全部通过同一把密钥、同一个 OpenAI 风格的接口完成,不需要为每家供应商单独注册。
因为 Token Station 使用的正是 GenOffice 自定义提供方接口本就预期的 OpenAI 兼容线上协议,接入它只是一次路由变更,不是一次重写。
配置步骤:给你的 GenOffice 代码打补丁
下面是把 GenOffice 的 AI 流量从 Genspark 改路由到 Token Station 所需的实际改动。把它们应用到你自己的 fork 或分支上即可;这些改动不依赖任何特定的 GenOffice 版本。
1. 安装依赖,确认基线版本能正常运行
你需要在本机安装 Node.js 和 npm。
git clone <your-fork-url> genoffice
cd genoffice
npm install
npm run dev
确认外壳能正常启动,AI 面板也能正常打开。此时它默认跑在 Genspark 上,只要不在未登录状态下发送消息,就不会弹出登录提示。
2. 给共享的 provider 包加上环境变量覆盖逻辑
packages/ai-provider 里已经定义了一个自定义提供方:任意 OpenAI 兼容的 baseUrl / apiKey / model。我们添加一个小函数,从环境变量里读取这些值来填充它,这与代码库里给 Genspark 自身密钥(GSK_API_KEY)用的模式完全一致。
packages/ai-provider/src/providers.ts
export const TOKEN_STATION_BASE_URL = 'https://models.bytefuture.ai/v1'
const TOKEN_STATION_DEFAULT_MODEL = 'anthropic/claude-opus-4-8'
export function applyTokenStationEnvOverride(
settings: AiSettings,
env: NodeJS.ProcessEnv = process.env,
): AiSettings {
const apiKey = env.TOKEN_STATION_API_KEY
if (!apiKey) return settings
return {
provider: 'custom',
providers: {
...settings.providers,
custom: {
apiKey,
model: env.TOKEN_STATION_MODEL || TOKEN_STATION_DEFAULT_MODEL,
baseUrl: TOKEN_STATION_BASE_URL,
},
},
}
}
把它和 defaultAiSettings、resolveAiSettings 一起从包的 index.ts 中导出。
3. 让每个应用不再强制使用 Genspark,改为调用新的覆盖函数
Docs、Sheets、Slides 各自注册了一个 ai:get-settings IPC 处理函数,每次读取时都会硬性把 provider 重置回 Genspark。删掉那一行,改为调用新的覆盖函数。三个文件里的改法完全一致:
apps/docs/src/main/docs-main.ts(apps/slides/src/main/ai-ipc.ts 和 apps/sheets/src/main/sheets-main.ts 中的改法与此相同)
// before
ipcMain.handle('ai:get-settings', (): AiSettings => {
const stored = readJson<Partial<AiSettings> & LegacyAiSettings>(SETTINGS_PATH(), {})
const settings = resolveAiSettings(stored, defaultAiSettings())
settings.provider = 'genspark' // ← delete this
return settings
})
// after
ipcMain.handle('ai:get-settings', (): AiSettings => {
const stored = readJson<Partial<AiSettings> & LegacyAiSettings>(SETTINGS_PATH(), {})
return applyTokenStationEnvOverride(resolveAiSettings(stored, defaultAiSettings()))
})
Sheets 的处理函数在两处写法上略有不同:它接收的是一个 IPC 通道常量而不是字符串字面量,而且开头会先调用一次 sessionFor(event) 检查。但改动的实质完全一样:删掉强制 genspark 的那一行,改为调用新的覆盖函数。
4. 获取密钥,配置到环境变量中
在 Token Station 注册,从控制台拿到一个 API 密钥,然后把它设为持久化的环境变量,并重启终端(环境变量只对之后启动的进程生效)。
# Windows (PowerShell)
[Environment]::SetEnvironmentVariable("TOKEN_STATION_API_KEY", "gw_...", "User")
# macOS / Linux — add to your shell profile
export TOKEN_STATION_API_KEY=gw_...
可选:设置 TOKEN_STATION_MODEL 为 Token Station 上任意 provider/model 形式的 ID(例如 openai/gpt-5.5)来覆盖默认模型。重新启动 GenOffice:Docs、Sheets、Slides 里的聊天、编辑和规划功能现在都跑在 Token Station 上了。Slides 的一键生成整套演示稿功能是唯一还需要额外打补丁的功能,见第 5 步。
5. Slides 生成演示稿功能还需要一个补丁
Slides 的 generate_deck/regenerate_slide 工具最初直接调用一个只支持 Genspark 的云端接口,完全绕过了 provider 系统。它们需要单独打三部分补丁。如果你只想接通 Docs 和 Sheets,做到第 4 步就可以停下。
apps/slides/src/renderer/ai/slides-skill.ts:给 DeckAccess 接口加两个可选字段,一个同步的 aiProvider() getter,以及一个返回校验过的元素列表(而不是 HTML 标记)的 composePageElements() 方法。然后把两个工具的判断逻辑从「硬编码只认 Genspark」改为按当前 provider 判断:
const useCloud = cloudAvailable
&& (access.aiProvider?.() ?? 'genspark') === 'genspark'
if (!useCloud) {
// fall back to runLocalDeckGeneration() / runLocalRegenerateSlide()
}
apps/slides/src/renderer/ai/local-deck-gen.ts(新文件):这是真正负责组合内容的模块,向所配置的 provider 请求每一页的布局 JSON(形状、文本框、图表、图片),校验之后用应用自身智能体工具已经在用的 add_shape / add_text_box / add_chart / insert_web_image 原语来构建页面。
apps/slides/src/renderer/ai/AiPanel.tsx:把新增的两个 DeckAccess 字段接到 generateStyleSkill/planDeckOutline 已经在用的同一条请求路径上。这一步很容易漏掉:不接的话 aiProvider 会一直是 undefined,上面的判断逻辑会悄悄退回到 Genspark。
aiProvider: () => settingsRef.current.provider,
composePageElements: async (args) => {
const { system, user } = buildPageComposePrompt(args)
const r = await runLlmOnce(system, user, undefined, true, args.signal)
if (!r.ok || !r.text) return { ok: false, error: r.error ?? tGlobal('aiErrEmptyOutput') }
return parsePageElementsJson(r.text, args.canvasW, args.canvasH)
},
本地路径 v1 版本的已知限制:目前只支持追加,新页面会克隆当前最后一页,还不支持「替换整套演示稿」。本地版的 regenerate_slide 也只替换内容元素,背景和主题继承部分不会被改动,这一点与云端版本不同。
实际运行效果
三段简短演示,每个应用一段,全部跑在 Token Station 上。
延伸阅读
前往 models.bytefuture.ai 注册(1 美元免费额度,无需信用卡),导出 TOKEN_STATION_API_KEY,然后重新启动 GenOffice。一把密钥、一个接口,满足你 Docs、Sheets、Slides 会话需要的所有模型。