Sfoglia il codice sorgente

修改代码结构去掉不必要的代码

yichael 5 mesi fa
parent
commit
553f4152bd
68 ha cambiato i file con 1375 aggiunte e 146 eliminazioni
  1. 70 0
      BAT-TOOL/1-BUILD-EXE.bat
  2. 26 0
      build/README.md
  3. 207 0
      document/OnnxOCR速度优化参数.md
  4. 133 0
      document/中文OCR项目速度排名总结.md
  5. 154 0
      document/基于ChineseOCR的衍生项目.md
  6. 314 0
      main-js/func/adb-extract-chat-history.js
  7. 5 5
      main-js/func/extract-chat-history.js
  8. 66 0
      main-js/history.js
  9. 39 1
      package.json
  10. BIN
      py/extract-chat-history.py
  11. BIN
      py/img-reg.py
  12. BIN
      py/string-reg-location.py
  13. 5 5
      src/pages/Chat/Chat.jsx
  14. 2 2
      src/pages/Chat/Dialog/Dialog.jsx
  15. 2 2
      src/pages/Chat/Input/Input.jsx
  16. 2 2
      src/pages/Devices/Devices.jsx
  17. 158 0
      src/pages/Processing/Func/adb-extract-chat-history.js
  18. 4 3
      src/pages/Processing/Func/chat-history.js
  19. 1 1
      src/pages/Processing/Func/save-chat-history.js
  20. 1 1
      src/pages/Processing/Processing.js
  21. 2 2
      src/pages/Processing/Processing.jsx
  22. 46 42
      src/pages/Processing/action-parser.js
  23. 0 0
      src/pages/ScreenShot/InptEvent.js
  24. 2 2
      src/pages/ScreenShot/ScreenShot.js
  25. 4 4
      src/pages/ScreenShot/ScreenShot.jsx
  26. 0 0
      src/pages/ScreenShot/input-event.js
  27. 0 0
      src/pages/ScreenShot/scrcpy-config.js
  28. 0 0
      src/pages/ScreenShot/scrcpy-stream.js
  29. 0 0
      src/pages/ScreenShot/touch-event.js
  30. 3 3
      src/pages/home.jsx
  31. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-31-21.json
  32. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-52-19.json
  33. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-53-48.json
  34. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-59-33.json
  35. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-02-07.json
  36. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-12-55.json
  37. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-21-28.json
  38. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-23-06.json
  39. 0 5
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-32-04.json
  40. 34 0
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T22-23-31.json
  41. 54 0
      static/processing/微信聊天自动发送工作流/history/chat_2026-01-15T07-15-56.json
  42. 0 1
      static/processing/微信聊天自动发送工作流/history/summary.txt
  43. 41 25
      static/processing/微信聊天自动发送工作流/processing.json
  44. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_12-43-20/screenshot_ocr.png
  45. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-18-04/screenshot.png
  46. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-19-04/screenshot.png
  47. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-19-26/screenshot_ocr.png
  48. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-20-56/screenshot_ocr.png
  49. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-22-03/screenshot.png
  50. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-02-26/screenshot.png
  51. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-25-28/screenshot.png
  52. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-29-50/screenshot.png
  53. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-54-08/screenshot.png
  54. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-01-58/screenshot.png
  55. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-18-15/screenshot.png
  56. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-18-36/screenshot_ocr.png
  57. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-25-22/screenshot.png
  58. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-25-41/screenshot_ocr.png
  59. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-31-43/screenshot.png
  60. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-32-04/screenshot_ocr.png
  61. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-33-13/screenshot.png
  62. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-33-31/screenshot_ocr.png
  63. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-34-43/screenshot.png
  64. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-35-02/screenshot_ocr.png
  65. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_18-44-53/screenshot.png
  66. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_19-20-03/screenshot.png
  67. BIN
      static/processing/微信聊天自动发送工作流/tmp/2026-01-14_19-20-34/screenshot_ocr.png
  68. BIN
      temp_screenshot.png

+ 70 - 0
BAT-TOOL/1-BUILD-EXE.bat

@@ -0,0 +1,70 @@
+@echo off
+chcp 65001 >nul
+echo ========================================
+echo    AutoAndroidController 打包脚本
+echo ========================================
+echo.
+
+cd /d "%~dp0\.."
+
+echo [1/5] 检查 Node.js 环境...
+where node >nul 2>&1
+if %errorlevel% neq 0 (
+    echo [错误] 未找到 Node.js,请先安装 Node.js
+    pause
+    exit /b 1
+)
+node --version
+echo.
+
+echo [2/5] 检查并安装 electron-builder...
+call npm list electron-builder >nul 2>&1
+if %errorlevel% neq 0 (
+    echo 正在安装 electron-builder...
+    call npm install --save-dev electron-builder
+    if %errorlevel% neq 0 (
+        echo [错误] electron-builder 安装失败
+        pause
+        exit /b 1
+    )
+) else (
+    echo electron-builder 已安装
+)
+echo.
+
+echo [3/5] 安装项目依赖...
+call npm install
+if %errorlevel% neq 0 (
+    echo [错误] 依赖安装失败
+    pause
+    exit /b 1
+)
+echo.
+
+echo [4/5] 构建前端应用...
+call npm run build
+if %errorlevel% neq 0 (
+    echo [错误] 前端构建失败
+    pause
+    exit /b 1
+)
+echo.
+
+echo [5/5] 打包 Electron 应用为 exe...
+call npm run dist
+if %errorlevel% neq 0 (
+    echo [错误] 打包失败
+    pause
+    exit /b 1
+)
+echo.
+
+echo ========================================
+echo    打包完成!
+echo ========================================
+echo.
+echo 输出目录: dist-electron\
+echo.
+echo 安装程序位置: dist-electron\AutoAndroidController Setup *.exe
+echo.
+pause

+ 26 - 0
build/README.md

@@ -0,0 +1,26 @@
+# 图标文件说明
+
+请将应用图标文件放在此目录下:
+
+- **Windows 图标**: `icon.ico` (推荐尺寸: 256x256 或 512x512)
+- **macOS 图标**: `icon.icns` (可选)
+- **Linux 图标**: `icon.png` (可选)
+
+## 图标要求
+
+- **格式**: `.ico` 文件(Windows)
+- **尺寸**: 建议 256x256 或 512x512 像素
+- **文件名**: `icon.ico`
+
+## 如何创建图标
+
+1. 准备一张正方形图片(PNG 格式,透明背景)
+2. 使用在线工具转换为 ICO 格式:
+   - https://convertio.co/zh/png-ico/
+   - https://www.icoconverter.com/
+3. 将转换后的 `icon.ico` 文件放在此目录
+
+## 注意事项
+
+- 如果没有图标文件,打包时会使用默认图标
+- 图标文件大小建议控制在 1MB 以内

+ 207 - 0
document/OnnxOCR速度优化参数.md

