☰
基于Qt打造DeepSeek Orb悬浮球:让本地AI操作一键唤起
2026/10/31 3:33:43 网站建设 项目流程

1. 从命令行到悬浮球:我为什么要把 DeepSeek Harness 塞进一个球里

DeepSeek Harness 这个工具,圈内人应该不陌生。它本质上是一个让模型能力真正落到本地操作系统的执行框架——你可以把它理解成一个“翻译层”,把自然语言指令翻译成系统级的动作,比如读写文件、执行脚本、调用本地程序、处理批量任务。但问题也很明显:它的原生交互方式偏命令行,日常用起来总得开终端、敲命令、切窗口,操作链路太长。我平时写代码、整理文档、跑数据处理脚本,经常需要在编辑器、文件管理器和终端之间来回跳,每次想让它帮我干点活,都得先切到终端再输入指令,思路被打断的感觉非常难受。

于是我就想,能不能把它做成一个悬浮球?常驻在桌面最上层,鼠标一点就唤起输入框,说完需求它就在后台默默干活,干完再弹个通知。这个想法落地之后,就是DeepSeek Orb——一个基于 Qt 开发的桌面悬浮球客户端,开源在 GitHub 上。它解决的核心问题就三个:一键唤起、替你操作电脑、后台干活。适合谁用?经常和本地文件、脚本、批处理打交道的人;想让模型能力融入日常工作流但不想被终端束缚的人;以及喜欢折腾桌面效率工具的开发者。哪怕你之前没用过 DeepSeek Harness,只要你对“让电脑自己干活”这件事有兴趣,这篇内容都能给你一条可复现的路径。

2. 整体设计思路:为什么是悬浮球,而不是又一个聊天窗口

2.1 悬浮球形态的交互逻辑

市面上大多数本地 AI 工具都做成了聊天窗口——打开一个面板,输入问题,等回复,复制结果,再切到目标应用粘贴。这个流程在“问答”场景下没问题,但在“操作”场景下就非常割裂。我要的是让工具去改文件、去跑脚本,而不是给我一段文字让我自己复制粘贴。悬浮球的好处在于:它不占屏幕空间,始终浮在最上层,鼠标移过去就能唤起输入,说完需求直接回车,它就去执行了。整个过程不需要切换窗口,不需要找终端,交互路径从“切窗口→敲命令→等结果→切回来”压缩成了“点球→说话→继续干自己的事”。

从技术实现角度看,悬浮球本质上是一个无边框、置顶、半透明的 QWidget,通过Qt::FramelessWindowHint和Qt::WindowStaysOnTopHint两个窗口标志实现。它需要处理鼠标拖拽移动、点击展开输入框、失去焦点自动收起这几个核心交互。我试过用系统托盘图标替代,但托盘图标在 Windows 上容易被折叠,在 Linux 上不同桌面环境的支持程度参差不齐,悬浮球的跨平台一致性更好。

2.2 后台执行与前台交互的分离

另一个关键设计是前后台分离。悬浮球本身只负责收集输入和展示状态,真正的任务执行交给后台的 Harness 进程。这样做的好处是:即使 Harness 在跑一个耗时很长的任务,悬浮球本身也不会卡死,你依然可以拖拽它、查看进度、甚至取消任务。具体实现上,我用QProcess启动 Harness 的子进程,通过标准输入输出进行通信。Harness 每完成一个步骤就往 stdout 写一行 JSON 状态,悬浮球解析后更新 UI。这种设计还有一个好处:Harness 崩溃了不会带崩悬浮球,重启 Harness 进程就行。

注意:前后台分离意味着你需要处理进程间通信的异常情况,比如 Harness 进程意外退出、输出格式解析失败、任务超时等。我在早期版本里没做超时处理,结果一个死循环脚本把 Harness 卡住了,悬浮球一直在“执行中”状态转圈,只能手动杀进程。后来加了 30 秒无输出自动终止的逻辑才解决。

2.3 为什么选择 Qt 而不是 Electron

