feat: 新增按钮状态检测与等待完成逻辑
This commit is contained in:
32
SKILL.md
32
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. 文本问答
|
### A. 文本问答
|
||||||
1. 打开 `https://gemini.google.com`。
|
1. 打开 `https://gemini.google.com`。
|
||||||
2. 校验登录态(头像/输入框可见)。
|
2. 校验登录态(头像/输入框可见)。
|
||||||
3. 选择最强可用模型(优先 Gemini 3.1 Pro)。
|
3. 新建会话:`click('newChatBtn')`,确保干净上下文。
|
||||||
4. 将用户问题原样输入并发送。
|
4. 选择最强可用模型(优先 Gemini 3.1 Pro)。
|
||||||
5. 等待完整输出,提炼后回传(必要时附原文要点)。
|
5. 将用户问题原样输入并发送。
|
||||||
|
6. 调用 `waitForComplete()` 等待状态从 `loading` 变回 `idle`。
|
||||||
|
7. 等待完整输出,提炼后回传(必要时附原文要点)。
|
||||||
|
|
||||||
### B. 生图流程
|
### B. 生图流程
|
||||||
1. 打开 Gemini 页面并确认登录。
|
1. 打开 Gemini 页面并确认登录。
|
||||||
2. 选择最强可用模型(优先 Gemini 3.1 Pro)。
|
2. 新建会话:`click('newChatBtn')`,确保干净上下文。
|
||||||
3. 将用户提示词原样输入。
|
3. 选择最强可用模型(优先 Gemini 3.1 Pro)。
|
||||||
4. 发送后立即通知用户:正在绘图中。
|
4. 将用户提示词原样输入。
|
||||||
6. 结果出现后:
|
5. 发送后立即通知用户:正在绘图中。
|
||||||
|
6. 调用 `waitForComplete()` 等待生成完毕(生图默认超时 120s)。
|
||||||
|
7. 结果出现后:
|
||||||
- 优先用"下载原图"按钮获取原图。
|
- 优先用"下载原图"按钮获取原图。
|
||||||
- 若无下载按钮或失败,可对图片右键另存(通常是标清图)。
|
- 若无下载按钮或失败,可对图片右键另存(通常是标清图)。
|
||||||
7. 把图片返回用户;若有多张,按顺序全部回传。
|
8. 把图片返回用户;若有多张,按顺序全部回传。
|
||||||
|
|
||||||
## 失败回退
|
## 失败回退
|
||||||
|
|
||||||
|
|||||||
@@ -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. 右键另存为(标清)
|
2. 右键另存为(标清)
|
||||||
|
|
||||||
下载到本地后再通过渠道回传。
|
下载到本地后再通过渠道回传。
|
||||||
|
|
||||||
## 4) 用户提示文案(建议)
|
## 5) 用户提示文案(建议)
|
||||||
|
|
||||||
- 开始生图:
|
- 开始生图:
|
||||||
- `已收到,正在用 Gemini 给你绘图中 🎨`
|
- `已收到,正在用 Gemini 给你绘图中 🎨`
|
||||||
|
|||||||
@@ -6,10 +6,15 @@
|
|||||||
'[contenteditable="true"][data-placeholder*="Gemini"]',
|
'[contenteditable="true"][data-placeholder*="Gemini"]',
|
||||||
'div[contenteditable="true"][role="textbox"]'
|
'div[contenteditable="true"][role="textbox"]'
|
||||||
],
|
],
|
||||||
sendBtn: [
|
actionBtn: [
|
||||||
'button[aria-label*="发送"]',
|
'.send-button-container button.send-button',
|
||||||
'button[aria-label*="Send"]',
|
'.send-button-container button'
|
||||||
'button:has-text("发送")'
|
],
|
||||||
|
newChatBtn: [
|
||||||
|
'[data-test-id="new-chat-button"] a',
|
||||||
|
'[data-test-id="new-chat-button"]',
|
||||||
|
'a[aria-label="发起新对话"]',
|
||||||
|
'a[aria-label*="new chat" i]'
|
||||||
],
|
],
|
||||||
modelBtn: [
|
modelBtn: [
|
||||||
'button:has-text("Gemini")',
|
'button:has-text("Gemini")',
|
||||||
@@ -66,13 +71,47 @@
|
|||||||
return {ok:true};
|
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(){
|
function probe(){
|
||||||
|
var s=getStatus();
|
||||||
return {
|
return {
|
||||||
promptInput: !!find('promptInput'),
|
promptInput: !!find('promptInput'),
|
||||||
sendBtn: !!find('sendBtn'),
|
actionBtn: !!find('actionBtn'),
|
||||||
modelBtn: !!find('modelBtn')
|
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'};
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user