@jackwener/opencli 1.6.1 → 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/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/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 +1135 -184
- 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/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/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
package/src/clis/gemini/utils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CommandExecutionError } from '../../errors.js';
|
|
1
2
|
import type { IPage } from '../../types.js';
|
|
2
3
|
|
|
3
4
|
export const GEMINI_DOMAIN = 'gemini.google.com';
|
|
@@ -16,12 +17,88 @@ export interface GeminiTurn {
|
|
|
16
17
|
Text: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export interface GeminiSnapshot {
|
|
21
|
+
turns: GeminiTurn[];
|
|
22
|
+
transcriptLines: string[];
|
|
23
|
+
composerHasText: boolean;
|
|
24
|
+
isGenerating: boolean;
|
|
25
|
+
structuredTurnsTrusted: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GeminiStructuredAppend {
|
|
29
|
+
appendedTurns: GeminiTurn[];
|
|
30
|
+
hasTrustedAppend: boolean;
|
|
31
|
+
hasNewUserTurn: boolean;
|
|
32
|
+
hasNewAssistantTurn: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GeminiSubmissionBaseline {
|
|
36
|
+
snapshot: GeminiSnapshot;
|
|
37
|
+
preSendAssistantCount: number;
|
|
38
|
+
userAnchorTurn: GeminiTurn | null;
|
|
39
|
+
reason: 'user_turn' | 'composer_generating' | 'composer_transcript';
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
const GEMINI_RESPONSE_NOISE_PATTERNS = [
|
|
20
43
|
/Gemini can make mistakes\.?/gi,
|
|
21
44
|
/Google Terms/gi,
|
|
22
45
|
/Google Privacy Policy/gi,
|
|
23
46
|
/Opens in a new window/gi,
|
|
24
47
|
];
|
|
48
|
+
const GEMINI_TRANSCRIPT_CHROME_MARKERS = ['gemini', '我的内容', '对话', 'google terms', 'google privacy policy'];
|
|
49
|
+
|
|
50
|
+
const GEMINI_COMPOSER_SELECTORS = [
|
|
51
|
+
'.ql-editor[contenteditable="true"]',
|
|
52
|
+
'.ql-editor[role="textbox"]',
|
|
53
|
+
'.ql-editor[aria-label*="Gemini"]',
|
|
54
|
+
'[contenteditable="true"][aria-label*="Gemini"]',
|
|
55
|
+
'[aria-label="Enter a prompt for Gemini"]',
|
|
56
|
+
'[aria-label*="prompt for Gemini"]',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const GEMINI_COMPOSER_MARKER_ATTR = 'data-opencli-gemini-composer';
|
|
60
|
+
const GEMINI_COMPOSER_PREPARE_ATTEMPTS = 4;
|
|
61
|
+
const GEMINI_COMPOSER_PREPARE_WAIT_SECONDS = 1;
|
|
62
|
+
|
|
63
|
+
function buildGeminiComposerLocatorScript(): string {
|
|
64
|
+
const selectorsJson = JSON.stringify(GEMINI_COMPOSER_SELECTORS);
|
|
65
|
+
const markerAttrJson = JSON.stringify(GEMINI_COMPOSER_MARKER_ATTR);
|
|
66
|
+
return `
|
|
67
|
+
const isVisible = (el) => {
|
|
68
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
69
|
+
const style = window.getComputedStyle(el);
|
|
70
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
71
|
+
const rect = el.getBoundingClientRect();
|
|
72
|
+
return rect.width > 0 && rect.height > 0;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const markerAttr = ${markerAttrJson};
|
|
76
|
+
const clearComposerMarkers = (active) => {
|
|
77
|
+
document.querySelectorAll('[' + markerAttr + ']').forEach((node) => {
|
|
78
|
+
if (node !== active) node.removeAttribute(markerAttr);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const markComposer = (node) => {
|
|
83
|
+
if (!(node instanceof HTMLElement)) return null;
|
|
84
|
+
clearComposerMarkers(node);
|
|
85
|
+
node.setAttribute(markerAttr, '1');
|
|
86
|
+
return node;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const findComposer = () => {
|
|
90
|
+
const marked = document.querySelector('[' + markerAttr + '="1"]');
|
|
91
|
+
if (marked instanceof HTMLElement && isVisible(marked)) return marked;
|
|
92
|
+
|
|
93
|
+
const selectors = ${selectorsJson};
|
|
94
|
+
for (const selector of selectors) {
|
|
95
|
+
const node = Array.from(document.querySelectorAll(selector)).find((candidate) => candidate instanceof HTMLElement && isVisible(candidate));
|
|
96
|
+
if (node instanceof HTMLElement) return markComposer(node);
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
25
102
|
|
|
26
103
|
export function sanitizeGeminiResponseText(value: string, promptText: string): string {
|
|
27
104
|
let sanitized = value;
|
|
@@ -52,33 +129,160 @@ export function collectGeminiTranscriptAdditions(
|
|
|
52
129
|
const beforeSet = new Set(beforeLines);
|
|
53
130
|
const additions = currentLines
|
|
54
131
|
.filter((line) => !beforeSet.has(line))
|
|
55
|
-
.map((line) =>
|
|
132
|
+
.map((line) => extractGeminiTranscriptLineCandidate(line, promptText))
|
|
56
133
|
.filter((line) => line && line !== promptText);
|
|
57
134
|
|
|
58
135
|
return additions.join('\n').trim();
|
|
59
136
|
}
|
|
60
137
|
|
|
138
|
+
export function collapseAdjacentGeminiTurns(turns: GeminiTurn[]): GeminiTurn[] {
|
|
139
|
+
const collapsed: GeminiTurn[] = [];
|
|
140
|
+
|
|
141
|
+
for (const turn of turns) {
|
|
142
|
+
if (!turn || typeof turn.Role !== 'string' || typeof turn.Text !== 'string') continue;
|
|
143
|
+
const previous = collapsed.at(-1);
|
|
144
|
+
if (previous?.Role === turn.Role && previous.Text === turn.Text) continue;
|
|
145
|
+
collapsed.push(turn);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return collapsed;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hasGeminiTurnPrefix(before: GeminiTurn[], current: GeminiTurn[]): boolean {
|
|
152
|
+
if (before.length > current.length) return false;
|
|
153
|
+
return before.every((turn, index) => (
|
|
154
|
+
turn.Role === current[index]?.Role
|
|
155
|
+
&& turn.Text === current[index]?.Text
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function findLastMatchingGeminiTurnIndex(turns: GeminiTurn[], target: GeminiTurn | null): number | null {
|
|
160
|
+
if (!target) return null;
|
|
161
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
162
|
+
const turn = turns[index];
|
|
163
|
+
if (turn?.Role === target.Role && turn.Text === target.Text) {
|
|
164
|
+
return index;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function diffTrustedStructuredTurns(
|
|
171
|
+
before: GeminiSnapshot,
|
|
172
|
+
current: GeminiSnapshot,
|
|
173
|
+
): GeminiStructuredAppend {
|
|
174
|
+
if (!before.structuredTurnsTrusted || !current.structuredTurnsTrusted) {
|
|
175
|
+
return {
|
|
176
|
+
appendedTurns: [],
|
|
177
|
+
hasTrustedAppend: false,
|
|
178
|
+
hasNewUserTurn: false,
|
|
179
|
+
hasNewAssistantTurn: false,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!hasGeminiTurnPrefix(before.turns, current.turns)) {
|
|
184
|
+
return {
|
|
185
|
+
appendedTurns: [],
|
|
186
|
+
hasTrustedAppend: false,
|
|
187
|
+
hasNewUserTurn: false,
|
|
188
|
+
hasNewAssistantTurn: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const appendedTurns = current.turns.slice(before.turns.length);
|
|
193
|
+
return {
|
|
194
|
+
appendedTurns,
|
|
195
|
+
hasTrustedAppend: appendedTurns.length > 0,
|
|
196
|
+
hasNewUserTurn: appendedTurns.some((turn) => turn.Role === 'User'),
|
|
197
|
+
hasNewAssistantTurn: appendedTurns.some((turn) => turn.Role === 'Assistant'),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function diffTranscriptLines(before: GeminiSnapshot, current: GeminiSnapshot): string[] {
|
|
202
|
+
const beforeLines = new Set(before.transcriptLines);
|
|
203
|
+
return current.transcriptLines.filter((line) => !beforeLines.has(line));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isLikelyGeminiTranscriptChrome(line: string): boolean {
|
|
207
|
+
const lower = line.toLowerCase();
|
|
208
|
+
const markerHits = GEMINI_TRANSCRIPT_CHROME_MARKERS.filter((marker) => lower.includes(marker)).length;
|
|
209
|
+
return markerHits >= 2;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function extractGeminiTranscriptLineCandidate(transcriptLine: string, promptText: string): string {
|
|
213
|
+
const candidate = transcriptLine.trim();
|
|
214
|
+
if (!candidate) return '';
|
|
215
|
+
|
|
216
|
+
const prompt = promptText.trim();
|
|
217
|
+
const sanitized = sanitizeGeminiResponseText(candidate, promptText);
|
|
218
|
+
|
|
219
|
+
if (!prompt) return sanitized;
|
|
220
|
+
if (!candidate.includes(prompt)) return sanitized;
|
|
221
|
+
if (sanitized && sanitized !== prompt && sanitized !== candidate) return sanitized;
|
|
222
|
+
if (isLikelyGeminiTranscriptChrome(candidate)) return '';
|
|
223
|
+
|
|
224
|
+
// Some transcript snapshots flatten "prompt + answer" into a single line.
|
|
225
|
+
// Recover the answer only when the line starts with the current prompt.
|
|
226
|
+
if (candidate.startsWith(prompt)) {
|
|
227
|
+
const tail = candidate.slice(prompt.length).replace(/^[\s::,,-]+/, '').trim();
|
|
228
|
+
return tail ? sanitizeGeminiResponseText(tail, '') : '';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return sanitized;
|
|
232
|
+
}
|
|
233
|
+
|
|
61
234
|
function getStateScript(): string {
|
|
62
235
|
return `
|
|
63
236
|
(() => {
|
|
237
|
+
${buildGeminiComposerLocatorScript()}
|
|
238
|
+
|
|
64
239
|
const signInNode = Array.from(document.querySelectorAll('a, button')).find((node) => {
|
|
65
240
|
const text = (node.textContent || '').trim().toLowerCase();
|
|
66
241
|
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
67
242
|
const href = node.getAttribute('href') || '';
|
|
68
243
|
return text === 'sign in'
|
|
69
244
|
|| aria === 'sign in'
|
|
245
|
+
|| text === '登录'
|
|
246
|
+
|| aria === '登录'
|
|
70
247
|
|| href.includes('accounts.google.com/ServiceLogin');
|
|
71
248
|
});
|
|
72
249
|
|
|
73
|
-
const composer =
|
|
74
|
-
const sendButton = document.querySelector('button[aria-label="Send message"]');
|
|
250
|
+
const composer = findComposer();
|
|
75
251
|
|
|
76
252
|
return {
|
|
77
253
|
url: window.location.href,
|
|
78
254
|
title: document.title || '',
|
|
79
255
|
isSignedIn: signInNode ? false : (composer ? true : null),
|
|
80
256
|
composerLabel: composer?.getAttribute('aria-label') || '',
|
|
81
|
-
canSend: !!
|
|
257
|
+
canSend: !!composer,
|
|
258
|
+
};
|
|
259
|
+
})()
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function readGeminiSnapshotScript(): string {
|
|
264
|
+
return `
|
|
265
|
+
(() => {
|
|
266
|
+
${buildGeminiComposerLocatorScript()}
|
|
267
|
+
const composer = findComposer();
|
|
268
|
+
const composerText = composer?.textContent?.replace(/\\u00a0/g, ' ').trim() || '';
|
|
269
|
+
const isGenerating = !!Array.from(document.querySelectorAll('button, [role="button"]')).find((node) => {
|
|
270
|
+
const text = (node.textContent || '').trim().toLowerCase();
|
|
271
|
+
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
272
|
+
return text === 'stop response'
|
|
273
|
+
|| aria === 'stop response'
|
|
274
|
+
|| text === '停止回答'
|
|
275
|
+
|| aria === '停止回答';
|
|
276
|
+
});
|
|
277
|
+
const turns = ${getTurnsScript().trim()};
|
|
278
|
+
const transcriptLines = ${getTranscriptLinesScript().trim()};
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
turns,
|
|
282
|
+
transcriptLines,
|
|
283
|
+
composerHasText: composerText.length > 0,
|
|
284
|
+
isGenerating,
|
|
285
|
+
structuredTurnsTrusted: turns.length > 0 || transcriptLines.length === 0,
|
|
82
286
|
};
|
|
83
287
|
})()
|
|
84
288
|
`;
|
|
@@ -186,7 +390,16 @@ function getTurnsScript(): string {
|
|
|
186
390
|
];
|
|
187
391
|
|
|
188
392
|
const roots = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector)));
|
|
189
|
-
const unique = roots
|
|
393
|
+
const unique = roots
|
|
394
|
+
.filter((el, index, all) => all.indexOf(el) === index)
|
|
395
|
+
.filter(isVisible)
|
|
396
|
+
.sort((left, right) => {
|
|
397
|
+
if (left === right) return 0;
|
|
398
|
+
const relation = left.compareDocumentPosition(right);
|
|
399
|
+
if (relation & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
400
|
+
if (relation & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
401
|
+
return 0;
|
|
402
|
+
});
|
|
190
403
|
|
|
191
404
|
const turns = unique.map((el) => {
|
|
192
405
|
const text = clean(el.innerText || el.textContent || '');
|
|
@@ -206,53 +419,189 @@ function getTurnsScript(): string {
|
|
|
206
419
|
return role ? { Role: role, Text: text } : null;
|
|
207
420
|
}).filter(Boolean);
|
|
208
421
|
|
|
209
|
-
|
|
210
|
-
const seen = new Set();
|
|
211
|
-
for (const turn of turns) {
|
|
212
|
-
const key = turn.Role + '::' + turn.Text;
|
|
213
|
-
if (seen.has(key)) continue;
|
|
214
|
-
seen.add(key);
|
|
215
|
-
deduped.push(turn);
|
|
216
|
-
}
|
|
217
|
-
return deduped;
|
|
422
|
+
return turns;
|
|
218
423
|
})()
|
|
219
424
|
`;
|
|
220
425
|
}
|
|
221
426
|
|
|
222
|
-
function
|
|
427
|
+
function prepareComposerScript(): string {
|
|
223
428
|
return `
|
|
224
|
-
((
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
429
|
+
(() => {
|
|
430
|
+
${buildGeminiComposerLocatorScript()}
|
|
431
|
+
const composer = findComposer();
|
|
432
|
+
|
|
433
|
+
if (!(composer instanceof HTMLElement)) {
|
|
434
|
+
return { ok: false, reason: 'Could not find Gemini composer' };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
composer.focus();
|
|
228
439
|
const selection = window.getSelection();
|
|
229
440
|
const range = document.createRange();
|
|
230
|
-
range.selectNodeContents(
|
|
441
|
+
range.selectNodeContents(composer);
|
|
231
442
|
range.collapse(false);
|
|
232
443
|
selection?.removeAllRanges();
|
|
233
444
|
selection?.addRange(range);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
445
|
+
composer.textContent = '';
|
|
446
|
+
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: '', inputType: 'deleteContentBackward' }));
|
|
447
|
+
} catch (error) {
|
|
448
|
+
return {
|
|
449
|
+
ok: false,
|
|
450
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
ok: true,
|
|
456
|
+
label: composer.getAttribute('aria-label') || '',
|
|
457
|
+
};
|
|
458
|
+
})()
|
|
459
|
+
`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function composerHasTextScript(): string {
|
|
463
|
+
return `
|
|
464
|
+
(() => {
|
|
465
|
+
${buildGeminiComposerLocatorScript()}
|
|
466
|
+
const composer = findComposer();
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
hasText: !!(composer && ((composer.textContent || '').trim() || (composer.innerText || '').trim())),
|
|
470
|
+
};
|
|
471
|
+
})()
|
|
472
|
+
`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function insertComposerTextFallbackScript(text: string): string {
|
|
476
|
+
return `
|
|
477
|
+
((inputText) => {
|
|
478
|
+
${buildGeminiComposerLocatorScript()}
|
|
479
|
+
const composer = findComposer();
|
|
480
|
+
|
|
481
|
+
if (!(composer instanceof HTMLElement)) {
|
|
482
|
+
return { hasText: false, reason: 'Could not find Gemini composer' };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const selection = window.getSelection();
|
|
486
|
+
const range = document.createRange();
|
|
487
|
+
range.selectNodeContents(composer);
|
|
488
|
+
range.collapse(false);
|
|
489
|
+
selection?.removeAllRanges();
|
|
490
|
+
selection?.addRange(range);
|
|
491
|
+
|
|
492
|
+
composer.focus();
|
|
493
|
+
composer.textContent = '';
|
|
494
|
+
const execResult = typeof document.execCommand === 'function'
|
|
495
|
+
? document.execCommand('insertText', false, inputText)
|
|
496
|
+
: false;
|
|
497
|
+
|
|
498
|
+
if (!execResult) {
|
|
499
|
+
const paragraph = document.createElement('p');
|
|
500
|
+
const lines = String(inputText).split(/\\n/);
|
|
501
|
+
for (const [index, line] of lines.entries()) {
|
|
502
|
+
if (index > 0) paragraph.appendChild(document.createElement('br'));
|
|
503
|
+
paragraph.appendChild(document.createTextNode(line));
|
|
504
|
+
}
|
|
505
|
+
composer.replaceChildren(paragraph);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
composer.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, data: inputText, inputType: 'insertText' }));
|
|
509
|
+
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: inputText, inputType: 'insertText' }));
|
|
510
|
+
composer.dispatchEvent(new Event('change', { bubbles: true }));
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
hasText: !!((composer.textContent || '').trim() || (composer.innerText || '').trim()),
|
|
237
514
|
};
|
|
515
|
+
})(${JSON.stringify(text)})
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function submitComposerScript(): string {
|
|
520
|
+
return `
|
|
521
|
+
(() => {
|
|
522
|
+
${buildGeminiComposerLocatorScript()}
|
|
523
|
+
const composer = findComposer();
|
|
238
524
|
|
|
239
|
-
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"]');
|
|
240
525
|
if (!(composer instanceof HTMLElement)) {
|
|
241
526
|
throw new Error('Could not find Gemini composer');
|
|
242
527
|
}
|
|
243
528
|
|
|
244
|
-
|
|
529
|
+
const composerRect = composer.getBoundingClientRect();
|
|
530
|
+
const rootCandidates = [
|
|
531
|
+
composer.closest('form'),
|
|
532
|
+
composer.closest('[role="form"]'),
|
|
533
|
+
composer.closest('.input-area-container'),
|
|
534
|
+
composer.closest('.textbox-container'),
|
|
535
|
+
composer.closest('.input-wrapper'),
|
|
536
|
+
composer.parentElement,
|
|
537
|
+
composer.parentElement?.parentElement,
|
|
538
|
+
].filter(Boolean);
|
|
539
|
+
|
|
540
|
+
const seen = new Set();
|
|
541
|
+
const buttons = [];
|
|
542
|
+
for (const root of rootCandidates) {
|
|
543
|
+
root.querySelectorAll('button, [role="button"]').forEach((node) => {
|
|
544
|
+
if (!(node instanceof HTMLElement)) return;
|
|
545
|
+
if (seen.has(node)) return;
|
|
546
|
+
seen.add(node);
|
|
547
|
+
buttons.push(node);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const excludedPattern = /main menu|主菜单|microphone|麦克风|upload|上传|mode|模式|tools|工具|settings|临时对话|new chat|新对话/i;
|
|
552
|
+
const submitPattern = /send|发送|submit|提交/i;
|
|
553
|
+
let bestButton = null;
|
|
554
|
+
let bestScore = -1;
|
|
555
|
+
|
|
556
|
+
for (const button of buttons) {
|
|
557
|
+
if (!isVisible(button)) continue;
|
|
558
|
+
if (button instanceof HTMLButtonElement && button.disabled) continue;
|
|
559
|
+
if (button.getAttribute('aria-disabled') === 'true') continue;
|
|
560
|
+
|
|
561
|
+
const label = ((button.getAttribute('aria-label') || '') + ' ' + ((button.textContent || '').trim())).trim();
|
|
562
|
+
if (excludedPattern.test(label)) continue;
|
|
245
563
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
564
|
+
const rect = button.getBoundingClientRect();
|
|
565
|
+
const verticalDistance = Math.abs((rect.top + rect.bottom) / 2 - (composerRect.top + composerRect.bottom) / 2);
|
|
566
|
+
if (verticalDistance > 160) continue;
|
|
567
|
+
|
|
568
|
+
let score = 0;
|
|
569
|
+
if (submitPattern.test(label)) score += 10;
|
|
570
|
+
if (rect.left >= composerRect.right - 160) score += 3;
|
|
571
|
+
if (rect.left >= composerRect.left) score += 1;
|
|
572
|
+
if (rect.width <= 96 && rect.height <= 96) score += 1;
|
|
573
|
+
|
|
574
|
+
if (score > bestScore) {
|
|
575
|
+
bestScore = score;
|
|
576
|
+
bestButton = button;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (bestButton instanceof HTMLElement && bestScore >= 3) {
|
|
581
|
+
bestButton.click();
|
|
249
582
|
return 'button';
|
|
250
583
|
}
|
|
251
584
|
|
|
252
|
-
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
253
|
-
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
254
585
|
return 'enter';
|
|
255
|
-
})(
|
|
586
|
+
})()
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function dispatchComposerEnterScript(): string {
|
|
591
|
+
return `
|
|
592
|
+
(() => {
|
|
593
|
+
${buildGeminiComposerLocatorScript()}
|
|
594
|
+
const composer = findComposer();
|
|
595
|
+
|
|
596
|
+
if (!(composer instanceof HTMLElement)) {
|
|
597
|
+
throw new Error('Could not find Gemini composer');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
composer.focus();
|
|
601
|
+
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
602
|
+
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
603
|
+
return 'enter';
|
|
604
|
+
})()
|
|
256
605
|
`;
|
|
257
606
|
}
|
|
258
607
|
|
|
@@ -270,7 +619,14 @@ function clickNewChatScript(): string {
|
|
|
270
619
|
const candidates = Array.from(document.querySelectorAll('button, a')).filter((node) => {
|
|
271
620
|
const text = (node.textContent || '').trim().toLowerCase();
|
|
272
621
|
const aria = (node.getAttribute('aria-label') || '').trim().toLowerCase();
|
|
273
|
-
return isVisible(node) && (
|
|
622
|
+
return isVisible(node) && (
|
|
623
|
+
text === 'new chat'
|
|
624
|
+
|| aria === 'new chat'
|
|
625
|
+
|| text === '发起新对话'
|
|
626
|
+
|| aria === '发起新对话'
|
|
627
|
+
|| text === '新对话'
|
|
628
|
+
|| aria === '新对话'
|
|
629
|
+
);
|
|
274
630
|
});
|
|
275
631
|
|
|
276
632
|
const target = candidates.find((node) => !node.hasAttribute('disabled')) || candidates[0];
|
|
@@ -321,27 +677,150 @@ export async function startNewGeminiChat(page: IPage): Promise<'clicked' | 'navi
|
|
|
321
677
|
}
|
|
322
678
|
|
|
323
679
|
export async function getGeminiVisibleTurns(page: IPage): Promise<GeminiTurn[]> {
|
|
324
|
-
await
|
|
325
|
-
const turns = await page.evaluate(getTurnsScript()) as GeminiTurn[];
|
|
680
|
+
const turns = await getGeminiStructuredTurns(page);
|
|
326
681
|
if (Array.isArray(turns) && turns.length > 0) return turns;
|
|
327
682
|
|
|
328
683
|
const lines = await getGeminiTranscriptLines(page);
|
|
329
684
|
return lines.map((line) => ({ Role: 'System', Text: line }));
|
|
330
685
|
}
|
|
331
686
|
|
|
687
|
+
async function getGeminiStructuredTurns(page: IPage): Promise<GeminiTurn[]> {
|
|
688
|
+
await ensureGeminiPage(page);
|
|
689
|
+
const turns = collapseAdjacentGeminiTurns(await page.evaluate(getTurnsScript()) as GeminiTurn[]);
|
|
690
|
+
return Array.isArray(turns) ? turns : [];
|
|
691
|
+
}
|
|
692
|
+
|
|
332
693
|
export async function getGeminiTranscriptLines(page: IPage): Promise<string[]> {
|
|
333
694
|
await ensureGeminiPage(page);
|
|
334
695
|
return await page.evaluate(getTranscriptLinesScript()) as string[];
|
|
335
696
|
}
|
|
336
697
|
|
|
698
|
+
export async function readGeminiSnapshot(page: IPage): Promise<GeminiSnapshot> {
|
|
699
|
+
await ensureGeminiPage(page);
|
|
700
|
+
return await page.evaluate(readGeminiSnapshotScript()) as GeminiSnapshot;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function findLastUserTurnIndex(turns: GeminiTurn[]): number | null {
|
|
704
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
705
|
+
if (turns[index]?.Role === 'User') return index;
|
|
706
|
+
}
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function findLastUserTurn(turns: GeminiTurn[]): GeminiTurn | null {
|
|
711
|
+
const index = findLastUserTurnIndex(turns);
|
|
712
|
+
return index === null ? null : turns[index] ?? null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export async function waitForGeminiSubmission(
|
|
716
|
+
page: IPage,
|
|
717
|
+
before: GeminiSnapshot,
|
|
718
|
+
timeoutSeconds: number,
|
|
719
|
+
): Promise<GeminiSubmissionBaseline | null> {
|
|
720
|
+
const preSendAssistantCount = before.turns.filter((turn) => turn.Role === 'Assistant').length;
|
|
721
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutSeconds));
|
|
722
|
+
|
|
723
|
+
for (let index = 0; index < maxPolls; index += 1) {
|
|
724
|
+
await page.wait(index === 0 ? 0.5 : 1);
|
|
725
|
+
const current = await readGeminiSnapshot(page);
|
|
726
|
+
const structuredAppend = diffTrustedStructuredTurns(before, current);
|
|
727
|
+
const transcriptDelta = diffTranscriptLines(before, current);
|
|
728
|
+
|
|
729
|
+
if (structuredAppend.hasTrustedAppend && structuredAppend.hasNewUserTurn) {
|
|
730
|
+
return {
|
|
731
|
+
snapshot: current,
|
|
732
|
+
preSendAssistantCount,
|
|
733
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
734
|
+
reason: 'user_turn',
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!current.composerHasText && current.isGenerating) {
|
|
739
|
+
return {
|
|
740
|
+
snapshot: current,
|
|
741
|
+
preSendAssistantCount,
|
|
742
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
743
|
+
reason: 'composer_generating',
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (!current.composerHasText && transcriptDelta.length > 0) {
|
|
748
|
+
return {
|
|
749
|
+
snapshot: current,
|
|
750
|
+
preSendAssistantCount,
|
|
751
|
+
userAnchorTurn: findLastUserTurn(current.turns),
|
|
752
|
+
reason: 'composer_transcript',
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
|
|
337
760
|
export async function sendGeminiMessage(page: IPage, text: string): Promise<'button' | 'enter'> {
|
|
338
761
|
await ensureGeminiPage(page);
|
|
339
|
-
|
|
762
|
+
let prepared: { ok?: boolean; reason?: string } | undefined;
|
|
763
|
+
for (let attempt = 0; attempt < GEMINI_COMPOSER_PREPARE_ATTEMPTS; attempt += 1) {
|
|
764
|
+
prepared = await page.evaluate(prepareComposerScript()) as { ok?: boolean; reason?: string };
|
|
765
|
+
if (prepared?.ok) break;
|
|
766
|
+
if (attempt < GEMINI_COMPOSER_PREPARE_ATTEMPTS - 1) await page.wait(GEMINI_COMPOSER_PREPARE_WAIT_SECONDS);
|
|
767
|
+
}
|
|
768
|
+
if (!prepared?.ok) {
|
|
769
|
+
throw new CommandExecutionError(prepared?.reason || 'Could not find Gemini composer');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
let hasText = false;
|
|
773
|
+
if (page.nativeType) {
|
|
774
|
+
try {
|
|
775
|
+
await page.nativeType(text);
|
|
776
|
+
await page.wait(0.2);
|
|
777
|
+
const nativeState = await page.evaluate(composerHasTextScript()) as { hasText?: boolean };
|
|
778
|
+
hasText = !!nativeState?.hasText;
|
|
779
|
+
} catch {}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (!hasText) {
|
|
783
|
+
const fallbackState = await page.evaluate(insertComposerTextFallbackScript(text)) as { hasText?: boolean };
|
|
784
|
+
hasText = !!fallbackState?.hasText;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!hasText) {
|
|
788
|
+
throw new CommandExecutionError('Failed to insert text into Gemini composer');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const submitAction = await page.evaluate(submitComposerScript()) as 'button' | 'enter';
|
|
792
|
+
if (submitAction === 'button') {
|
|
793
|
+
await page.wait(1);
|
|
794
|
+
return 'button';
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (page.nativeKeyPress) {
|
|
798
|
+
try {
|
|
799
|
+
await page.nativeKeyPress('Enter');
|
|
800
|
+
} catch {
|
|
801
|
+
await page.evaluate(dispatchComposerEnterScript());
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
await page.evaluate(dispatchComposerEnterScript());
|
|
805
|
+
}
|
|
806
|
+
|
|
340
807
|
await page.wait(1);
|
|
341
|
-
return
|
|
808
|
+
return 'enter';
|
|
342
809
|
}
|
|
343
810
|
|
|
344
|
-
|
|
811
|
+
export const __test__ = {
|
|
812
|
+
GEMINI_COMPOSER_SELECTORS,
|
|
813
|
+
GEMINI_COMPOSER_MARKER_ATTR,
|
|
814
|
+
collapseAdjacentGeminiTurns,
|
|
815
|
+
clickNewChatScript,
|
|
816
|
+
diffTranscriptLines,
|
|
817
|
+
diffTrustedStructuredTurns,
|
|
818
|
+
hasGeminiTurnPrefix,
|
|
819
|
+
readGeminiSnapshot,
|
|
820
|
+
readGeminiSnapshotScript,
|
|
821
|
+
submitComposerScript,
|
|
822
|
+
insertComposerTextFallbackScript,
|
|
823
|
+
};
|
|
345
824
|
|
|
346
825
|
export async function getGeminiVisibleImageUrls(page: IPage): Promise<string[]> {
|
|
347
826
|
await ensureGeminiPage(page);
|
|
@@ -484,40 +963,89 @@ export async function exportGeminiImages(page: IPage, urls: string[]): Promise<G
|
|
|
484
963
|
}
|
|
485
964
|
export async function waitForGeminiResponse(
|
|
486
965
|
page: IPage,
|
|
487
|
-
|
|
966
|
+
baseline: GeminiSubmissionBaseline,
|
|
488
967
|
promptText: string,
|
|
489
968
|
timeoutSeconds: number,
|
|
490
969
|
): Promise<string> {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
970
|
+
if (timeoutSeconds <= 0) return '';
|
|
971
|
+
|
|
972
|
+
// Reply ownership must survive Gemini prepending older history later.
|
|
973
|
+
// Re-anchor on the submitted user turn when possible, and otherwise only
|
|
974
|
+
// accept assistants that are appended to the exact submission snapshot.
|
|
975
|
+
const pickStructuredReplyCandidate = (current: GeminiSnapshot): string => {
|
|
976
|
+
if (!current.structuredTurnsTrusted) return '';
|
|
977
|
+
|
|
978
|
+
const userAnchorTurnIndex = findLastMatchingGeminiTurnIndex(current.turns, baseline.userAnchorTurn);
|
|
979
|
+
if (userAnchorTurnIndex !== null) {
|
|
980
|
+
const candidate = current.turns
|
|
981
|
+
.slice(userAnchorTurnIndex + 1)
|
|
982
|
+
.filter((turn) => turn.Role === 'Assistant')
|
|
983
|
+
.at(-1);
|
|
984
|
+
return candidate ? sanitizeGeminiResponseText(candidate.Text, promptText) : '';
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (hasGeminiTurnPrefix(baseline.snapshot.turns, current.turns)) {
|
|
988
|
+
const appendedAssistant = current.turns
|
|
989
|
+
.slice(baseline.snapshot.turns.length)
|
|
990
|
+
.filter((turn) => turn.Role === 'Assistant')
|
|
991
|
+
.at(-1);
|
|
992
|
+
if (appendedAssistant) {
|
|
993
|
+
return sanitizeGeminiResponseText(appendedAssistant.Text, promptText);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return '';
|
|
501
998
|
};
|
|
502
999
|
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1000
|
+
const pickFallbackGeminiTranscriptReply = (current: GeminiSnapshot): string => current.transcriptLines
|
|
1001
|
+
.filter((line) => !baseline.snapshot.transcriptLines.includes(line))
|
|
1002
|
+
.map((line) => extractGeminiTranscriptLineCandidate(line, promptText))
|
|
1003
|
+
.filter(Boolean)
|
|
1004
|
+
.join('\n')
|
|
1005
|
+
.trim();
|
|
1006
|
+
|
|
1007
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutSeconds / 2));
|
|
1008
|
+
let lastStructured = '';
|
|
1009
|
+
let structuredStableCount = 0;
|
|
1010
|
+
let lastTranscript = '';
|
|
1011
|
+
let transcriptStableCount = 0;
|
|
1012
|
+
let transcriptMissCount = 0;
|
|
507
1013
|
|
|
508
1014
|
for (let index = 0; index < maxPolls; index += 1) {
|
|
509
|
-
await page.wait(index === 0 ? 1
|
|
510
|
-
const
|
|
511
|
-
|
|
1015
|
+
await page.wait(index === 0 ? 1 : 2);
|
|
1016
|
+
const current = await readGeminiSnapshot(page);
|
|
1017
|
+
const structuredCandidate = pickStructuredReplyCandidate(current);
|
|
1018
|
+
|
|
1019
|
+
if (structuredCandidate) {
|
|
1020
|
+
if (structuredCandidate === lastStructured) structuredStableCount += 1;
|
|
1021
|
+
else {
|
|
1022
|
+
lastStructured = structuredCandidate;
|
|
1023
|
+
structuredStableCount = 1;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (!current.isGenerating && structuredStableCount >= 2) {
|
|
1027
|
+
return structuredCandidate;
|
|
1028
|
+
}
|
|
512
1029
|
|
|
513
|
-
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
transcriptMissCount += 1;
|
|
1034
|
+
if (transcriptMissCount < 2) continue;
|
|
1035
|
+
|
|
1036
|
+
const transcriptCandidate = pickFallbackGeminiTranscriptReply(current);
|
|
1037
|
+
if (!transcriptCandidate) continue;
|
|
1038
|
+
|
|
1039
|
+
if (transcriptCandidate === lastTranscript) transcriptStableCount += 1;
|
|
514
1040
|
else {
|
|
515
|
-
|
|
516
|
-
|
|
1041
|
+
lastTranscript = transcriptCandidate;
|
|
1042
|
+
transcriptStableCount = 1;
|
|
517
1043
|
}
|
|
518
1044
|
|
|
519
|
-
if (
|
|
1045
|
+
if (!current.isGenerating && transcriptStableCount >= 2) {
|
|
1046
|
+
return transcriptCandidate;
|
|
1047
|
+
}
|
|
520
1048
|
}
|
|
521
1049
|
|
|
522
|
-
return
|
|
1050
|
+
return '';
|
|
523
1051
|
}
|