选 Qt 的理由很直接:体积小、启动快、系统集成度高。Electron 方案我也考虑过,但一个简单的悬浮球要打包上百 MB 的 Chromium 运行时,启动还要等好几秒,实在不划算。Qt 编译出来的可执行文件在 Windows 上大概 15MB,Linux 上更小,启动基本是秒开。而且 Qt 对系统级操作的支持更原生,比如全局快捷键、窗口置顶、透明背景、系统托盘这些,Qt 都有现成的 API,不需要额外装一堆 npm 包。

当然 Qt 也有代价:C++ 的开发效率确实不如 JavaScript/TypeScript,尤其是 UI 布局的调整,每次改完都要重新编译。我的应对策略是把 UI 逻辑尽量简化,悬浮球本身只有三个状态:收起、展开输入、执行中。每个状态的界面元素不超过五个,用 QML 或者简单的 QWidget 布局都能快速搞定。

3. 核心细节拆解:悬浮球、Harness 通信与任务调度

3.1 悬浮球的窗口属性与交互实现

悬浮球的核心窗口属性配置如下,这几个标志缺一不可:

setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); setAttribute(Qt::WA_TranslucentBackground);

FramelessWindowHint去掉标题栏和边框,WindowStaysOnTopHint保证始终置顶,Tool标志让它在任务栏上不显示独立条目(避免任务栏被占满)。WA_TranslucentBackground配合绘制圆形背景实现半透明效果。鼠标拖拽移动通过重写mousePressEvent和mouseMoveEvent实现,记录按下时的偏移量,移动时更新窗口位置。

展开输入框的逻辑是:点击悬浮球后,窗口从圆形扩展成圆角矩形,显示一个QLineEdit和一个发送按钮。失去焦点时自动收起,但如果在执行任务中则保持展开状态显示进度。这里有个细节:收起动画用QPropertyAnimation做 200ms 的几何过渡,比直接 show/hide 体验好很多。

3.2 与 Harness 的进程通信协议

悬浮球和 Harness 之间用 JSON Lines 协议通信,每行一个完整的 JSON 对象。悬浮球发给 Harness 的指令格式:

{"type": "task", "id": "uuid", "prompt": "把 Downloads 里所有超过 100MB 的文件移到 Archive 文件夹"}

Harness 返回的状态更新格式:

{"type": "status", "id": "uuid", "state": "running", "step": "扫描文件", "progress": 0.3} {"type": "status", "id": "uuid", "state": "done", "result": "已移动 12 个文件"} {"type": "status", "id": "uuid", "state": "error", "message": "权限不足"}

选择 JSON Lines 而不是 HTTP 或 WebSocket 的原因是:实现简单,不需要额外起服务端口,QProcess 的标准输入输出天然支持流式读写。Harness 那边只需要在 Python 里用sys.stdin逐行读取、print输出即可,不需要引入任何网络库。

提示:Harness 的输出必须用flush=True强制刷新缓冲区,否则悬浮球这边会一直等不到数据。这个坑我踩过,调试了半小时才发现是 Python 的 stdout 缓冲问题。

3.3 任务队列与并发控制

悬浮球支持同时提交多个任务,但 Harness 侧默认串行执行。任务队列用QQueue管理,每个任务有唯一 ID、状态(等待/执行中/完成/失败)和时间戳。UI 上用一个简单的列表展示队列,点击可以查看详情或取消任务。并发控制方面,我限制同时最多一个任务在执行,其余排队。这样做是为了避免多个任务同时操作文件系统导致冲突——比如一个任务在移动文件,另一个任务在读取同一批文件,结果就不可预测了。

如果你确实需要并发,可以在 Harness 侧用多进程实现,但悬浮球这边要处理好状态聚合。我的建议是:除非任务之间完全独立(比如一个在跑数据处理,一个在查天气),否则还是串行更稳妥。

4. 实操过程:从零搭建 DeepSeek Orb 的完整步骤

4.1 环境准备与依赖安装

先列一下我用的环境组合,这套在 Windows 11 和 Ubuntu 22.04 上都验证过:

组件版本说明
Qt6.5 LTS核心框架,用在线安装器装
CMake3.24+构建工具
编译器MSVC 2022 / GCC 11Windows 用 MSVC,Linux 用 GCC
Python3.10+Harness 运行环境
DeepSeek Harness最新版从官方仓库拉取

