想法已经打出来了,只是还想把语气调整一下。言好就在你正在使用的输入框里,帮你完成这一步。
照常输入,按下回车。需要调整时,润色后的文字会回到原输入框;你看过后,再按一次回车发送。
输入:这个方案不行,你再改改,明天给我。
表达示意:这个方案还需要调整,麻烦你再修改一下,明天发给我。
默认提示词要求保留原意、不添加新信息。实际结果取决于所选模型和提示词。
| 言好带来的体验 | |
|---|---|
| 原地润色 | 在当前输入框完成调整,省去复制到另一个工具再粘回来的步骤 |
| 回车触发 | 将润色接在发送之前,修改后的内容由你确认 |
| 快速判断 | 可启用 Jev 模型先快速判断是否需要润色;无需调整时直接发送原文,省去润色模型的调用和等待 |
| 按应用开启 | 拖入想使用的应用,自行选择生效范围 |
| 自由配置 | 接入 OpenAI 兼容接口,设置模型和表达偏好 |
| 轻巧常驻 | 收在菜单栏,通过状态提示和调用历史查看结果 |
下载发布版(无需安装开发工具)
- 在 GitHub Releases 下载
Hola-v版本号-macOS-universal.dmg,适用于 Apple Silicon 和 Intel Mac。 - 双击打开 DMG,在出现的安装窗口中将
Hola.app拖到「应用程序」图标上。复制完成后,从「应用程序」打开 Hola,并推出安装磁盘映像。ZIP 仍可作为备用格式下载。 - 发布版使用 固定自签证书签名,未经过 Apple 公证。如果提示“无法验证开发者”或“Apple 无法检查其是否包含恶意软件”,先关闭提示,再按下表操作:
| 系统版本 | 允许打开的位置 |
|---|---|
| macOS 13 及更新版本 | 系统设置 → 隐私与安全性 → 安全性,找到 Hola 被阻止的提示,点击 仍要打开 |
| macOS 12 | 系统偏好设置 → 安全性与隐私 → 通用,找到 Hola 被阻止的提示,点击 仍要打开;如需修改设置,先解锁左下角的锁 |
按系统提示输入密码或使用 Touch ID,再确认打开。请先确认安装包来自上面的项目 Releases;此操作只为 Hola 添加例外,无需允许所有未知应用,也无需安装或信任额外证书。若没有出现按钮,先尝试打开 Hola 一次,再返回设置查看。Apple 官方说明
允许打开与下方的辅助功能、输入监控授权是两件事,两者都需要完成。应用运行后显示在菜单栏。
升级发布版:先退出 Hola,再用新版替换「应用程序」中的旧版。发布版持续复用同一张自签证书及应用标识,以保持签名身份;从旧的 ad-hoc 版本升级时签名身份会变化。系统仍可能再次要求允许打开或授予权限。如果权限已勾选但功能仍不可用,在对应权限列表中移除旧 Hola,再添加 /Applications/Hola.app 并开启,最后退出并重新打开应用。
从源码构建
准备 macOS 12 或更新版本,以及 Xcode Command Line Tools。尚未安装开发工具时,先运行:
xcode-select --install在项目目录中一键构建并重启应用:
bash scripts/build-and-restart.sh脚本会先完成构建和签名,再退出正在运行的开发版,打开并验证仓库中的 build/HolaDev.app。点击菜单栏图标会直接打开面板,默认停在「通用」。分区依次是通用、模型、人设、指令、日志。构建失败时不会退出旧版;已安装的发布版 Hola.app 可以继续运行,不会被替换。首次启动开发版时会复制一次现有配置,之后两版分别保存。由于开发版使用独立的应用 ID,macOS 需要为 HolaDev.app 单独授予辅助功能和输入监控权限。
应用名称为 Hola,中文品牌名为 言好。从旧应用标识升级后,请为 Hola 重新授予系统权限;首次启动会迁移旧版配置和调用历史。
构建脚本会在登录钥匙串中创建或复用 Hola Local 本机签名身份,并完成签名校验。默认生成 HolaDev.app(ID local.holadev),菜单栏图标显示黄色;发布流水线生成 Hola.app(ID local.hola),使用普通模板图标。当前使用本机自签名,尚未进行 Developer ID 公证。
打开 系统设置 → 隐私与安全性,为实际运行的 Hola 应用授予:
| 权限 | 用途 |
|---|---|
| 辅助功能 | 读取当前草稿,将润色结果写回输入框 |
| 输入监控 | 在选定应用中接管回车,触发润色流程 |
授权后退出并重新打开应用。言好常驻菜单栏,点击 Hola 图标 直接打开面板。
- 点击菜单栏图标,在 通用 里授予辅助功能和输入监控权限。
- 在 模型 里填写 OpenAI 兼容接口的 地址、Token 和模型名称。修改会立即生效。权限、这份配置和至少一个目标应用都完成后,开启拦截 才会可用。Jev 也在这一页,默认关闭。
- 从「应用程序」目录拖入一个或多个
.app,选择言好的生效范围。 - 打开 开启拦截。判断提示词和润色提示词在 人设。
回到目标应用,先用一段非敏感草稿体验回车润色。
照常输入 → 按下回车 → 检查与润色
│
├─ 无需修改 → 补发回车,交给原应用处理
│
└─ 有所修改 → 回填草稿 → 你确认后再次回车
| 情况 | 会发生什么 |
|---|---|
| 润色结果与原文一致(忽略首尾空白) | 补发回车;在回车发送的聊天应用中,即发送原文 |
| 润色结果有所调整 | 回填到原输入框,等待你再次按回车确认 |
| 回填后你又修改了文字 | 下次回车重新触发润色 |
| 接口请求失败 | 不自动发送,显示失败状态,可再次回车重试 |
| 读取输入框失败 | 拦截本次回车并显示错误 |
| 写回失败或无法核验 | 不自动发送;检查输入框中的实际内容后再操作 |
| 使用 Shift / Command 等修饰键加回车 | 保持原应用行为 |
每个目标应用可以选择劫持范围。新添加的应用默认是 部分劫持。
部分劫持 时,某个输入框第一次按回车,输入框旁会询问:
| 选择 | 这次回车 | 以后 |
|---|---|---|
| 不劫持 | 已经拦截,不会发送 | 这一类输入框不再询问,之后的回车保持原应用行为 |
| 允许劫持 | 进入润色流程 | 之后这一类输入框按回车都会接管 |
| 全部劫持 | 进入润色流程 | 该应用改为全部劫持,之后所有输入框都会接管 |
| 放弃 | 已经拦截,不会发送 | 不记住,下次仍会询问 |
还没决定是否劫持的输入框,输入时以及弹出询问时都会显示黄色边框。选不劫持后边框消失。已经允许、或应用是全部劫持时,输入框同样显示黄色边框。草稿已经命中任意一条指令时,边框改为蓝色。
记住的是输入框的种类,不是它在会话列表里的序号。种类路径里只有 AXTextArea 保留序号,其余组件的序号都去掉,再拼上子角色和占位符。同一应用里,占位符相同的各个会话输入框共用一次选择。标题、当前草稿和 identifier 不参与匹配。选择重启后仍然有效,并显示在设置里对应应用的下方。可以改成另一种,或选 下次再问 后重新询问。改成全部劫持后,这些选择会保留,但当前会接管该应用的所有输入框。范围修改后立即生效。已经添加的应用会保留原先保存的范围。按旧路径记住的选择对不上新种类,那些输入框会再问一次。
每个目标应用可以点 选择组件,在该应用里连续点选多个界面组件作为上下文。已选项会列在选择面板和设置里,可以逐个移除,也可以填写备注。输入框也能选,和它是否已经允许劫持、或不劫持无关;设置里每个输入框旁边也有 加入上下文。按 Esc 或 完成选择 结束。之后每次回车,言好读取这些组件的当前文字,用 <context tag="备注">文字</context> 加在交给模型的正文前面。备注末尾的冒号会去掉,所以备注「单聊:」会写成 tag="单聊"。没有备注时写成 <context>文字</context>。没有读到文字的组件不会写入上下文。标记只供模型理解,不会写回输入框;模型若改写标记,这次不会写回。指令脚本的 input 仍然是输入框里的原文,读到的上下文从第二个参数 context 传入。
润色期间,输入框边框会以黄色呼吸;这次草稿命中了指令时改为蓝色呼吸。菜单栏仍显示进度。润色并回填后边框变为绿色,再次回车发出或按下其它键后结束。完成后,菜单栏 绿点 表示成功,红点 表示失败;打开面板的 日志 可查看原文、结果、提示词和处理详情。
设置中可开启“开机自启动(登录后启动)”(macOS 13+);如果系统要求批准,请在系统登录项设置中允许 Hola。macOS 12 可在系统偏好设置 → 用户与群组 → 登录项中手动添加应用。
每次打开应用后,只要辅助功能权限、输入监控权限、已保存的 OpenAI 配置和目标应用都可用,就会自动开启回车处理;手动停止只对当前这次运行有效。无法开启时,把指针停在底部的拦截开关上可以看到原因。未开启时,菜单栏图标右下角显示黄色圆点。
言好在启动时跟随系统首选语言,也支持 macOS 为 App 单独设置的语言:
| 首选语言 | 界面显示 |
|---|---|
| 中文(包括简体、繁体及地区变体) | 简体中文 |
| 英语及其他语言 | English |
菜单、设置、状态提示、错误信息和调用历史界面均支持中英文。修改系统或 App 语言后,退出并重新打开 Hola 即可生效。
默认提示词跟随界面语言,但会要求模型保留草稿原有语言,不会因为界面是英文就要求把中文草稿翻译为英文。已保存的自定义提示词、草稿及历史记录内容保持原样;历史中的处理说明保留记录时的语言。README 可通过顶部链接手动切换语言。
支持 OpenAI 兼容的 chat/completions 接口,可填写 Base URL 或完整接口地址。
| 配置项 | 说明 |
|---|---|
| 接口地址 | 服务商提供的 Base URL,例如 https://api.openai.com/v1 |
| Token | 对应服务商的 API 凭据 |
| 模型 | 该接口支持的模型名称 |
| 额外请求参数 | 可选的 JSON 对象,合并进 Chat Completions 请求体;例如 {"reasoning_effort":"high","stream":false}。不能覆盖 model 或 messages,当前仅支持 stream: false |
| 润色提示词 | 定义表达风格,例如更自然、更简洁或更适合工作沟通 |
请求默认使用 stream: false。使用已识别的推理模型时,默认加入 {"reasoning_effort":"low"};其他模型不自动添加推理参数。额外请求参数中的值优先于默认值。
默认润色提示词:
你是文字润色助手。保持原文语言,在不改变原意、不添加新信息的前提下,让这段话更通顺、得体。只输出润色后的文本本身,不要加引号或任何解释。
Jev 默认关闭。启用后,会先判断草稿是否需要润色,再决定是否调用润色模型。
| 模式 | 处理流程 |
|---|---|
| 默认:关闭 Jev | 每轮都请求润色接口,根据返回结果决定直接发送或回填 |
| 开启 Jev | 先判断;无需润色时直接补发回车,需要时才请求润色接口 |
在面板的 指令 分区添加规则。规则按顺序匹配用户输入的完整原文;例如正则 ^# 匹配以 # 开头的草稿。脚本是一个函数表达式,言好会以原始草稿 input 和 context 调用它。context 是 JSON 数组,每项为 { tag, text },对应这次读到的上下文;没有读到时是 []。返回结果对象或 Promise 均可:
async (input, context) => {
return { interrupt: true, replacement: input.slice(1) };
}interrupt: true 会拦截本次回车;有非空 replacement 时写回草稿,确认后再次按回车发送。interrupt: false 会继续原有润色流程,并忽略 replacement。脚本失败或替换失败时不会发送。指令只在已选择目标应用的输入框中执行。
规则按从上到下的顺序匹配,拖动左侧手柄可以调整优先级。面板底部 导出 右侧的 测试 会打开测试窗口:先按当前指令匹配,没有拦截时再用当前人设试跑润色。测试使用正在编辑的规则和提示词,不记入历史,也不会发送。文稿可以手写,也可以从历史记录里选择曾经出现的原文、调整后结果或未采用的润色结果。
发起网络请求使用 fetch,用法与 Fetch 标准 一致。脚本里可以直接 await fetch(url, init),同时提供 Headers、Request、Response、AbortController、FormData、Blob 和 URLSearchParams。HTTP 4xx/5xx 不会让 Promise 失败,先看 response.ok;URL 不合法、连不上或完整性校验失败会以 TypeError 拒绝,用 AbortSignal 中止时以 AbortError 或 TimeoutError 拒绝。整段脚本(包括 fetch)需要在 10 秒内返回,超时不会发送。
执行本机命令使用 execute,用法与 Node.js child_process.exec 的 Promise 形式一致。await execute(command, options?) 成功时得到 { stdout, stderr }。command 在当前用户权限下交给 shell,默认 /bin/sh -c。options 可包含 cwd、env、encoding、shell、timeout、maxBuffer、killSignal、signal。encoding 默认 utf8,设为 "buffer" 时 stdout / stderr 是 Uint8Array。env 是完整的环境变量表。非 0 退出、超时或输出超过 maxBuffer(默认 1 MiB)会拒绝 Promise,错误对象上有 code、killed、signal、cmd、stdout、stderr。signal 中止时以 AbortError 拒绝。整段脚本(包括 execute)需要在 10 秒内返回,超时不会发送。
async (input) => {
const response = await fetch("https://example.com/rewrite", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: input }),
});
if (!response.ok) throw new Error(String(response.status));
const data = await response.json();
return { interrupt: true, replacement: data.text };
} async (input) => {
const { stdout } = await execute("printf %s " + JSON.stringify(input));
return { interrupt: true, replacement: stdout };
}设置面板支持 导出 / 导入,可迁移目标应用列表、模型配置和指令。导入会替换文件中对应的设置;导出文件包含接口 Token 和指令脚本,请妥善保存。
应用支持取决于输入框。 言好通过 macOS Accessibility 读写普通文本输入框的 AXValue。自绘编辑器或未开放相关能力的应用可能无法使用;拖入应用并不代表已经验证兼容。发送行为也取决于目标应用如何处理回车。
含图片等嵌入对象的草稿会以 <object id="…"> 占位符交给模型。模型修改占位符时不会写回;文字发生变化时,仅在目标输入框支持局部文字写回的情况下修改对象两侧的文字,避免整段覆盖附件。
写回或补发回车前,工具会核对前台应用、输入框和草稿内容。AX 读写并非原子操作,也无法保证识别目标应用复用输入框的所有情况;处理期间请等待完成,切换会话后重新触发。使用中文输入法时,先确认候选词已上屏。
- 处理范围:选定应用当前焦点输入框中的草稿。若为该应用选了上下文组件,回车时还会读取这些组件的文字,加在交给模型的正文前面。不读取密码控件。
- 模型请求:草稿会发送至你配置的润色服务;开启 Jev 后也会发送至 Jev 服务。选中的上下文会一并放在正文前面。
- 本地配置:设置及 Token 保存在
UserDefaults,当前未使用 Keychain 保存 Token。 - 调用历史:最近 100 条记录保存在本地,包含草稿、注入的上下文、结果与提示词,可在调用历史窗口清空。旧记录没有上下文这一项。
本地历史文件位置
~/Library/Application Support/local.hola/round-history.json
~/Library/Application Support/local.holadev/round-history.json (开发版)
项目使用 Swift 和系统框架构建,无第三方源码依赖。
推送到 GitHub 的 main 分支会自动递增补丁版本、构建 Apple Silicon / Intel 通用包并发布到 Releases。配置要求、版本规则和失败重跑方式见自动发布说明。
| 文件 | 内容 |
|---|---|
| Sources/main.swift | 菜单栏、设置、草稿读写、模型调用与历史记录 |
| Sources/Localization.swift | 系统语言选择与本地化文案格式化 |
| Resources | 中英语言资源、应用与菜单栏图标 |
| build.sh | 编译、应用打包和本机签名 |
| Info.plist | 应用标识及系统配置 |
| 手工验收 | 目标应用、接口和输入行为的验收清单 |
| 验证记录 | 已执行的检查与尚未验证的范围 |
可运行 bash scripts/test-localization.sh 检查语言选择、资源完整性与动态文案(需先构建)。
根据现有验证记录,已在 macOS 环境完成构建与签名校验。真实模型接口与目标应用的端到端流程仍待验证。
Hola · 言好
你的意思,更好的表达。