# Gemini Flow ## 1) 登录校验 最小校验项: - 页面存在可输入提问的输入框 - 右上角有用户头像或账户入口 若未登录:提示用户先在 openclaw profile 浏览器中登录。 ## 2) 模型策略 优先级: 1. Gemini 3.1 Pro 2. 当前页面可见的次优 Pro/Advanced 模型 若切换失败,保留默认并告知用户。 ## 3) 按钮状态检测 `.send-button-container` 内的按钮通过 `aria-label` 区分三种状态: - **空闲(idle)**:aria-label 为麦克风相关,按钮 disabled,输入框为空。 - **可发送(ready)**:aria-label 为"发送"/"Send",输入框有内容。 - **生成中(loading)**:aria-label 为"停止"/"Stop",Gemini 正在输出。 使用方式: - `GeminiOps.getStatus()` → 返回 `{status: 'idle'|'ready'|'loading', label, disabled}` - `GeminiOps.pollStatus()` → 返回 `{status, label, pageVisible, ts}`,**毫秒级返回**,供调用端分段轮询 ### CDP 保活轮询(重要) **禁止**在页面内做长 Promise 等待(旧版 `waitForComplete` 已移除)。 正确做法:调用端每 8~10 秒 evaluate 一次 `GeminiOps.pollStatus()`,自行累计耗时并判断超时。 这确保 CDP WebSocket 通道持续有消息流量,避免被网关判定空闲而断开连接。 ## 4) 生图结果获取 > ⚠️ **严禁使用浏览器截图(screenshot)获取生成图片。** > - **默认流程**:通过 `src` URL 右键另存为(Save Image As)保存到本地,再发送给用户。 > - **高清流程**:仅当用户明确要求高清/原图时,才调用 `downloadLatestImage()` 点击原图下载按钮。 Gemini 一次只生成一张图片,流程上只关心**最新生成的那张**,历史图片不做处理。 调用 `GeminiOps.getLatestImage()` 获取最新一张生成图片。 ### DOM 结构 ```
``` ### 图片定位 - 选择器:`img.image.loaded` - `image` class = Gemini 的图片元素 - `loaded` class = 图片已渲染完成(未加载完的不会有此 class) - 两个 class 同时存在才算有效图片 - DOM 中可能存在多张历史图片,**取最后一个**即为最新生成 ### 下载按钮定位 - 从 `img` 向上找到最近的 `.image-container` 祖先容器 - 在容器内查找 `mat-icon[fonticon="download"]`(即下载原图按钮) - `getLatestImage()` 返回 `hasDownloadBtn` 字段标识是否有下载按钮 ### API 所有操作函数的返回值都包含 `debug` 字段,记录该次调用每一步的日志(含时间戳、步骤名、成功/失败、上下文详情),方便排查问题和改进策略。 - `GeminiOps.getLatestImage()` → 获取最新一张图片信息 ```json { "ok": true, "src": "https://lh3.googleusercontent.com/...", "alt": "AI 生成", "width": 1024, "height": 1024, "hasDownloadBtn": true, "debug": [ {"ts": 1710000000000, "fn": "getLatestImage", "step": "start", "ok": true}, {"ts": 1710000000001, "fn": "getLatestImage", "step": "query_imgs", "ok": true, "detail": {"totalFound": 1}}, {"ts": 1710000000002, "fn": "getLatestImage", "step": "picked_latest", "ok": true, "detail": {"index": 0, "src": "https://lh3.google..."}}, {"ts": 1710000000003, "fn": "getLatestImage", "step": "find_container", "ok": true}, {"ts": 1710000000004, "fn": "getLatestImage", "step": "find_download_btn", "ok": true} ] } ``` - `GeminiOps.downloadLatestImage()` → 点击最新图片的下载原图按钮(仅用户要求高清时) ```json {"ok": true, "src": "https://lh3.googleusercontent.com/...", "debug": [...]} ``` - `GeminiOps.extractImageBase64()` → **默认图片获取方式**,从 DOM 直接提取 Base64 ```json { "ok": true, "dataUrl": "data:image/png;base64,iVBORw0KGgo...", "width": 1024, "height": 1024, "method": "canvas", "debug": [...] } ``` 提取策略(自动选择,无需调用端干预): 1. **Canvas 提取**(优先):将已渲染的 `` 绘制到虚拟 Canvas,同步导出 `toDataURL('image/png')`。零网络请求,毫秒级完成。`method` 返回 `"canvas"`。 2. **Fetch fallback**:若 Canvas 因跨域 tainted 而报错,自动回退到页面内 `fetch(img.src)` → `blob` → `FileReader.readAsDataURL()`。`method` 返回 `"fetch"`。 > ⚠️ 该函数返回 **Promise**。CDP 调用时必须设置 `awaitPromise: true`: > ```js > // CDP Runtime.evaluate 示例 > { expression: "GeminiOps.extractImageBase64()", awaitPromise: true, returnByValue: true } > ``` 调用端拿到 `dataUrl` 后,去掉 `data:image/png;base64,` 前缀,解码为二进制存为 `.png` 文件即可。 - `GeminiOps.probe()` / `click()` / `fillPrompt()` / `pollStatus()` → 同样携带 `debug` 字段 - `GeminiOps.getDebugLog()` → 获取完整累积日志(不清空),用于事后排查 ```json {"log": [...], "count": 15} ``` ### debug 日志格式 每条日志条目: | 字段 | 类型 | 说明 | |---|---|---| | `ts` | number | 毫秒级时间戳 | | `fn` | string | 函数名,如 `click`、`getLatestImage` | | `step` | string | 步骤名,如 `start`、`find_container`、`clicked` | | `ok` | boolean | 该步骤是否成功 | | `detail` | object? | 可选,上下文信息(匹配的选择器、找到的元素数量等) | 调用端应将 `debug` 数组回传给用户,便于分析定位失败原因和优化选择器策略。 ``` ### 图片交付流程(重要) **默认流程(Base64 提取):** 1. 调用 `GeminiOps.getLatestImage()` 确认图片已渲染完成(`ok: true`) 2. 调用 `GeminiOps.extractImageBase64()` 提取图片数据(需 `awaitPromise: true`) 3. 去掉 `dataUrl` 的 `data:image/png;base64,` 前缀,解码为二进制,保存为 `.png` 文件 4. 将本地图片文件发送给用户 **高清流程(仅用户要求时):** 1. 调用 `GeminiOps.getLatestImage()` 确认图片已渲染完成 2. 调用 `GeminiOps.downloadLatestImage()` 点击原图下载按钮 3. 将下载到本地的高清原图文件发送给用户 > **严禁**在任何环节使用浏览器截图(screenshot)代替保存图片。 ### 回退 - `ok === false` → 页面可能还在渲染,等几秒再调一次 - 连续两次失败 → 做 snapshot 排查页面状态(snapshot 仅用于排查,不用于交付图片) ## 5) 用户提示文案(建议) - 开始生图: - `已收到,正在用 Gemini 给你绘图中 🎨` - 生成中超时: - `还在渲染中,我继续盯着,马上回你。` - 完成: - `画好了,给你发图啦~`