Qt 安装时记得勾选Qt Widgets和Qt Core,如果要用 QML 就再加Qt Quick。Linux 下还需要装libxcb相关的开发包,否则编译会报缺头文件。

4.2 悬浮球主窗口的代码实现

主窗口类继承QWidget,构造函数里初始化 UI 和 Harness 进程:

class OrbWindow : public QWidget { Q_OBJECT public: OrbWindow(QWidget *parent = nullptr) : QWidget(parent) { setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); setAttribute(Qt::WA_TranslucentBackground); resize(60, 60); harness = new QProcess(this); connect(harness, &QProcess::readyReadStandardOutput, this, &OrbWindow::onHarnessOutput); harness->start("python", {"-m", "harness", "--stdio"}); } private slots: void onHarnessOutput() { while (harness->canReadLine()) { QByteArray line = harness->readLine(); QJsonDocument doc = QJsonDocument::fromJson(line); // 解析状态并更新 UI } } private: QProcess *harness; };

这段代码的关键点是QProcess::readyReadStandardOutput信号,它在 Harness 有输出时触发,然后在槽函数里逐行读取。注意要用canReadLine()循环读取,因为一次信号可能对应多行输出。

4.3 Harness 侧的适配改造

Harness 原生是命令行交互模式,需要加一个--stdio参数让它进入 JSON Lines 模式。改造点在入口脚本里:

import sys, json def stdio_mode(): for line in sys.stdin: try: task = json.loads(line.strip()) result = execute_task(task["prompt"]) print(json.dumps({ "type": "status", "id": task["id"], "state": "done", "result": result }), flush=True) except Exception as e: print(json.dumps({ "type": "status", "id": task.get("id", ""), "state": "error", "message": str(e) }), flush=True)

execute_task就是 Harness 原有的任务执行入口,这里只是包了一层 JSON 解析和输出格式化。如果你用的是 Harness 的插件体系,可以在插件加载完成后调用stdio_mode()进入循环。

4.4 编译打包与开机自启

Windows 下用 CMake 生成 MSVC 工程,编译 Release 版本,然后用windeployqt把 Qt 依赖库拷到输出目录。Linux 下直接cmake --build就行,生成的可执行文件依赖系统的 Qt 库。开机自启在 Windows 上通过注册表HKCU\Software\Microsoft\Windows\CurrentVersion\Run添加条目,Linux 上在~/.config/autostart/放一个.desktop文件。

注意:打包时记得把 Harness 的 Python 依赖也带上,或者确保目标机器有 Python 环境。我一开始只打包了 Qt 程序,结果换台机器跑就报“找不到 harness 模块”。后来用 PyInstaller 把 Harness 打成了独立可执行文件,悬浮球启动时直接调这个 exe,省去了环境依赖的麻烦。

5. 常见问题与排查技巧实录

5.1 悬浮球不显示或点击无响应

这个问题最常见的原因是窗口标志冲突。如果你同时设置了Qt::Tool和Qt::Window,在某些桌面环境下窗口会直接不显示。排查方法是先用最简配置——只留FramelessWindowHint和WindowStaysOnTopHint,确认能显示后再逐个加其他标志。另外 Linux 下如果用的是 Wayland 会话,置顶功能可能受限,建议切到 X11 会话测试。

5.2 Harness 进程启动失败

先检查 Python 路径和模块是否可导入。在终端里手动跑python -m harness --stdio,看报什么错。常见的有:缺少依赖包(pip install -r requirements.txt)、Python 版本不匹配(Harness 要求 3.10+)、权限问题(Linux 下没给可执行权限)。如果 Harness 启动后立刻退出,用QProcess::readAllStandardError()把 stderr 读出来打印,通常能看到具体原因。

5.3 任务执行到一半卡住

前面提过,Harness 输出没有 flush 是首要嫌疑。其次检查任务本身是否有交互式输入等待——比如某个脚本在等用户按回车。Harness 在 stdio 模式下不应该有任何交互式提示,所有需要确认的操作都应该通过参数预设。如果任务确实需要长时间运行,在悬浮球侧加一个心跳检测:超过 60 秒没有状态更新就标记为“可能卡住”,给用户一个取消按钮。

5.4 中文输入法下回车键行为异常

这是 Qt 在 Windows 上的一个老问题:输入法组合状态下按回车,QLineEdit的returnPressed信号可能不触发。解决办法是重写keyPressEvent,判断event->key() == Qt::Key_Return且输入法不在组合状态时才发送任务。或者简单点,在输入框旁边放一个明确的发送按钮,不依赖回车。

问题现象可能原因排查步骤解决方案
悬浮球不显示窗口标志冲突逐个移除标志测试只保留必要标志
Harness 启动失败依赖缺失/路径错误终端手动运行补依赖/修正路径
任务卡住输出未刷新/交互等待检查 flush/脚本逻辑加 flush/去交互
回车无响应输入法组合状态切换英文输入测试重写 keyPressEvent

6. 进阶玩法:让悬浮球更懂你的工作流

6.1 自定义快捷指令

悬浮球支持配置快捷指令,比如输入/clean自动展开成“清理 Downloads 里超过 30 天的临时文件”。配置文件放在~/.deepseek-orb/shortcuts.json,格式很简单:

{ "clean": "删除 Downloads 目录下超过 30 天未修改的 .tmp 和 .log 文件", "backup": "把 Documents 目录打包成 zip 放到 Backup 文件夹,文件名带日期" }

悬浮球启动时加载这个文件,输入以/开头时自动匹配并替换。这个功能特别适合重复性高的操作,省去每次打一长串指令的麻烦。

6.2 任务执行结果的通知与日志

任务完成后,悬浮球会弹一个系统通知(Windows 用QSystemTrayIcon::showMessage,Linux 用notify-send)。同时所有任务的输入输出都追加写入~/.deepseek-orb/history.log,方便回溯。日志格式是每行一个 JSON,包含时间戳、任务 ID、指令原文、执行结果。我习惯每周翻一次日志,看看哪些任务跑得频繁,然后把它做成快捷指令。

6.3 与编辑器/文件管理器的联动

进阶用法是通过命令行参数让悬浮球接收外部输入。比如在 VS Code 里配置一个任务,把当前选中的代码片段通过orb --prompt "解释这段代码"发给悬浮球。文件管理器里也可以加右键菜单,选中文件后直接发给悬浮球处理。这些联动不需要改悬浮球核心代码,只要支持--prompt参数启动时自动填充输入框并执行就行。

7. 我在实际使用中踩过的坑和总结的经验

第一个坑是进程管理。早期版本我没有在悬浮球退出时杀掉 Harness 子进程,结果悬浮球关了,Harness 还在后台跑,下次启动又起一个新的,时间长了内存里堆了一堆僵尸进程。后来在closeEvent里加了harness->terminate()和harness->waitForFinished(3000)才解决。如果你也做类似的前后台分离架构,记得处理好进程生命周期。

第二个坑是跨平台路径问题。Windows 用反斜杠,Linux 用正斜杠,Harness 收到的路径如果没做归一化,在另一个平台上就会找不到文件。我的做法是在悬浮球侧统一用QDir::toNativeSeparators()转换,Harness 侧再用os.path.normpath()处理一遍,双保险。

第三个坑是权限。Linux 下如果 Harness 要操作一些系统目录,需要提权,但悬浮球本身不应该以 root 运行。我的方案是 Harness 侧对需要提权的操作单独处理,通过pkexec弹窗请求授权,而不是整个进程提权。这样安全性更好,也不会因为悬浮球以 root 运行导致 Qt 的某些功能异常。

最后分享一个小技巧:悬浮球的透明度可以做成可调的,在设置里加一个滑块。我一开始固定 80% 不透明度,后来发现不同壁纸下视觉效果差异很大,深色壁纸上 80% 刚好,浅色壁纸上就太透了看不清。做成可调之后体验好很多。这个改动代码量很小,就是给setWindowOpacity传个变量的事,但用户体验提升明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询