@@ -0,0 +1,207 @@
+# OnnxOCR 速度优化参数说明
+
+> 简单的 OnnxOCR 速度优化参数说明,适用于微信聊天截图场景
+
+## 主要提速方法
+
+### 1. 禁用角度分类器(推荐)⭐⭐⭐⭐⭐
+
+**参数**: `use_angle_cls=False`
+
+**当前配置**: `use_angle_cls=True`
+
+**效果**: 提升 20-30% 速度
+
+**使用方法**:
+```python
+ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=False)
+```
+
+**说明**: 
+- 角度分类器用于检测文字方向(0度/180度)
+- 微信聊天截图都是正常方向,不需要角度分类
+- 禁用后可以跳过角度检测步骤,显著提速
+- 对准确率影响很小
+
+---
+
+### 2. 启用 GPU 加速(如果有 GPU)⭐⭐⭐⭐⭐
+
+**参数**: `use_gpu=True`
+
+**当前配置**: `use_gpu=False`
+
+**效果**: 提升 3-10 倍速度(如果有 GPU 且已安装 CUDA)
+
+**使用方法**:
+```python
+ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=True)
+```
+
+**注意**: 
+- 需要安装 CUDA 和 ONNX Runtime GPU 版本
+- 如果没有 GPU,`use_gpu=True` 会报错
+
+---
+
+### 3. 降低检测图片尺寸限制 ⭐⭐⭐⭐
+
+**参数**: `det_limit_side_len=640`(默认 960)
+
+**效果**: 提升 10-20% 速度
+
+**使用方法**:
+```python
+ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=False, det_limit_side_len=640)
+```
+
+**注意**: 
+- 降低尺寸可以加快检测速度
+- 但可能影响小文字的检测准确率
+- 建议根据实际图片尺寸调整(手机截图可以设置为 640 或 480)
+
+---
+
+### 4. 在调用时禁用角度分类 ⭐⭐⭐
+
+**方法**: 在 `ocr()` 方法调用时传递 `cls=False`
+
+**使用方法**:
+```python
+ocr = ONNXPaddleOcr(use_angle_cls=True, use_gpu=False)  # 初始化时仍为 True
+ocr_result = ocr.ocr(screenshot, cls=False)  # 调用时禁用角度分类
+```
+
+**效果**: 与禁用 `use_angle_cls` 类似,但可以在调用时动态控制
+
+---
+
+## 推荐配置
+
+### 方案 1: 最大速度(推荐)⭐⭐⭐⭐⭐
+
+**适用于**: 微信聊天截图等正常方向的图片
+
+```python
+ocr = ONNXPaddleOcr(
+    use_angle_cls=False,      # 禁用角度分类器
+    use_gpu=False,             # 如果有 GPU,改为 True
+    det_limit_side_len=640     # 降低检测尺寸(可选)
+)
+```
+
+**预期提升**: 30-50% 速度提升
+
+---
+
+### 方案 2: 仅禁用角度分类器 ⭐⭐⭐⭐
+
+**适用于**: 最简单的优化,几乎不影响准确率
+
+```python
+ocr = ONNXPaddleOcr(
+    use_angle_cls=False,       # 仅禁用角度分类器
+    use_gpu=False
+)
+```
+
+**预期提升**: 20-30% 速度提升
+
+---
+
+## 性能对比
+
+| 配置 | 速度提升 | 准确率影响 | 推荐场景 |
+|------|---------|-----------|---------|
+| `use_angle_cls=False` | +20-30% | -5% | 正常图片(推荐) |
+| `use_gpu=True` | +200-500% | 无 | 有 GPU 时 |
+| `det_limit_side_len=640` | +10-20% | -5-10% | 小图片 |
+| 组合方案 1 | +30-50% | -10-15% | 追求速度 |
+
+*注:实际性能提升因硬件和图片而异*
+
+---
+
+## 在当前项目中的应用
+
+### 修改位置: `main-js/func/extract-chat-history.js`
+
+**第 117 行和第 357 行**:
+
+```javascript
+// 当前代码
+ocr = ONNXPaddleOcr(use_angle_cls=True, use_gpu=False)
+
+// 优化后(推荐)
+ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=False)
+```
+
+---
+
+## 角度分类器说明
+
+### 什么是角度分类器?
+
+角度分类器用于检测文字方向:
+- **0度**:正常方向(从左到右、从上到下)
+- **180度**:倒置方向(需要旋转180度才能识别)
+
+### 为什么可以禁用?
+
+对于微信聊天截图:
+- ✅ 文字都是正常方向(不倒置)
+- ✅ 不需要检测角度
+- ✅ 禁用后可以跳过角度检测步骤,提升速度
+
+### 什么时候需要启用?
+
+当图片中可能有倒置文字时:
+- 扫描文档(可能倒置)
+- 拍照识别(可能旋转)
+- 批量处理各种方向的图片
+
+---
+
+## 注意事项
+
+1. **角度分类器**: 
+   - 禁用后,如果图片中有倒置的文字(180度旋转),可能无法识别
+   - 对于正常的手机截图(微信聊天),通常不需要角度分类
+
+2. **GPU 加速**: 
+   - 需要安装 CUDA 和 ONNX Runtime GPU 版本
+   - 如果没有 GPU,`use_gpu=True` 会报错
+
+3. **检测尺寸**: 
+   - `det_limit_side_len` 太小可能漏掉小文字
+   - 建议根据实际图片尺寸调整
+
+4. **准确率权衡**: 
+   - 速度优化通常会影响准确率
+   - 建议在实际场景中测试,找到最佳平衡点
+
+---
+
+## 总结
+
+对于微信聊天截图场景:
+
+1. **首选方案**: 禁用角度分类器
+   ```python
+   ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=False)
+   ```
+   原因:微信聊天截图通常都是正常方向,不需要角度分类
+
+2. **如果有 GPU**: 启用 GPU 加速
+   ```python
+   ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=True)
+   ```
+
+3. **如果图片较小**: 降低检测尺寸
+   ```python
+   ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=False, det_limit_side_len=640)
+   ```
+
+---
+
+*最后更新: 2026-01-14*

+ 133 - 0
document/中文OCR项目速度排名总结.md

@@ -0,0 +1,133 @@
+# GitHub 中文 OCR 项目识别速度排名总结
+
+> 注:实际识别速度受硬件配置、输入图像质量、模型大小等因素影响,以下排名基于模型大小、实现语言和优化程度综合评估。
+
+## 🚀 超高速(模型 < 10MB,支持优化推理引擎)
+
+### 1. **chineseocr_lite** ⭐⭐⭐⭐⭐
+- **GitHub**: https://github.com/DayBreak-u/chineseocr_lite
+- **模型大小**: 4.7MB(最小)
+- **特点**: 
+  - 支持 ncnn、mnn、tnn 等高性能推理引擎
+  - 支持竖排文字识别
+  - 超轻量级,速度最快
+- **适用场景**: 对速度要求极高的场景,移动端/嵌入式设备
+
+### 2. **ddddocr** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/86maid/ddddocr
+- **实现语言**: Rust
+- **特点**: 
+  - Rust 实现,性能优异
+  - 专注于验证码识别
+  - 轻量级设计
+- **适用场景**: 验证码识别、快速文字识别
+
+## ⚡ 高速(模型 10-50MB,高性能实现)
+
+### 3. **rust-paddle-ocr** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/zibo-chen/rust-paddle-ocr
+- **实现语言**: Rust
+- **特点**: 
+  - 支持 PaddleOCR v4/v5 模型
+  - 提供 Rust 库、C API 动态库和 CLI 工具
+  - 支持多语言(中文、英文、日文)
+- **适用场景**: 需要高性能的 Rust/C++ 项目集成
+
+### 4. **PaddleOCR-json** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/hiroi-sora/PaddleOCR-json
+- **实现语言**: C++
+- **特点**: 
+  - 基于 PaddleOCR 的命令行程序
+  - 输出 JSON 格式,易于集成
+  - C++ 实现,速度快
+- **适用场景**: 命令行工具、API 服务
+
+### 5. **PaddleOCRSharp** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/raoyutian/PaddleOCRSharp
+- **实现语言**: .NET
+- **模型大小**: 8.6MB(超轻量级)
+- **特点**: 
+  - .NET 本地类库,可离线使用
+  - 支持文本识别、检测、表格识别
+  - 针对小图识别优化
+- **适用场景**: .NET 应用集成
+
+## 🔥 中高速(模型 50-200MB,平衡速度与精度)
+
+### 6. **PaddleOCR** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/PaddlePaddle/PaddleOCR
+- **特点**: 
+  - 最流行的中文 OCR 项目
+  - 支持 80+ 种语言
+  - 提供多种模型尺寸(超轻量、轻量、标准)
+  - 准确率高,生态完善
+- **适用场景**: 通用 OCR 场景,需要高准确率
+
+### 7. **CnOCR** ⭐⭐⭐
+- **GitHub**: https://github.com/breezedeus/CnOCR
+- **实现语言**: PyTorch (Python)
+- **特点**: 
+  - 提供 20+ 个预训练模型
+  - 支持中文/英文识别
+  - 安装即用,易于上手
+- **适用场景**: Python 项目,需要快速部署
+
+## 📦 软件/工具类(基于上述引擎)
+
+### 8. **Umi-OCR** ⭐⭐⭐
+- **GitHub**: https://github.com/hiroi-sora/Umi-OCR
+- **特点**: 
+  - 基于 PaddleOCR 的开源离线 OCR 软件
+  - 支持截图、批量导入、PDF 识别
+  - 内置多国语言库
+  - 友好的用户界面
+- **适用场景**: 桌面应用、批量处理
+
+## 📊 速度对比参考(相对)
+
+| 项目 | 模型大小 | 实现语言 | 速度评级 | 推荐场景 |
+|------|---------|---------|---------|---------|
+| chineseocr_lite | 4.7MB | C++/ncnn | ⭐⭐⭐⭐⭐ | 极致速度需求 |
+| ddddocr | 小 | Rust | ⭐⭐⭐⭐ | 验证码识别 |
+| rust-paddle-ocr | 中等 | Rust | ⭐⭐⭐⭐ | Rust 项目 |
+| PaddleOCR-json | 中等 | C++ | ⭐⭐⭐⭐ | 命令行工具 |
+| PaddleOCRSharp | 8.6MB | .NET | ⭐⭐⭐⭐ | .NET 应用 |
+| PaddleOCR | 可变 | Python/C++ | ⭐⭐⭐ | 通用 OCR |
+| CnOCR | 可变 | Python | ⭐⭐⭐ | Python 快速部署 |
+| Umi-OCR | 中等 | Python | ⭐⭐⭐ | 桌面应用 |
+
+## 🎯 选择建议
+
+### 追求极致速度
+- **首选**: `chineseocr_lite`(模型最小,支持优化引擎)
+- **备选**: `ddddocr`(Rust 实现,高性能)
+
+### 需要高准确率
+- **首选**: `PaddleOCR`(生态完善,准确率高)
+- **备选**: `CnOCR`(易于使用)
+
+### 特定技术栈
+- **Rust 项目**: `rust-paddle-ocr`
+- **.NET 项目**: `PaddleOCRSharp`
+- **Python 项目**: `PaddleOCR` 或 `CnOCR`
+
+### 桌面应用
+- **首选**: `Umi-OCR`(界面友好,功能完整)
+
+## ⚠️ 注意事项
+
+1. **硬件影响**: CPU/GPU 性能会显著影响识别速度
+2. **模型选择**: 可以在准确率和速度之间权衡(PaddleOCR 提供多种模型)
+3. **批量处理**: 某些项目对批量处理有优化
+4. **实际测试**: 建议根据实际场景和硬件进行测试,选择最适合的方案
+
+## 📚 相关资源
+
+- PaddleOCR 官方文档: https://github.com/PaddlePaddle/PaddleOCR
+- chineseocr_lite 文档: https://github.com/DayBreak-u/chineseocr_lite
+- CnOCR 文档: https://github.com/breezedeus/CnOCR
+
+---
+
+*最后更新: 2026-01-14*
+*注: 排名基于公开信息和项目特点综合评估,实际性能请以实测为准*

