فهرست منبع

修改项目结构大小写

yichael 5 ماه پیش
والد
کامیت
e0b4f405fe

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

@@ -1,207 +0,0 @@
-# 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*

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

@@ -1,133 +0,0 @@
-# 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*
-*注: 排名基于公开信息和项目特点综合评估,实际性能请以实测为准*

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

@@ -1,154 +0,0 @@
-# 基于 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*
-*注: 项目信息基于公开资料整理,实际性能请以实测为准*

+ 53 - 4
document/工作流语言使用说明.md

@@ -18,7 +18,7 @@
 
 - **基础语法(控制流 / 定时)**:`scheduleOnce`、`schedule`、`for`、`while`、`if`
 - **基础 action(模拟手机操作)**:`press`、`swipe`、`scroll`
-- **扩展标签(Func)**:除基础能力外的所有扩展功能,全部由 `src/pages/Processing/Func/` 下的脚本文件决定;**每个脚本文件名就是一个标签名**。工作流能用哪些扩展标签,以该目录为准。
+- **扩展标签(Func)**:除基础能力外的所有扩展功能,全部由 `src/pages/processing/func/` 下的脚本文件决定;**每个脚本文件名就是一个标签名**。工作流能用哪些扩展标签,以该目录为准。
 
 ## 触发条件(Triggers:scheduleOnce / schedule)
 
@@ -89,7 +89,56 @@
 | `swipe` | 滑动 | `{"type":"swipe","value":"down-up"}` |
 | `scroll` | 滚动(小幅度) | `{"type":"scroll","value":"down-up"}` |
 
-> 说明:项目内部为了实现完整自动化,还存在一些内置能力(如 `locate/click/input/ocr/ai-generate/delay/set` 等)。但从“语言设计”角度,**低层模拟手机操作统一归为 action(press/swipe/scroll)**,其余更高阶能力推荐以 Func 标签方式抽象出来(见下文)。
+> 说明:项目内部为了实现完整自动化,还存在一些内置能力(如 `locate/click/input/ocr/ai-generate/delay/set/random` 等)。但从"语言设计"角度,**低层模拟手机操作统一归为 action(press/swipe/scroll)**,其余更高阶能力推荐以 Func 标签方式抽象出来(见下文)。
+
+## 内置操作(delay / set / random)
+
+### delay(延迟)
+```json
+{ "type": "delay", "value": "2s" }
+{ "type": "delay", "value": "{randomValue}s" }  // 支持变量引用
+```
+- `value`: 延迟时间,支持格式:`"2s"`(秒)、`"5m"`(分钟)、`"2h"`(小时)
+- 支持变量引用,例如:`"{randomValue}s"` 会解析变量值后加上单位
+
+### set(设置变量)
+```json
+{ "type": "set", "variable": "count", "value": "10" }
+{ "type": "set", "variable": "message", "value": "{lastMessage}" }  // 支持变量引用
+```
+- `variable`: 变量名
+- `value`: 变量值,支持变量引用
+
+### random(生成随机数)
+```json
+{ "type": "random", "variable": "randomValue", "min": 1, "max": 100, "integer": true }
+{ "type": "random", "variable": "delayTime", "min": 3.5, "max": 8.5, "integer": false }
+```
+- `variable`: 保存随机数的变量名
+- `min`: 最小值(数字)
+- `max`: 最大值(数字)
+- `integer`: 是否生成整数(默认 `true`)
+  - `true`: 生成整数,范围 `[min, max]`(包含两端)
+  - `false`: 生成浮点数,范围 `[min, max)`(不包含 max)
+
+**示例:随机点赞概率**
+```json
+{
+  "type": "random",
+  "variable": "randomValue",
+  "min": 1,
+  "max": 100,
+  "integer": true,
+  "delay": "0.5s"
+},
+{
+  "type": "if",
+  "condition": "{randomValue} <= 30",
+  "then": [
+    { "type": "click", "method": "image", "target": "点赞按钮.png" }
+  ]
+}
+```
 
 ## 定位方式(method)
 
@@ -102,7 +151,7 @@
 
 扩展标签统一放在:
 
-- `src/pages/Processing/Func/`
+- `src/pages/processing/func/`
 
 该目录下目前提供的标签(即“除了基础功能之外还能调用哪些能力”):
 
@@ -110,7 +159,7 @@
 - **`string-reg-location`**:通过文字匹配获取位置坐标
 - **`save-chat-history`**:把消息/会话记录保存到 `history` 目录
 
-> 注意:以上列表应当与 `src/pages/Processing/Func/` 目录内容保持一致;后续新增能力只需要新增对应脚本文件即可。
+> 注意:以上列表应当与 `src/pages/processing/func/` 目录内容保持一致;后续新增能力只需要新增对应脚本文件即可。
 
 ## 扩展标签示例:extract-chat-history
 

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

@@ -1,9 +1,9 @@
 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 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/Input/Input.js

