MindTrack Desk · 灵迹桌面
《MindTrackDesk:基于AI的Windows屏幕记忆助手》 摘要: MindTrackDesk是一款面向开发者和创作者的桌面工具,通过自动记录屏幕活动并利用AI生成工作轨迹。该项目采用Python+PySide6开发,主要功能包括:实时窗口监听(带防抖机制)、智能截图(支持多屏适配)、调用豆包多模态API分析内容,以及本地SQLite存储时间线数据。其特色在于将传统时间追踪与多模态AI结
项目地址:https://github.com/CanFlyhang/mindtrack-desk
一、为什么我需要一个「屏幕记忆助手」?
作为开发者、产品人、独立创作者,你是不是也有过这样的瞬间:
-
一整天在电脑前忙忙碌碌,下班时却说不清「今天到底干了啥」;
-
早上还在看一篇很重要的技术文章,下午想回顾时已经完全找不到网页;
-
排查 Bug 一顿操作猛如虎,过两天再遇到同类问题,竟然连当时的操作路径都不记得了;
-
想做周报 / 复盘,只能凭印象「脑补」工作内容。
我们的大脑擅长当下的「聚焦」,却很不擅长完整、客观地记录自己的工作轨迹。
而在 LLM 多模态能力越来越强的今天,一个显而易见的问题是:
能不能让 AI 帮我自动记住「我在屏幕前都做了什么」,而且可以随时回放和检索?
带着这个问题,我做了一个小而美的桌面项目 —— MindTrack Desk · 灵迹桌面。
这篇文章,想和你分享:
-
我是怎么用 Python + PySide6 + 豆包多模态,实现一个「Windows 屏幕记忆助手」的;
-
在这个过程中,我踩过哪些坑、做了哪些设计取舍;
- 更重要的是:这种「AI + 时间线」的方式,对我们的工作与思考可能意味着什么。
二、MindTrack Desk 是个什么东西?
一句话概括:
一个运行在 Windows 上的悬浮窗工具,自动监听当前活动窗口变化,截屏发给豆包多模态模型,总结你正在做的事情,并按时间线保存下来。
它做了几件很简单但组合起来很有意思的事:
1. 悬浮窗 UI
-
半透明深色风格、圆角、无边框、可拖拽;
-
始终置顶,可以随手点「开始监控 / 暂停监控」;
-
支持一键打开「历史记录」窗口。
2. 窗口监听
-
使用
pywin32实时获取当前前台窗口句柄和标题; -
做了「防抖动」逻辑:只有在一个窗口稳定停留一段时间(例如 1.5 秒)才会触发;
-
避免你在多个窗口来回切换时产生大量垃圾记录。
3. 自动截屏 + 多模态总结
-
使用
mss截取当前前台窗口区域,必要时退化为全屏截图; -
将图片压缩为 JPEG,转为 Base64;
-
通过火山引擎 Ark Runtime 调用豆包多模态模型(如
doubao-seed-2-0-pro-260215或 vision 模型),让模型总结当前屏幕内容。
4. 本地时间线记录
-
使用 SQLite 记录:时间戳 / 窗口标题 / AI 总结 / 截图路径;
-
截图保存到
logs/images/; -
提供一个历史记录窗口,左侧列表 + 右侧大图 + 文本摘要,方便回顾。
你可以把它当成一个自动生成「工作流水账」的 AI 小伙伴。
三、从想法到实现:整体架构一图流
项目整体架构很简单,也尽量保持「工程可读性」:
mindtrack-desk/
├── main.py # 程序入口,初始化 Qt 应用与控制器
├── src/
│ ├── ui/
│ │ ├── floating_window.py # 悬浮窗 UI
│ │ ├── history_window.py # 历史记录窗口 UI
│ │ └── styles.qss # 全局样式(深色主题)
│ ├── services/
│ │ ├── monitor.py # WindowWatcher:监控当前活动窗口的 QThread
│ │ ├── capture.py # ScreenCapture:截图 + Base64 编码
│ │ ├── ai_client.py # ArkClient:调用豆包多模态 API
│ │ ├── storage.py # DataManager:SQLite 存储
│ │ └── worker.py # AnalysisWorker:截图 + 调用 AI + 落地的后台线程
│ └── app_controller.py # AppController:业务中枢,串联 UI 与服务
└── logs/
└── images/ # 截图目录
用一句话概括:
> UI 做交互,服务做能力,控制器做编排。
下面我挑几个关键模块展开讲讲。
四、关键技术细节拆解
4.1 PySide6 悬浮窗:既要「酷」,又要好用
悬浮窗的定位是「安静地存在,但随时可用」。在 UI 设计上我做了几个小决定:
-
无边框、圆角、深色 + 半透明背景;
-
始终置顶,不会被其他窗口遮挡;
-
支持拖拽,用户想放哪就放哪;
-
只保留几个核心信息:当前状态 / 操作按钮 / 历史入口。
核心代码(部分)如下:
class FloatingWindow(QWidget):
# 定义信号,供控制器连接
start_monitoring_signal = Signal() # 开始监控信号
stop_monitoring_signal = Signal() # 停止监控信号
open_history_signal = Signal() # 打开历史记录信号
def __init__(self):
super().__init__()
self.is_monitoring = False # 当前是否在监控
self._init_ui()
def _init_ui(self):
# 去边框 + 置顶 + 工具窗
self.setWindowFlags(
Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
)
# 启用透明背景,用于自绘圆角
self.setAttribute(Qt.WA_TranslucentBackground)
self.resize(300, 150)
# 省略布局代码...
def toggle_monitoring(self):
"""切换监控状态"""
self.is_monitoring = not self.is_monitoring
if self.is_monitoring:
self.start_monitoring_signal.emit()
else:
self.stop_monitoring_signal.emit()
# 重写鼠标事件,实现窗口拖拽
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self._drag_pos = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.move(event.globalPosition().toPoint() - self._drag_pos)
样式统一放在 styles.qss 中,方便后续更换主题 / 做皮肤系统。
4.2 活动窗口监听:别太「敏感」
刚开始我天真地以为:
不就是
GetForegroundWindow轮询一下就完了吗?
现实是:人会「来回切窗口」,还会「手抖点错再切回来」。如果不做防抖,几分钟就能刷出几十条日志,非常噪音。
最终我在 WindowWatcher 里做了三件事:
-
轮询当前前台窗口句柄;
-
如果句柄变化,开始计时;
-
只有保持稳定超过
debounce_seconds(默认 1.5s)才真正触发事件。
简化后的逻辑:
class WindowWatcher(QThread):
# 发出句柄和标题
window_changed = Signal(int, str)
def __init__(self, debounce_seconds=1.5):
super().__init__()
self._running = False
self._debounce_seconds = debounce_seconds
self._last_hwnd = 0
self._pending_hwnd = 0
self._pending_start = 0.0
def run(self):
self._running = True
while self._running:
try:
hwnd = win32gui.GetForegroundWindow()
# 省略防抖逻辑实现...
except Exception as e:
print(f"WindowWatcher 异常: {e}")
time.sleep(0.2) # 轮询间隔
def stop(self):
self._running = False
self.wait() # 等待线程退出
为了避免程序退出时出现 QThread: Destroyed while thread is still running 的经典警告,我在 AppController 里统一做了线程清理(包括监控线程和工作线程),保证优雅关闭。
4.3 截图与多屏适配:mss 的小坑与小惊喜
截图我选了 mss,原因很简单:
-
跨平台、性能不错;
-
对多屏支持较好,可以直接拿到各个显示器的坐标。
在本项目里,一般用的是「按窗口坐标截取」:
class ScreenCapture:
def capture_active_window(self, hwnd):
"""根据窗口句柄截取当前窗口区域"""
try:
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width = right - left
height = bottom - top
if width <= 0 or height <= 0:
return self.capture_full_screen()
with mss.mss() as sct:
monitor = {
"top": top,
"left": left,
"width": width,
"height": height,
}
sct_img = sct.grab(monitor)
img = Image.frombytes(
"RGB",
sct_img.size,
sct_img.bgra,
"raw",
"BGRX",
)
return img
except Exception as e:
print(f"窗口截图失败,使用全屏截图兜底: {e}")
return self.capture_full_screen()
这里特意留了一个兜底策略:窗口截取失败时会退化为全屏截图,避免因为某些奇怪窗口导致整个流程中断。
4.4 调用豆包多模态:请求设计与错误兜底
Ark 客户端我采用官方推荐的 Ark Runtime SDK,核心关注点有两个:
1. 不要把 API Key 写死在代码里:通过 `.env` + `python-dotenv` 管理;
2. 确保没配 Key 时不会直接程序崩溃:返回友好的错误提示。
简化版的 ArkClient 如下:
class ArkClient:
def __init__(self):
load_dotenv() # 加载 .env 文件
self._api_key = os.getenv("ARK_API_KEY")
self._model = os.getenv("ARK_MODEL_NAME", "doubao-seed-2-0-pro-260215")
self._base_url = "https://ark.cn-beijing.volces.com/api/v3"
self._client = None
if self._api_key:
# 有 Key 时才初始化客户端
self._client = Ark(
base_url=self._base_url,
api_key=self._api_key,
)
else:
print("警告:未配置 ARK_API_KEY,AI 分析功能将不可用。")
def analyze_image(self, base64_image: str, prompt: str) -> str:
"""调用多模态模型分析图片内容"""
if not self._client:
return "错误:未配置 ARK_API_KEY,无法调用多模态模型。"
try:
# 这里根据 Ark / 豆包具体接口形态构造请求
# 示例:以图文混合消息的形式发送
resp = self._client.chat.completions.create(
model=self._model,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
},
},
],
}
],
)
return resp.choices[0].message.content
except Exception as e:
print(f"调用豆包多模态失败: {e}")
return f"分析失败:{e}"
4.5 本地存储与历史回放:数字记忆的一块「硬盘」
这里刻意没引入复杂的 ORM,而是用最朴素的 SQLite:
class DataManager:
def __init__(self, db_path="logs/history.db"):
self._db_path = db_path
self._init_db()
def _init_db(self):
"""初始化数据库表结构"""
conn = sqlite3.connect(self._db_path)
cursor = conn.cursor()
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
window_title TEXT,
summary TEXT,
image_path TEXT
)
"""
)
conn.commit()
conn.close()
历史记录窗口 HistoryWindow 用的是非常直观的布局:
-
左侧
QListWidget做时间线列表; -
右侧上方显示截图、下方显示 AI 总结;
-
支持滚动、文本选择复制。
这块的实现对 PySide6 初学者也比较友好,推荐你直接看源码体验一下。
五、这个小项目想引发哪些思考?
写这个项目的时候,我一直在想几个问题,也想抛出来看看有没有人有共鸣:
5.1 数字时代的「第二大脑」,需要什么形态?
现在很多人都在用:
-
Obsidian / Notion / 思源 / Logseq / 飞书文档……
手动写笔记、整理知识体系。
这些工具的前提是:我得愿意思考 &愿意记录。
但很多时候,我们只是想:
「我不想自己记录,但希望有一份还算靠谱的『工作轨迹』备份在那里,必要时能翻出来。」
MindTrack Desk 想做的,就是这一块。
它记录的不是「你思考的结论」,而是:
-
你看过哪些页面;
-
你写过哪些代码;
-
你打开过哪些工具;
-
你在某一段时间里,大概在干什么。
这是一个更底层的「行为时间线」,可能是未来各种第二大脑系统的重要输入。
5.2 多模态 LLM 能不能变成一种「被动记录器」?
传统的时间追踪工具(比如 Toggl、RescueTime)更多是统计「在哪个软件上花了多久时间」。
而多模态 LLM 可以做的事情是:
-
不仅知道你在「Chrome」里待了 30 分钟;
-
还可以知道你在看「某篇论文 / 某篇文档 / 某段代码」;
-
甚至可以帮你做:
-
摘要;
-
任务推断(例如「你在修一个后端接口 Bug」);
-
TODO 抽取(例如「似乎还有两个 TODO 没完成」)。
-
MindTrack Desk 只是一个非常粗糙的第一步:把屏幕画面送给模型,让模型自己说说「你在干什么」。
后面完全可以进一步探索:
-
结合键鼠事件、窗口标题、进程名做更精准的任务识别;
-
把一天的屏幕事件聚合成一个「日记摘要」;
-
回放一段时间线时,自动提示你「这里曾经解决过一个 Bug,要不要转成知识库条目?」。
5.3 隐私与边界:什么不该被记录?
任何涉及自动截屏的项目,都绕不开隐私问题。
所以在设计 MindTrack Desk 的时候,我刻意做了几个限制与预留
- 所有截图 & 结构化数据 仅保存在本地,不主动上传任何第三方存储;
-
只有在调用多模态 API 时,当前这张图会发送到豆包服务端做一次推理;
-
未来可以增加:
-
黑名单应用(例如密码管理器、网银、隐私聊天软件等);
-
局部涂抹 / 模糊敏感区域;
-
一键清空某个时间段的所有记录。
-
这块其实很值得认真设计:
什么样的数据是对自己有帮助的?什么又是你不希望任何系统触碰的?
欢迎在 Issue / 评论区一起讨论。
六、如何参与共建?
项目地址:
你可以从这些方向入手:
- 🎨 UI / UX 改进
-
更酷的主题 / 动画效果;
-
桌面小挂件模式;
-
支持不同布局的历史回放。
- 🧠 多模态能力增强
-
针对不同场景设计不同 Prompt(写代码 / 看文档 / 刷 B 站);
-
为某些特定软件做「定制化总结模板」;
-
引入更多模型后端,做「多模型路由」。
- 🛡 隐私与控制
-
黑名单窗口(不截图 / 不记录);
-
敏感字段自动模糊处理;
-
数据导出 / 加密存储。
- 📈 知识化与时间线重构
-
把每天的记录聚合成「日报 / 周报」;
-
和 Obsidian / Logseq 等知识库做联动;
-
做成一个「AI 驱动的时光轴」。
非常欢迎:
-
提 Issue 讨论新的想法;
-
提 PR 一起迭代代码;
-
Fork 后玩出自己的一套改造版本。
七、写在最后
MindTrack Desk 只是一个非常早期的探索版本,它没有解决所有问题,甚至还有不少粗糙的地方。
但我很喜欢它的出发点:
在 AI 时代,让机器帮我们记住那些「容易被遗忘的过程」,
然后把有限的注意力,留给真正值得思考的事情。
如果你也对这种「屏幕记忆 + 多模态理解 + 时间线回放」的方向感兴趣,
欢迎来 GitHub 或 CSDN 找我,一起把这个小项目打磨得更有趣一点。
也欢迎你顺手点一个 Star ✨,
让更多人看到这个关于「数字记忆」的小实验。
更多推荐


所有评论(0)