+ 154 - 0
document/基于ChineseOCR的衍生项目.md

@@ -0,0 +1,154 @@
+# 基于 ChineseOCR 的衍生项目总结
+
+> ChineseOCR 是一个基于 YOLO V3 和 CRNN 的中文自然场景文字检测与识别开源项目。以下列出了基于 ChineseOCR 的主要衍生项目。
+
+## 🎯 核心衍生项目
+
+### 1. **chineseocr_lite** ⭐⭐⭐⭐⭐
+- **GitHub**: https://github.com/DayBreak-u/chineseocr_lite
+- **特点**: 
+  - 超轻量级版本(模型仅 4.7MB)
+  - 支持竖排文字识别
+  - 支持 ncnn、mnn、tnn 等高性能推理引擎
+  - 适合资源受限的设备部署
+- **改进方向**: 轻量化、速度优化
+- **适用场景**: 移动端、嵌入式设备、对速度要求极高的场景
+
+### 2. **trocr-chinese** ⭐⭐⭐⭐
+- **GitHub**: https://github.com/chineseocr/trocr-chinese
+- **特点**: 
+  - 基于微软 TrOCR(Transformer OCR)框架
+  - 专门针对中文场景优化
+  - 支持单行、多行、横竖排文字识别
+  - 支持不规则文字识别(如印章、公式等)
+- **改进方向**: 使用 Transformer 架构,提升准确率
+- **适用场景**: 需要高准确率的场景,不规则文字识别
+
+### 3. **darknet-ocr** ⭐⭐⭐
+- **GitHub**: https://github.com/chineseocr/darknet-ocr
+- **特点**: 
+  - 基于 Darknet 框架的文本检测
+  - 结合 CNN OCR 进行识别
+  - 轻量级实现
+- **改进方向**: 使用 Darknet 框架
+- **适用场景**: 需要轻量级文本检测的场景
+
+### 4. **table-ocr** ⭐⭐⭐
+- **GitHub**: https://github.com/chineseocr/table-ocr
+- **特点**: 
+  - 专门用于表格识别的 OCR
+  - 扩展了 ChineseOCR 的应用范围
+  - 支持表格结构识别
+- **改进方向**: 表格专用识别
+- **适用场景**: 表格识别、结构化数据提取
+
+## 🔌 插件/集成项目
+
+### 5. **chineseocr_umi_plugin** ⭐⭐⭐
+- **GitHub**: https://github.com/hiroi-sora/Umi-OCR_plugins
+- **特点**: 
+  - Umi-OCR 的插件
+  - 集成 ChineseOCR 的轻量级模型
+  - 支持中英文识别
+  - 可作为 Umi-OCR 的插件使用
+- **改进方向**: 插件化集成
+- **适用场景**: Umi-OCR 用户,需要中文识别插件
+
+## 📊 项目对比
+
+| 项目 | 模型大小 | 核心改进 | 速度 | 准确率 | 适用场景 |
+|------|---------|---------|------|--------|---------|
+| chineseocr_lite | 4.7MB | 轻量化、优化引擎 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 移动端、嵌入式 |
+| trocr-chinese | 中等 | Transformer 架构 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 高精度需求 |
+| darknet-ocr | 小 | Darknet 框架 | ⭐⭐⭐⭐ | ⭐⭐⭐ | 轻量级检测 |
+| table-ocr | 中等 | 表格专用 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 表格识别 |
+| chineseocr_umi_plugin | 小 | 插件集成 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Umi-OCR 集成 |
+
+## 🔍 项目特点分析
+
+### chineseocr_lite
+- **优势**: 
+  - 模型最小(4.7MB)
+  - 支持多种优化推理引擎(ncnn/mnn/tnn)
+  - 速度最快
+  - 支持竖排文字识别
+- **劣势**: 
+  - 准确率可能略低于标准版
+- **推荐场景**: 对速度要求极高、资源受限的场景
+
+### trocr-chinese
+- **优势**: 
+  - 使用 Transformer 架构,准确率高
+  - 支持不规则文字识别
+  - 专门针对中文优化
+- **劣势**: 
+  - 模型较大
+  - 速度相对较慢
+- **推荐场景**: 需要高准确率的场景,不规则文字识别
+
+### darknet-ocr
+- **优势**: 
+  - 基于 Darknet 框架,轻量级
+  - 文本检测效果好
+- **劣势**: 
+  - 功能相对单一
+- **推荐场景**: 只需要文本检测的场景
+
+### table-ocr
+- **优势**: 
+  - 专门针对表格优化
+  - 支持表格结构识别
+- **劣势**: 
+  - 仅适用于表格场景
+- **推荐场景**: 表格识别、结构化数据提取
+
+### chineseocr_umi_plugin
+- **优势**: 
+  - 插件化设计,易于集成
+  - 与 Umi-OCR 完美配合
+- **劣势**: 
+  - 依赖 Umi-OCR 平台
+- **推荐场景**: 使用 Umi-OCR 的用户
+
+## 💡 选择建议
+
+### 追求极致速度
+- **首选**: `chineseocr_lite`(模型最小,支持优化引擎)
+
+### 追求高准确率
+- **首选**: `trocr-chinese`(Transformer 架构,准确率最高)
+
+### 特定场景
+- **表格识别**: `table-ocr`
+- **文本检测**: `darknet-ocr`
+- **Umi-OCR 集成**: `chineseocr_umi_plugin`
+
+### 通用场景
+- 如果不需要特定优化,可以考虑使用原始的 **ChineseOCR** 项目
+
+## 📚 相关资源
+
+- ChineseOCR 原项目: https://github.com/chineseocr/chineseocr
+- chineseocr_lite: https://github.com/DayBreak-u/chineseocr_lite
+- trocr-chinese: https://github.com/chineseocr/trocr-chinese
+- Umi-OCR_plugins: https://github.com/hiroi-sora/Umi-OCR_plugins
+
+## 🎯 总结
+
+基于 ChineseOCR 的衍生项目主要分为几个方向:
+
+1. **轻量化方向**:chineseocr_lite(极致轻量)
+2. **高精度方向**:trocr-chinese(Transformer 架构)
+3. **特定场景方向**:table-ocr(表格)、darknet-ocr(检测)
+4. **集成方向**:chineseocr_umi_plugin(插件)
+
+选择哪个项目主要取决于:
+- 对速度的要求
+- 对准确率的要求
+- 具体的应用场景
+- 可用的计算资源
+
+---
+
+*最后更新: 2026-01-14*
+*注: 项目信息基于公开资料整理,实际性能请以实测为准*

+ 314 - 0
main-js/func/adb-extract-chat-history.js

