diff --git a/SKILL.md b/SKILL.md index 8a95e36..8994dac 100644 --- a/SKILL.md +++ b/SKILL.md @@ -21,22 +21,38 @@ description: 通过 Gemini 官网(gemini.google.com)执行问答与生图操 ## 标准执行流程 +### 按钮状态机 + +Gemini 页面的操作按钮(`.send-button-container` 内)通过 `aria-label` 反映当前状态: + +| aria-label | 状态 | 含义 | +|---|---|---| +| 麦克风 | `idle` | 输入框为空,空闲中 | +| 发送 / Send | `ready` | 输入框有内容,可发送 | +| 停止 / Stop | `loading` | 已发送,正在生成回答 | + +可通过 `GeminiOps.getStatus()` 获取当前状态,通过 `GeminiOps.waitForComplete()` 轮询等待生成完毕。 + ### A. 文本问答 1. 打开 `https://gemini.google.com`。 2. 校验登录态(头像/输入框可见)。 -3. 选择最强可用模型(优先 Gemini 3.1 Pro)。 -4. 将用户问题原样输入并发送。 -5. 等待完整输出,提炼后回传(必要时附原文要点)。 +3. 新建会话:`click('newChatBtn')`,确保干净上下文。 +4. 选择最强可用模型(优先 Gemini 3.1 Pro)。 +5. 将用户问题原样输入并发送。 +6. 调用 `waitForComplete()` 等待状态从 `loading` 变回 `idle`。 +7. 等待完整输出,提炼后回传(必要时附原文要点)。 ### B. 生图流程 1. 打开 Gemini 页面并确认登录。 -2. 选择最强可用模型(优先 Gemini 3.1 Pro)。 -3. 将用户提示词原样输入。 -4. 发送后立即通知用户:正在绘图中。 -6. 结果出现后: +2. 新建会话:`click('newChatBtn')`,确保干净上下文。 +3. 选择最强可用模型(优先 Gemini 3.1 Pro)。 +4. 将用户提示词原样输入。 +5. 发送后立即通知用户:正在绘图中。 +6. 调用 `waitForComplete()` 等待生成完毕(生图默认超时 120s)。 +7. 结果出现后: - 优先用"下载原图"按钮获取原图。 - 若无下载按钮或失败,可对图片右键另存(通常是标清图)。 -7. 把图片返回用户;若有多张,按顺序全部回传。 +8. 把图片返回用户;若有多张,按顺序全部回传。 ## 失败回退 diff --git a/references/gemini-flow.md b/references/gemini-flow.md index 17ea365..6b7f83e 100644 --- a/references/gemini-flow.md +++ b/references/gemini-flow.md @@ -16,15 +16,27 @@ 若切换失败,保留默认并告知用户。 -## 3) 生图结果获取 +## 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.waitForComplete(timeout, interval)` → 返回 Promise,状态脱离 `loading` 后 resolve + +## 4) 生图结果获取 优先顺序: -1. 图片右上角“下载原图” +1. 图片右上角"下载原图" 2. 右键另存为(标清) 下载到本地后再通过渠道回传。 -## 4) 用户提示文案(建议) +## 5) 用户提示文案(建议) - 开始生图: - `已收到,正在用 Gemini 给你绘图中 🎨` diff --git a/scripts/gemini_ui_shortcuts.js b/scripts/gemini_ui_shortcuts.js index 09f066f..2553269 100644 --- a/scripts/gemini_ui_shortcuts.js +++ b/scripts/gemini_ui_shortcuts.js @@ -6,10 +6,15 @@ '[contenteditable="true"][data-placeholder*="Gemini"]', 'div[contenteditable="true"][role="textbox"]' ], - sendBtn: [ - 'button[aria-label*="发送"]', - 'button[aria-label*="Send"]', - 'button:has-text("发送")' + actionBtn: [ + '.send-button-container button.send-button', + '.send-button-container button' + ], + newChatBtn: [ + '[data-test-id="new-chat-button"] a', + '[data-test-id="new-chat-button"]', + 'a[aria-label="发起新对话"]', + 'a[aria-label*="new chat" i]' ], modelBtn: [ 'button:has-text("Gemini")', @@ -66,13 +71,47 @@ return {ok:true}; } + function getStatus(){ + const btn=find('actionBtn'); + if(!btn) return {status:'unknown',error:'btn_not_found'}; + const label=(btn.getAttribute('aria-label')||'').trim(); + const disabled=btn.getAttribute('aria-disabled')==='true'; + if(/停止|Stop/i.test(label)) return {status:'loading',label}; + if(/发送|Send|Submit/i.test(label)) return {status:'ready',label,disabled}; + return {status:'idle',label,disabled}; + } + + function waitForComplete(timeout,interval){ + timeout=timeout||120000; + interval=interval||2000; + return new Promise(function(resolve){ + var elapsed=0; + var timer=setInterval(function(){ + elapsed+=interval; + var s=getStatus(); + if(s.status!=='loading'){ + clearInterval(timer); + resolve({ok:true,status:s.status,elapsed}); + return; + } + if(elapsed>=timeout){ + clearInterval(timer); + resolve({ok:false,status:'timeout',elapsed}); + } + },interval); + }); + } + function probe(){ + var s=getStatus(); return { promptInput: !!find('promptInput'), - sendBtn: !!find('sendBtn'), - modelBtn: !!find('modelBtn') + actionBtn: !!find('actionBtn'), + newChatBtn: !!find('newChatBtn'), + modelBtn: !!find('modelBtn'), + status: s.status }; } - window.GeminiOps = {probe, click, fillPrompt, selectors:S, version:'0.1.0'}; + window.GeminiOps = {probe, click, fillPrompt, getStatus, waitForComplete, selectors:S, version:'0.3.0'}; })();