yangg1224/smart_remarkable

GitHub: yangg1224/smart_remarkable

为 reMarkable 墨水屏平板打造的 Vision-LLM 智能体,能截取手写内容发送给大模型并将回复以真实笔迹绘制回屏幕。

Stars: 51 | Forks: 5

# Smart Remarkable 一款专为 reMarkable 平板设计的 Vision-LLM agent。它会监视你的书写内容,并在你触发时,将屏幕截图发送给 LLM,然后将回复绘制(或打字)到屏幕上。 本项目是 [awwaiid/ghostwriter](https://github.com/awwaiid/ghostwriter) 的一个分支,并扩展了选择模式 (Select Mode)。 它还具有**选择模式**:套索圈选手写区域,即可将 LLM 的回复绘制到你指定的框中。由于回复是真实的笔迹,因此之后你可以使用 reMarkable 自带的选择工具对其进行移动和调整大小。 **新增:LLM 按钮。** 当你使用 reMarkable 自带的选择工具套索圈选文本时,常规剪切/复制/粘贴菜单旁边会显示一个 **LLM** 按钮——点击它即可对该选区启动选择模式,无需点击角落或使用手势。它是由一个小型扩展 (`xovi-ext/llmbutton`) 实现的,该扩展会挂钩至 xochitl 的 UI。 **新增:Draw 按钮。** 第二个按钮 **Draw** 位于 LLM 按钮旁边(同一个扩展)。套索圈选一个区域后点击它而不是 LLM:如果选区主要是手写/键入的文本,它会在选区下方绘制一个铅笔涂鸦风格的简笔画,用来说明你写的内容;如果选区已经是一幅图画或草图,它会擦除原图并在相同位置重新绘制一个改进的、细节更丰富的版本。详情请参阅 [SELECT_MODE.md](SELECT_MODE.md)。 **新增:图像生成绘图 (`--image-model`)。** 默认情况下,Draw 按钮生成的画作是由聊天 LLM 编写的 SVG,其上限是示意图风格的线条艺术。传入 `--image-model`(默认为 `gemini-2.5-flash-image`,即 Google 的 "nano banana"),草图将由真正的图像生成模型进行渲染——聊天 LLM 仅对选区进行分类并编写图像 prompt——然后进行骨架追踪以转换为笔迹。草图增强变为了真正的图生图:你圈选的图画会被附加到请求中,并且在进行优化版本绘制之前,会通过 xochitl 自带的选区删除功能移除原始笔迹。需要 `GEMINI_API_KEY` 或 `GOOGLE_API_KEY`。 ## 目录 - [功能](#features) - [用法](#usage) - [安装](#install) - [LLM API Keys](#llm-api-keys) - [架构](#architecture) - [许可证](#license) - [致谢](#credits) ## 功能 - **监视并绘制循环。** 后台任务等待触摸触发(点击屏幕角落、四指点击或物理 "LLM" 按键——见下文),直接从 xochitl 的 framebuffer 中截取当前页面,将其发送给支持视觉的 LLM,并将回复写回屏幕——可以通过虚拟键盘 (`draw_text`) 键入,也可以作为 LLM 生成的 SVG 手绘笔迹 (`draw_svg`)。 - **选择模式** (`--select-mode`)。点击触发角落,然后在一处手写内容周围点击两个对角将其选中,接着再点击两个对角来选择回复的绘制位置。裁剪后的选区会发送给 LLM,回复会作为真实的墨迹缩放/居中放置到你指定的框内——这是真正的笔迹,之后你可以使用 reMarkable 原生选择工具对其进行移动和调整大小。完整操作流程请参阅 `SELECT_MODE.md`。 - **LLM 按钮 (`xovi-ext/llmbutton`)。** 一个 XOVI 原生扩展 (`llmbutton.so`),它在 Qt 场景图层级挂钩到正在运行的 `xochitl` 进程中,并在原生的剪切/复制/粘贴选择菜单旁边注入一个 "LLM" 按钮。点击它会写入一个触发文件 (`/tmp/llm_button_trigger`),从而对当前选区启动选择模式——无需点击角落。 - **Draw 按钮**(同一个扩展)。位于 LLM 旁边的第二个注入按钮。在套索圈选区域后点击它会写入 `/tmp/draw_button_trigger`,并路由到 `prompts/draw.json` 的 `draw_sketch` 工具,而不是 LLM 回复:如果选区主要是文本,模型会在其下方绘制一个说明性的涂鸦;如果选区已经是图画,程序会擦除原始墨迹并在同一位置重新绘制一个改进版本。具体机制请参阅 [SELECT_MODE.md](SELECT_MODE.md)。 - **图像生成绘图** (`--image-model`, `--image-api-key`)。通过图像生成模型(默认为 `gemini-2.5-flash-image`,"nano banana")重新路由 Draw 按钮:聊天 LLM 对选区进行分类并编写详细的图像 prompt (`prompts/draw_image.json`),`src/image_gen.rs` 调用 Gemini 图像 API——在增强模式下会附加你草图的放大、清理背景后的裁剪图——返回的线条艺术经过阈值处理、细化为 1 像素骨架,并追踪为笔迹 (`Pen::draw_bitmap_centerline`)。对于原位重绘,会通过 xochitl 自带的选择菜单精确删除原始笔迹(程序会定位并点击其垃圾桶按钮;密集的硬件橡皮擦扫描是备选方案),并且只有在生成成功后才会进行——失败的 API 调用永远不会破坏你的草图。 - **支持旋转的输入。** 每次截图都会检测 xochitl 的 UI 是否渲染为 180° 旋转(设备倒置),将图像标准化为用户的朝向以便进行 LLM 和跑马灯检测,并在注入边界处将所有合成的笔迹/触摸坐标进行镜像处理——因此无论你以何种方式握持平板,点击、擦除和绘图都能准确无误地定位。 - **Web 配置 UI** (`--web-server`, `--web-port`)。一个基于 `warp` 的 HTTP 服务器(默认端口 `8080`),提供一个小型静态 UI,用于查看和实时编辑正在运行的配置,立即应用更改(在内存中,通过 watch channel 热重载,同时取消进行中的 LLM 调用)并将其持久化到 `~/.smart_remarkable.toml`。还暴露了 `POST /api/simulation/trigger` 接口,以便在不触摸设备的情况下手动触发。 - **图像分割** (`--apply-segmentation`)。在调用 LLM 之前,对屏幕截图运行基于轮廓的区域检测 (`imageproc`),将检测到的墨迹区域的文本描述附加到 prompt 中,以实现更好的空间定位。 - **Anthropic 独有功能:思考与网络搜索。** `--thinking` 启用扩展思考(`--thinking-tokens`,默认 `5000`,设置预算);`--web-search` 为 Claude 提供服务器端的网络搜索工具(每次调用最多使用 5 次)。两者在 OpenAI/Google 引擎上均为空操作。 - **分层、可持久化的配置。** `--save-config` 会将完全解析后的配置(默认值 < `~/.smart_remarkable.toml` < `SMART_REMARKABLE_*` 环境变量 < CLI 参数)写入 `~/.smart_remarkable.toml` 并退出,这样你就可以将首选参数固化进去,而无需在每次启动时重新输入。 - **模拟 / 离线测试。** `--input-png` 将实时截图替换为静态图像;`--test-mode ` 加上 `--test-touch-events-file`、`--test-screenshot-dir`、`--test-auto-trigger-delay` 和 `--test-interaction-log` 使得整个 触摸→截图→LLM→绘图 的流水线可以在桌面上无头运行,无需硬件。`--no-draw`、`--no-submit`、`--no-loop` 和 `--no-trigger` 可以组合用于完全离线的单次运行(由 `run_eval.sh` 的评估测试工具使用)。 ## 用法 **正常模式**,在设备上的 SSH 会话中运行: ``` ANTHROPIC_API_KEY=sk-... ./smart_remarkable ``` 1. 在页面上写点东西。 2. 点击触发角落(默认为右上角;使用 `--trigger-corner` 更改)。 3. 该工具会截取页面,发送给 LLM,并将回复键入或绘制回屏幕上。 **选择模式**: ``` ANTHROPIC_API_KEY=sk-... ./smart_remarkable --select-mode ``` 1. 点击触发角落以准备。 2. 在手写问题周围点击两个对角(强制要求至少 40px 的框,因此点击不精确也没关系)。 3. 点击回复预期绘制位置的两个对角。 4. 裁剪后的选区将发送给 LLM;回复将作为笔迹缩放并绘制到放置框中。之后可使用 xochitl 的原生选择工具对其进行移动/调整大小。 点击之间没有屏幕提示——顺序始终是:触发 → 2 次选择点击 → 2 次放置点击。在你熟悉手势的过程中,可以通过 SSH 运行并加上 `--log-level debug` 查看调试信息。 **LLM 按钮 / Draw 按钮**:安装了 `xovi-ext/llmbutton` 后,使用 xochitl 自带的选择工具套索圈选文本时,剪切/复制/粘贴旁边会显示 **LLM** 和 **Draw** 按钮——点击其中任意一个即可对该选区运行选择模式,无需角落点击触发,分别对应 LLM 回复流程或草图/重绘流程。 **关键 CLI 参数** | 参数 | 默认值 | 用途 | |---|---|---| | `--engine` | 从 `--model` 自动推断 | 强制使用 `openai` / `anthropic` / `google` | | `-m, --model` | `claude-sonnet-4-6` | 模型名称 | | `--engine-base-url` | 提供商默认值 | 覆盖 API base URL | | `--engine-api-key` | 来自环境变量 | API key | | `--prompt` | `general.json` | Prompt 模板(在选择模式下自动切换为 `selection.json`) | | `--select-mode` | 关闭 | 启用选择模式 | | `--image-model` | 关闭(如果单独传入则为 `gemini-2.5-flash-image`) | 使用图像生成模型渲染 Draw 按钮草图 | | `--image-api-key` | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | 图像模型的 API key | | `--trigger-corner` | `UR` | `UR`/`UL`/`LR`/`LL` | | `--apply-segmentation` | 关闭 | 向 prompt 添加基于计算机视觉 (CV) 的空间提示 | | `--web-search` / `--thinking` / `--thinking-tokens` | 关闭 / 关闭 / `5000` | Anthropic 独有功能 | | `--web-server` / `--web-port` | 关闭 / `8080` | 实时配置 UI/API | | `--save-config` | 关闭 | 持久化解析后的配置并退出 | | `--log-level` | `info` | `debug`, `trace` 等 | | `--input-png`, `--test-mode`, `--test-touch-events-file`, `--test-screenshot-dir`, `--test-auto-trigger-delay` | — | 离线模拟 / 测试 | | `--no-submit`, `--no-draw`, `--no-loop`, `--no-trigger` | 关闭 | 为测试跳过流水线中的部分环节 | | `--debug-tap`, `--debug-drag`, `--debug-lasso`, `--debug-type`, `--debug-svg`, `--debug-erase` | — | 单次设备 I/O 助手,运行后退出 | **示例命令** ``` # 使用 Claude 运行,左上角触发,verbose logging ANTHROPIC_API_KEY=sk-... ./smart_remarkable --trigger-corner UL --log-level debug # 使用特定 Gemini 模型选择模式并启用 web config UI GOOGLE_API_KEY=... ./smart_remarkable --select-mode -m gemini-2.5-pro --web-server # 针对已保存的截图进行离线测试,无绘制,单次执行 ./smart_remarkable --input-png ./test.png --no-draw --no-loop --no-trigger --no-submit # 使用 nano-banana 图像生成选择模式,用于 Draw 按钮 OPENAI_API_KEY=... GEMINI_API_KEY=... ./smart_remarkable --select-mode \ --trigger-corner four-finger -m gpt-5.4 --image-model ``` ## 安装 **工具链**:Rust `1.92.0`(固定在 `.tool-versions` 中)。 **本地构建** ``` cargo build --release # 或 ./build.sh local ``` 二进制文件位于:`target/release/smart_remarkable`。 **使用 Docker 交叉编译 (`cross`)** ``` cargo install cross --git https://github.com/cross-rs/cross rustup target add armv7-unknown-linux-gnueabihf aarch64-unknown-linux-gnu # reMarkable 2 (armv7) cross build --release --target=armv7-unknown-linux-gnueabihf # 或: ./build.sh # Paper Pro (aarch64) cross build --release --target=aarch64-unknown-linux-gnu # 或: ./build.sh rmpp ``` `build.sh` 也可以为你执行 scp 上传:将主机名作为第一个参数传入(默认为 `remarkable`);任何以 `rmpp` 开头的名称都会构建/传输 aarch64 架构版本到 `root@`,其他名称则构建/传输 armv7 架构版本。例如 `./build.sh rmpp-mytablet` 或 `./build.sh 192.168.1.117`。 **不使用 Docker 进行交叉编译(仅限 macOS、Paper Pro / aarch64)** ``` brew tap messense/macos-cross-toolchains && brew install aarch64-unknown-linux-gnu rustup target add aarch64-unknown-linux-gnu CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-unknown-linux-gnu-gcc \ cargo build --release --target aarch64-unknown-linux-gnu ``` 在此仓库中,针对 reMarkable 2 (armv7) 目标尚无经过验证的无 Docker 路径——`.cargo/config.toml` 中有一个注释掉的、基于 messense 风格的 `arm-remarkable-linux-gnueabi-gcc` 工具链模板,但尚未填写内容或启用。 **通过 scp 部署** ``` scp target/armv7-unknown-linux-gnueabihf/release/smart_remarkable root@: # 或 scp target/aarch64-unknown-linux-gnu/release/smart_remarkable root@: ``` 在平板上的“设置 → 帮助 → 关于”中找到设备 IP 和 root 密码**在设备上运行** ``` ssh root@ ANTHROPIC_API_KEY=sk-... ./smart_remarkable --select-mode ``` - **需要开发者模式**(设置 → 通用 → 软件 → 高级)。启用它会恢复设备出厂设置并使保修失效。 - 使用 `nohup ./smart_remarkable &` 在后台运行。 - **Paper Pro uinput 注意事项**:自带的 uinput 内核模块会自动加载,并针对 OS 版本 3.16–3.18 和 3.22(`utils/rmpp/uinput-3.16.ko`, `-3.17.ko`, `-3.18.ko`, `-3.22.ko`)进行了预编译。其他 OS 版本可能需要重新编译模块——参见 `utils/rmpp/`。 ## LLM API Keys 将你计划使用的提供商的 key 设置为环境变量,或者将它们放入本地 `.env` 文件中(通过 `dotenv` 加载): ``` export OPENAI_API_KEY=your-key-here export ANTHROPIC_API_KEY=your-key-here export GOOGLE_API_KEY=your-key-here export GEMINI_API_KEY=your-key-here # image generation (--image-model); GOOGLE_API_KEY also works ``` 注意:图像生成不在 Gemini 免费额度范围内——该 key 对应的 Google AI Studio 项目需要启用计费(`gemini-2.5-flash-image` 每张图片约收费 $0.04)。 - `--engine` 显式选择后端(`openai`, `anthropic`, `google`)。如果省略,则会根据 `--model` 名称的前缀进行推断(`gpt*` → openai,`claude*` → anthropic,`gemini*` → google);如果无法推断,则会报错并要求你传入 `--engine`。 - `--engine-api-key` / `--engine-base-url` 覆盖相应的提供商环境变量(`OPENAI_API_KEY`/`OPENAI_BASE_URL`,`ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL`,`GOOGLE_API_KEY`/`GOOGLE_BASE_URL`)。如果 CLI 参数和环境变量均未设置 API key,程序将会 panic——没有任何备用方案。Base URL 具有硬编码的默认值(`api.openai.com`,`api.anthropic.com`,`generativelanguage.googleapis.com`)。 - **默认模型**:`claude-sonnet-4-6`,在未指定 `--engine` 时会自动解析为 `anthropic` 引擎。 ## 架构 **数据流**:触摸触发 → 截图 → LLM → 绘制/打字。 1. **触发** (`touch.rs`, `coordinator::trigger_task`) — 监听 `/dev/input/eventN`,等待角落点击释放、四指点击,或对 `/tmp/llm_button_trigger` 或 `/tmp/draw_button_trigger` 的外部写入(来自 `llmbutton` xovi 扩展)。触发源会被追踪为 `touch::TriggerSource`(`Touch` / `LlmButton` / `DrawButton`)并贯穿 `TriggerEvent`/`processing_task`,后者仅在针对 `DrawButton` 时选择 `prompts/draw.json` 而不是配置的 `--prompt`。在选择模式下,它还会收集定义选择矩形和放置矩形的那两对角落点击。 2. **捕获** (`screenshot.rs`) — 直接从 `/proc//mem` 读取 `xochitl` 的 framebuffer,对其进行解码/旋转/颜色校正,生成标准化的 768×1024 PNG;可以通过连通域分析检测原生选择跑马灯。 3. **协调** (`coordinator.rs`) — 一个由 `mpsc`/`watch` channel 连接的 `tokio` 异步任务流水线(`trigger_task` → `processing_task` → `progress_task`)。`processing_task` 可选地裁剪到选区矩形,可选地运行 `segmenter.rs`,加载 JSON prompt 模板,并将 base64 图像 + prompt 交给 LLM 引擎。`progress_task` 在等待期间通过虚拟键盘打出 "Thinking..." 的点动画。 4. **LLM 调用** (`src/llm_engine/`) — 一个共享的 `LLMEngine` trait 抽象了 `openai.rs`、`anthropic.rs`、`google.rs`。每个实现都会构建特定于提供商的强制工具调用请求,并调用模型所选工具对应的回调:`draw_text`、`draw_svg`、`draw_answer`(用于选择模式的结构化、基于线条的布局)或 `draw_sketch`(Draw 按钮——报告 `selection_is_drawing` 以选择在选区下方绘制涂鸦还是擦除并原位重绘)。 5. **绘图** (`pen.rs`, `keyboard.rs`) — `pen.rs` 通过 `resvg`/`usvg`(文本转路径)和 `svg2polylines`(追踪)解析 SVG,将其转换为作为原始 `evdev` 事件注入的虚拟笔迹;`skeleton.rs` 提供了一种通过 Zhang-Suen 细化算法进行中心线追踪的替代渲染路径。对于原位重绘,`Pen::erase_rect` 首先使用 `BTN_TOOL_RUBBER`(真正的橡皮擦硬件信号)扫描框体——xochitl 会忽略普通笔迹,无论当前选择了哪种工具栏工具,都将其视为擦除操作。`keyboard.rs` 驱动 `uinput` 虚拟键盘键入文本和进度点动画。 **核心模块** | 模块 | 职责 | |---|---| | `main.rs` | CLI 入口点、配置/引擎组装、工具注册、配置更改重启循环、`--debug-*` 单次助手 | | `coordinator.rs` | 异步任务图:触发检测、进度报告、截图→LLM→工具流水线 | | `touch.rs` | 原始触摸/evdev 读取、角落/四指触发检测、坐标映射、手势助手 | | `screenshot.rs` | Framebuffer 捕获、解码、选择跑马灯检测、裁剪 | | `pen.rs` | 虚拟笔 (`evdev`/uinput):SVG/bitmap 渲染策略 | | `keyboard.rs` | 虚拟键盘 (`evdev`/uinput):文本键入、进度点动画 | | `device.rs` | `DeviceModel` 检测 (RM2 / Paper Pro) 及各设备常量 | | `segmenter.rs` | 用于空间定位的基于轮廓的区域检测 | | `skeleton.rs` | Zhang-Suen 细化 / 中心线追踪 | | `util.rs` | SVG↔bitmap 栅格化、适应矩形逻辑、uinput 设置 | | `llm_engine/` | `LLMEngine` trait + `openai.rs`/`anthropic.rs`/`google.rs` | | `image_gen.rs` | 用于 `--image-model` (nano banana) 的 Gemini 图像生成客户端 | | `config.rs` | 通过 `figment`/`toml` 实现分层配置,热重载 watch channel | | `cancellation.rs` | 协作式取消令牌 | | `status.rs` | 用于 Web UI 的共享状态快照 | | `web_server.rs` + `src/web/` | 可选的 `warp` HTTP 服务器 + 静态配置 UI | | `simulation/` | 桌面环境下的触摸/截图硬件替代方案、交互记录 | | `src/bin/experiment.rs` | 用于临时实验的辅助二进制文件 | | `embedded_assets.rs` | 通过 `rust-embed` 将 `prompts/*.json` 打包进编译后的二进制文件 | **技术栈**:Rust + `tokio` 异步任务图;`clap` 用于 CLI;`reqwest`/`ureq` 用于 LLM HTTP 调用;`resvg`/`usvg`/`svg2polylines` 用于 SVG 转笔迹渲染;`evdev`/`uinput` 用于虚拟触摸/笔/键盘设备;`figment`/`toml` 用于分层配置;`warp` 用于可选的 Web UI;`cross`-rs (Docker) 或 messense 工具链用于交叉编译到 armv7/aarch64;`prompts/*.json` 系统(`general.json`、`selection.json`,以及每个注册工具对应的一个 `tool_*.json` schema)驱动着所有三个提供商后端的 LLM 工具调用。 **LLM/Draw 按钮**扩展 (`xovi-ext/llmbutton`) 在架构上是独立的:它是一个基于 XOVI 扩展框架构建的、独立的 C 共享库,加载到*原生的* `xochitl` 进程中(通过 `dlsym` 解析 Qt6 符号,遍历活跃的 QtQuick 场景图以将这两个按钮注入原生选择菜单),而不是 Rust `smart_remarkable` 二进制文件的一部分——两者仅通过文件系统触发文件 `/tmp/llm_button_trigger` 和 `/tmp/draw_button_trigger` 进行通信。 ## 许可证 MIT — 参见 [LICENSE](LICENSE)。发布的二进制文件中还嵌入了一个 GPL-2.0 内核模块;参见 [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md)。 ## 致谢 派生自 [awwaiid/ghostwriter](https://github.com/awwaiid/ghostwriter)。 本项目参考过的资源: * [Awesome reMarkable](https://github.com/reHackable/awesome-reMarkable) * 屏幕捕获改编自 [reSnap](https://github.com/cloudsftp/reSnap) * 屏幕绘制技术受 [rmkit lamp](https://github.com/rmkit-dev/rmkit/blob/master/src/lamp/main.cpy) 启发 * 通过 [resvg](https://github.com/RazrFalcon/resvg) 实现 SVG 转 PNG * 通过 [rM-input-devices](https://github.com/pl-semiotics/rM-input-devices) 实现虚拟键盘输入
标签:AI智能体, DLL 劫持, Petitpotam, reMarkable, 人机交互, 可视化界面, 图像生成, 大语言模型, 智能画板, 视觉语言模型, 请求拦截, 通知系统