@@ -0,0 +1,314 @@
+/**
+ * 使用 ADB XML 解析方式提取聊天记录
+ * 通过 uiautomator dump 获取 UI 层次结构 XML,然后解析提取文本
+ */
+
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import { join, dirname, isAbsolute } from 'path';
+import { fileURLToPath } from 'url';
+import { getCachedAdbPath } from '../config.js';
+
+const execAsync = promisify(exec);
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+/**
+ * 使用 ADB uiautomator dump 获取屏幕文本
+ * @param {string} ipPort - 设备 ID/IP:Port
+ * @param {string} method - 提取方法('full-screen' 或 'last-message')
+ * @param {string} folderPath - 工作流文件夹路径(可选,如果提供则保存 XML 到 tmp 目录)
+ * @returns {Promise<{success: boolean, text?: string, error?: string}>}
+ */
+export async function extractChatHistoryByAdbXml(ipPort, method = 'full-screen', folderPath = null) {
+  try {
+    if (!ipPort) {
+      return { success: false, error: '缺少设备 ID' };
+    }
+
+    const adbPath = getCachedAdbPath();
+
+    // 使用 uiautomator dump 获取 UI 层次结构 XML
+    // 先 dump 到设备文件,然后读取内容
+    let xmlContent = '';
+    
+    try {
+      // 执行 uiautomator dump 到设备文件
+      // 尝试多种方式获取 UI dump
+      console.log('[ADB XML] 尝试方法1: uiautomator dump 到文件');
+      const dumpCommand = `${adbPath} -s ${ipPort} shell uiautomator dump /sdcard/ui_dump.xml`;
+      const { stdout: dumpStdout, stderr: dumpStderr } = await execAsync(dumpCommand, {
+        timeout: 10000,
+        maxBuffer: 1024 * 1024,
+        encoding: 'utf8'
+      });
+      
+      // 检查 dump 是否成功(通常输出 "UI hierchary dumped to: /sdcard/ui_dump.xml")
+      console.log('[ADB XML] dump 命令输出:', dumpStdout);
+      if (dumpStderr) {
+        console.log('[ADB XML] dump 命令错误输出:', dumpStderr);
+      }
+      
+      // 等待一小段时间,确保文件写入完成
+      await new Promise(resolve => setTimeout(resolve, 500));
+      
+      // 读取 XML 文件内容
+      const catCommand = `${adbPath} -s ${ipPort} shell cat /sdcard/ui_dump.xml`;
+      const { stdout } = await execAsync(catCommand, {
+        timeout: 10000,
+        maxBuffer: 10 * 1024 * 1024,
+        encoding: 'utf8'
+      });
+      
+      console.log('[ADB XML] 读取到的原始内容长度:', stdout.length);
+      console.log('[ADB XML] 读取到的原始内容(前500字符):', stdout.substring(0, 500));
+      
+      // 清理可能的提示信息(uiautomator dump 可能会在 XML 前输出提示)
+      // 查找 XML 开始标记
+      const xmlStartIndex = stdout.indexOf('<?xml');
+      if (xmlStartIndex > 0) {
+        xmlContent = stdout.substring(xmlStartIndex);
+      } else if (stdout.includes('<hierarchy')) {
+        // 如果没有 XML 声明,查找 hierarchy 标签
+        const hierarchyStartIndex = stdout.indexOf('<hierarchy');
+        xmlContent = stdout.substring(hierarchyStartIndex);
+      } else {
+        xmlContent = stdout;
+      }
+      
+      // 清理设备上的临时文件
+      try {
+        await execAsync(`${adbPath} -s ${ipPort} shell rm /sdcard/ui_dump.xml`, {
+          timeout: 3000
+        });
+      } catch (rmError) {
+        // 忽略删除失败
+      }
+    } catch (error) {
+      console.error('[ADB XML] 获取 UI dump 失败:', error);
+      return { success: false, error: `获取 UI dump 失败: ${error.message}` };
+    }
+
+    if (!xmlContent || !xmlContent.trim()) {
+      console.error('[ADB XML] UI dump 内容为空');
+      return { success: false, error: 'UI dump 内容为空' };
+    }
+
+    // 调试:输出 XML 内容的前1000字符和总长度
+    console.log('[ADB XML] UI dump 内容长度:', xmlContent.length);
+    console.log('[ADB XML] UI dump 内容(前1000字符):', xmlContent.substring(0, 1000));
+    
+    // 检查是否包含 XML 结构
+    if (!xmlContent.includes('<hierarchy') && !xmlContent.includes('<?xml')) {
+      console.error('[ADB XML] UI dump 内容不包含有效的 XML 结构');
+      console.error('[ADB XML] 实际内容:', xmlContent.substring(0, 500));
+      return { success: false, error: 'UI dump 内容格式不正确,未找到 XML 结构' };
+    }
+    
+    // 检查 XML 是否为空(只有空的 hierarchy 节点)
+    const nodeCount = (xmlContent.match(/<node/g) || []).length;
+    console.log('[ADB XML] XML 中的 node 节点数量:', nodeCount);
+    
+    if (nodeCount <= 1) {
+      console.warn('[ADB XML] 警告:XML 中几乎没有节点,可能是微信使用了自定义视图,uiautomator dump 无法获取聊天内容');
+      console.warn('[ADB XML] 建议:微信聊天内容可能使用 Canvas 或自定义 View 绘制,uiautomator dump 无法捕获这些内容');
+      console.warn('[ADB XML] 解决方案:1. 检查是否需要开启辅助功能权限 2. 考虑使用 OCR 方式 3. 尝试其他 ADB 命令');
+      
+      // 尝试使用 dumpsys 获取窗口信息(作为备选方案)
+      try {
+        console.log('[ADB XML] 尝试方法2: dumpsys window 获取窗口信息');
+        const dumpsysCommand = `${adbPath} -s ${ipPort} shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'`;
+        const { stdout: dumpsysStdout } = await execAsync(dumpsysCommand, {
+          timeout: 5000,
+          maxBuffer: 1024 * 1024,
+          encoding: 'utf8'
+        });
+        console.log('[ADB XML] dumpsys window 输出:', dumpsysStdout);
+      } catch (dumpsysError) {
+        console.warn('[ADB XML] dumpsys 命令执行失败:', dumpsysError.message);
+      }
+    }
+
+    // 如果提供了 folderPath,保存 XML 到 tmp 目录
+    if (folderPath) {
+      try {
+        const { mkdir, writeFile } = await import('fs/promises');
+        
+        // 确保 folderPath 是绝对路径
+        let absoluteFolderPath = folderPath;
+        if (!isAbsolute(folderPath)) {
+          if (folderPath.startsWith('static/processing/')) {
+            const folderName = folderPath.replace('static/processing/', '');
+            absoluteFolderPath = join(__dirname, '..', '..', 'static', 'processing', folderName);
+          } else if (folderPath.startsWith('static\\processing\\')) {
+            const folderName = folderPath.replace('static\\processing\\', '');
+            absoluteFolderPath = join(__dirname, '..', '..', 'static', 'processing', folderName);
+          } else {
+            absoluteFolderPath = join(__dirname, '..', '..', 'static', 'processing', folderPath);
+          }
+        }
+        
+        // 创建 tmp 目录
+        const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19).replace('T', '_');
+        const tmpDir = join(absoluteFolderPath, 'tmp', timestamp);
+        await mkdir(tmpDir, { recursive: true });
+        
+        // 保存 XML 文件
+        const xmlPath = join(tmpDir, 'ui_dump.xml');
+        await writeFile(xmlPath, xmlContent, 'utf-8');
+        console.log(`[ADB XML] XML 已保存到: ${xmlPath}`);
+      } catch (saveError) {
+        console.warn('[ADB XML] 保存 XML 到 tmp 目录失败:', saveError);
+        // 保存失败不影响主流程,继续执行
+      }
+    }
+
+    // 解析 XML,提取所有 text 属性
+    const texts = extractTextsFromXml(xmlContent);
+
+    console.log('[ADB XML] 提取到的文本数量:', texts.length);
+    if (texts.length > 0) {
+      console.log('[ADB XML] 提取到的文本(前5条):', texts.slice(0, 5));
+    }
+
+    if (texts.length === 0) {
+      // 调试:尝试直接匹配所有 text 属性(不过滤)
+      const allTexts = extractTextsFromXml(xmlContent, true);
+      console.log('[ADB XML] 未过滤的文本数量:', allTexts.length);
+      if (allTexts.length > 0) {
+        console.log('[ADB XML] 未过滤的文本(前5条):', allTexts.slice(0, 5));
+      }
+      
+      // 如果 XML 中几乎没有节点,说明 uiautomator dump 无法获取微信的聊天内容
+      const nodeCount = (xmlContent.match(/<node/g) || []).length;
+      if (nodeCount <= 1) {
+        return { 
+          success: false, 
+          error: '微信聊天内容使用自定义视图(Canvas/自定义View),uiautomator dump 无法获取。建议:1. 检查是否需要开启辅助功能权限 2. 考虑使用 OCR 方式提取文本' 
+        };
+      }
+      
+      return { success: false, error: '未能从 UI dump 中提取到文本' };
+    }
+
+    // 根据 method 返回结果
+    if (method === 'last-message') {
+      // 返回最后一条消息(通常是最后出现的文本)
+      return {
+        success: true,
+        text: texts[texts.length - 1] || ''
+      };
+    } else {
+      // 返回所有文本(用换行符连接)
+      return {
+        success: true,
+        text: texts.join('\n')
+      };
+    }
+  } catch (error) {
+    console.error('ADB XML 解析失败:', error);
+    return { success: false, error: error.message || 'ADB XML 解析失败' };
+  }
+}
+
+/**
+ * 从 XML 内容中提取所有文本
+ * 使用正则表达式提取 text 属性(简单但有效)
+ * @param {string} xmlContent - XML 内容
+ * @param {boolean} skipFilter - 是否跳过过滤(用于调试)
+ * @returns {string[]} 提取的文本数组
+ */
+function extractTextsFromXml(xmlContent, skipFilter = false) {
+  const texts = [];
+  
+  // 方法1: 使用正则表达式匹配 text="..." 属性(支持转义字符)
+  const textRegex1 = /text\s*=\s*"((?:[^"\\]|\\.)*)"|text\s*=\s*'((?:[^'\\]|\\.)*)'/g;
+  let match;
+  
+  while ((match = textRegex1.exec(xmlContent)) !== null) {
+    let text = match[1] || match[2] || '';
+    
+    // 处理转义字符
+    if (text) {
+      text = text
+        .replace(/\\"/g, '"')
+        .replace(/\\'/g, "'")
+        .replace(/\\\\/g, '\\');
+    }
+    
+    if (text && text.trim()) {
+      const trimmedText = text.trim();
+      if (skipFilter || !isUIControlText(trimmedText)) {
+        texts.push(trimmedText);
+      }
+    }
+  }
+  
+  // 方法2: 如果方法1没有匹配到任何内容,尝试更简单的正则表达式
+  if (texts.length === 0) {
+    console.log('[ADB XML] 方法1未匹配到文本,尝试方法2(简单正则)');
+    const textRegex2 = /text="([^"]*)"|text='([^']*)'/g;
+    let match2;
+    
+    while ((match2 = textRegex2.exec(xmlContent)) !== null) {
+      const text = match2[1] || match2[2] || '';
+      if (text && text.trim()) {
+        const trimmedText = text.trim();
+        if (skipFilter || !isUIControlText(trimmedText)) {
+          texts.push(trimmedText);
+        }
+      }
+    }
+  }
+  
+  // 方法3: 如果仍然没有匹配到,尝试匹配 content-desc 属性(某些应用使用 content-desc 而不是 text)
+  if (texts.length === 0) {
+    console.log('[ADB XML] 方法2未匹配到文本,尝试方法3(content-desc)');
+    const contentDescRegex = /content-desc="([^"]*)"|content-desc='([^']*)'/g;
+    let match3;
+    
+    while ((match3 = contentDescRegex.exec(xmlContent)) !== null) {
+      const text = match3[1] || match3[2] || '';
+      if (text && text.trim()) {
+        const trimmedText = text.trim();
+        if (skipFilter || !isUIControlText(trimmedText)) {
+          texts.push(trimmedText);
+        }
+      }
+    }
+  }
+  
+  // 调试:输出匹配结果
+  if (texts.length > 0) {
+    console.log(`[ADB XML] 成功提取 ${texts.length} 条文本`);
+  } else {
+    // 输出 XML 片段,帮助调试
+    const sampleXml = xmlContent.substring(0, 2000);
+    console.log('[ADB XML] 未能匹配到任何文本,XML 片段:', sampleXml);
+  }
+  
+  return texts;
+}
+
+/**
+ * 判断是否为 UI 控件文本(按钮、标签等)
+ * 可以根据实际需求调整过滤规则
+ * @param {string} text - 文本内容
+ * @returns {boolean} 是否为 UI 控件文本
+ */
+function isUIControlText(text) {
+  // 暂时不过滤任何文本,保留所有文本
+  // 如果后续需要过滤,可以在这里添加逻辑
+  return false;
+  
+  // 以下是之前的过滤逻辑(已禁用)
+  // const uiControlKeywords = [
+  //   '发送', '返回', '确定', '取消', '搜索', '设置',
+  //   '更多', '分享', '复制', '删除', '编辑',
+  //   'SEND', 'BACK', 'OK', 'CANCEL', 'SEARCH', 'SETTINGS'
+  // ];
+  // if (text.length <= 3 && uiControlKeywords.includes(text)) {
+  //   return true;
+  // }
+  // return false;
+}

