@jackwener/opencli 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/bun.lock +615 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1133 -182
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/twitter/search.js +67 -5
- package/dist/clis/twitter/search.test.js +83 -5
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/twitter/search.test.ts +88 -5
- package/src/clis/twitter/search.ts +68 -5
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CommandExecutionError } from '../../errors.js';
|
|
1
2
|
export const GEMINI_DOMAIN = 'gemini.google.com';
|
|
2
3
|
export const GEMINI_APP_URL = 'https://gemini.google.com/app';
|
|
3
4
|
const GEMINI_RESPONSE_NOISE_PATTERNS = [
|
|
@@ -6,6 +7,57 @@ const GEMINI_RESPONSE_NOISE_PATTERNS = [
|
|
|
6
7
|
/Google Privacy Policy/gi,
|
|
7
8
|
/Opens in a new window/gi,
|
|
8
9
|
];
|
|
10
|
+
const GEMINI_TRANSCRIPT_CHROME_MARKERS = ['gemini', '我的内容', '对话', 'google terms', 'google privacy policy'];
|
|
11
|
+
const GEMINI_COMPOSER_SELECTORS = [
|
|
12
|
+
'.ql-editor[contenteditable="true"]',
|
|
13
|
+
'.ql-editor[role="textbox"]',
|
|
14
|
+
'.ql-editor[aria-label*="Gemini"]',
|
|
15
|
+
'[contenteditable="true"][aria-label*="Gemini"]',
|
|
16
|
+
'[aria-label="Enter a prompt for Gemini"]',
|
|
17
|
+
'[aria-label*="prompt for Gemini"]',
|
|
18
|
+
];
|
|
19
|
+
const GEMINI_COMPOSER_MARKER_ATTR = 'data-opencli-gemini-composer';
|
|
20
|
+
const GEMINI_COMPOSER_PREPARE_ATTEMPTS = 4;
|
|
21
|
+
const GEMINI_COMPOSER_PREPARE_WAIT_SECONDS = 1;
|
|
22
|
+
function buildGeminiComposerLocatorScript() {
|
|
23
|
+
const selectorsJson = JSON.stringify(GEMINI_COMPOSER_SELECTORS);
|
|
24
|
+
const markerAttrJson = JSON.stringify(GEMINI_COMPOSER_MARKER_ATTR);
|
|
25
|
+
return `
|
|
26
|
+
const isVisible = (el) => {
|
|
27
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
28
|
+
const style = window.getComputedStyle(el);
|
|
29
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
30
|
+
const rect = el.getBoundingClientRect();
|
|
31
|
+
return rect.width > 0 && rect.height > 0;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const markerAttr = ${markerAttrJson};
|
|
35
|
+
const clearComposerMarkers = (active) => {
|
|
36
|
+
document.querySelectorAll('[' + markerAttr + ']').forEach((node) => {
|
|
37
|
+
if (node !== active) node.removeAttribute(markerAttr);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const markComposer = (node) => {
|
|
42
|
+
if (!(node instanceof HTMLElement)) return null;
|
|
43
|
+
clearComposerMarkers(node);
|
|
44
|
+
node.setAttribute(markerAttr, '1');
|
|
45
|
+
return node;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const findComposer = () => {
|
|
49
|
+
const marked = document.querySelector('[' + markerAttr + '="1"]');
|
|
50
|
+
if (marked instanceof HTMLElement && isVisible(marked)) return marked;
|
|
51
|
+
|
|
52
|
+
const selectors = ${selectorsJson};
|
|
53
|
+
for (const selector of selectors) {
|
|
54
|
+
const node = Array.from(document.querySelectorAll(selector)).find((candidate) => candidate instanceof HTMLElement && isVisible(candidate));
|
|
55
|
+
if (node instanceof HTMLElement) return markComposer(node);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
};
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
9
61
|
export function sanitizeGeminiResponseText(value, promptText) {
|
|
10
62
|
let sanitized = value;
|
|
11
63
|
for (const pattern of GEMINI_RESPONSE_NOISE_PATTERNS) {
|
|
@@ -29,31 +81,146 @@ export function collectGeminiTranscriptAdditions(beforeLines, currentLines, prom
|
|
|
29
81
|
const beforeSet = new Set(beforeLines);
|
|
30
82
|
const additions = currentLines
|
|
31
83
|
.filter((line) => !beforeSet.has(line))
|
|
32
|
-
.map((line) =>
|
|
84
|
+
.map((line) => extractGeminiTranscriptLineCandidate(line, promptText))
|
|
33
85
|
.filter((line) => line && line !== promptText);
|
|
34
86
|
return additions.join('\n').trim();
|
|
35
87
|
}
|
|
88
|
+
export function collapseAdjacentGeminiTurns(turns) {
|
|
89
|
+
const collapsed = [];
|
|
90
|
+
for (const turn of turns) {
|
|
91
|
+
if (!turn || typeof turn.Role !== 'string' || typeof turn.Text !== 'string')
|
|
92
|
+
continue;
|
|
93
|
+
const previous = collapsed.at(-1);
|
|
94
|
+
if (previous?.Role === turn.Role && previous.Text === turn.Text)
|
|
95
|
+
continue;
|
|
96
|
+
collapsed.push(turn);
|
|
97
|
+
}
|
|
98
|
+
return collapsed;
|
|
99
|
+
}
|
|
100
|
+
function hasGeminiTurnPrefix(before, current) {
|
|
101
|
+
if (before.length > current.length)
|
|
102
|
+
return false;
|
|
103
|
+
return before.every((turn, index) => (turn.Role === current[index]?.Role
|
|
104
|
+
&& turn.Text === current[index]?.Text));
|
|
105
|
+
}
|
|
106
|
+
function findLastMatchingGeminiTurnIndex(turns, target) {
|
|
107
|
+
if (!target)
|
|
108
|
+
return null;
|
|
109
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
110
|
+
const turn = turns[index];
|
|
111
|
+
if (turn?.Role === target.Role && turn.Text === target.Text) {
|
|
112
|
+
return index;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function diffTrustedStructuredTurns(before, current) {
|
|
118
|
+
if (!before.structuredTurnsTrusted || !current.structuredTurnsTrusted) {
|
|
119
|
+
return {
|
|
120
|
+
appendedTurns: [],
|
|
121
|
+
hasTrustedAppend: false,
|
|
122
|
+
hasNewUserTurn: false,
|
|
123
|
+
hasNewAssistantTurn: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!hasGeminiTurnPrefix(before.turns, current.turns)) {
|
|
127
|
+
return {
|
|
128
|
+
appendedTurns: [],
|
|
129
|
+
hasTrustedAppend: false,
|
|
130
|
+
hasNewUserTurn: false,
|
|
131
|
+
hasNewAssistantTurn: false,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const appendedTurns = current.turns.slice(before.turns.length);
|
|
135
|
+
return {
|
|
136
|
+
appendedTurns,
|
|
137
|
+
hasTrustedAppend: appendedTurns.length > 0,
|
|
138
|
+
hasNewUserTurn: appendedTurns.some((turn) => turn.Role === 'User'),
|
|
139
|
+
hasNewAssistantTurn: appendedTurns.some((turn) => turn.Role === 'Assistant'),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function diffTranscriptLines(before, current) {
|
|
143
|
+
const beforeLines = new Set(before.transcriptLines);
|
|
144
|
+
return current.transcriptLines.filter((line) => !beforeLines.has(line));
|
|
145
|
+
}
|
|
146
|
+
function isLikelyGeminiTranscriptChrome(line) {
|
|
147
|
+
const lower = line.toLowerCase();
|
|
148
|
+
const markerHits = GEMINI_TRANSCRIPT_CHROME_MARKERS.filter((marker) => lower.includes(marker)).length;
|
|
149
|
+
return markerHits >= 2;
|
|
150
|
+
}
|
|
151
|
+
function extractGeminiTranscriptLineCandidate(transcriptLine, promptText) {
|
|
152
|
+
const candidate = transcriptLine.trim();
|
|
153
|
+
if (!candidate)
|
|
154
|
+
return '';
|
|
155
|
+
const prompt = promptText.trim();
|
|
156
|
+
const sanitized = sanitizeGeminiResponseText(candidate, promptText);
|
|
157
|
+
if (!prompt)
|
|
158
|
+
return sanitized;
|
|
159
|
+
if (!candidate.includes(prompt))
|
|
160
|
+
return sanitized;
|
|
161
|
+
if (sanitized && sanitized !== prompt && sanitized !== candidate)
|
|
162
|
+
return sanitized;
|
|
163
|
+
if (isLikelyGeminiTranscriptChrome(candidate))
|
|
164
|
+
return '';
|
|
165
|
+
// Some transcript snapshots flatten "prompt + answer" into a single line.
|
|
166
|
+
// Recover the answer only when the line starts with the current prompt.
|
|
167
|
+
if (candidate.startsWith(prompt)) {
|
|
168
|
+
const tail = candidate.slice(prompt.length).replace(/^[\s::,,-]+/, '').trim();
|
|
169
|
+
return tail ? sanitizeGeminiResponseText(tail, '') : '';
|
|
170
|
+
}
|
|
171
|
+
return sanitized;
|
|
172
|
+
}
|
|
36
173
|
function getStateScript() {
|
|
37
174
|
return `
|
|
38
175
|
(() => {
|
|
176
|
+
${buildGeminiComposerLocatorScript()}
|
|
177
|
+
|
|
39
178
|
const signInNode = Array.from(document.querySelectorAll('a, button')).find((node) => {
|
|
40
179
|
const text = (node.textContent || '').trim().toLowerCase();
|
|
41
180
|
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
42
181
|
const href = node.getAttribute('href') || '';
|
|
43
182
|
return text === 'sign in'
|
|
44
183
|
|| aria === 'sign in'
|
|
184
|
+
|| text === '登录'
|
|
185
|
+
|| aria === '登录'
|
|
45
186
|
|| href.includes('accounts.google.com/ServiceLogin');
|
|
46
187
|
});
|
|
47
188
|
|
|
48
|
-
const composer =
|
|
49
|
-
const sendButton = document.querySelector('button[aria-label="Send message"]');
|
|
189
|
+
const composer = findComposer();
|
|
50
190
|
|
|
51
191
|
return {
|
|
52
192
|
url: window.location.href,
|
|
53
193
|
title: document.title || '',
|
|
54
194
|
isSignedIn: signInNode ? false : (composer ? true : null),
|
|
55
195
|
composerLabel: composer?.getAttribute('aria-label') || '',
|
|
56
|
-
canSend: !!
|
|
196
|
+
canSend: !!composer,
|
|
197
|
+
};
|
|
198
|
+
})()
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
function readGeminiSnapshotScript() {
|
|
202
|
+
return `
|
|
203
|
+
(() => {
|
|
204
|
+
${buildGeminiComposerLocatorScript()}
|
|
205
|
+
const composer = findComposer();
|
|
206
|
+
const composerText = composer?.textContent?.replace(/\\u00a0/g, ' ').trim() || '';
|
|
207
|
+
const isGenerating = !!Array.from(document.querySelectorAll('button, [role="button"]')).find((node) => {
|
|
208
|
+
const text = (node.textContent || '').trim().toLowerCase();
|
|
209
|
+
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
210
|
+
return text === 'stop response'
|
|
211
|
+
|| aria === 'stop response'
|
|
212
|
+
|| text === '停止回答'
|
|
213
|
+
|| aria === '停止回答';
|
|
214
|
+
});
|
|
215
|
+
const turns = ${getTurnsScript().trim()};
|
|
216
|
+
const transcriptLines = ${getTranscriptLinesScript().trim()};
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
turns,
|
|
220
|
+
transcriptLines,
|
|
221
|
+
composerHasText: composerText.length > 0,
|
|
222
|
+
isGenerating,
|
|
223
|
+
structuredTurnsTrusted: turns.length > 0 || transcriptLines.length === 0,
|
|
57
224
|
};
|
|
58
225
|
})()
|
|
59
226
|
`;
|
|
@@ -159,7 +326,16 @@ function getTurnsScript() {
|
|
|
159
326
|
];
|
|
160
327
|
|
|
161
328
|
const roots = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector)));
|
|
162
|
-
const unique = roots
|
|
329
|
+
const unique = roots
|
|
330
|
+
.filter((el, index, all) => all.indexOf(el) === index)
|
|
331
|
+
.filter(isVisible)
|
|
332
|
+
.sort((left, right) => {
|
|
333
|
+
if (left === right) return 0;
|
|
334
|
+
const relation = left.compareDocumentPosition(right);
|
|
335
|
+
if (relation & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
336
|
+
if (relation & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
337
|
+
return 0;
|
|
338
|
+
});
|
|
163
339
|
|
|
164
340
|
const turns = unique.map((el) => {
|
|
165
341
|
const text = clean(el.innerText || el.textContent || '');
|
|
@@ -179,52 +355,184 @@ function getTurnsScript() {
|
|
|
179
355
|
return role ? { Role: role, Text: text } : null;
|
|
180
356
|
}).filter(Boolean);
|
|
181
357
|
|
|
182
|
-
|
|
183
|
-
const seen = new Set();
|
|
184
|
-
for (const turn of turns) {
|
|
185
|
-
const key = turn.Role + '::' + turn.Text;
|
|
186
|
-
if (seen.has(key)) continue;
|
|
187
|
-
seen.add(key);
|
|
188
|
-
deduped.push(turn);
|
|
189
|
-
}
|
|
190
|
-
return deduped;
|
|
358
|
+
return turns;
|
|
191
359
|
})()
|
|
192
360
|
`;
|
|
193
361
|
}
|
|
194
|
-
function
|
|
362
|
+
function prepareComposerScript() {
|
|
195
363
|
return `
|
|
196
|
-
((
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
364
|
+
(() => {
|
|
365
|
+
${buildGeminiComposerLocatorScript()}
|
|
366
|
+
const composer = findComposer();
|
|
367
|
+
|
|
368
|
+
if (!(composer instanceof HTMLElement)) {
|
|
369
|
+
return { ok: false, reason: 'Could not find Gemini composer' };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
composer.focus();
|
|
200
374
|
const selection = window.getSelection();
|
|
201
375
|
const range = document.createRange();
|
|
202
|
-
range.selectNodeContents(
|
|
376
|
+
range.selectNodeContents(composer);
|
|
203
377
|
range.collapse(false);
|
|
204
378
|
selection?.removeAllRanges();
|
|
205
379
|
selection?.addRange(range);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
380
|
+
composer.textContent = '';
|
|
381
|
+
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: '', inputType: 'deleteContentBackward' }));
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return {
|
|
384
|
+
ok: false,
|
|
385
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
ok: true,
|
|
391
|
+
label: composer.getAttribute('aria-label') || '',
|
|
209
392
|
};
|
|
393
|
+
})()
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
function composerHasTextScript() {
|
|
397
|
+
return `
|
|
398
|
+
(() => {
|
|
399
|
+
${buildGeminiComposerLocatorScript()}
|
|
400
|
+
const composer = findComposer();
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
hasText: !!(composer && ((composer.textContent || '').trim() || (composer.innerText || '').trim())),
|
|
404
|
+
};
|
|
405
|
+
})()
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
function insertComposerTextFallbackScript(text) {
|
|
409
|
+
return `
|
|
410
|
+
((inputText) => {
|
|
411
|
+
${buildGeminiComposerLocatorScript()}
|
|
412
|
+
const composer = findComposer();
|
|
413
|
+
|
|
414
|
+
if (!(composer instanceof HTMLElement)) {
|
|
415
|
+
return { hasText: false, reason: 'Could not find Gemini composer' };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const selection = window.getSelection();
|
|
419
|
+
const range = document.createRange();
|
|
420
|
+
range.selectNodeContents(composer);
|
|
421
|
+
range.collapse(false);
|
|
422
|
+
selection?.removeAllRanges();
|
|
423
|
+
selection?.addRange(range);
|
|
424
|
+
|
|
425
|
+
composer.focus();
|
|
426
|
+
composer.textContent = '';
|
|
427
|
+
const execResult = typeof document.execCommand === 'function'
|
|
428
|
+
? document.execCommand('insertText', false, inputText)
|
|
429
|
+
: false;
|
|
430
|
+
|
|
431
|
+
if (!execResult) {
|
|
432
|
+
const paragraph = document.createElement('p');
|
|
433
|
+
const lines = String(inputText).split(/\\n/);
|
|
434
|
+
for (const [index, line] of lines.entries()) {
|
|
435
|
+
if (index > 0) paragraph.appendChild(document.createElement('br'));
|
|
436
|
+
paragraph.appendChild(document.createTextNode(line));
|
|
437
|
+
}
|
|
438
|
+
composer.replaceChildren(paragraph);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
composer.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, data: inputText, inputType: 'insertText' }));
|
|
442
|
+
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: inputText, inputType: 'insertText' }));
|
|
443
|
+
composer.dispatchEvent(new Event('change', { bubbles: true }));
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
hasText: !!((composer.textContent || '').trim() || (composer.innerText || '').trim()),
|
|
447
|
+
};
|
|
448
|
+
})(${JSON.stringify(text)})
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
function submitComposerScript() {
|
|
452
|
+
return `
|
|
453
|
+
(() => {
|
|
454
|
+
${buildGeminiComposerLocatorScript()}
|
|
455
|
+
const composer = findComposer();
|
|
210
456
|
|
|
211
|
-
const composer = document.querySelector('[aria-label="Enter a prompt for Gemini"], [aria-label*="prompt for Gemini"], .ql-editor[aria-label*="Gemini"], [contenteditable="true"][aria-label*="Gemini"]');
|
|
212
457
|
if (!(composer instanceof HTMLElement)) {
|
|
213
458
|
throw new Error('Could not find Gemini composer');
|
|
214
459
|
}
|
|
215
460
|
|
|
216
|
-
|
|
461
|
+
const composerRect = composer.getBoundingClientRect();
|
|
462
|
+
const rootCandidates = [
|
|
463
|
+
composer.closest('form'),
|
|
464
|
+
composer.closest('[role="form"]'),
|
|
465
|
+
composer.closest('.input-area-container'),
|
|
466
|
+
composer.closest('.textbox-container'),
|
|
467
|
+
composer.closest('.input-wrapper'),
|
|
468
|
+
composer.parentElement,
|
|
469
|
+
composer.parentElement?.parentElement,
|
|
470
|
+
].filter(Boolean);
|
|
471
|
+
|
|
472
|
+
const seen = new Set();
|
|
473
|
+
const buttons = [];
|
|
474
|
+
for (const root of rootCandidates) {
|
|
475
|
+
root.querySelectorAll('button, [role="button"]').forEach((node) => {
|
|
476
|
+
if (!(node instanceof HTMLElement)) return;
|
|
477
|
+
if (seen.has(node)) return;
|
|
478
|
+
seen.add(node);
|
|
479
|
+
buttons.push(node);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const excludedPattern = /main menu|主菜单|microphone|麦克风|upload|上传|mode|模式|tools|工具|settings|临时对话|new chat|新对话/i;
|
|
484
|
+
const submitPattern = /send|发送|submit|提交/i;
|
|
485
|
+
let bestButton = null;
|
|
486
|
+
let bestScore = -1;
|
|
487
|
+
|
|
488
|
+
for (const button of buttons) {
|
|
489
|
+
if (!isVisible(button)) continue;
|
|
490
|
+
if (button instanceof HTMLButtonElement && button.disabled) continue;
|
|
491
|
+
if (button.getAttribute('aria-disabled') === 'true') continue;
|
|
492
|
+
|
|
493
|
+
const label = ((button.getAttribute('aria-label') || '') + ' ' + ((button.textContent || '').trim())).trim();
|
|
494
|
+
if (excludedPattern.test(label)) continue;
|
|
495
|
+
|
|
496
|
+
const rect = button.getBoundingClientRect();
|
|
497
|
+
const verticalDistance = Math.abs((rect.top + rect.bottom) / 2 - (composerRect.top + composerRect.bottom) / 2);
|
|
498
|
+
if (verticalDistance > 160) continue;
|
|
499
|
+
|
|
500
|
+
let score = 0;
|
|
501
|
+
if (submitPattern.test(label)) score += 10;
|
|
502
|
+
if (rect.left >= composerRect.right - 160) score += 3;
|
|
503
|
+
if (rect.left >= composerRect.left) score += 1;
|
|
504
|
+
if (rect.width <= 96 && rect.height <= 96) score += 1;
|
|
217
505
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
506
|
+
if (score > bestScore) {
|
|
507
|
+
bestScore = score;
|
|
508
|
+
bestButton = button;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (bestButton instanceof HTMLElement && bestScore >= 3) {
|
|
513
|
+
bestButton.click();
|
|
221
514
|
return 'button';
|
|
222
515
|
}
|
|
223
516
|
|
|
224
|
-
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
225
|
-
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
226
517
|
return 'enter';
|
|
227
|
-
})(
|
|
518
|
+
})()
|
|
519
|
+
`;
|
|
520
|
+
}
|
|
521
|
+
function dispatchComposerEnterScript() {
|
|
522
|
+
return `
|
|
523
|
+
(() => {
|
|
524
|
+
${buildGeminiComposerLocatorScript()}
|
|
525
|
+
const composer = findComposer();
|
|
526
|
+
|
|
527
|
+
if (!(composer instanceof HTMLElement)) {
|
|
528
|
+
throw new Error('Could not find Gemini composer');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
composer.focus();
|
|
532
|
+
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
533
|
+
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
534
|
+
return 'enter';
|
|
535
|
+
})()
|
|
228
536
|
`;
|
|
229
537
|
}
|
|
230
538
|
function clickNewChatScript() {
|
|
@@ -241,7 +549,14 @@ function clickNewChatScript() {
|
|
|
241
549
|
const candidates = Array.from(document.querySelectorAll('button, a')).filter((node) => {
|
|
242
550
|
const text = (node.textContent || '').trim().toLowerCase();
|
|
243
551
|
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
244
|
-
return isVisible(node) && (
|
|
552
|
+
return isVisible(node) && (
|
|
553
|
+
text === 'new chat'
|
|
554
|
+
|| aria === 'new chat'
|
|
555
|
+
|| text === '发起新对话'
|
|
556
|
+
|| aria === '发起新对话'
|
|
557
|
+
|| text === '新对话'
|
|
558
|
+
|| aria === '新对话'
|
|
559
|
+
);
|
|
245
560
|
});
|
|
246
561
|
|
|
247
562
|
const target = candidates.find((node) => !node.hasAttribute('disabled')) || candidates[0];
|
|
@@ -288,23 +603,133 @@ export async function startNewGeminiChat(page) {
|
|
|
288
603
|
return action;
|
|
289
604
|
}
|
|
290
605
|
export async function getGeminiVisibleTurns(page) {
|
|
291
|
-
await
|
|
292
|
-
const turns = await page.evaluate(getTurnsScript());
|
|
606
|
+
const turns = await getGeminiStructuredTurns(page);
|
|
293
607
|
if (Array.isArray(turns) && turns.length > 0)
|
|
294
608
|
return turns;
|
|
295
609
|
const lines = await getGeminiTranscriptLines(page);
|
|
296
610
|
return lines.map((line) => ({ Role: 'System', Text: line }));
|
|
297
611
|
}
|
|
612
|
+
async function getGeminiStructuredTurns(page) {
|
|
613
|
+
await ensureGeminiPage(page);
|
|
614
|
+
const turns = collapseAdjacentGeminiTurns(await page.evaluate(getTurnsScript()));
|
|
615
|
+
return Array.isArray(turns) ? turns : [];
|
|
616
|
+
}
|
|
298
617
|
export async function getGeminiTranscriptLines(page) {
|
|
299
618
|
await ensureGeminiPage(page);
|
|
300
619
|
return await page.evaluate(getTranscriptLinesScript());
|
|
301
620
|
}
|
|
621
|
+
export async function readGeminiSnapshot(page) {
|
|
622
|
+
await ensureGeminiPage(page);
|
|
623
|
+
return await page.evaluate(readGeminiSnapshotScript());
|
|
624
|
+
}
|
|
625
|
+
function findLastUserTurnIndex(turns) {
|
|
626
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
627
|
+
if (turns[index]?.Role === 'User')
|
|
628
|
+
return index;
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
function findLastUserTurn(turns) {
|
|
633
|
+
const index = findLastUserTurnIndex(turns);
|
|
634
|
+
return index === null ? null : turns[index] ?? null;
|
|
635
|
+
}
|
|
636
|
+
export async function waitForGeminiSubmission(page, before, timeoutSeconds) {
|
|
637
|
+
const preSendAssistantCount = before.turns.filter((turn) => turn.Role === 'Assistant').length;
|
|
638
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutSeconds));
|
|
639
|
+
for (let index = 0; index < maxPolls; index += 1) {
|
|
640
|
+
await page.wait(index === 0 ? 0.5 : 1);
|
|
641
|
+
const current = await readGeminiSnapshot(page);
|
|
642
|
+
const structuredAppend = diffTrustedStructuredTurns(before, current);
|
|
643
|
+
const transcriptDelta = diffTranscriptLines(before, current);
|
|
644
|
+
if (structuredAppend.hasTrustedAppend && structuredAppend.hasNewUserTurn) {
|
|
645
|
+
return {
|
|
646
|
+
snapshot: current,
|
|
647
|
+
preSendAssistantCount,
|
|
648
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
649
|
+
reason: 'user_turn',
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
if (!current.composerHasText && current.isGenerating) {
|
|
653
|
+
return {
|
|
654
|
+
snapshot: current,
|
|
655
|
+
preSendAssistantCount,
|
|
656
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
657
|
+
reason: 'composer_generating',
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (!current.composerHasText && transcriptDelta.length > 0) {
|
|
661
|
+
return {
|
|
662
|
+
snapshot: current,
|
|
663
|
+
preSendAssistantCount,
|
|
664
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
665
|
+
reason: 'composer_transcript',
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
302
671
|
export async function sendGeminiMessage(page, text) {
|
|
303
672
|
await ensureGeminiPage(page);
|
|
304
|
-
|
|
673
|
+
let prepared;
|
|
674
|
+
for (let attempt = 0; attempt < GEMINI_COMPOSER_PREPARE_ATTEMPTS; attempt += 1) {
|
|
675
|
+
prepared = await page.evaluate(prepareComposerScript());
|
|
676
|
+
if (prepared?.ok)
|
|
677
|
+
break;
|
|
678
|
+
if (attempt < GEMINI_COMPOSER_PREPARE_ATTEMPTS - 1)
|
|
679
|
+
await page.wait(GEMINI_COMPOSER_PREPARE_WAIT_SECONDS);
|
|
680
|
+
}
|
|
681
|
+
if (!prepared?.ok) {
|
|
682
|
+
throw new CommandExecutionError(prepared?.reason || 'Could not find Gemini composer');
|
|
683
|
+
}
|
|
684
|
+
let hasText = false;
|
|
685
|
+
if (page.nativeType) {
|
|
686
|
+
try {
|
|
687
|
+
await page.nativeType(text);
|
|
688
|
+
await page.wait(0.2);
|
|
689
|
+
const nativeState = await page.evaluate(composerHasTextScript());
|
|
690
|
+
hasText = !!nativeState?.hasText;
|
|
691
|
+
}
|
|
692
|
+
catch { }
|
|
693
|
+
}
|
|
694
|
+
if (!hasText) {
|
|
695
|
+
const fallbackState = await page.evaluate(insertComposerTextFallbackScript(text));
|
|
696
|
+
hasText = !!fallbackState?.hasText;
|
|
697
|
+
}
|
|
698
|
+
if (!hasText) {
|
|
699
|
+
throw new CommandExecutionError('Failed to insert text into Gemini composer');
|
|
700
|
+
}
|
|
701
|
+
const submitAction = await page.evaluate(submitComposerScript());
|
|
702
|
+
if (submitAction === 'button') {
|
|
703
|
+
await page.wait(1);
|
|
704
|
+
return 'button';
|
|
705
|
+
}
|
|
706
|
+
if (page.nativeKeyPress) {
|
|
707
|
+
try {
|
|
708
|
+
await page.nativeKeyPress('Enter');
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
await page.evaluate(dispatchComposerEnterScript());
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
await page.evaluate(dispatchComposerEnterScript());
|
|
716
|
+
}
|
|
305
717
|
await page.wait(1);
|
|
306
|
-
return
|
|
718
|
+
return 'enter';
|
|
307
719
|
}
|
|
720
|
+
export const __test__ = {
|
|
721
|
+
GEMINI_COMPOSER_SELECTORS,
|
|
722
|
+
GEMINI_COMPOSER_MARKER_ATTR,
|
|
723
|
+
collapseAdjacentGeminiTurns,
|
|
724
|
+
clickNewChatScript,
|
|
725
|
+
diffTranscriptLines,
|
|
726
|
+
diffTrustedStructuredTurns,
|
|
727
|
+
hasGeminiTurnPrefix,
|
|
728
|
+
readGeminiSnapshot,
|
|
729
|
+
readGeminiSnapshotScript,
|
|
730
|
+
submitComposerScript,
|
|
731
|
+
insertComposerTextFallbackScript,
|
|
732
|
+
};
|
|
308
733
|
export async function getGeminiVisibleImageUrls(page) {
|
|
309
734
|
await ensureGeminiPage(page);
|
|
310
735
|
return await page.evaluate(`
|
|
@@ -429,35 +854,77 @@ export async function exportGeminiImages(page, urls) {
|
|
|
429
854
|
})(${urlsJson})
|
|
430
855
|
`);
|
|
431
856
|
}
|
|
432
|
-
export async function waitForGeminiResponse(page,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
return
|
|
441
|
-
const
|
|
442
|
-
|
|
857
|
+
export async function waitForGeminiResponse(page, baseline, promptText, timeoutSeconds) {
|
|
858
|
+
if (timeoutSeconds <= 0)
|
|
859
|
+
return '';
|
|
860
|
+
// Reply ownership must survive Gemini prepending older history later.
|
|
861
|
+
// Re-anchor on the submitted user turn when possible, and otherwise only
|
|
862
|
+
// accept assistants that are appended to the exact submission snapshot.
|
|
863
|
+
const pickStructuredReplyCandidate = (current) => {
|
|
864
|
+
if (!current.structuredTurnsTrusted)
|
|
865
|
+
return '';
|
|
866
|
+
const userAnchorTurnIndex = findLastMatchingGeminiTurnIndex(current.turns, baseline.userAnchorTurn);
|
|
867
|
+
if (userAnchorTurnIndex !== null) {
|
|
868
|
+
const candidate = current.turns
|
|
869
|
+
.slice(userAnchorTurnIndex + 1)
|
|
870
|
+
.filter((turn) => turn.Role === 'Assistant')
|
|
871
|
+
.at(-1);
|
|
872
|
+
return candidate ? sanitizeGeminiResponseText(candidate.Text, promptText) : '';
|
|
873
|
+
}
|
|
874
|
+
if (hasGeminiTurnPrefix(baseline.snapshot.turns, current.turns)) {
|
|
875
|
+
const appendedAssistant = current.turns
|
|
876
|
+
.slice(baseline.snapshot.turns.length)
|
|
877
|
+
.filter((turn) => turn.Role === 'Assistant')
|
|
878
|
+
.at(-1);
|
|
879
|
+
if (appendedAssistant) {
|
|
880
|
+
return sanitizeGeminiResponseText(appendedAssistant.Text, promptText);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return '';
|
|
443
884
|
};
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
885
|
+
const pickFallbackGeminiTranscriptReply = (current) => current.transcriptLines
|
|
886
|
+
.filter((line) => !baseline.snapshot.transcriptLines.includes(line))
|
|
887
|
+
.map((line) => extractGeminiTranscriptLineCandidate(line, promptText))
|
|
888
|
+
.filter(Boolean)
|
|
889
|
+
.join('\n')
|
|
890
|
+
.trim();
|
|
891
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutSeconds / 2));
|
|
892
|
+
let lastStructured = '';
|
|
893
|
+
let structuredStableCount = 0;
|
|
894
|
+
let lastTranscript = '';
|
|
895
|
+
let transcriptStableCount = 0;
|
|
896
|
+
let transcriptMissCount = 0;
|
|
448
897
|
for (let index = 0; index < maxPolls; index += 1) {
|
|
449
|
-
await page.wait(index === 0 ? 1
|
|
450
|
-
const
|
|
451
|
-
|
|
898
|
+
await page.wait(index === 0 ? 1 : 2);
|
|
899
|
+
const current = await readGeminiSnapshot(page);
|
|
900
|
+
const structuredCandidate = pickStructuredReplyCandidate(current);
|
|
901
|
+
if (structuredCandidate) {
|
|
902
|
+
if (structuredCandidate === lastStructured)
|
|
903
|
+
structuredStableCount += 1;
|
|
904
|
+
else {
|
|
905
|
+
lastStructured = structuredCandidate;
|
|
906
|
+
structuredStableCount = 1;
|
|
907
|
+
}
|
|
908
|
+
if (!current.isGenerating && structuredStableCount >= 2) {
|
|
909
|
+
return structuredCandidate;
|
|
910
|
+
}
|
|
452
911
|
continue;
|
|
453
|
-
|
|
454
|
-
|
|
912
|
+
}
|
|
913
|
+
transcriptMissCount += 1;
|
|
914
|
+
if (transcriptMissCount < 2)
|
|
915
|
+
continue;
|
|
916
|
+
const transcriptCandidate = pickFallbackGeminiTranscriptReply(current);
|
|
917
|
+
if (!transcriptCandidate)
|
|
918
|
+
continue;
|
|
919
|
+
if (transcriptCandidate === lastTranscript)
|
|
920
|
+
transcriptStableCount += 1;
|
|
455
921
|
else {
|
|
456
|
-
|
|
457
|
-
|
|
922
|
+
lastTranscript = transcriptCandidate;
|
|
923
|
+
transcriptStableCount = 1;
|
|
924
|
+
}
|
|
925
|
+
if (!current.isGenerating && transcriptStableCount >= 2) {
|
|
926
|
+
return transcriptCandidate;
|
|
458
927
|
}
|
|
459
|
-
if (stableCount >= 2 || index === maxPolls - 1)
|
|
460
|
-
return candidate;
|
|
461
928
|
}
|
|
462
|
-
return
|
|
929
|
+
return '';
|
|
463
930
|
}
|