@@ -127,7 +127,7 @@ const REQUIREMENTS_PROMPT = `你是一个友好的自动化工作流生成助手
 语法分层(重要):
 - 基础语法(控制流/定时):scheduleOnce、schedule、for、while、if
 - 基础 action(模拟手机操作):press、swipe、scroll
-- 扩展标签(Func):除基础能力外的所有扩展功能,全部由 src/pages/Processing/Func/ 目录下的脚本文件决定;每个脚本文件名就是一个“标签”
+- 扩展标签(Func):除基础能力外的所有扩展功能,全部由 src/pages/processing/func/ 目录下的脚本文件决定;每个脚本文件名就是一个"标签"
 
 在生成工作流时:
 - 逻辑结构尽量用 for/while/if + triggers(schedules) 表达
@@ -577,7 +577,7 @@ ${images.map((img, index) => `图片${index + 1}: 原始文件名 ${img.original
       const requestBody = {
         input: fullPrompt
       };
-
+      
       console.log('Sending request to Doubao API:', requestBody);
 
       const response = await fetch('https://ai-anim.com/api/doubaoText2text', {

+ 1156 - 0
src/pages/Chat/Input/input-hooks.jsx

@@ -0,0 +1,1156 @@
+import { useState, useRef } from 'react';
+
+// useInput - 管理输入组件的状态和逻辑
+export function useInput(onSendMessage, onLoadingChange) {
+  const { 
+    onSend, 
+    onUpload, 
+    uploadedImages, 
+    removeImage,
+    workflowRequirements,
+  } = InputLogic(onSendMessage, onLoadingChange);
+  const [message, setMessage] = useState('');
+  const [isLoading, setIsLoading] = useState(false);
+  const fileInputRef = useRef(null);
+  const textInputRef = useRef(null);
+
+  const handleSend = async () => {
+    const trimmed = message.trim();
+    // 允许在有图片时发送(即使没有文字),或者有文字时发�?
+    if ((!trimmed && uploadedImages.length === 0) || isLoading) return;
+    
+    // 先清空输入框(在发送前清空,提供更好的用户体验�?
+    setMessage('');
+    
+    setIsLoading(true);
+    try {
+      await onSend?.(trimmed || ''); // 如果没有文字,传递空字符�?
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  const handleContainerClick = (e) => {
+    // 如果点击的是容器本身(不是按钮、输入框或其他交互元素),则聚焦输入�?
+    if (e.target === e.currentTarget) {
+      textInputRef.current?.focus();
+    }
+  };
+
+  // 有文字或上传了图片就可以发�?
+  const isSendDisabled = (!message.trim() && uploadedImages.length === 0) || isLoading;
+
+  const handleUploadClick = () => {
+    fileInputRef.current?.click();
+  };
+
+  const handleFileChange = async (e) => {
+    const files = e.target.files;
+    if (!files || files.length === 0) return;
+
+    for (const file of files) {
+      if (file.type.startsWith('image/')) {
+        await onUpload?.(file);
+      }
+    }
+
+    // 清空input,允许重复选择同一文件
+    e.target.value = '';
+  };
+
+  const handleSendKeyDown = (e) => {
+    if ((e.key === 'Enter' || e.key === ' ') && !isSendDisabled) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleTextareaKeyDown = (e) => {
+    // Enter 发送(不按 Ctrl�?
+    if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
+      e.preventDefault();
+      handleSend();
+      return;
+    }
+    // 其他所有按键都允许默认行为(包括Backspace、Delete等)
+    // 不要阻止任何其他按键的默认行�?
+  };
+
+  const handleUploadKeyDown = (e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
+      handleUploadClick();
+    }
+  };
+
+  const getSendButtonClassName = () => {
+    return `send-btn ${isSendDisabled ? 'disabled' : ''}`;
+  };
+
+  const getSendButtonOnClick = () => {
+    return !isSendDisabled ? handleSend : undefined;
+  };
+
+  const getSendButtonTabIndex = () => {
+    return isSendDisabled ? -1 : 0;
+  };
+
+  return {
+    message,
+    setMessage,
+    isLoading,
+    fileInputRef,
+    textInputRef,
+    handleSend,
+    handleContainerClick,
+    handleUploadClick,
+    handleFileChange,
+    handleSendKeyDown,
+    handleTextareaKeyDown,
+    handleUploadKeyDown,
+    isSendDisabled,
+    getSendButtonClassName,
+    getSendButtonOnClick,
+    getSendButtonTabIndex,
+    uploadedImages,
+    removeImage,
+  };
+}
+
+// InputLogic - 处理消息发送到GPT API
+
+// 第一阶段提示词:分析需求,返回需要的信息
+const REQUIREMENTS_PROMPT = `你是一个友好的自动化工作流生成助手。你需要像聊天一样与用户对话�?
+
+如果用户想要创建自动化工作流,你需要分析需求并返回需要的信息。请用自然、友好的语言与用户交流,保持对话的连贯性和友好性�?
+
+语法分层(重要)�?
+- 基础语法(控制流/定时):scheduleOnce、schedule、for、while、if
+- 基础 action(模拟手机操作):press、swipe、scroll
+- 扩展标签(Func):除基础能力外的所有扩展功能,全部�?src/pages/processing/Func/ 目录下的脚本文件决定;每个脚本文件名就是一个“标签”�?
+
+在生成工作流时:
+- 逻辑结构尽量�?for/while/if + triggers(schedules) 表达
+- 具体手机操作尽量落到 press/swipe/scroll
+- 当需求超出基础 action(例如:提取会话/消息记录、文字定位等),请使用对应的 Func 标签(例如:extract-chat-history、string-reg-location、save-chat-history�?
+
+(实现层说明:项目内部为兼容与落地,仍存�?locate/click/input/ocr/ai-generate/delay/set 等内置能力;但提示用户与生成语法时,请优先按上述分层来组织。)
+
+9. if - 条件判断
+   - condition: 条件表达式(�?"{count} > 5"�?
+   - then: 条件为真时执行的操作数组
+   - else: 条件为假时执行的操作数组(可选)
+
+10. for - 循环
+    - variable: 循环变量�?
+    - items: 数组(可以是变量 {arrayVariable}�?
+    - body: 循环体操作数�?
+
+11. delay - 延迟
+    - value: 延迟时间(如 "2s", "1m", "30 minutes"�?
+
+12. set - 设置变量
+    - variable: 变量�?
+    - value: 变量�?
+
+13. scroll/swipe - 滚动/滑动
+    - value: 方向�?up-down", "down-up", "left-right", "right-left"�?
+
+定时任务配置�?
+- 单次执行: {"datetime": "2026/1/14 01:21"}
+- 每天执行: {"time": "09:00", "repeat": "daily"}
+- 每周执行: {"time": "09:00", "weekdays": ["monday", "wednesday", "friday"]}
+- 间隔执行: {"interval": "30 minutes"}
+
+重要规则�?
+- 变量使用 {variableName} 格式引用
+- 根据用户描述合理设置delay延迟时间
+- 如果用户提到循环、条件判断,使用for/if操作
+- 如果用户提到定时执行,添加triggers配置
+- 如果用户提到AI生成内容,使用ai-generate操作
+- 如果用户提到消息记录、聊天记录、历史记录、对话记录,使用extract-messages、save-messages、generate-summary操作
+- 图片文件名按顺序命名�?1.png"�?2.png"�?
+- **特别重要**:如果工作流中需要输入文字(使用 input �?ai-generate 操作),且这些文字需要根据场景背景和用户角色来生成,你必须询问用户以下信息:
+  - 场景的背景:这个工作流在什么场景下使用?(例如:微信聊天、客服回复、评论互动等�?
+  - 用户的角色:用户在这个场景中扮演什么角色?(例如:刚认识女方的男生、客服人员、产品推广者等�?
+  - 这些信息对于生成合适的文字内容非常重要,请务必在询问需求时主动询问
+
+请分析用户的需求,根据上述操作类型,判断需要哪些图片和文字参考:
+- 图片(needsImages):用于 locate/click �?image 方法,或 ocr �?by-avatar 方法
+- 文字参考(needsTexts):用于 locate/click �?text 方法
+
+如果用户想要创建自动化工作流�?
+1. 先友好地确认理解用户的需�?
+2. 然后返回JSON格式的需求信息(不包含在自然语言回复中,仅作为JSON返回�?
+
+需求分析的JSON格式�?
+{
+  "needsImages": 2,  // 需要多少张图片(用于图像匹配定位)
+  "needsTexts": 1,   // 需要多少个文字参考(用于文字识别定位�?
+  "imageDescriptions": ["登录按钮", "用户名输入框"],  // 每张图片的描�?
+  "textDescriptions": ["登录按钮文字"]  // 每个文字参考的描述
+}
+
+返回格式:先自然语言回复,然后单独一行JSON�?
+
+**特别提醒**:如果工作流涉及输入文字(如自动回复、自动评论、自动发送消息等),请务必询问用户场景背景和角色信息�?
+
+例如(不涉及文字输入的场景)�?
+我理解了,您想创建一个登录流程的自动化工作流。为了生成准确的工作流配置,我需要您提供以下信息�?
+
+📋 需要的信息�?
+🖼�?需�?2 张图片:登录按钮截图、用户名输入框截�?
+📝 需�?1 个文字参考:登录按钮上的文字
+
+\`\`\`json
+{
+  "needsImages": 2,
+  "needsTexts": 1,
+  "imageDescriptions": ["登录按钮", "用户名输入框"],
+  "textDescriptions": ["登录按钮文字"]
+}
+\`\`\`
+
+例如(涉及文字输入的场景):
+我理解了,您想创建一个自动聊天回复的自动化工作流。为了生成准确且符合场景的文字内容,我需要了解以下信息:
+
+📋 需要的信息�?
+🖼�?需�?2 张图片:好友头像截图、我的头像截�?
+📝 需�?1 个文字参考:输入框位�?
+💬 **场景信息**(重要)�?
+   - 场景背景:这个聊天是在什么场景下进行的?(例如:微信聊天、QQ群聊、客服对话等�?
+   - 您的角色:您在这个场景中扮演什么角色?(例如:刚认识女方的男生、客服人员、产品推广者等�?
+
+\`\`\`json
+{
+  "needsImages": 2,
+  "needsTexts": 1,
+  "imageDescriptions": ["好友头像", "我的头像"],
+  "textDescriptions": ["输入框位�?]
+}
+\`\`\`
+
+如果用户的话题与工作流无关,请友好地自然回复,不需要返回JSON。如果用户是在询问如何使用、或者其他一般性问题,请用自然语言友好地回答。`;
+
+// 第二阶段提示词:生成工作�?
+const SYSTEM_PROMPT = `你是一个友好的自动化工作流生成助手。用户已经提供了所需的图片和文字参考,现在你需要生成JSON格式的工作流配置文件�?
+
+请用自然、友好的语言与用户交流,保持对话的连贯性。在生成工作流时,请先简短地确认一下,然后生成JSON格式的工作流配置�?
+
+工作流JSON格式(新版本):
+{
+  "version": "1.0",
+  "name": "工作流名�?,
+  "triggers": [
+    {
+      "type": "schedule",
+      "schedule": {
+        "time": "09:00",
+        "weekdays": ["monday", "wednesday", "friday"]
+      }
+    }
+  ],
+  "variables": {
+    "topic": "美食"
+  },
+  "actions": [
+    {
+      "type": "locate",
+      "method": "image",
+      "target": "button.png",
+      "variable": "buttonPos",
+      "delay": "1s"
+    },
+    {
+      "type": "click",
+      "method": "position",
+      "target": "{buttonPos}",
+      "delay": "2s"
+    },
+    {
+      "type": "input",
+      "target": "输入�?,
+      "value": "文字内容",
+      "delay": "1s"
+    },
+    {
+      "type": "ai-generate",
+      "prompt": "生成关于{topic}的文�?,
+      "variable": "article",
+      "delay": "10s"
+    },
+    {
+      "type": "if",
+      "condition": "{count} > 5",
+      "then": [
+        {
+          "type": "click",
+          "method": "text",
+          "target": "确定"
+        }
+      ]
+    },
+    {
+      "type": "for",
+      "variable": "item",
+      "items": ["选项1", "选项2"],
+      "body": [
+        {
+          "type": "click",
+          "method": "text",
+          "target": "{item}"
+        }
+      ]
+    }
+  ]
+}
+
+核心操作类型�?
+1. locate - 定位元素
+   - method: "image"(图像匹配)| "text"(文字识别)| "coordinate"(坐标)
+   - target: 目标内容
+   - variable: 保存位置到变�?
+
+2. click - 点击
+   - method: "position" | "image" | "text" | "coordinate"
+   - target: 目标(可以是变量 {variable}�?
+
+3. input - 输入文字
+   - target: 输入框位置(文字或变量)
+   - value: 要输入的文本(可以是变量 {variable}�?
+
+4. ai-generate - AI生成内容
+   - prompt: 提示词(支持变量 {variable}�?
+   - variable: 保存结果到变�?
+
+5. ocr - 文字识别
+   - method: "by-avatar" | "by-position" | "full-screen"
+   - avatar: 头像图片(by-avatar时)
+   - variable: 保存识别结果
+
+6. extract-messages - 提取消息记录
+   - avatar1: 第一个参与者的头像/标识图片路径
+   - avatar2: 第二个参与者的头像/标识图片路径
+   - variable: 保存消息记录到变量(格式:对�? 消息\n�? 消息�?
+
+7. save-messages - 保存消息记录
+   - variable: 包含消息记录的变量名(保存到 history 文件夹)
+
+8. generate-summary - 生成消息总结
+   - variable: 包含消息记录的变量名
+   - summaryVariable: 保存总结结果的变量名
+   - model: AI模型名称(可选)
+
+9. if - 条件判断
+   - condition: 条件表达式(�?"{count} > 5"�?
+   - then: 条件为真时执行的操作数组
+   - else: 条件为假时执行的操作数组(可选)
+
+10. for - 循环
+    - variable: 循环变量�?
+    - items: 数组(可以是变量 {arrayVariable}�?
+    - body: 循环体操作数�?
+
+11. delay - 延迟
+    - value: 延迟时间(如 "2s", "1m", "30 minutes"�?
+
+12. set - 设置变量
+    - variable: 变量�?
+    - value: 变量�?
+
+13. scroll/swipe - 滚动/滑动
+    - value: 方向�?up-down", "down-up", "left-right", "right-left"�?
+
+定时任务配置�?
+- 单次执行: {"datetime": "2026/1/14 01:21"}
+- 每天执行: {"time": "09:00", "repeat": "daily"}
+- 每周执行: {"time": "09:00", "weekdays": ["monday", "wednesday", "friday"]}
+- 间隔执行: {"interval": "30 minutes"}
+
+重要规则�?
+- 使用新格式(type字段),但保持向后兼容旧格式
+- 变量使用 {variableName} 格式引用
+- 根据用户描述合理设置delay延迟时间
+- 如果用户提到循环、条件判断,使用for/if操作
+- 如果用户提到定时执行,添加triggers配置
+- 如果用户提到AI生成内容,使用ai-generate操作
+- 如果用户提到消息记录、聊天记录、历史记录、对话记录,使用extract-messages、save-messages、generate-summary操作
+- 在ai-generate的prompt中可以使用总结变量引用历史消息记录的总结
+- 图片文件名按顺序命名�?1.png"�?2.png"�?
+
+返回格式�?
+1. 如果用户已经提供了所有需要的图片和文字参考,且明确要求生成工作流�?
+   - 先用简短的自然语言确认(如"好的,我现在为您生成工作流配�?.."�?
+   - 然后单独一行返回JSON格式的工作流配置(可以用代码块包裹)
+   
+2. 如果用户只是在询问问题或者还没准备好生成工作流:
+   - 用自然语言友好地回答用户的问题
+   - 不需要返回JSON
+
+请保持对话的自然和友好,不要生硬地只返回JSON。`;
+
+export function InputLogic(onSendMessage, onLoadingChange) {
+  // 存储上传的图�?
+  const [uploadedImages, setUploadedImages] = useState([]);
+  
+  // img2text API可用性标记(使用useRef避免重复输出警告�?
+  const img2textApiAvailableRef = useRef(true);
+  
+  // 调用img2text API获取图片描述
+  const img2text = async (imageBase64, prompt = '请描述这张图片的内容') => {
+    // 如果已经知道API不可用,直接返回null,不尝试调用
+    if (!img2textApiAvailableRef.current) {
+      return null;
+    }
+    
+    try {
+      // 确保base64数据是完整的data URL格式
+      let imageUrl = imageBase64;
+      if (!imageBase64.startsWith('data:')) {
+        // 如果没有前缀,假设是PNG格式的base64数据
+        imageUrl = `data:image/png;base64,${imageBase64}`;
+      }
+
+      // 使用AbortController设置超时,快速失�?
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 1000); // 1秒超�?
+
+      const response = await fetch('https://ai-anim.com/api/img2text', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          prompt: prompt,
+          imageUrl: imageUrl
+        }),
+        signal: controller.signal,
+      });
+
+      clearTimeout(timeoutId);
+
+      if (!response.ok) {
+        const errorText = await response.text();
+        throw new Error(`img2text API error: ${response.status} ${response.statusText} - ${errorText}`);
+      }
+
+      const data = await response.json();
+      // 支持多种可能的返回格�?
+      return data.text || data.description || data.result || data.output_text || data.content || '';
+    } catch (error) {
+      // img2text API是可选的,失败不影响主要功能
+      // 标记API不可用,之后直接返回null,不再尝�?
+      if (img2textApiAvailableRef.current) {
+        img2textApiAvailableRef.current = false;
+      }
+      // 静默返回null,不输出任何错误信息(因为这是可选功能)
+      return null;
+    }
+  };
+  // 存储工作流需求信息(需要多少图片和文字�?
+  const [workflowRequirements, setWorkflowRequirements] = useState(null);
+  // 存储用户输入的文字参考(用于文字识别�?
+  const [referenceTexts, setReferenceTexts] = useState([]);
+  // 存储AI生成的工作流JSON(不显示在聊天框�?
+  const [workflowJson, setWorkflowJson] = useState(null);
+
+  // 删除图片
+  const removeImage = (index) => {
+    setUploadedImages(prev => prev.filter((_, i) => i !== index));
+  };
+
+  // 上传图片并命�?
+  const onUpload = async (file) => {
+    try {
+      // 读取图片为base64
+      const reader = new FileReader();
+      const base64 = await new Promise((resolve, reject) => {
+        reader.onload = () => {
+          const result = reader.result;
+          // 提取base64数据部分(去掉data:image/...;base64,前缀�?
+          const base64Data = result.split(',')[1];
+          resolve(base64Data);
+        };
+        reader.onerror = reject;
+        reader.readAsDataURL(file);
+      });
+
+      // 生成临时文件名(使用时间戳)
+      const timestamp = Date.now();
+      const tempFileName = `temp_${timestamp}.${file.name.split('.').pop()}`;
+
+      // 存储图片信息
+      const imageInfo = {
+        file: file,
+        base64: base64,
+        tempName: tempFileName,
+        originalName: file.name,
+        timestamp: timestamp,
+      };
+
+      setUploadedImages(prev => [...prev, imageInfo]);
+      console.log('图片已上�?', imageInfo.originalName);
+    } catch (error) {
+      console.error('上传图片失败:', error);
+    }
+  };
+
+  // 根据用户描述给图片命名(使用AI�?
+  const nameImagesWithAI = async (userPrompt, images) => {
+    if (!images || images.length === 0) return images;
+
+    try {
+      // 构建提示词,让AI根据用户描述给图片命�?
+      const imagePrompt = `用户上传�?${images.length} 张图片,用于工作流自动化。请根据用户的描述,为每张图片生成一个简洁的描述性文件名(只返回JSON数组,不要其他文字)�?
+
+用户描述�?{userPrompt}
+
+图片信息�?
+${images.map((img, index) => `图片${index + 1}: 原始文件�?${img.originalName}`).join('\n')}
+
+请返回JSON数组,格式如下:
+["登录按钮.png", "用户名输入框.png", "密码输入�?png"]
+
+要求�?
+1. 文件名要简洁,能清楚描述图片内�?
+2. 使用中文或英文都可以
+3. 必须包含文件扩展名(.png�?
+4. 文件名要符合工作流中使用的命名规范`;
+
+      const response = await fetch('https://ai-anim.com/api/text2textByModel', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          prompt: imagePrompt,
+          modelName: 'gpt-5-nano-ca'
+        })
+      });
+
+      if (!response.ok) {
+        throw new Error('AI命名失败');
+      }
+
+      const data = await response.json();
+      const responseText = data.data?.output_text || data.text || data.content || '';
+      
+      // 解析AI返回的文件名数组
+      let imageNames = [];
+      try {
+        // 尝试直接解析JSON
+        imageNames = JSON.parse(responseText.trim());
+      } catch (e) {
+        // 尝试从代码块中提�?
+        const match = responseText.match(/\[[\s\S]*?\]/);
+        if (match) {
+          imageNames = JSON.parse(match[0]);
+        } else {
+          // 如果解析失败,使用默认命�?
+          imageNames = images.map((img, index) => `${index + 1}.png`);
+        }
+      }
+
+      // 更新图片信息,添加AI生成的文件名
+      return images.map((img, index) => ({
+        ...img,
+        aiName: imageNames[index] || `${index + 1}.png`
+      }));
+    } catch (error) {
+      console.error('AI命名图片失败:', error);
+      // 如果AI命名失败,使用默认命�?
+      return images.map((img, index) => ({
+        ...img,
+        aiName: `${index + 1}.png`
+      }));
+    }
+  };
+
+  const sendToGPT = async (userPrompt, images = []) => {
+    // 构建图片信息提示(如果图片已经命名)
+    let imageInfoPrompt = '';
+    if (images.length > 0) {
+      imageInfoPrompt = `\n\n用户上传了以下图片,请在生成工作流时使用这些图片文件名:\n${images.map((img, index) => `${index + 1}. ${img.aiName || img.tempName} - ${img.originalName}`).join('\n')}\n\n在生成工作流时,如果操作需要点击图片,请使用上述图片文件名(如 "登录按钮.png")。`;
+    }
+
+    // 构建完整的提示词:系统提示词 + 图片信息 + 用户输入
+    const fullPrompt = `${SYSTEM_PROMPT}${imageInfoPrompt}\n\n用户需求:${userPrompt}`;
+      
+      const requestBody = {
+        input: fullPrompt
+      };
+
+      console.log('Sending request to Doubao API:', requestBody);
+
+      const response = await fetch('https://ai-anim.com/api/doubaoText2text', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(requestBody),
+      });
+
+      console.log('Response status:', response.status, response.statusText);
+
+      if (!response.ok) {
+        let errorText;
+        try {
+          errorText = await response.text();
+          // 尝试解析�?JSON
+          try {
+            const errorJson = JSON.parse(errorText);
+            console.error('API Error Response (JSON):', errorJson);
+            // 如果 JSON 中有 error 字段,使用它
+            if (errorJson.error) {
+              throw new Error(`服务器错�? ${errorJson.error}`);
+            }
+            if (errorJson.message) {
+              throw new Error(`服务器错�? ${errorJson.message}`);
+            }
+          } catch (parseError) {
+            // 不是 JSON,使用原始文�?
+            console.error('API Error Response (Text):', errorText);
+          }
+        } catch (readError) {
+          errorText = `无法读取错误响应: ${readError.message}`;
+        }
+        throw new Error(`HTTP ${response.status}: ${errorText}`);
+      }
+
+      const data = await response.json();
+      console.log('API Response:', data);
+    
+      return data;
+  };
+
+  // 分析需求,返回需要的信息
+  const analyzeRequirements = async (userPrompt) => {
+    const response = await fetch('https://ai-anim.com/api/text2textByModel', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        prompt: `${REQUIREMENTS_PROMPT}\n\n用户需求:${userPrompt}`,
+        modelName: 'gpt-5-nano-ca'
+      })
+    });
+
+    if (!response.ok) {
+      throw new Error('分析需求失败');
+    }
+
+    const data = await response.json();
+    const responseText = data.data?.output_text || data.text || data.content || '';
+    
+    // 提取自然语言回复(去除JSON部分�?
+    let naturalLanguageReply = responseText.trim();
+    
+    // 尝试从回复中提取JSON(可能在代码块中,也可能单独一行)
+    let requirements = null;
+    
+    // 方法1: 尝试从代码块中提取JSON
+    const codeBlockMatch = responseText.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
+    if (codeBlockMatch) {
+      try {
+        requirements = JSON.parse(codeBlockMatch[1]);
+        // 移除代码块部分,保留自然语言
+        naturalLanguageReply = responseText.replace(/```(?:json)?\s*\{[\s\S]*?\}\s*```/g, '').trim();
+      } catch (e) {
+        // 解析失败,继续尝试其他方�?
+      }
+    }
+    
+    // 方法2: 尝试匹配JSON对象
+    if (!requirements) {
+      const jsonMatch = responseText.match(/\{[\s\S]*"needsImages"[\s\S]*\}/);
+      if (jsonMatch) {
+        try {
+          requirements = JSON.parse(jsonMatch[0]);
+          // 移除JSON部分,保留自然语言
+          naturalLanguageReply = responseText.replace(/\{[\s\S]*"needsImages"[\s\S]*\}/, '').trim();
+        } catch (e) {
+          // 解析失败
+        }
+      }
+    }
+    
+    // 方法3: 尝试直接解析整个文本
+    if (!requirements) {
+      try {
+        requirements = JSON.parse(responseText.trim());
+      } catch (e) {
+        // 不是纯JSON
+      }
+    }
+    
+    // 如果找到JSON,返回JSON和自然语言回复
+    if (requirements && typeof requirements === 'object') {
+      // 将自然语言回复保存到requirements中,以便后续显示
+      if (naturalLanguageReply && naturalLanguageReply !== responseText.trim()) {
+        requirements.naturalLanguageReply = naturalLanguageReply;
+      }
+      return requirements;
+    }
+    
+    // 如果没有找到JSON,可能是用户的问题与工作流无关,返回错误信息
+    return { 
+      error: naturalLanguageReply || '请描述你的工作流需求,例如:生成一个登录流程,先点击登录按钮,然后输入用户名和密码'
+    };
+  };
+
+  // 解析用户输入的文字参考(按行分割�?
+  const parseReferenceTexts = (text) => {
+    // 如果已经有工作流需求,提取文字参�?
+    if (workflowRequirements && workflowRequirements.needsTexts > 0) {
+      // 尝试从文本中提取,可能是用换行、逗号或分号分�?
+      const lines = text.split(/[\n,�?;]/).map(t => t.trim()).filter(t => t);
+      return lines.slice(0, workflowRequirements.needsTexts);
+    }
+    return [];
+  };
+
+  const onSend = async (text) => {
+    if (!text.trim() && uploadedImages.length === 0) return;
+
+    // 保存上传的图片信息(用于后续使用和显示)
+    const currentImages = [...uploadedImages];
+    
+    // 添加用户消息(包含图片信息,包含base64数据以便在聊天界面显示)
+    const userMessage = {
+      role: 'user',
+      content: text,
+      images: currentImages.map(img => ({
+        name: img.originalName,
+        tempName: img.tempName,
+        base64: img.base64, // 包含base64数据,用于在聊天界面显示
+        originalName: img.originalName
+      })),
+      timestamp: new Date(),
+    };
+    onSendMessage?.(userMessage);
+    
+    // 发送消息后立即清空上传的图片,让输入框的提示方块消�?
+    setUploadedImages([]);
+
+    // 开始加�?
+    onLoadingChange?.(true);
+
+    try {
+      // 第一阶段:如果没有需求信息,先分析需�?
+      if (!workflowRequirements) {
+        // 如果有参考图,先调用img2text获取图片描述
+        let imageDescriptionsPrompt = '';
+        if (currentImages.length > 0) {
+          const imageDescriptions = [];
+          for (const img of currentImages) {
+            const description = await img2text(img.base64, '请详细描述这张图片的内容,包括图片中的界面元素、按钮、文字等信息');
+            if (description) {
+              imageDescriptions.push(description);
+            }
+          }
+          
+          if (imageDescriptions.length > 0) {
+            imageDescriptionsPrompt = `\n\n用户提供了以下参考图片及其描述:\n${imageDescriptions.map((desc, index) => `图片${index + 1}�?{desc}`).join('\n')}\n\n请根据这些图片描述,分析用户需要哪些图片和文字参考来生成工作流。`;
+          }
+        }
+        
+        const requirements = await analyzeRequirements(text + imageDescriptionsPrompt);
+        
+        if (requirements.error) {
+          // 如果返回错误信息,直接显示(可能是自然语言回复�?
+          const errorMessage = {
+            role: 'assistant',
+            content: requirements.error,
+            timestamp: new Date(),
+          };
+          onSendMessage?.(errorMessage);
+          return;
+        }
+
+        // 保存需求信�?
+        setWorkflowRequirements(requirements);
+        
+        // 显示需求信息给用户(优先显示AI的自然语言回复,然后显示需求列表)
+        const needsImages = requirements.needsImages || 0;
+        const needsTexts = requirements.needsTexts || 0;
+        
+        let requirementMessage = '';
+        
+        // 如果有自然语言回复,先显示
+        if (requirements.naturalLanguageReply) {
+          requirementMessage = requirements.naturalLanguageReply + '\n\n';
+        }
+        
+        // 然后显示需求信�?
+        if (needsImages > 0 || needsTexts > 0) {
+          requirementMessage += '📋 根据您的需求,我需要以下信息:\n\n';
+          
+          if (needsImages > 0) {
+            requirementMessage += `🖼�?需�?${needsImages} 张图片:\n`;
+            (requirements.imageDescriptions || []).forEach((desc, index) => {
+              requirementMessage += `${index + 1}. ${desc}\n`;
+            });
+            requirementMessage += '\n';
+          }
+          
+          if (needsTexts > 0) {
+            requirementMessage += `📝 需�?${needsTexts} 个文字参考:\n`;
+            (requirements.textDescriptions || []).forEach((desc, index) => {
+              requirementMessage += `${index + 1}. ${desc}\n`;
+            });
+            requirementMessage += '\n';
+          }
+          
+          requirementMessage += '璇锋寜椤哄簭涓婁紶鍥剧墖鍜岃緭鍏ユ枃瀛楀弬鑰冿紝鐒跺悗鍐嶆鍙戦€佹秷鎭€?;
+        }
+        
+        const reqMessage = {
+          role: 'assistant',
+          content: requirementMessage || '濂界殑锛屾垜鐞嗚В浜嗘偍鐨勯渶姹傘€?,
+          timestamp: new Date(),
+        };
+        onSendMessage?.(reqMessage);
+        return;
+      }
+
+      // 第二阶段:验证用户提供的信息是否完整
+      const needsImages = workflowRequirements.needsImages || 0;
+      const needsTexts = workflowRequirements.needsTexts || 0;
+      const providedImages = currentImages.length;
+      const parsedTexts = parseReferenceTexts(text);
+      const providedTexts = parsedTexts.length;
+
+      if (providedImages < needsImages || providedTexts < needsTexts) {
+        // 信息不完整,提示用户
+        let missingMessage = '鉂?淇℃伅涓嶅畬鏁达紝璇锋彁渚涳細\n\n';
+        if (providedImages < needsImages) {
+          missingMessage += 馃柤锔?杩橀渶瑕?\ 寮犲浘鐗嘰n;
+        }
+        if (providedTexts < needsTexts) {
+          missingMessage += 馃摑 杩橀渶瑕?\ 涓枃瀛楀弬鑰僜n;
+        }
+        missingMessage += '\n璇锋寜椤哄簭涓婁紶鍥剧墖鍜岃緭鍏ユ枃瀛楀弬鑰冿紝鐒跺悗鍐嶆鍙戦€佹秷鎭€?;
+        
+        const missingMsg = {
+          role: 'assistant',
+          content: missingMessage,
+          timestamp: new Date(),
+        };
+        onSendMessage?.(missingMsg);
+        return;
+      }
+
+      // 第三阶段:信息完整,生成工作�?
+      let namedImages = [];
+      if (currentImages.length > 0) {
+        try {
+          // 使用需求描述给图片命名
+          const imageDescriptions = workflowRequirements.imageDescriptions || [];
+          namedImages = currentImages.map((img, index) => ({
+            ...img,
+            aiName: imageDescriptions[index] 
+              ? `${imageDescriptions[index]}.png` 
+              : `${index + 1}.png`
+          }));
+        } catch (nameError) {
+          console.error('图片命名失败,使用默认命�?', nameError);
+          namedImages = currentImages.map((img, index) => ({
+            ...img,
+            aiName: `${index + 1}.png`
+          }));
+        }
+      }
+
+      // 构建包含文字参考的提示�?
+      let textReferencesPrompt = '';
+      if (parsedTexts.length > 0) {
+        textReferencesPrompt = `\n\n用户提供的文字参考:\n${parsedTexts.map((t, i) => `${i + 1}. ${t}`).join('\n')}\n\n在生成工作流时,如果操作需要定位文字,请使用上述文字参考。`;
+        // 保存文字参考到state
+        setReferenceTexts(parsedTexts);
+      }
+
+      // 如果有参考图,调用img2text获取图片描述并加入到prompt�?
+      let imageDescriptionsPrompt = '';
+      if (currentImages.length > 0) {
+        const imageDescriptions = [];
+        for (const img of currentImages) {
+          const description = await img2text(img.base64, '请详细描述这张图片的内容,包括图片中的界面元素、按钮、文字、布局等信息,这些信息将用于生成自动化工作�?);
+          if (description) {
+            imageDescriptions.push(description);
+          }
+        }
+        
+        if (imageDescriptions.length > 0) {
+          imageDescriptionsPrompt = `\n\n参考图片描述:\n${imageDescriptions.map((desc, index) => {
+            const imgName = namedImages[index]?.aiName || `${index + 1}.png`;
+            return `图片 ${imgName}�?{desc}`;
+          }).join('\n')}\n\n请根据这些图片描述,在生成工作流时合理使用对应的图片文件名。`;
+        }
+      }
+
+      // 发送到GPT并获取回复(传递已命名的图片信息和文字参考)
+      const response = await sendToGPT(text + textReferencesPrompt + imageDescriptionsPrompt, namedImages);
+      
+      // 根据实际 API 响应结构解析文本
+      // API 返回: { success: true, data: { output_text: "..." } }
+      const responseText = response.data?.output_text || 
+                          response.text || 
+                          response.content || 
+                          response.message || 
+                          response.data?.text ||
+                          response.data?.content ||
+                          response.result ||
+                          (typeof response === 'string' ? response : JSON.stringify(response));
+      
+      // 检测是否是JSON格式的工作流
+      const detectedWorkflowJson = extractWorkflowJsonFromText(responseText);
+      
+      if (detectedWorkflowJson) {
+        // 如果检测到工作流JSON,保存到内存中,不显示在聊天�?
+        setWorkflowJson(detectedWorkflowJson);
+        
+        // 准备图片数据(base64和文件名�?
+        const imagesData = namedImages.map(img => ({
+          name: img.aiName || img.tempName,
+          base64: img.base64,
+          originalName: img.originalName
+        }));
+
+        // 建立图片文件名映射:将JSON中可能使用的简单文件名(如 "1.png")映射到实际保存的文件名
+        // 这样即使AI使用�?"1.png"�?2.png" 等简单名称,也能正确映射到实际的文件�?
+        const imageNameMap = {};
+        namedImages.forEach((img, index) => {
+          const actualName = img.aiName || img.tempName;
+          // 映射 "1.png", "2.png" �?
+          imageNameMap[`${index + 1}.png`] = actualName;
+          // 也映射原始名称(如果有)
+          if (img.originalName) {
+            imageNameMap[img.originalName] = actualName;
+          }
+        });
+
+        // 在保存前,更新工作流JSON中的图片文件�?
+        const updatedWorkflowJson = updateImagePathsInWorkflow(detectedWorkflowJson, imageNameMap);
+
+        // 直接保存工作流(不显示JSON内容�?
+        try {
+          if (window.electronAPI && window.electronAPI.saveWorkflow) {
+            const saveResult = await window.electronAPI.saveWorkflow(updatedWorkflowJson, imagesData);
+            if (saveResult.success) {
+              // 保存成功,清空所有状态(图片已经在发送时清空�?
+              setWorkflowRequirements(null);
+              setReferenceTexts([]);
+              setWorkflowJson(null);
+              
+              // 显示成功消息(不包含JSON内容�?
+              const successMessage = {
+                role: 'assistant',
+                content: `�?工作流已成功生成并保存!\n\n文件夹名称:${saveResult.folderName}`,
+                timestamp: new Date(),
+              };
+              onSendMessage?.(successMessage);
+              
+              // 触发工作流列表刷新事�?
+              window.dispatchEvent(new CustomEvent('workflow-saved'));
+              return;
+            } else {
+              // 保存失败,显示错�?
+              console.error('保存工作流失�?', saveResult.error);
+              const errorMessage = {
+                role: 'assistant',
+                content: `�?保存工作流失败:${saveResult.error}`,
+                timestamp: new Date(),
+                error: true,
+              };
+              onSendMessage?.(errorMessage);
+              return;
+            }
+          }
+        } catch (saveError) {
+          console.error('保存工作流时出错:', saveError);
+          const errorMessage = {
+            role: 'assistant',
+            content: `�?保存工作流时出错�?{saveError.message}`,
+            timestamp: new Date(),
+            error: true,
+          };
+          onSendMessage?.(errorMessage);
+          return;
+        }
+      }
+      
+      // 如果没有检测到工作流JSON,显示原始回�?
+      const gptMessage = {
+        role: 'assistant',
+        content: responseText || '抱歉,我无法理解您的请求�?,
+        timestamp: new Date(),
+      };
+      onSendMessage?.(gptMessage);
+    } catch (error) {
+      // 添加错误消息,显示更详细的错误信�?
+      let errorContent = '抱歉,发送消息时出现错误�?;
+      
+      if (error.message) {
+        if (error.message.includes('服务器错�?)) {
+          errorContent = error.message;
+        } else if (error.message.includes('HTTP 500')) {
+          errorContent = '服务器内部错误,请检�?API 端点是否正确,或联系服务器管理员�?;
+        } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
+          errorContent = '网络连接失败,请检查网络连接或服务器是否可访问�?;
+        } else {
+          errorContent = `错误�?{error.message}`;
+        }
+      }
+      
+      const errorMessage = {
+        role: 'assistant',
+        content: errorContent,
+        timestamp: new Date(),
+        error: true,
+      };
+      onSendMessage?.(errorMessage);
+    } finally {
+      // 结束加载
+      onLoadingChange?.(false);
+    }
+  };
+
+  return {
+    onSend,
+    onUpload,
+    uploadedImages,
+    removeImage,
+    workflowRequirements,
+    setWorkflowRequirements,
+    referenceTexts,
+    setReferenceTexts,
+  };
+}
+
+// 更新工作流JSON中的图片路径
+function updateImagePathsInWorkflow(workflowJson, imageNameMap) {
+  // 深拷贝,避免修改原始对象
+  const updated = JSON.parse(JSON.stringify(workflowJson));
+  
+  // 递归更新actions中的图片路径
+  function updateActions(actions) {
+    if (!Array.isArray(actions)) return;
+    
+    for (const action of actions) {
+      // locate �?click 操作可能使用图片
+      if ((action.type === 'locate' || action.type === 'click') && action.method === 'image' && action.target) {
+        const target = action.target;
+        // 检查是否是映射表中的文件名
+        if (imageNameMap[target]) {
+          action.target = imageNameMap[target];
+        } else {
+          // 尝试匹配 "1.png", "2.png" 等格�?
+          const match = target.match(/^(\d+)\.png$/i);
+          if (match) {
+            const index = parseInt(match[1]) - 1;
+            if (imageNameMap[`${index + 1}.png`]) {
+              action.target = imageNameMap[`${index + 1}.png`];
+            }
+          }
+        }
+      }
+      
+      // ocr 操作可能使用头像图片
+      if (action.type === 'ocr' && action.method === 'by-avatar' && action.avatar) {
+        const avatar = action.avatar;
+        if (imageNameMap[avatar]) {
+          action.avatar = imageNameMap[avatar];
+        } else {
+          const match = avatar.match(/^(\d+)\.png$/i);
+          if (match) {
+            const index = parseInt(match[1]) - 1;
+            if (imageNameMap[`${index + 1}.png`]) {
+              action.avatar = imageNameMap[`${index + 1}.png`];
+            }
+          }
+        }
+      }
+      
+      // 递归处理嵌套�?actions(if、for、while�?
+      if (action.then && Array.isArray(action.then)) {
+        updateActions(action.then);
+      }
+      if (action.else && Array.isArray(action.else)) {
+        updateActions(action.else);
+      }
+      if (action.body && Array.isArray(action.body)) {
+        updateActions(action.body);
+      }
+    }
+  }
+  
+  if (updated.actions && Array.isArray(updated.actions)) {
+    updateActions(updated.actions);
+  }
+  
+  return updated;
+}
+
+// 从文本中提取JSON格式的工作流(支持新旧格式)
+function extractWorkflowJsonFromText(text) {
+    if (!text) return null;
+    
+    // 尝试直接解析整个文本
+    try {
+      const parsed = JSON.parse(text.trim());
+      // 新格式:包含 actions 字段
+      if (parsed && typeof parsed === 'object' && Array.isArray(parsed.actions)) {
+        return parsed;
+      }
+      // 旧格式:直接是数�?
+      if (Array.isArray(parsed) && parsed.length > 0) {
+        return { actions: parsed };
+      }
+    } catch (e) {
+      // 不是纯JSON,继续尝试提�?
+    }
+    
+    // 尝试从代码块中提取JSON
+    const codeBlockMatch = text.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
+    if (codeBlockMatch) {
+      try {
+        const parsed = JSON.parse(codeBlockMatch[1]);
+        if (parsed && typeof parsed === 'object') {
+          // 新格式:包含 actions
+          if (Array.isArray(parsed.actions)) {
+          return parsed;
+          }
+          // 旧格式:直接是数�?
+          if (Array.isArray(parsed)) {
+            return { actions: parsed };
+          }
+        }
+      } catch (e) {
+        // 解析失败
+      }
+    }
+    
+    // 尝试查找JSON对象(查找包�?actions"的对象,支持多行�?
+    const jsonMatch = text.match(/\{[\s\S]*?"actions"[\s\S]*?\}/);
+    if (jsonMatch) {
+      try {
+        const parsed = JSON.parse(jsonMatch[0]);
+        if (parsed && typeof parsed === 'object' && Array.isArray(parsed.actions)) {
+          return parsed;
+        }
+      } catch (e) {
+        // 解析失败,尝试更宽松的匹�?
+        try {
+          // 尝试匹配整个JSON对象(包括嵌套结构)
+          const fullJsonMatch = text.match(/\{(?:[^{}]|(?:\{[^{}]*\}))*\}/);
+          if (fullJsonMatch) {
+            const parsed = JSON.parse(fullJsonMatch[0]);
+            if (parsed && typeof parsed === 'object' && Array.isArray(parsed.actions)) {
+              return parsed;
+            }
+          }
+        } catch (e2) {
+        // 解析失败
+        }
+      }
+    }
+    
+    return null;
+}

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