+ 5 - 5
main-js/func/extract-chat-history.js

@@ -114,10 +114,10 @@ def extract_chat_history(screenshot_path, friend_avatar_path, my_avatar_path, de
         avatar_positions = find_avatar_positions(screenshot_path, friend_avatar_path, my_avatar_path)
         
         # 获取 OCR 实例
-        ocr = ONNXPaddleOcr(use_angle_cls=True, use_gpu=False)
+        ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=True)
         
-        # 执行全屏 OCR
-        ocr_result = ocr.ocr(screenshot)
+        # 执行全屏 OCR(cls=False 避免角度分类器警告)
+        ocr_result = ocr.ocr(screenshot, cls=False)
         
         if not ocr_result or not ocr_result[0]:
             return {'success': False, 'error': 'OCR 识别失败'}
@@ -354,8 +354,8 @@ if __name__ == '__main__':
             print(json.dumps({'success': False, 'error': '无法读取截图文件'}, ensure_ascii=False))
             sys.exit(1)
         
-        ocr = ONNXPaddleOcr(use_angle_cls=True, use_gpu=False)
-        ocr_result = ocr.ocr(screenshot)
+        ocr = ONNXPaddleOcr(use_angle_cls=False, use_gpu=True)
+        ocr_result = ocr.ocr(screenshot, cls=False)
         
         if not ocr_result or not ocr_result[0]:
             print(json.dumps({'success': False, 'error': 'OCR 识别失败'}, ensure_ascii=False))

+ 66 - 0
main-js/history.js

@@ -601,6 +601,46 @@ export function registerIpcHandlers() {
     }
   });
 
