commit 23093a40818472b12c265d64c869c295d1bc0eb6 Author: WJZ_P <110795301+WJZ-P@users.noreply.github.com> Date: Thu Mar 12 23:25:42 2026 +0800 feat: bootstrap gemini web ops skill with image/text flows diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..d228017 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,57 @@ +--- +name: gemini-web-ops +description: 通过 Gemini 官网(gemini.google.com)执行问答与生图操作。用户提到“问问Gemini/让Gemini回答/去Gemini问”,或出现“生图/画图/绘图/nano banana/nanobanana/生成图片”等关键词时触发。默认使用可用模型中最强档(优先 Gemini 3.1 Pro),按任务切换文本问答或图片生成流程,并把结果回传给用户。 +--- + +# Gemini Web Ops + +## 核心规则 + +1. 使用 OpenClaw 内置浏览器,`profile="openclaw"`。 +2. 涉及生图关键词(如:生图、绘图、画一张、nano banana)时,优先用无头浏览器流程执行。 +3. 文本问答任务(如“问问Gemini xxx”)走 Gemini 文本提问链路。 +4. 默认模型:可用列表中最强模型,优先 `Gemini 3.1 Pro`。 +5. 执行生图后先向用户回报“正在绘图中”,完成后回传图片。 + +## 任务分流 + +- **文本问答**触发词:`问问Gemini`、`让Gemini回答`、`去Gemini问`。 +- **生图任务**触发词:`生图`、`画`、`绘图`、`海报`、`nano banana`、`nanobanana`、`image generation`。 +- 若请求含糊,先确认:是文本回答还是要出图。 + +## 标准执行流程 + +### A. 文本问答 +1. 打开 `https://gemini.google.com`。 +2. 校验登录态(头像/输入框可见)。 +3. 选择最强可用模型(优先 Gemini 3.1 Pro)。 +4. 将用户问题原样输入并发送。 +5. 等待完整输出,提炼后回传(必要时附原文要点)。 + +### B. 生图流程 +1. 打开 Gemini 页面并确认登录。 +2. 选择最强可用模型(优先 Gemini 3.1 Pro)。 +3. 将用户提示词原样输入。 +4. 开启/勾选图片生成能力(若 UI 有“生成图片/图片”开关)。 +5. 发送后立即通知用户:正在绘图中。 +6. 结果出现后: + - 优先用“下载原图”按钮获取原图。 + - 若无下载按钮或失败,可对图片右键另存(通常是标清图)。 +7. 把图片返回用户;若有多张,按顺序全部回传。 + +## 失败回退 + +1. 元素定位失败:刷新页面后重试一次。 +2. 模型不可用:降级到次优 Gemini 模型并告知。 +3. 生成超时:回报“仍在生成中”,继续等待一次;再次超时则请用户换短提示词。 + +## 低 token 优先策略 + +- 优先使用 `scripts/gemini_ui_shortcuts.js` 的快捷选择器。 +- 先 evaluate 批量动作,再 snapshot 精准兜底。 +- 避免高频全量快照。 + +## 参考 + +- 详细执行与回退:`references/gemini-flow.md` +- 关键词与路由:`references/intent-routing.md` diff --git a/references/gemini-flow.md b/references/gemini-flow.md new file mode 100644 index 0000000..17ea365 --- /dev/null +++ b/references/gemini-flow.md @@ -0,0 +1,34 @@ +# Gemini Flow + +## 1) 登录校验 + +最小校验项: +- 页面存在可输入提问的输入框 +- 右上角有用户头像或账户入口 + +若未登录:提示用户先在 openclaw profile 浏览器中登录。 + +## 2) 模型策略 + +优先级: +1. Gemini 3.1 Pro +2. 当前页面可见的次优 Pro/Advanced 模型 + +若切换失败,保留默认并告知用户。 + +## 3) 生图结果获取 + +优先顺序: +1. 图片右上角“下载原图” +2. 右键另存为(标清) + +下载到本地后再通过渠道回传。 + +## 4) 用户提示文案(建议) + +- 开始生图: + - `已收到,正在用 Gemini 给你绘图中 🎨` +- 生成中超时: + - `还在渲染中,我继续盯着,马上回你。` +- 完成: + - `画好了,给你发图啦~` diff --git a/references/intent-routing.md b/references/intent-routing.md new file mode 100644 index 0000000..63157df --- /dev/null +++ b/references/intent-routing.md @@ -0,0 +1,24 @@ +# Intent Routing + +## 文本问答触发 + +- 问问Gemini xxx +- 去Gemini帮我问 xxx +- 让Gemini总结 xxx + +动作:走文本问答链路。 + +## 生图触发 + +- 给我画一只小猫 +- 生图:未来城市海报 +- nano banana 风格来一张 + +动作:走生图链路,并先反馈“正在绘图中”。 + +## 混合请求 + +例如: +- 先问Gemini这个产品定位,再出一张封面图 + +动作:拆成两步执行,先问答后生图。 diff --git a/scripts/gemini_ui_shortcuts.js b/scripts/gemini_ui_shortcuts.js new file mode 100644 index 0000000..3115a1b --- /dev/null +++ b/scripts/gemini_ui_shortcuts.js @@ -0,0 +1,83 @@ +(function initGeminiOps(){ + const S = { + promptInput: [ + 'textarea[aria-label*="Gemini"]', + 'textarea[placeholder*="Gemini"]', + 'div[contenteditable="true"]' + ], + sendBtn: [ + 'button[aria-label*="发送"]', + 'button[aria-label*="Send"]', + 'button:has-text("发送")' + ], + imageToggle: [ + 'button:has-text("图片")', + 'button:has-text("生成图片")', + '[role="button"]:has-text("Image")' + ], + modelBtn: [ + 'button:has-text("Gemini")', + '[role="button"][aria-haspopup="menu"]' + ] + }; + + function visible(el){ + if(!el) return false; + const r=el.getBoundingClientRect(); + const st=getComputedStyle(el); + return r.width>0 && r.height>0 && st.display!=='none' && st.visibility!=='hidden'; + } + + function q(sel){ + try{ + if(sel.includes(':has-text(')){ + const m=sel.match(/^(.*):has-text\("(.*)"\)$/); + if(!m) return null; + const nodes=[...document.querySelectorAll(m[1]||'*')]; + return nodes.find(n=>visible(n)&&n.textContent?.includes(m[2]))||null; + } + return [...document.querySelectorAll(sel)].find(visible)||null; + }catch{return null;} + } + + function find(key){ + for(const s of (S[key]||[])){ + const el=q(s); + if(el) return el; + } + return null; + } + + function click(key){ + const el=find(key); + if(!el) return {ok:false,key,error:'not_found'}; + el.click(); + return {ok:true,key}; + } + + function fillPrompt(text){ + const el=find('promptInput'); + if(!el) return {ok:false,error:'prompt_not_found'}; + el.focus(); + if(el.tagName==='TEXTAREA'){ + el.value=text; + el.dispatchEvent(new Event('input',{bubbles:true})); + }else{ + document.execCommand('selectAll',false,null); + document.execCommand('insertText',false,text); + el.dispatchEvent(new Event('input',{bubbles:true})); + } + return {ok:true}; + } + + function probe(){ + return { + promptInput: !!find('promptInput'), + sendBtn: !!find('sendBtn'), + imageToggle: !!find('imageToggle'), + modelBtn: !!find('modelBtn') + }; + } + + window.GeminiOps = {probe, click, fillPrompt, selectors:S, version:'0.1.0'}; +})();