@@ -1,7 +1,7 @@
 /**
  * Func 标签:extract-chat-history
  *
- * 约定:src/pages/Processing/Func/ 目录下每个文件名就是一个"可用标签/能力"。
+ * 约定:src/pages/processing/func/ 目录下每个文件名就是一个"可用标签/能力"。
  * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
  *
  * 运行时真实执行逻辑由 ActionParser + electronAPI.extractChatHistory + main-js/func/extract-chat-history.js 实现。

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

@@ -1,7 +1,7 @@
 /**
  * Func 标签:save-chat-history
  *
- * 约定:src/pages/Processing/Func/ 目录下每个文件名就是一个“可用标签/能力”
+ * 约定:src/pages/processing/func/ 目录下每个文件名就是一个"可用标签/能力"
  * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
  *
  * 语义:把提取到的消息记录持久化保存到工作流目录 history 下。

+ 1 - 1
src/pages/Processing/Func/string-reg-location.js

@@ -1,7 +1,7 @@
 /**
  * Func 标签:string-reg-location
  *
- * 约定:src/pages/Processing/Func/ 目录下每个文件名就是一个“可用标签/能力”
+ * 约定:src/pages/processing/func/ 目录下每个文件名就是一个"可用标签/能力"
  * 本文件用于声明该标签存在(供文档/提示词/后续动态加载使用)。
  *
  * 语义:通过 OCR/文字匹配找到指定字符串所在位置并返回坐标(常用于定位/点击)。

+ 2 - 2
src/pages/home.jsx

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