+  // 获取最新的聊天记录文件
+  async function getLatestHistoryFile(absoluteWorkflowPath) {
+    try {
+      const historyDir = join(absoluteWorkflowPath, 'history');
+      const files = await readdir(historyDir, { withFileTypes: true });
+      
+      // 过滤出 JSON 文件(chat_*.json)
+      const jsonFiles = files
+        .filter(file => file.isFile() && file.name.startsWith('chat_') && file.name.endsWith('.json'))
+        .map(file => ({
+          name: file.name,
+          path: join(historyDir, file.name)
+        }));
+
+      if (jsonFiles.length === 0) {
+        return null;
+      }
+
+      // 获取所有文件的统计信息(修改时间)
+      const filesWithStats = await Promise.all(
+        jsonFiles.map(async (file) => {
+          const stats = await stat(file.path);
+          return {
+            ...file,
+            mtime: stats.mtime
+          };
+        })
+      );
+
+      // 按修改时间排序(最新的在前)
+      filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
+
+      // 返回最新的文件
+      return filesWithStats[0];
+    } catch (error) {
+      // 如果目录不存在或读取失败,返回 null
+      return null;
+    }
+  }
+
   // 保存聊天记录到 history 文件夹
   ipcMain.handle('save-chat-history', async (event, workflowFolderPath, historyData) => {
     try {
@@ -621,6 +661,32 @@ export function registerIpcHandlers() {
       const historyDir = join(absoluteWorkflowPath, 'history');
       await mkdir(historyDir, { recursive: true });
 
+      // 获取最新的历史文件
+      const latestFile = await getLatestHistoryFile(absoluteWorkflowPath);
+      
+      // 如果存在最新文件,对比内容
+      if (latestFile) {
+        try {
+          const latestContent = await readFile(latestFile.path, 'utf-8');
+          const latestData = JSON.parse(latestContent);
+          
+          // 对比消息数量:如果新的消息数量没有增加,不保存
+          if (latestData.messages && Array.isArray(latestData.messages)) {
+            const latestMessageCount = latestData.messages.length;
+            const newMessageCount = historyData.messages ? historyData.messages.length : 0;
+            
+            if (newMessageCount <= latestMessageCount) {
+              console.log(`聊天记录未增加(最新文件: ${latestMessageCount} 条,当前: ${newMessageCount} 条),跳过保存`);
+              return { success: true, skipped: true, reason: 'no_new_messages' };
+            }
+            
+            console.log(`聊天记录已增加(最新文件: ${latestMessageCount} 条,当前: ${newMessageCount} 条),将保存`);
+          }
+        } catch (compareError) {
+          console.warn('对比历史文件失败,继续保存:', compareError);
+        }
+      }
+
       // 生成文件名(使用时间戳)
       const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
       const fileName = `chat_${timestamp}.json`;

+ 39 - 1
package.json

@@ -10,7 +10,9 @@
     "preview": "vite preview",
     "electron": "electron .",
     "electron:dev": "concurrently -k --success first \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
-    "electron:build": "npm run build && electron ."
+    "electron:build": "npm run build && electron .",
+    "pack": "electron-builder --dir",
+    "dist": "npm run build && electron-builder"
   },
   "keywords": [],
   "author": "",
@@ -26,5 +28,41 @@
     "electron-devtools-installer": "^4.0.0",
     "vite": "^5.0.8",
     "wait-on": "^7.2.0"
+  },
+  "build": {
+    "appId": "com.autoadroidcontroller.app",
+    "productName": "AutoAndroidController",
+    "directories": {
+      "output": "dist-electron"
+    },
+    "files": [
+      "dist/**/*",
+      "main.js",
+      "main-js/**/*",
+      "preload.cjs",
+      "static/**/*",
+      "py/**/*",
+      "!py/venv/**/*",
+      "!py/__pycache__/**/*",
+      "!py/**/__pycache__/**/*",
+      "package.json"
+    ],
+    "win": {
+      "target": [
+        {
+          "target": "nsis",
+          "arch": [
+            "x64"
+          ]
+        }
+      ],
+      "icon": "build/icon.ico"
+    },
+    "nsis": {
+      "oneClick": false,
+      "allowToChangeInstallationDirectory": true,
+      "createDesktopShortcut": true,
+      "createStartMenuShortcut": true
+    }
   }
 }

BIN
py/extract-chat-history.py


BIN
py/img-reg.py


BIN
py/string-reg-location.py


+ 5 - 5
src/pages/Chat/Chat.jsx

@@ -1,9 +1,9 @@
-import './Chat.css';
+import './chat.css';
 import { useState } from 'react';
-import { ChatLogic } from './Chat.js';
-import Dialog from './Dialog/Dialog.jsx';
-import History from '../Processing/Processing.jsx';
-import Input from './Input/Input.jsx';
+import { ChatLogic } from './chat.js';
+import Dialog from './Dialog/dialog.jsx';
+import History from '../Processing/processing.jsx';
+import Input from './Input/input.jsx';
 
 function Chat() {
   const [messages, setMessages] = useState([]);

+ 2 - 2
src/pages/Chat/Dialog/Dialog.jsx

@@ -1,5 +1,5 @@
-import './Dialog.css';
-import { DialogLogic } from './Dialog.js';
+import './dialog.css';
+import { DialogLogic } from './dialog.js';
 import { useEffect, useRef } from 'react';
 
 function Dialog({ messages = [], isLoading = false }) {

+ 2 - 2
src/pages/Chat/Input/Input.jsx

@@ -1,5 +1,5 @@
-import './Input.css';
-import { useInput } from './Input.js';
+import './input.css';
+import { useInput } from './input.js';
 
 // SVG 图标组件
 const UploadIcon = () => (

+ 2 - 2
src/pages/Devices/Devices.jsx

@@ -1,5 +1,5 @@
-import './Devices.css';
-import { DevicesLogic } from './Devices.js';
+import './devices.css';
+import { DevicesLogic } from './devices.js';
 
 // Devices 组件:显示设备列表和连接/断开功能
 function Devices() {

+ 158 - 0
src/pages/Processing/Func/adb-extract-chat-history.js

@@ -0,0 +1,158 @@
+/**
+ * Func 标签:adb-extract-chat-history
+ * 
+ * 使用 ADB uiautomator dump 获取 UI 层次结构 XML,然后解析 XML 提取聊天记录文本
+ * 相比 OCR 方式,这种方法更快、更准确,且无需 OCR 库
+ * 
+ * 注意:由于 ADB 命令需要在主进程中执行,此文件提供渲染进程接口
+ * 实际实现逻辑需要在主进程中添加(main-js/history.js 和 main-js/func/adb-extract-chat-history.js)
+ */
+
+export const tagName = 'adb-extract-chat-history';
+
+export const schema = {
+  description: '使用 ADB XML 解析方式提取屏幕上的聊天记录文本(替代 OCR 方式)',
+  inputs: {
+    device: '设备 ID/IP:Port',
+    method: '提取方法(full-screen:全屏提取,last-message:仅最后一条消息)',
+    variable: '输出变量名(保存提取的文本)',
+  },
+  outputs: {
+    variable: '提取的聊天文本(字符串)',
+  },
+};
+
+/**
+ * 使用 ADB XML 解析方式获取屏幕文本
+ * 
+ * 实现原理:
+ * 1. 使用 `adb shell uiautomator dump` 获取当前屏幕的 UI 层次结构 XML
+ * 2. 解析 XML,提取所有节点的 text 属性
+ * 3. 过滤并合并文本内容
+ * 4. 返回提取的文本
+ * 
+ * 优势:
+ * - 速度更快(无需 OCR 识别,通常 < 1 秒 vs OCR 2-5 秒)
+ * - 准确度更高(直接获取 UI 文本属性,无识别错误)
+ * - 无需 OCR 库(纯 Node.js 实现)
+ * - 资源消耗更低(无需截图和 OCR 计算)
+ * 
+ * 挑战:
+ * - 微信 UI 结构可能比较复杂,需要找到正确的节点
+ * - 可能需要过滤 UI 控件文本(按钮、标签等)
+ * - 不同版本微信的 UI 结构可能不同
+ * 
+ * @param {Object} params - 参数对象
+ * @param {string} params.device - 设备 ID/IP:Port
+ * @param {string} params.method - 提取方法('full-screen' 或 'last-message')
+ * @returns {Promise<{success: boolean, text?: string, error?: string}>}
+ */
+export async function executeAdbExtractChatHistory({ device, method = 'full-screen' }) {
+  try {
+    // 检查 electronAPI 是否可用
+    if (!window.electronAPI) {
+      return { 
+        success: false, 
+        error: 'electronAPI 不可用' 
+      };
+    }
+
+    // 注意:需要在主进程中添加 'adb-extract-chat-history' IPC handler
+    // 实际实现应该在 main-js/func/adb-extract-chat-history.js 中
+    // 这里提供接口调用示例
+    
+    // 如果主进程已经实现了 IPC handler,可以这样调用:
+    // const result = await window.electronAPI.adbExtractChatHistory(device, method);
+    
+    // 目前返回提示信息,需要实现主进程逻辑
+    return {
+      success: false,
+      error: 'ADB XML 解析功能需要在主进程中实现。请实现 main-js/func/adb-extract-chat-history.js 和 main-js/history.js 中的相关代码。'
+    };
+  } catch (error) {
+    console.error('执行 adb-extract-chat-history 失败:', error);
+    return { 
+      success: false, 
+      error: error.message || 'ADB XML 解析失败' 
+    };
+  }
+}
+
+/**
+ * 实现方案说明(需要在主进程中实现):
+ * 
+ * 1. 在 main-js/func/adb-extract-chat-history.js 中实现核心逻辑:
+ *    - 使用 `adb shell uiautomator dump /sdcard/ui_dump.xml` 获取 UI XML
+ *    - 使用 `adb shell cat /sdcard/ui_dump.xml` 读取 XML 内容(或直接输出到 stdout)
+ *    - 解析 XML,提取所有节点的 text 属性
+ *    - 过滤空文本和 UI 控件文本
+ *    - 返回提取的文本
+ * 
+ * 2. 在 main-js/history.js 中添加 IPC handler:
+ *    - 添加 `adbExtractChatHistory` 函数
+ *    - 在 `registerIpcHandlers` 中注册 'adb-extract-chat-history' handler
+ * 
+ * 3. 在 preload.cjs 中添加 IPC 接口:
+ *    - 添加 `adbExtractChatHistory: (ipPort, method) => ipcRenderer.invoke('adb-extract-chat-history', ipPort, method)`
+ * 
+ * 4. XML 解析可以使用:
+ *    - Node.js 内置方式(正则表达式,适用于简单 XML)
+ *    - 或者安装 XML 解析库(如 fast-xml-parser、xmldom)
+ * 
+ * 5. 示例实现代码(main-js/func/adb-extract-chat-history.js):
+ * 
+ *    import { exec } from 'child_process';
+ *    import { promisify } from 'util';
+ *    import { getCachedAdbPath } from '../config.js';
+ *    
+ *    const execAsync = promisify(exec);
+ *    
+ *    export async function extractChatHistoryByAdbXml(ipPort, method = 'full-screen') {
+ *      try {
+ *        const adbPath = getCachedAdbPath();
+ *        
+ *        // 1. 获取 UI dump(可以直接输出到 stdout,避免 pull)
+ *        const dumpCommand = `${adbPath} -s ${ipPort} shell uiautomator dump /dev/tty`;
+ *        // 或者:${adbPath} -s ${ipPort} shell uiautomator dump /sdcard/ui_dump.xml
+ *        // 然后:${adbPath} -s ${ipPort} shell cat /sdcard/ui_dump.xml
+ *        
+ *        const { stdout } = await execAsync(dumpCommand, {
+ *          timeout: 10000,
+ *          maxBuffer: 10 * 1024 * 1024,
+ *          encoding: 'utf8'
+ *        });
+ *        
+ *        // 2. 解析 XML(使用正则表达式提取 text 属性,或使用 XML 解析库)
+ *        const texts = [];
+ *        const textRegex = /text="([^"]*)"/g;
+ *        let match;
+ *        while ((match = textRegex.exec(stdout)) !== null) {
+ *          const text = match[1];
+ *          if (text && text.trim() && !isUIControlText(text)) {
+ *            texts.push(text.trim());
+ *          }
+ *        }
+ *        
+ *        // 3. 根据 method 返回结果
+ *        if (method === 'last-message') {
+ *          return {
+ *            success: true,
+ *            text: texts[texts.length - 1] || ''
+ *          };
+ *        } else {
+ *          return {
+ *            success: true,
+ *            text: texts.join('\n')
+ *          };
+ *        }
+ *      } catch (error) {
+ *        return { success: false, error: error.message };
+ *      }
+ *    }
+ *    
+ *    function isUIControlText(text) {
+ *      // 过滤 UI 控件文本(按钮、标签等)
+ *      const uiControlKeywords = ['发送', '返回', '确定', '取消', '搜索', '设置'];
+ *      return uiControlKeywords.some(keyword => text === keyword);
+ *    }
+ */

+ 4 - 3
src/pages/Processing/Func/ChatHistory.js → src/pages/Processing/Func/chat-history.js

@@ -146,10 +146,11 @@ function parseChatHistoryText(chatHistoryText) {
   const lines = chatHistoryText.split('\n').filter(line => line.trim());
 
   for (const line of lines) {
-    // 匹配格式:好友: xxx 或 我: xxx
-    const match = line.match(/^(好友|我):\s*(.+)$/);
+    // 匹配格式:对方: xxx、好友: xxx 或 我: xxx
+    const match = line.match(/^(对方|好友|我):\s*(.+)$/);
     if (match) {
-      const sender = match[1] === '好友' ? 'friend' : 'me';
+      const senderLabel = match[1];
+      const sender = (senderLabel === '对方' || senderLabel === '好友') ? 'friend' : 'me';
       const text = match[2].trim();
       messages.push({ sender, text });
     }

+ 1 - 1
src/pages/Processing/Func/save-chat-history.js

@@ -5,7 +5,7 @@
  * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
  *
  * 语义:把提取到的消息记录持久化保存到工作流目录 history 下。
- * 当前项目里对应能力主要由 ChatHistory.js + IPC(save-chat-history)实现承载。
+ * 当前项目里对应能力主要由 chat-history.js + IPC(save-chat-history)实现承载。
  */
 
 export const tagName = 'save-chat-history';

+ 1 - 1
src/pages/Processing/Processing.js

@@ -1,5 +1,5 @@
 import { useState, useEffect, useRef } from 'react';
-import { parseWorkflow, parseActions, executeActionSequence } from './ActionParser.js';
+import { parseWorkflow, parseActions, executeActionSequence } from './action-parser.js';
 
 export function useHistory() {
   const [folders, setFolders] = useState([]);

+ 2 - 2
src/pages/Processing/Processing.jsx

@@ -1,5 +1,5 @@
-import './Processing.css';
-import { useHistory } from './Processing.js';
+import './processing.css';
+import { useHistory } from './processing.js';
 
 // SVG 图标组件
 const LoopIcon = ({ active = false }) => (

+ 46 - 42
src/pages/Processing/ActionParser.js → src/pages/Processing/action-parser.js

@@ -6,8 +6,11 @@ const DEFAULT_SCROLL_DISTANCE = 100; // 默认每次滚动距离(像素)
 // 变量上下文(用于存储变量值)
 let variableContext = {};
 
+// 全局步骤计数器(用于连续的步骤编号)
+let globalStepCounter = 0;
+
 // 导入聊天历史记录管理模块
-import { saveChatHistory, generateHistorySummary, getHistorySummary } from './Func/ChatHistory.js';
+import { saveChatHistory, generateHistorySummary, getHistorySummary } from './Func/chat-history.js';
 // 导入 extract-chat-history 执行函数
 import { executeExtractChatHistory } from './Func/extract-chat-history.js';
 
@@ -98,35 +101,8 @@ function parseDelayString(delayStr) {
  * @returns {number} 需要等待的毫秒数
  */
 function calculateWaitTime(data, delay) {
-  let targetTime = null;
-  
-  // 如果有 data 时间,解析它
-  if (data && data.trim() !== '') {
-    targetTime = parseTimeString(data);
-    if (!targetTime) {
-      console.warn(`无法解析时间字符串: ${data},将立即执行`);
-      targetTime = new Date(); // 解析失败则立即执行
-    }
-  } else {
-    // 没有 data 时间,使用当前时间
-    targetTime = new Date();
-  }
-  
-  // 解析 delay
-  const delayMs = parseDelayString(delay);
-  if (delayMs === null) {
-    console.warn(`无法解析延迟字符串: ${delay},将不延迟`);
-  } else {
-    // 在目标时间基础上加上延迟
-    targetTime = new Date(targetTime.getTime() + delayMs);
-  }
-  
-  // 计算需要等待的时间
-  const now = new Date();
-  const waitTime = targetTime.getTime() - now.getTime();
-  
-  // 如果目标时间已过,返回0(立即执行)
-  return Math.max(0, waitTime);
+  // 始终返回 0,不等待,立即执行
+  return 0;
 }
 
 /**
@@ -182,7 +158,9 @@ function evaluateCondition(condition, context = variableContext) {
     
     // 手动解析简单的条件表达式(不使用eval)
     // 支持: ==, !=, >, <, >=, <=, &&, ||
-    return parseConditionExpression(expr);
+    const result = parseConditionExpression(expr);
+    console.log(`条件评估: "${condition}" -> "${expr}" = ${result}`);
+    return result;
   } catch (error) {
     console.error('条件评估失败:', error, condition);
     return false;
@@ -374,8 +352,11 @@ function parseNewFormatAction(action) {
       parsed.avatar = resolveValue(action.avatar);
       break;
     case 'extract-messages':
+    case 'extract-chat-history':
       parsed.avatar1 = action.avatar1;
       parsed.avatar2 = action.avatar2;
+      parsed.lastMessageVariable = action.lastMessageVariable;
+      parsed.senderVariable = action.senderVariable;
       // 向后兼容:支持旧的 friendAvatar 和 myAvatar
       if (action.friendAvatar && !parsed.avatar1) parsed.avatar1 = action.friendAvatar;
       if (action.myAvatar && !parsed.avatar2) parsed.avatar2 = action.myAvatar;
@@ -928,10 +909,24 @@ export async function executeAction(action, device, folderPath, resolution) {
           console.log(`消息记录已保存到变量 ${action.variable},共 ${messageCount} 条消息`);
         }
 
+        // 如果提供了 lastMessageVariable,保存最后一条消息的文本
+        if (action.lastMessageVariable && chatResult.messages && chatResult.messages.length > 0) {
+          const lastMessage = chatResult.messages[chatResult.messages.length - 1];
+          variableContext[action.lastMessageVariable] = lastMessage.text || '';
+        }
+
+        // 如果提供了 senderVariable,保存最后一条消息的发送者
+        if (action.senderVariable && chatResult.messages && chatResult.messages.length > 0) {
+          const lastMessage = chatResult.messages[chatResult.messages.length - 1];
+          variableContext[action.senderVariable] = lastMessage.sender || 'unknown';
+          console.log(`最后一条消息发送者已保存到变量 ${action.senderVariable}: ${lastMessage.sender}`);
+        }
+
         return { 
           success: true, 
           messages: chatResult.messages || [],
-          messagesText: chatResult.messagesText || ''
+          messagesText: chatResult.messagesText || '',
+          lastMessage: chatResult.messages && chatResult.messages.length > 0 ? chatResult.messages[chatResult.messages.length - 1] : null
         };
       }
 
@@ -1209,6 +1204,11 @@ export async function executeActionSequence(
   shouldStop = null,
   depth = 0
 ) {
+  // 如果是顶层(depth === 0),重置全局步骤计数器
+  if (depth === 0) {
+    globalStepCounter = 0;
+  }
+  
   let completedSteps = 0;
   const stepPrefix = depth > 0 ? '  '.repeat(depth) : '';
 
@@ -1357,28 +1357,32 @@ export async function executeActionSequence(
         onStepComplete(i + 1, actions.length, stepName, 0, times, t + 1);
       }
 
+      // 使用全局步骤计数器
+      globalStepCounter++;
+      const currentStepNumber = globalStepCounter;
+      
+      // 记录步骤开始时间
+      const stepStartTime = Date.now();
+      
+      // 打印步骤开始执行
       if (times > 1) {
-        console.log(`${stepPrefix}执行步骤 ${i + 1}/${actions.length} (第 ${t + 1}/${times} 次): ${action.type}`);
+        console.log(`${stepPrefix}步骤 ${currentStepNumber} 开始执行: ${action.type} (第 ${t + 1}/${times} 次)`);
       } else {
-        console.log(`${stepPrefix}执行步骤 ${i + 1}/${actions.length}: ${action.type}`);
+        console.log(`${stepPrefix}步骤 ${currentStepNumber} 开始执行: ${action.type}`);
       }
 
-      // 记录步骤开始时间
-      const stepStartTime = Date.now();
-      const stepStartTimeStr = new Date(stepStartTime).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 });
-      console.log(`${stepPrefix}[${stepStartTimeStr}] 步骤 ${i + 1} 开始执行: ${action.type}`);
-
       // 执行操作
       const result = await executeAction(action, device, folderPath, resolution);
 
       // 记录步骤结束时间
       const stepEndTime = Date.now();
-      const stepEndTimeStr = new Date(stepEndTime).toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 });
       const stepDuration = stepEndTime - stepStartTime;
-      console.log(`${stepPrefix}[${stepEndTimeStr}] 步骤 ${i + 1} 执行完成: ${action.type}, 耗时: ${stepDuration}ms (${(stepDuration / 1000).toFixed(2)}秒)`);
+      
+      // 打印步骤执行完成
+      console.log(`${stepPrefix}步骤 ${currentStepNumber} 执行完成: ${action.type}, 耗时: ${stepDuration}ms (${(stepDuration / 1000).toFixed(2)}秒)`);
 
       if (!result.success) {
-        console.error(`${stepPrefix}步骤 ${i + 1} 执行失败:`, result.error);
+        console.error(`${stepPrefix}步骤 ${currentStepNumber} 执行失败:`, result.error);
         return { success: false, error: result.error, completedSteps: i };
       }
 

+ 0 - 0
src/pages/ScreenShot/InptEvent.js


+ 2 - 2
src/pages/ScreenShot/ScreenShot.js

@@ -1,5 +1,5 @@
-import { useEffect, useRef, useState, useCallback } from 'react';
-import ScrcpyConfig from './ScrcpyConfig.js';
+import { useEffect, useRef, useState, useCallback } from 'react';
+import ScrcpyConfig from './scrcpy-config.js';
 
 // 截屏逻辑:监听设备预览事件,轮询 adb 截屏并展示
 export function ScreenShotLogic() {

+ 4 - 4
src/pages/ScreenShot/ScreenShot.jsx

@@ -1,7 +1,7 @@
-import './ScreenShot.css';
-import { ScreenShotLogic, useActionStepDisplay } from './ScreenShot.js';
-import { useTouchEvents } from './TouchEvent.js';
-import { useInputEvents } from './InputEvent.js';
+import './screenshot.css';
+import { ScreenShotLogic, useActionStepDisplay } from './screenshot.js';
+import { useTouchEvents } from './touch-event.js';
+import { useInputEvents } from './input-event.js';
 import { useRef } from 'react';
 
 function ScreenShot() {

+ 0 - 0
src/pages/ScreenShot/InputEvent.js → src/pages/ScreenShot/input-event.js


+ 0 - 0
src/pages/ScreenShot/ScrcpyConfig.js → src/pages/ScreenShot/scrcpy-config.js


+ 0 - 0
src/pages/ScreenShot/ScrcpyStream.js → src/pages/ScreenShot/scrcpy-stream.js


+ 0 - 0
src/pages/ScreenShot/TouchEvent.js → src/pages/ScreenShot/touch-event.js


+ 3 - 3
src/pages/home.jsx

@@ -1,8 +1,8 @@
 import './home.css';
 import { HomeLogic } from './home.js';
-import Devices from './Devices/Devices.jsx';
-import ScreenShot from './ScreenShot/ScreenShot.jsx';
-import Chat from './Chat/Chat.jsx';
+import Devices from './Devices/devices.jsx';
+import ScreenShot from './ScreenShot/screenshot.jsx';
+import Chat from './Chat/chat.jsx';
 
 function Home() {
   const { showDevices, 

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-31-21.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T19:31:21.167Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-52-19.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T19:52:19.692Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-53-48.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T19:53:48.692Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T19-59-33.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T19:59:33.371Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-02-07.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T20:02:07.382Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-12-55.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T20:12:55.593Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-21-28.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T20:21:28.411Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-23-06.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T20:23:06.849Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 0 - 5
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T20-32-04.json

@@ -1,5 +0,0 @@
-{
-  "timestamp": "2026-01-14T20:32:04.322Z",
-  "messageCount": 0,
-  "messages": []
-}

+ 34 - 0
static/processing/微信聊天自动发送工作流/history/chat_2026-01-14T22-23-31.json

@@ -0,0 +1,34 @@
+{
+  "timestamp": "2026-01-14T22:23:31.772Z",
+  "messageCount": 7,
+  "messages": [
+    {
+      "sender": "me",
+      "text": "你好"
+    },
+    {
+      "sender": "friend",
+      "text": "你好"
+    },
+    {
+      "sender": "me",
+      "text": "在干嘛"
+    },
+    {
+      "sender": "friend",
+      "text": "在吃饭"
+    },
+    {
+      "sender": "friend",
+      "text": "你吃了吗"
+    },
+    {
+      "sender": "me",
+      "text": "刚回家准备吃"
+    },
+    {
+      "sender": "friend",
+      "text": "准备吃啥"
+    }
+  ]
+}

+ 54 - 0
static/processing/微信聊天自动发送工作流/history/chat_2026-01-15T07-15-56.json

@@ -0,0 +1,54 @@
+{
+  "timestamp": "2026-01-15T07:15:56.486Z",
+  "messageCount": 12,
+  "messages": [
+    {
+      "sender": "friend",
+      "text": "C"
+    },
+    {
+      "sender": "friend",
+      "text": "¥15.00"
+    },
+    {
+      "sender": "me",
+      "text": "你好"
+    },
+    {
+      "sender": "friend",
+      "text": "你好"
+    },
+    {
+      "sender": "me",
+      "text": "在干嘛"
+    },
+    {
+      "sender": "me",
+      "text": "你好"
+    },
+    {
+      "sender": "friend",
+      "text": "你好"
+    },
+    {
+      "sender": "me",
+      "text": "在干嘛"
+    },
+    {
+      "sender": "friend",
+      "text": "在吃饭"
+    },
+    {
+      "sender": "friend",
+      "text": "你吃了吗"
+    },
+    {
+      "sender": "me",
+      "text": "刚回家准备吃"
+    },
+    {
+      "sender": "friend",
+      "text": "准备吃啥"
+    }
+  ]
+}

+ 0 - 1
static/processing/微信聊天自动发送工作流/history/summary.txt

@@ -1 +0,0 @@
-以日常问候与近况更新为主,互问午饭、在家与否、是否在想对方;氛围轻松随意,带有调侃与亲密感。

+ 41 - 25
static/processing/微信聊天自动发送工作流/processing.json

@@ -5,8 +5,10 @@
 	"variables": {
 		"lastMessage": "",
 		"currentMessage": "",
+		"lastSender": "",
 		"reply": "",
-		"sendPos": ""
+		"sendPos": "",
+		"chatHistory": ""
 	},
 	"actions": [
 		{
@@ -14,9 +16,12 @@
 			"condition": "true",
 			"body": [
 				{
-					"type": "ocr",
-					"method": "full-screen",
-					"variable": "currentMessage",
+					"type": "extract-chat-history",
+					"avatar1": "好友头像.png",
+					"avatar2": "我的头像.png",
+					"variable": "chatHistory",
+					"lastMessageVariable": "currentMessage",
+					"senderVariable": "lastSender",
 					"delay": "1s"
 				},
 				{
@@ -24,36 +29,47 @@
 					"condition": "{currentMessage} != {lastMessage}",
 					"then": [
 						{
-							"type": "ai-generate",
-							"prompt": "你是一个刚认识女方不久的男生,正在和女方通过微信聊天。你的目标是:通过自然、有趣的对话让女方对你产生好感,建立良好的第一印象。\n\n女方最新发来的消息:\"{currentMessage}\"\n\n请根据以上信息,代入我的角色,生成一条自然、有趣的回复。要求:\n1. 完全代入刚认识女方不久的男生角色,用轻松、有趣、略带暧昧的口吻回复;\n2. 回复要自然流畅,像真实聊天一样,不要过于刻意或油腻;\n3. 可以适当展现幽默感、关心或好奇心,让对话更有趣;\n4. 如果识别的内容不清楚或包含无关文字,请根据核心意思理解并回复;\n5. 回复长度控制在20字以内;\n6. 目标是让女方对你产生好感,但不要过于直接或冒进。\n\n请严格按照以下JSON格式返回,不要包含任何其他文字:\n{\n  \"reply\": \"你的回复内容\"\n}",
-							"variable": "reply",
-							"delay": "5s"
-						},
-						{
-							"type": "input",
-							"target": "输入框",
-							"value": "{reply}",
+							"type": "save-chat-history",
+							"variable": "chatHistory",
 							"delay": "0.5s"
 						},
 						{
 							"type": "if",
-							"condition": "{sendPos} == \"\" || {sendPos} == undefined",
+							"condition": "{lastSender} == \"friend\"",
 							"then": [
 								{
-									"type": "locate",
-									"method": "image",
-									"target": "微信聊天界面的发送按钮定位图.png",
-									"variable": "sendPos",
+									"type": "ai-generate",
+									"prompt": "你是一个刚认识女方不久的男生,正在和女方通过微信聊天。你的目标是:通过自然、有趣的对话让女方对你产生好感,建立良好的第一印象。\n\n女方最新发来的消息:\"{currentMessage}\"\n\n请根据以上信息,代入我的角色,生成一条自然、有趣的回复。要求:\n1. 完全代入刚认识女方不久的男生角色,用轻松、有趣、略带暧昧的口吻回复;\n2. 回复要自然流畅,像真实聊天一样,不要过于刻意或油腻;\n3. 可以适当展现幽默感、关心或好奇心,让对话更有趣;\n4. 如果识别的内容不清楚或包含无关文字,请根据核心意思理解并回复;\n5. 回复长度控制在20字以内;\n6. 目标是让女方对你产生好感,但不要过于直接或冒进。\n\n请严格按照以下JSON格式返回,不要包含任何其他文字:\n{\n  \"reply\": \"你的回复内容\"\n}",
+									"variable": "reply",
+									"delay": "5s"
+								},
+								{
+									"type": "input",
+									"target": "输入框",
+									"value": "{reply}",
+									"delay": "0.5s"
+								},
+								{
+									"type": "if",
+									"condition": "{sendPos} == \"\" || {sendPos} == undefined",
+									"then": [
+										{
+											"type": "locate",
+											"method": "image",
+											"target": "微信聊天界面的发送按钮定位图.png",
+											"variable": "sendPos",
+											"delay": "0.5s"
+										}
+									]
+								},
+								{
+									"type": "click",
+									"method": "position",
+									"target": "{sendPos}",
 									"delay": "0.5s"
 								}
 							]
 						},
-						{
-							"type": "click",
-							"method": "position",
-							"target": "{sendPos}",
-							"delay": "0.5s"
-						},
 						{
 							"type": "set",
 							"variable": "lastMessage",
@@ -69,4 +85,4 @@
 			]
 		}
 	]
-}
+}

BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_12-43-20/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-18-04/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-19-04/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-19-26/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-20-56/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_13-22-03/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-02-26/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-25-28/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-29-50/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_16-54-08/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-01-58/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-18-15/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-18-36/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-25-22/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-25-41/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-31-43/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-32-04/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-33-13/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-33-31/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-34-43/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_17-35-02/screenshot_ocr.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_18-44-53/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_19-20-03/screenshot.png


BIN
static/processing/微信聊天自动发送工作流/tmp/2026-01-14_19-20-34/screenshot_ocr.png


BIN
temp_screenshot.png