page-agent 0.0.0
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/LICENSE +21 -0
- package/NOTICE +23 -0
- package/README.md +102 -0
- package/dist/lib/PageAgent.d.ts +297 -0
- package/dist/lib/page-agent.js +3410 -0
- package/dist/lib/page-agent.js.map +1 -0
- package/package.json +117 -0
|
@@ -0,0 +1,3410 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
try {
|
|
4
|
+
if (typeof document != "undefined") {
|
|
5
|
+
var elementStyle = document.createElement("style");
|
|
6
|
+
elementStyle.appendChild(document.createTextNode("._wrapper_d1n0a_1 {\n position: fixed;\n bottom: 100px;\n left: 50%;\n transform: translateX(-50%) translateY(20px);\n opacity: 0;\n z-index: 2147483642; /* 比 SimulatorMask 高一层 */\n box-sizing: border-box;\n\n overflow: visible;\n\n * {\n box-sizing: border-box;\n }\n\n --width: 360px;\n --height: 40px;\n --border-radius: 12px;\n\n --side-space: 12px; /* 控制栏两侧的间距 */\n --history-width: calc(var(--width) - var(--side-space) * 2);\n\n --color-1: rgb(57, 182, 255);\n --color-2: rgb(189, 69, 251);\n --color-3: rgb(255, 87, 51);\n --color-4: rgb(255, 214, 0);\n\n width: var(--width);\n height: var(--height);\n\n transition: all 0.3s ease-in-out;\n\n /* 响应式设计 */\n @media (max-width: 480px) {\n width: calc(100vw - 40px);\n left: 20px;\n transform: none;\n }\n\n ._background_d1n0a_40 {\n position: absolute;\n inset: -2px -8px;\n border-radius: calc(var(--border-radius) + 4px);\n filter: blur(16px);\n overflow: hidden;\n /* mix-blend-mode: lighten; */\n /* display: none; */\n\n &::before {\n content: '';\n z-index: -1;\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n /* left: -100%; */\n left: 0;\n top: 0;\n\n background-image: linear-gradient(\n to bottom left,\n var(--color-1),\n var(--color-2),\n var(--color-1)\n );\n animation: _mask-running_d1n0a_1 2s linear infinite;\n }\n &::after {\n content: '';\n z-index: -1;\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n left: 0;\n top: 0;\n\n background-image: linear-gradient(\n to bottom left,\n var(--color-2),\n var(--color-1),\n var(--color-2)\n );\n animation: _mask-running_d1n0a_1 2s linear infinite;\n animation-delay: 1s;\n }\n }\n}\n\n@keyframes _mask-running_d1n0a_1 {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(100%);\n }\n}\n\n/* 控制栏 */\n._header_d1n0a_100 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n user-select: none;\n\n position: absolute;\n inset: 0;\n\n cursor: pointer;\n flex-shrink: 0; /* 防止 header 被压缩 */\n\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(10px);\n border-radius: var(--border-radius);\n background-clip: padding-box;\n\n box-shadow:\n 0 0 0px 2px rgba(255, 255, 255, 0.4),\n 0 0 5px 1px rgba(255, 255, 255, 0.3);\n\n ._statusSection_d1n0a_122 {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-height: 24px; /* 确保垂直居中 */\n\n ._indicator_d1n0a_129 {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.5);\n flex-shrink: 0;\n animation: none; /* 默认无动画 */\n\n /* 运行状态 - 有动画 */\n &._thinking_d1n0a_138 {\n background: rgb(57, 182, 255);\n animation: _pulse_d1n0a_1 0.8s ease-in-out infinite;\n }\n\n &._tool_executing_d1n0a_143 {\n background: rgb(189, 69, 251);\n animation: _pulse_d1n0a_1 0.6s ease-in-out infinite;\n }\n\n &._retry_d1n0a_148 {\n background: rgb(255, 214, 0);\n animation: _retryPulse_d1n0a_1 1s ease-in-out infinite;\n }\n\n /* 静止状态 - 无动画 */\n &._completed_d1n0a_154,\n &._input_d1n0a_155,\n &._output_d1n0a_156 {\n background: rgb(34, 197, 94);\n animation: none;\n }\n\n &._error_d1n0a_161 {\n background: rgb(239, 68, 68);\n animation: none;\n }\n }\n\n ._statusText_d1n0a_167 {\n color: white;\n font-size: 12px;\n line-height: 1;\n font-weight: 500;\n transition: all 0.3s ease-in-out;\n position: relative;\n overflow: hidden;\n display: flex;\n align-items: center;\n min-height: 24px; /* 确保垂直居中 */\n\n &._fadeOut_d1n0a_179 {\n animation: _statusTextFadeOut_d1n0a_1 0.3s ease forwards;\n }\n\n &._fadeIn_d1n0a_183 {\n animation: _statusTextFadeIn_d1n0a_1 0.3s ease forwards;\n }\n }\n }\n\n ._controls_d1n0a_189 {\n display: flex;\n align-items: center;\n gap: 4px;\n\n ._controlButton_d1n0a_194 {\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 4px;\n background: rgba(255, 255, 255, 0.1);\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n line-height: 1;\n\n &:hover {\n background: rgba(255, 255, 255, 0.2);\n }\n }\n\n ._pauseButton_d1n0a_213 {\n font-weight: 600;\n &._paused_d1n0a_215 {\n background: rgba(34, 197, 94, 0.2); /* 绿色背景表示可以继续 */\n color: rgb(34, 197, 94);\n\n &:hover {\n background: rgba(34, 197, 94, 0.3);\n }\n }\n }\n\n ._stopButton_d1n0a_225 {\n background: rgba(239, 68, 68, 0.2);\n color: rgb(255, 41, 41);\n font-weight: 600;\n\n &:hover {\n background: rgba(239, 68, 68, 0.3);\n }\n }\n }\n}\n\n@keyframes _statusTextFadeIn_d1n0a_1 {\n 0% {\n opacity: 0;\n transform: translateY(5px);\n }\n 100% {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes _statusTextFadeOut_d1n0a_1 {\n 0% {\n opacity: 1;\n transform: translateY(0);\n }\n 100% {\n opacity: 0;\n transform: translateY(-5px);\n }\n}\n\n._historySectionWrapper_d1n0a_259 {\n position: absolute;\n width: var(--history-width);\n bottom: var(--height);\n left: var(--side-space);\n z-index: -2;\n\n padding-top: 0px;\n visibility: collapse;\n overflow: hidden;\n\n transition: all 0.2s;\n\n background: rgba(2, 0, 20, 0.5);\n /* background: rgba(186, 186, 186, 0.2); */\n backdrop-filter: blur(10px);\n\n text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);\n\n border-top-left-radius: calc(var(--border-radius) + 4px);\n border-top-right-radius: calc(var(--border-radius) + 4px);\n\n /* border: 2px solid rgba(255, 255, 255, 0.8); */\n border: 2px solid rgba(255, 255, 255, 0.4);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);\n\n /* @media (prefers-color-scheme: dark) {\n box-shadow:\n 0 8px 32px 0 rgba(0, 0, 0, 0.85),\n 0 2px 12px 0 rgba(57, 182, 255, 0.1);\n } */\n\n ._expanded_d1n0a_291 & {\n padding-top: 8px;\n visibility: visible;\n }\n\n ._historySection_d1n0a_259 {\n position: relative;\n overflow-y: auto;\n overscroll-behavior: contain;\n scrollbar-width: none;\n max-height: 0;\n padding-inline: 8px;\n\n transition: max-height 0.2s;\n\n ._expanded_d1n0a_291 & {\n max-height: 400px;\n }\n\n ._historyItem_d1n0a_310 {\n /* backdrop-filter: blur(10px); */\n padding: 8px 10px;\n margin-bottom: 6px;\n background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));\n border-radius: 8px;\n border-left: 2px solid rgba(57, 182, 255, 0.5);\n font-size: 12px;\n color: white;\n /* color: black; */\n line-height: 1.3;\n position: relative;\n overflow: hidden;\n\n /* 微妙的内阴影 */\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.1),\n 0 1px 3px rgba(0, 0, 0, 0.1);\n\n &::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);\n }\n\n &:hover {\n background: linear-gradient(135deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.06));\n /* transform: translateY(-1px); */\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, 0.15),\n 0 2px 4px rgba(0, 0, 0, 0.15);\n }\n\n &:last-child {\n margin-bottom: 10px;\n }\n\n &._completed_d1n0a_154,\n &._input_d1n0a_155,\n &._output_d1n0a_156 {\n border-left-color: rgb(34, 197, 94);\n background: linear-gradient(135deg, rgba(34, 197, 94, 0.1), rgba(34, 197, 94, 0.05));\n }\n\n &._error_d1n0a_161 {\n border-left-color: rgb(239, 68, 68);\n background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), rgba(239, 68, 68, 0.05));\n }\n\n &._retry_d1n0a_148 {\n border-left-color: rgb(255, 214, 0);\n background: linear-gradient(135deg, rgba(255, 214, 0, 0.1), rgba(255, 214, 0, 0.05));\n }\n\n /* 突出显示 done 成功结果 */\n &._doneSuccess_d1n0a_369 {\n background: linear-gradient(\n 135deg,\n rgba(34, 197, 94, 0.25),\n rgba(34, 197, 94, 0.15),\n rgba(34, 197, 94, 0.08)\n );\n border: none;\n border-left: 4px solid rgb(34, 197, 94);\n box-shadow:\n 0 4px 12px rgba(34, 197, 94, 0.3),\n inset 0 1px 0 rgba(255, 255, 255, 0.2),\n 0 0 20px rgba(34, 197, 94, 0.1);\n font-weight: 600;\n color: rgb(220, 252, 231);\n padding: 10px 12px;\n margin-bottom: 8px;\n border-radius: 8px;\n position: relative;\n overflow: hidden;\n\n &::before {\n background: linear-gradient(90deg, transparent, rgba(34, 197, 94, 0.4), transparent);\n }\n\n &::after {\n content: '';\n position: absolute;\n top: 0;\n left: -100%;\n width: 100%;\n height: 100%;\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);\n animation: _shimmer_d1n0a_1 2s ease-in-out infinite;\n }\n\n ._historyContent_d1n0a_405 {\n ._statusIcon_d1n0a_406 {\n font-size: 16px;\n animation: _celebrate_d1n0a_1 0.8s ease-in-out;\n filter: drop-shadow(0 2px 4px rgba(34, 197, 94, 0.5));\n }\n }\n }\n\n /* 突出显示 done 失败结果 */\n &._doneError_d1n0a_415 {\n background: linear-gradient(\n 135deg,\n rgba(239, 68, 68, 0.25),\n rgba(239, 68, 68, 0.15),\n rgba(239, 68, 68, 0.08)\n );\n border: none;\n border-left: 4px solid rgb(239, 68, 68);\n box-shadow:\n 0 4px 12px rgba(239, 68, 68, 0.3),\n inset 0 1px 0 rgba(255, 255, 255, 0.2),\n 0 0 20px rgba(239, 68, 68, 0.1);\n font-weight: 600;\n color: rgb(254, 226, 226);\n padding: 10px 12px;\n margin-bottom: 8px;\n border-radius: 8px;\n position: relative;\n overflow: hidden;\n\n &::before {\n background: linear-gradient(90deg, transparent, rgba(239, 68, 68, 0.4), transparent);\n }\n\n ._historyContent_d1n0a_405 {\n ._statusIcon_d1n0a_406 {\n font-size: 16px;\n filter: drop-shadow(0 2px 4px rgba(239, 68, 68, 0.5));\n }\n }\n }\n\n ._historyContent_d1n0a_405 {\n display: flex;\n align-items: center;\n gap: 8px;\n\n word-break: break-all;\n white-space: pre-wrap;\n\n /* overflow-x: auto; */\n\n ._statusIcon_d1n0a_406 {\n font-size: 12px;\n flex-shrink: 0;\n line-height: 1;\n transition: all 0.3s ease;\n }\n }\n\n ._historyMeta_d1n0a_466 {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.6);\n /* color: rgb(61, 61, 61); */\n margin-top: 8px;\n line-height: 1;\n }\n }\n }\n}\n\n/* 动画关键帧 - 更快的闪烁 */\n@keyframes _pulse_d1n0a_1 {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.4;\n transform: scale(1.3);\n }\n}\n\n/* 重试动画 - 旋转脉冲 */\n@keyframes _retryPulse_d1n0a_1 {\n 0%,\n 100% {\n opacity: 1;\n transform: scale(1) rotate(0deg);\n }\n 25% {\n opacity: 0.6;\n transform: scale(1.2) rotate(90deg);\n }\n 50% {\n opacity: 0.8;\n transform: scale(1.1) rotate(180deg);\n }\n 75% {\n opacity: 0.6;\n transform: scale(1.2) rotate(270deg);\n }\n}\n\n/* 庆祝动画 */\n@keyframes _celebrate_d1n0a_1 {\n 0%,\n 100% {\n transform: scale(1);\n }\n 25% {\n transform: scale(1.2) rotate(-5deg);\n }\n 75% {\n transform: scale(1.2) rotate(5deg);\n }\n}\n\n/* done 卡片的光泽效果 */\n@keyframes _shimmer_d1n0a_1 {\n 0% {\n left: -100%;\n }\n 100% {\n left: 100%;\n }\n}\n\n/* 输入区域样式 */\n._inputSectionWrapper_d1n0a_536 {\n position: absolute;\n width: var(--history-width);\n top: var(--height);\n left: var(--side-space);\n z-index: -1;\n\n visibility: visible;\n overflow: hidden;\n\n height: 48px;\n\n transition: all 0.2s;\n\n background: rgba(186, 186, 186, 0.2);\n backdrop-filter: blur(10px);\n\n border-bottom-left-radius: calc(var(--border-radius) + 4px);\n border-bottom-right-radius: calc(var(--border-radius) + 4px);\n\n border: 2px solid rgba(255, 255, 255, 0.3);\n box-shadow: 0 1px 16px rgba(0, 0, 0, 0.4);\n\n &._hidden_d1n0a_559 {\n visibility: collapse;\n height: 0;\n }\n\n ._inputSection_d1n0a_536 {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 8px 8px;\n\n ._taskInput_d1n0a_570 {\n flex: 1;\n background: rgba(255, 255, 255, 0.4);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 10px;\n padding-inline: 10px;\n color: rgb(20, 20, 20);\n font-size: 12px;\n height: 28px;\n line-height: 1;\n outline: none;\n transition: all 0.2s ease;\n\n /* text-shadow: 0 0 2px rgba(255, 255, 255, 0.8); */\n\n /* border-color: rgba(57, 182, 255, 0.3); */\n\n &::placeholder {\n color: rgb(53, 53, 53);\n }\n\n &:focus {\n background: rgba(255, 255, 255, 0.8);\n border-color: rgba(57, 182, 255, 0.6);\n box-shadow: 0 0 0 2px rgba(57, 182, 255, 0.2);\n }\n }\n }\n}\n._wrapper_1oy2s_1 {\n position: fixed;\n inset: 0;\n z-index: 2147483641; /* 确保在所有元素之上,除了 panel */\n /* pointer-events: none; */\n cursor: not-allowed;\n overflow: hidden;\n\n display: none;\n}\n/* AI 光标样式 */\n._cursor_1vrf3_2 {\n position: absolute;\n width: var(--cursor-size, 75px);\n height: var(--cursor-size, 75px);\n pointer-events: none;\n z-index: 10000;\n transform: translate(-30%, -30%);\n\n animation: _cursor-enter_1vrf3_1 300ms ease-out forwards;\n}\n\n._cursorBorder_1vrf3_13 {\n position: absolute;\n inset: 0;\n background: linear-gradient(45deg, rgb(57, 182, 255), rgb(189, 69, 251));\n mask-image: url(https://img.alicdn.com/imgextra/i1/O1CN01YHLVYR1LvqWIyo5kH_!!6000000001362-2-tps-202-202.png);\n mask-size: 100% 100%;\n mask-repeat: no-repeat;\n animation: _cursor-breathe_1vrf3_1 2s ease-in-out infinite;\n}\n\n._cursorFilling_1vrf3_23 {\n position: absolute;\n inset: 0;\n background: url(https://img.alicdn.com/imgextra/i3/O1CN01JZOqOS1Tu1sIKbPLW_!!6000000002441-2-tps-202-202.png);\n background-size: 100% 100%;\n background-repeat: no-repeat;\n}\n\n._cursorRipple_1vrf3_31 {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n._cursor_1vrf3_2._clicking_1vrf3_37 ._cursorRipple_1vrf3_31::after {\n content: '';\n position: absolute;\n width: 100%;\n height: 100%;\n left: -30%;\n top: -30%;\n border: 4px solid rgba(57, 182, 255, 1);\n border-radius: 50%;\n animation: _cursor-ripple_1vrf3_1 300ms ease-out forwards;\n}\n\n/* 光标动画关键帧 */\n@keyframes _cursor-breathe_1vrf3_1 {\n 0%,\n 100% {\n transform: scale(1);\n opacity: 0.9;\n }\n 50% {\n transform: scale(1.05);\n opacity: 1;\n }\n}\n\n@keyframes _cursor-rotate_1vrf3_1 {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes _cursor-enter_1vrf3_1 {\n 0% {\n transform: translate(-30%, -30%) scale(0.5);\n opacity: 0;\n }\n 100% {\n transform: translate(-30%, -30%) scale(1);\n opacity: 1;\n }\n}\n\n@keyframes _cursor-ripple_1vrf3_1 {\n 0% {\n transform: scale(0);\n opacity: 1;\n }\n 100% {\n transform: scale(2);\n opacity: 0;\n }\n}"));
|
|
7
|
+
document.head.appendChild(elementStyle);
|
|
8
|
+
}
|
|
9
|
+
} catch (e) {
|
|
10
|
+
console.error("vite-plugin-css-injected-by-js", e);
|
|
11
|
+
}
|
|
12
|
+
})();
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __typeError = (msg) => {
|
|
15
|
+
throw TypeError(msg);
|
|
16
|
+
};
|
|
17
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
18
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
19
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
20
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
21
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
22
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
23
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
24
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
25
|
+
var _openai, _model, _bus, _wrapper, _indicator, _statusText, _historySection, _expandButton, _pauseButton, _stopButton, _inputSection, _taskInput, _bus2, _state, _isExpanded, _pageAgent, _userAnswerResolver, _isWaitingForUserAnswer, _Panel_instances, update_fn, show_fn, hide_fn, reset_fn, togglePause_fn, updatePauseButton_fn, stopAgent_fn, submitTask_fn, handleUserAnswer_fn, showInputArea_fn, hideInputArea_fn, shouldShowInputArea_fn, createWrapper_fn, setupEventListeners_fn, toggle_fn, expand_fn, collapse_fn, animateTextChange_fn, updateStatusIndicator_fn, updateHistory_fn, scrollToBottom_fn, createHistoryItem_fn, _cursor, _currentCursorX, _currentCursorY, _targetCursorX, _targetCursorY, _SimulatorMask_instances, createCursor_fn, moveCursorToTarget_fn, _llm, _totalWaitTime, _abortController, _PageAgent_instances, packMacroTool_fn, getSystemPrompt_fn, assembleUserPrompt_fn, onDone_fn, getBrowserState_fn, updateTree_fn;
|
|
26
|
+
import { generateText, stepCountIs, tool } from "ai";
|
|
27
|
+
import chalk from "chalk";
|
|
28
|
+
import zod from "zod";
|
|
29
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
30
|
+
import { Motion } from "ai-motion";
|
|
31
|
+
const VIEWPORT_EXPANSION = -1;
|
|
32
|
+
const DEFAULT_MODEL_NAME = "gpt-41-mini-0414-global";
|
|
33
|
+
const DEFAULT_API_KEY = "not-needed";
|
|
34
|
+
const DEFAULT_BASE_URL = "http://localhost:3000/api/agent";
|
|
35
|
+
const MACRO_TOOL_NAME = "AgentOutput";
|
|
36
|
+
const LLM_MAX_RETRIES = 2;
|
|
37
|
+
const MAX_STEPS = 20;
|
|
38
|
+
const domTree = /* @__PURE__ */ __name((args = {
|
|
39
|
+
doHighlightElements: true,
|
|
40
|
+
focusHighlightIndex: -1,
|
|
41
|
+
viewportExpansion: 0,
|
|
42
|
+
debugMode: false,
|
|
43
|
+
/**
|
|
44
|
+
* @edit
|
|
45
|
+
*/
|
|
46
|
+
/** @type {Element[]} */
|
|
47
|
+
interactiveBlacklist: [],
|
|
48
|
+
/** @type {Element[]} */
|
|
49
|
+
interactiveWhitelist: [],
|
|
50
|
+
highlightOpacity: 0.1,
|
|
51
|
+
highlightLabelOpacity: 0.5
|
|
52
|
+
}) => {
|
|
53
|
+
const { interactiveBlacklist, interactiveWhitelist, highlightOpacity, highlightLabelOpacity } = args;
|
|
54
|
+
const { doHighlightElements, focusHighlightIndex, viewportExpansion, debugMode } = args;
|
|
55
|
+
let highlightIndex = 0;
|
|
56
|
+
const extraData = /* @__PURE__ */ new WeakMap();
|
|
57
|
+
function addExtraData(element, data) {
|
|
58
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
|
|
59
|
+
extraData.set(element, { ...extraData.get(element), ...data });
|
|
60
|
+
}
|
|
61
|
+
__name(addExtraData, "addExtraData");
|
|
62
|
+
const DOM_CACHE = {
|
|
63
|
+
boundingRects: /* @__PURE__ */ new WeakMap(),
|
|
64
|
+
clientRects: /* @__PURE__ */ new WeakMap(),
|
|
65
|
+
computedStyles: /* @__PURE__ */ new WeakMap(),
|
|
66
|
+
clearCache: /* @__PURE__ */ __name(() => {
|
|
67
|
+
DOM_CACHE.boundingRects = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
DOM_CACHE.clientRects = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
DOM_CACHE.computedStyles = /* @__PURE__ */ new WeakMap();
|
|
70
|
+
}, "clearCache")
|
|
71
|
+
};
|
|
72
|
+
function getCachedBoundingRect(element) {
|
|
73
|
+
if (!element) return null;
|
|
74
|
+
if (DOM_CACHE.boundingRects.has(element)) {
|
|
75
|
+
return DOM_CACHE.boundingRects.get(element);
|
|
76
|
+
}
|
|
77
|
+
const rect = element.getBoundingClientRect();
|
|
78
|
+
if (rect) {
|
|
79
|
+
DOM_CACHE.boundingRects.set(element, rect);
|
|
80
|
+
}
|
|
81
|
+
return rect;
|
|
82
|
+
}
|
|
83
|
+
__name(getCachedBoundingRect, "getCachedBoundingRect");
|
|
84
|
+
function getCachedComputedStyle(element) {
|
|
85
|
+
if (!element) return null;
|
|
86
|
+
if (DOM_CACHE.computedStyles.has(element)) {
|
|
87
|
+
return DOM_CACHE.computedStyles.get(element);
|
|
88
|
+
}
|
|
89
|
+
const style = window.getComputedStyle(element);
|
|
90
|
+
if (style) {
|
|
91
|
+
DOM_CACHE.computedStyles.set(element, style);
|
|
92
|
+
}
|
|
93
|
+
return style;
|
|
94
|
+
}
|
|
95
|
+
__name(getCachedComputedStyle, "getCachedComputedStyle");
|
|
96
|
+
function getCachedClientRects(element) {
|
|
97
|
+
if (!element) return null;
|
|
98
|
+
if (DOM_CACHE.clientRects.has(element)) {
|
|
99
|
+
return DOM_CACHE.clientRects.get(element);
|
|
100
|
+
}
|
|
101
|
+
const rects = element.getClientRects();
|
|
102
|
+
if (rects) {
|
|
103
|
+
DOM_CACHE.clientRects.set(element, rects);
|
|
104
|
+
}
|
|
105
|
+
return rects;
|
|
106
|
+
}
|
|
107
|
+
__name(getCachedClientRects, "getCachedClientRects");
|
|
108
|
+
const DOM_HASH_MAP = {};
|
|
109
|
+
const ID = { current: 0 };
|
|
110
|
+
const HIGHLIGHT_CONTAINER_ID = "playwright-highlight-container";
|
|
111
|
+
function highlightElement(element, index, parentIframe = null) {
|
|
112
|
+
if (!element) return index;
|
|
113
|
+
const overlays = [];
|
|
114
|
+
let label = null;
|
|
115
|
+
let labelWidth = 20;
|
|
116
|
+
let labelHeight = 16;
|
|
117
|
+
let cleanupFn = null;
|
|
118
|
+
try {
|
|
119
|
+
let container = document.getElementById(HIGHLIGHT_CONTAINER_ID);
|
|
120
|
+
if (!container) {
|
|
121
|
+
container = document.createElement("div");
|
|
122
|
+
container.id = HIGHLIGHT_CONTAINER_ID;
|
|
123
|
+
container.style.position = "fixed";
|
|
124
|
+
container.style.pointerEvents = "none";
|
|
125
|
+
container.style.top = "0";
|
|
126
|
+
container.style.left = "0";
|
|
127
|
+
container.style.width = "100%";
|
|
128
|
+
container.style.height = "100%";
|
|
129
|
+
container.style.zIndex = "2147483640";
|
|
130
|
+
container.style.backgroundColor = "transparent";
|
|
131
|
+
document.body.appendChild(container);
|
|
132
|
+
}
|
|
133
|
+
const rects = element.getClientRects();
|
|
134
|
+
if (!rects || rects.length === 0) return index;
|
|
135
|
+
const colors = [
|
|
136
|
+
"#FF0000",
|
|
137
|
+
"#00FF00",
|
|
138
|
+
"#0000FF",
|
|
139
|
+
"#FFA500",
|
|
140
|
+
"#800080",
|
|
141
|
+
"#008080",
|
|
142
|
+
"#FF69B4",
|
|
143
|
+
"#4B0082",
|
|
144
|
+
"#FF4500",
|
|
145
|
+
"#2E8B57",
|
|
146
|
+
"#DC143C",
|
|
147
|
+
"#4682B4"
|
|
148
|
+
];
|
|
149
|
+
const colorIndex = index % colors.length;
|
|
150
|
+
let baseColor = colors[colorIndex];
|
|
151
|
+
const backgroundColor = baseColor + Math.floor(highlightOpacity * 255).toString(16).padStart(2, "0");
|
|
152
|
+
baseColor = baseColor + Math.floor(highlightLabelOpacity * 255).toString(16).padStart(2, "0");
|
|
153
|
+
let iframeOffset = { x: 0, y: 0 };
|
|
154
|
+
if (parentIframe) {
|
|
155
|
+
const iframeRect = parentIframe.getBoundingClientRect();
|
|
156
|
+
iframeOffset.x = iframeRect.left;
|
|
157
|
+
iframeOffset.y = iframeRect.top;
|
|
158
|
+
}
|
|
159
|
+
const fragment = document.createDocumentFragment();
|
|
160
|
+
for (const rect of rects) {
|
|
161
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
162
|
+
const overlay = document.createElement("div");
|
|
163
|
+
overlay.style.position = "fixed";
|
|
164
|
+
overlay.style.border = `2px solid ${baseColor}`;
|
|
165
|
+
overlay.style.backgroundColor = backgroundColor;
|
|
166
|
+
overlay.style.pointerEvents = "none";
|
|
167
|
+
overlay.style.boxSizing = "border-box";
|
|
168
|
+
const top = rect.top + iframeOffset.y;
|
|
169
|
+
const left = rect.left + iframeOffset.x;
|
|
170
|
+
overlay.style.top = `${top}px`;
|
|
171
|
+
overlay.style.left = `${left}px`;
|
|
172
|
+
overlay.style.width = `${rect.width}px`;
|
|
173
|
+
overlay.style.height = `${rect.height}px`;
|
|
174
|
+
fragment.appendChild(overlay);
|
|
175
|
+
overlays.push({ element: overlay, initialRect: rect });
|
|
176
|
+
}
|
|
177
|
+
const firstRect = rects[0];
|
|
178
|
+
label = document.createElement("div");
|
|
179
|
+
label.className = "playwright-highlight-label";
|
|
180
|
+
label.style.position = "fixed";
|
|
181
|
+
label.style.background = baseColor;
|
|
182
|
+
label.style.color = "white";
|
|
183
|
+
label.style.padding = "1px 4px";
|
|
184
|
+
label.style.borderRadius = "4px";
|
|
185
|
+
label.style.fontSize = `${Math.min(12, Math.max(8, firstRect.height / 2))}px`;
|
|
186
|
+
label.textContent = index.toString();
|
|
187
|
+
labelWidth = label.offsetWidth > 0 ? label.offsetWidth : labelWidth;
|
|
188
|
+
labelHeight = label.offsetHeight > 0 ? label.offsetHeight : labelHeight;
|
|
189
|
+
const firstRectTop = firstRect.top + iframeOffset.y;
|
|
190
|
+
const firstRectLeft = firstRect.left + iframeOffset.x;
|
|
191
|
+
let labelTop = firstRectTop + 2;
|
|
192
|
+
let labelLeft = firstRectLeft + firstRect.width - labelWidth - 2;
|
|
193
|
+
if (firstRect.width < labelWidth + 4 || firstRect.height < labelHeight + 4) {
|
|
194
|
+
labelTop = firstRectTop - labelHeight - 2;
|
|
195
|
+
labelLeft = firstRectLeft + firstRect.width - labelWidth;
|
|
196
|
+
if (labelLeft < iframeOffset.x) labelLeft = firstRectLeft;
|
|
197
|
+
}
|
|
198
|
+
labelTop = Math.max(0, Math.min(labelTop, window.innerHeight - labelHeight));
|
|
199
|
+
labelLeft = Math.max(0, Math.min(labelLeft, window.innerWidth - labelWidth));
|
|
200
|
+
label.style.top = `${labelTop}px`;
|
|
201
|
+
label.style.left = `${labelLeft}px`;
|
|
202
|
+
fragment.appendChild(label);
|
|
203
|
+
const updatePositions = /* @__PURE__ */ __name(() => {
|
|
204
|
+
const newRects = element.getClientRects();
|
|
205
|
+
let newIframeOffset = { x: 0, y: 0 };
|
|
206
|
+
if (parentIframe) {
|
|
207
|
+
const iframeRect = parentIframe.getBoundingClientRect();
|
|
208
|
+
newIframeOffset.x = iframeRect.left;
|
|
209
|
+
newIframeOffset.y = iframeRect.top;
|
|
210
|
+
}
|
|
211
|
+
overlays.forEach((overlayData, i) => {
|
|
212
|
+
if (i < newRects.length) {
|
|
213
|
+
const newRect = newRects[i];
|
|
214
|
+
const newTop = newRect.top + newIframeOffset.y;
|
|
215
|
+
const newLeft = newRect.left + newIframeOffset.x;
|
|
216
|
+
overlayData.element.style.top = `${newTop}px`;
|
|
217
|
+
overlayData.element.style.left = `${newLeft}px`;
|
|
218
|
+
overlayData.element.style.width = `${newRect.width}px`;
|
|
219
|
+
overlayData.element.style.height = `${newRect.height}px`;
|
|
220
|
+
overlayData.element.style.display = newRect.width === 0 || newRect.height === 0 ? "none" : "block";
|
|
221
|
+
} else {
|
|
222
|
+
overlayData.element.style.display = "none";
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
if (newRects.length < overlays.length) {
|
|
226
|
+
for (let i = newRects.length; i < overlays.length; i++) {
|
|
227
|
+
overlays[i].element.style.display = "none";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (label && newRects.length > 0) {
|
|
231
|
+
const firstNewRect = newRects[0];
|
|
232
|
+
const firstNewRectTop = firstNewRect.top + newIframeOffset.y;
|
|
233
|
+
const firstNewRectLeft = firstNewRect.left + newIframeOffset.x;
|
|
234
|
+
let newLabelTop = firstNewRectTop + 2;
|
|
235
|
+
let newLabelLeft = firstNewRectLeft + firstNewRect.width - labelWidth - 2;
|
|
236
|
+
if (firstNewRect.width < labelWidth + 4 || firstNewRect.height < labelHeight + 4) {
|
|
237
|
+
newLabelTop = firstNewRectTop - labelHeight - 2;
|
|
238
|
+
newLabelLeft = firstNewRectLeft + firstNewRect.width - labelWidth;
|
|
239
|
+
if (newLabelLeft < newIframeOffset.x) newLabelLeft = firstNewRectLeft;
|
|
240
|
+
}
|
|
241
|
+
newLabelTop = Math.max(0, Math.min(newLabelTop, window.innerHeight - labelHeight));
|
|
242
|
+
newLabelLeft = Math.max(0, Math.min(newLabelLeft, window.innerWidth - labelWidth));
|
|
243
|
+
label.style.top = `${newLabelTop}px`;
|
|
244
|
+
label.style.left = `${newLabelLeft}px`;
|
|
245
|
+
label.style.display = "block";
|
|
246
|
+
} else if (label) {
|
|
247
|
+
label.style.display = "none";
|
|
248
|
+
}
|
|
249
|
+
}, "updatePositions");
|
|
250
|
+
const throttleFunction = /* @__PURE__ */ __name((func, delay) => {
|
|
251
|
+
let lastCall = 0;
|
|
252
|
+
return (...args2) => {
|
|
253
|
+
const now = performance.now();
|
|
254
|
+
if (now - lastCall < delay) return;
|
|
255
|
+
lastCall = now;
|
|
256
|
+
return func(...args2);
|
|
257
|
+
};
|
|
258
|
+
}, "throttleFunction");
|
|
259
|
+
const throttledUpdatePositions = throttleFunction(updatePositions, 16);
|
|
260
|
+
window.addEventListener("scroll", throttledUpdatePositions, true);
|
|
261
|
+
window.addEventListener("resize", throttledUpdatePositions);
|
|
262
|
+
cleanupFn = /* @__PURE__ */ __name(() => {
|
|
263
|
+
window.removeEventListener("scroll", throttledUpdatePositions, true);
|
|
264
|
+
window.removeEventListener("resize", throttledUpdatePositions);
|
|
265
|
+
overlays.forEach((overlay) => overlay.element.remove());
|
|
266
|
+
if (label) label.remove();
|
|
267
|
+
}, "cleanupFn");
|
|
268
|
+
container.appendChild(fragment);
|
|
269
|
+
return index + 1;
|
|
270
|
+
} finally {
|
|
271
|
+
if (cleanupFn) {
|
|
272
|
+
(window._highlightCleanupFunctions = window._highlightCleanupFunctions || []).push(
|
|
273
|
+
cleanupFn
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
__name(highlightElement, "highlightElement");
|
|
279
|
+
function isScrollableElement(element) {
|
|
280
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const style = getCachedComputedStyle(element);
|
|
284
|
+
if (!style) return null;
|
|
285
|
+
const display = style.display;
|
|
286
|
+
if (display === "inline" || display === "inline-block") {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const overflowX = style.overflowX;
|
|
290
|
+
const overflowY = style.overflowY;
|
|
291
|
+
const scrollableX = overflowX === "auto" || overflowX === "scroll";
|
|
292
|
+
const scrollableY = overflowY === "auto" || overflowY === "scroll";
|
|
293
|
+
if (!scrollableX && !scrollableY) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const scrollWidth = element.scrollWidth - element.clientWidth;
|
|
297
|
+
const scrollHeight = element.scrollHeight - element.clientHeight;
|
|
298
|
+
const threshold = 4;
|
|
299
|
+
if (scrollWidth < threshold && scrollHeight < threshold) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
if (!scrollableY && scrollWidth < threshold) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (!scrollableX && scrollHeight < threshold) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const distanceToTop = element.scrollTop;
|
|
309
|
+
const distanceToLeft = element.scrollLeft;
|
|
310
|
+
const distanceToRight = element.scrollWidth - element.clientWidth - element.scrollLeft;
|
|
311
|
+
const distanceToBottom = element.scrollHeight - element.clientHeight - element.scrollTop;
|
|
312
|
+
const scrollData = {
|
|
313
|
+
top: distanceToTop,
|
|
314
|
+
right: distanceToRight,
|
|
315
|
+
bottom: distanceToBottom,
|
|
316
|
+
left: distanceToLeft
|
|
317
|
+
};
|
|
318
|
+
addExtraData(element, {
|
|
319
|
+
scrollable: true,
|
|
320
|
+
scrollData
|
|
321
|
+
});
|
|
322
|
+
return scrollData;
|
|
323
|
+
}
|
|
324
|
+
__name(isScrollableElement, "isScrollableElement");
|
|
325
|
+
function isTextNodeVisible(textNode) {
|
|
326
|
+
try {
|
|
327
|
+
if (viewportExpansion === -1) {
|
|
328
|
+
const parentElement2 = textNode.parentElement;
|
|
329
|
+
if (!parentElement2) return false;
|
|
330
|
+
try {
|
|
331
|
+
return parentElement2.checkVisibility({
|
|
332
|
+
checkOpacity: true,
|
|
333
|
+
checkVisibilityCSS: true
|
|
334
|
+
});
|
|
335
|
+
} catch (e) {
|
|
336
|
+
const style = window.getComputedStyle(parentElement2);
|
|
337
|
+
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const range = document.createRange();
|
|
341
|
+
range.selectNodeContents(textNode);
|
|
342
|
+
const rects = range.getClientRects();
|
|
343
|
+
if (!rects || rects.length === 0) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
let isAnyRectVisible = false;
|
|
347
|
+
let isAnyRectInViewport = false;
|
|
348
|
+
for (const rect of rects) {
|
|
349
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
350
|
+
isAnyRectVisible = true;
|
|
351
|
+
if (!(rect.bottom < -viewportExpansion || rect.top > window.innerHeight + viewportExpansion || rect.right < -viewportExpansion || rect.left > window.innerWidth + viewportExpansion)) {
|
|
352
|
+
isAnyRectInViewport = true;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (!isAnyRectVisible || !isAnyRectInViewport) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const parentElement = textNode.parentElement;
|
|
361
|
+
if (!parentElement) return false;
|
|
362
|
+
try {
|
|
363
|
+
return parentElement.checkVisibility({
|
|
364
|
+
checkOpacity: true,
|
|
365
|
+
checkVisibilityCSS: true
|
|
366
|
+
});
|
|
367
|
+
} catch (e) {
|
|
368
|
+
const style = window.getComputedStyle(parentElement);
|
|
369
|
+
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {
|
|
372
|
+
console.warn("Error checking text node visibility:", e);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
__name(isTextNodeVisible, "isTextNodeVisible");
|
|
377
|
+
function isElementAccepted(element) {
|
|
378
|
+
if (!element || !element.tagName) return false;
|
|
379
|
+
const alwaysAccept = /* @__PURE__ */ new Set([
|
|
380
|
+
"body",
|
|
381
|
+
"div",
|
|
382
|
+
"main",
|
|
383
|
+
"article",
|
|
384
|
+
"section",
|
|
385
|
+
"nav",
|
|
386
|
+
"header",
|
|
387
|
+
"footer"
|
|
388
|
+
]);
|
|
389
|
+
const tagName = element.tagName.toLowerCase();
|
|
390
|
+
if (alwaysAccept.has(tagName)) return true;
|
|
391
|
+
const leafElementDenyList = /* @__PURE__ */ new Set([
|
|
392
|
+
"svg",
|
|
393
|
+
"script",
|
|
394
|
+
"style",
|
|
395
|
+
"link",
|
|
396
|
+
"meta",
|
|
397
|
+
"noscript",
|
|
398
|
+
"template"
|
|
399
|
+
]);
|
|
400
|
+
return !leafElementDenyList.has(tagName);
|
|
401
|
+
}
|
|
402
|
+
__name(isElementAccepted, "isElementAccepted");
|
|
403
|
+
function isElementVisible(element) {
|
|
404
|
+
const style = getCachedComputedStyle(element);
|
|
405
|
+
return element.offsetWidth > 0 && element.offsetHeight > 0 && style?.visibility !== "hidden" && style?.display !== "none";
|
|
406
|
+
}
|
|
407
|
+
__name(isElementVisible, "isElementVisible");
|
|
408
|
+
function isInteractiveElement(element) {
|
|
409
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (interactiveBlacklist.includes(element)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (interactiveWhitelist.includes(element)) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
const tagName = element.tagName.toLowerCase();
|
|
419
|
+
const style = getCachedComputedStyle(element);
|
|
420
|
+
const interactiveCursors = /* @__PURE__ */ new Set([
|
|
421
|
+
"pointer",
|
|
422
|
+
// Link/clickable elements
|
|
423
|
+
"move",
|
|
424
|
+
// Movable elements
|
|
425
|
+
"text",
|
|
426
|
+
// Text selection
|
|
427
|
+
"grab",
|
|
428
|
+
// Grabbable elements
|
|
429
|
+
"grabbing",
|
|
430
|
+
// Currently grabbing
|
|
431
|
+
"cell",
|
|
432
|
+
// Table cell selection
|
|
433
|
+
"copy",
|
|
434
|
+
// Copy operation
|
|
435
|
+
"alias",
|
|
436
|
+
// Alias creation
|
|
437
|
+
"all-scroll",
|
|
438
|
+
// Scrollable content
|
|
439
|
+
"col-resize",
|
|
440
|
+
// Column resize
|
|
441
|
+
"context-menu",
|
|
442
|
+
// Context menu available
|
|
443
|
+
"crosshair",
|
|
444
|
+
// Precise selection
|
|
445
|
+
"e-resize",
|
|
446
|
+
// East resize
|
|
447
|
+
"ew-resize",
|
|
448
|
+
// East-west resize
|
|
449
|
+
"help",
|
|
450
|
+
// Help available
|
|
451
|
+
"n-resize",
|
|
452
|
+
// North resize
|
|
453
|
+
"ne-resize",
|
|
454
|
+
// Northeast resize
|
|
455
|
+
"nesw-resize",
|
|
456
|
+
// Northeast-southwest resize
|
|
457
|
+
"ns-resize",
|
|
458
|
+
// North-south resize
|
|
459
|
+
"nw-resize",
|
|
460
|
+
// Northwest resize
|
|
461
|
+
"nwse-resize",
|
|
462
|
+
// Northwest-southeast resize
|
|
463
|
+
"row-resize",
|
|
464
|
+
// Row resize
|
|
465
|
+
"s-resize",
|
|
466
|
+
// South resize
|
|
467
|
+
"se-resize",
|
|
468
|
+
// Southeast resize
|
|
469
|
+
"sw-resize",
|
|
470
|
+
// Southwest resize
|
|
471
|
+
"vertical-text",
|
|
472
|
+
// Vertical text selection
|
|
473
|
+
"w-resize",
|
|
474
|
+
// West resize
|
|
475
|
+
"zoom-in",
|
|
476
|
+
// Zoom in
|
|
477
|
+
"zoom-out"
|
|
478
|
+
// Zoom out
|
|
479
|
+
]);
|
|
480
|
+
const nonInteractiveCursors = /* @__PURE__ */ new Set([
|
|
481
|
+
"not-allowed",
|
|
482
|
+
// Action not allowed
|
|
483
|
+
"no-drop",
|
|
484
|
+
// Drop not allowed
|
|
485
|
+
"wait",
|
|
486
|
+
// Processing
|
|
487
|
+
"progress",
|
|
488
|
+
// In progress
|
|
489
|
+
"initial",
|
|
490
|
+
// Initial value
|
|
491
|
+
"inherit"
|
|
492
|
+
// Inherited value
|
|
493
|
+
//? Let's just include all potentially clickable elements that are not specifically blocked
|
|
494
|
+
// 'none', // No cursor
|
|
495
|
+
// 'default', // Default cursor
|
|
496
|
+
// 'auto', // Browser default
|
|
497
|
+
]);
|
|
498
|
+
function doesElementHaveInteractivePointer(element2) {
|
|
499
|
+
if (element2.tagName.toLowerCase() === "html") return false;
|
|
500
|
+
if (style?.cursor && interactiveCursors.has(style.cursor)) return true;
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
__name(doesElementHaveInteractivePointer, "doesElementHaveInteractivePointer");
|
|
504
|
+
let isInteractiveCursor = doesElementHaveInteractivePointer(element);
|
|
505
|
+
if (isInteractiveCursor) {
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
const interactiveElements = /* @__PURE__ */ new Set([
|
|
509
|
+
"a",
|
|
510
|
+
// Links
|
|
511
|
+
"button",
|
|
512
|
+
// Buttons
|
|
513
|
+
"input",
|
|
514
|
+
// All input types (text, checkbox, radio, etc.)
|
|
515
|
+
"select",
|
|
516
|
+
// Dropdown menus
|
|
517
|
+
"textarea",
|
|
518
|
+
// Text areas
|
|
519
|
+
"details",
|
|
520
|
+
// Expandable details
|
|
521
|
+
"summary",
|
|
522
|
+
// Summary element (clickable part of details)
|
|
523
|
+
"label",
|
|
524
|
+
// Form labels (often clickable)
|
|
525
|
+
"option",
|
|
526
|
+
// Select options
|
|
527
|
+
"optgroup",
|
|
528
|
+
// Option groups
|
|
529
|
+
"fieldset",
|
|
530
|
+
// Form fieldsets (can be interactive with legend)
|
|
531
|
+
"legend"
|
|
532
|
+
// Fieldset legends
|
|
533
|
+
]);
|
|
534
|
+
const explicitDisableTags = /* @__PURE__ */ new Set([
|
|
535
|
+
"disabled",
|
|
536
|
+
// Standard disabled attribute
|
|
537
|
+
// 'aria-disabled', // ARIA disabled state
|
|
538
|
+
"readonly"
|
|
539
|
+
// Read-only state
|
|
540
|
+
// 'aria-readonly', // ARIA read-only state
|
|
541
|
+
// 'aria-hidden', // Hidden from accessibility
|
|
542
|
+
// 'hidden', // Hidden attribute
|
|
543
|
+
// 'inert', // Inert attribute
|
|
544
|
+
// 'aria-inert', // ARIA inert state
|
|
545
|
+
// 'tabindex="-1"', // Removed from tab order
|
|
546
|
+
// 'aria-hidden="true"' // Hidden from screen readers
|
|
547
|
+
]);
|
|
548
|
+
if (interactiveElements.has(tagName)) {
|
|
549
|
+
if (style?.cursor && nonInteractiveCursors.has(style.cursor)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
for (const disableTag of explicitDisableTags) {
|
|
553
|
+
if (element.hasAttribute(disableTag) || element.getAttribute(disableTag) === "true" || element.getAttribute(disableTag) === "") {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (element.disabled) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
if (element.readOnly) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
if (element.inert) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
const role = element.getAttribute("role");
|
|
569
|
+
const ariaRole = element.getAttribute("aria-role");
|
|
570
|
+
if (element.getAttribute("contenteditable") === "true" || element.isContentEditable) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
if (element.classList && (element.classList.contains("button") || element.classList.contains("dropdown-toggle") || element.getAttribute("data-index") || element.getAttribute("data-toggle") === "dropdown" || element.getAttribute("aria-haspopup") === "true")) {
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
const interactiveRoles = /* @__PURE__ */ new Set([
|
|
577
|
+
"button",
|
|
578
|
+
// Directly clickable element
|
|
579
|
+
// 'link', // Clickable link
|
|
580
|
+
"menu",
|
|
581
|
+
// Menu container (ARIA menus)
|
|
582
|
+
"menubar",
|
|
583
|
+
// Menu bar container
|
|
584
|
+
"menuitem",
|
|
585
|
+
// Clickable menu item
|
|
586
|
+
"menuitemradio",
|
|
587
|
+
// Radio-style menu item (selectable)
|
|
588
|
+
"menuitemcheckbox",
|
|
589
|
+
// Checkbox-style menu item (toggleable)
|
|
590
|
+
"radio",
|
|
591
|
+
// Radio button (selectable)
|
|
592
|
+
"checkbox",
|
|
593
|
+
// Checkbox (toggleable)
|
|
594
|
+
"tab",
|
|
595
|
+
// Tab (clickable to switch content)
|
|
596
|
+
"switch",
|
|
597
|
+
// Toggle switch (clickable to change state)
|
|
598
|
+
"slider",
|
|
599
|
+
// Slider control (draggable)
|
|
600
|
+
"spinbutton",
|
|
601
|
+
// Number input with up/down controls
|
|
602
|
+
"combobox",
|
|
603
|
+
// Dropdown with text input
|
|
604
|
+
"searchbox",
|
|
605
|
+
// Search input field
|
|
606
|
+
"textbox",
|
|
607
|
+
// Text input field
|
|
608
|
+
"listbox",
|
|
609
|
+
// Selectable list
|
|
610
|
+
"option",
|
|
611
|
+
// Selectable option in a list
|
|
612
|
+
"scrollbar"
|
|
613
|
+
// Scrollable control
|
|
614
|
+
]);
|
|
615
|
+
const hasInteractiveRole = interactiveElements.has(tagName) || role && interactiveRoles.has(role) || ariaRole && interactiveRoles.has(ariaRole);
|
|
616
|
+
if (hasInteractiveRole) return true;
|
|
617
|
+
try {
|
|
618
|
+
if (typeof getEventListeners === "function") {
|
|
619
|
+
const listeners = getEventListeners(element);
|
|
620
|
+
const mouseEvents = ["click", "mousedown", "mouseup", "dblclick"];
|
|
621
|
+
for (const eventType of mouseEvents) {
|
|
622
|
+
if (listeners[eventType] && listeners[eventType].length > 0) {
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const getEventListenersForNode = element?.ownerDocument?.defaultView?.getEventListenersForNode || window.getEventListenersForNode;
|
|
628
|
+
if (typeof getEventListenersForNode === "function") {
|
|
629
|
+
const listeners = getEventListenersForNode(element);
|
|
630
|
+
const interactionEvents = [
|
|
631
|
+
"click",
|
|
632
|
+
"mousedown",
|
|
633
|
+
"mouseup",
|
|
634
|
+
"keydown",
|
|
635
|
+
"keyup",
|
|
636
|
+
"submit",
|
|
637
|
+
"change",
|
|
638
|
+
"input",
|
|
639
|
+
"focus",
|
|
640
|
+
"blur"
|
|
641
|
+
];
|
|
642
|
+
for (const eventType of interactionEvents) {
|
|
643
|
+
for (const listener of listeners) {
|
|
644
|
+
if (listener.type === eventType) {
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const commonMouseAttrs = ["onclick", "onmousedown", "onmouseup", "ondblclick"];
|
|
651
|
+
for (const attr of commonMouseAttrs) {
|
|
652
|
+
if (element.hasAttribute(attr) || typeof element[attr] === "function") {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
} catch (e) {
|
|
657
|
+
}
|
|
658
|
+
if (isScrollableElement(element)) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
__name(isInteractiveElement, "isInteractiveElement");
|
|
664
|
+
function isTopElement(element) {
|
|
665
|
+
if (viewportExpansion === -1) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
const rects = getCachedClientRects(element);
|
|
669
|
+
if (!rects || rects.length === 0) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
let isAnyRectInViewport = false;
|
|
673
|
+
for (const rect2 of rects) {
|
|
674
|
+
if (rect2.width > 0 && rect2.height > 0 && !// Only check non-empty rects
|
|
675
|
+
(rect2.bottom < -viewportExpansion || rect2.top > window.innerHeight + viewportExpansion || rect2.right < -viewportExpansion || rect2.left > window.innerWidth + viewportExpansion)) {
|
|
676
|
+
isAnyRectInViewport = true;
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (!isAnyRectInViewport) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
let doc = element.ownerDocument;
|
|
684
|
+
if (doc !== window.document) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
let rect = Array.from(rects).find((r) => r.width > 0 && r.height > 0);
|
|
688
|
+
if (!rect) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
const shadowRoot = element.getRootNode();
|
|
692
|
+
if (shadowRoot instanceof ShadowRoot) {
|
|
693
|
+
const centerX = rect.left + rect.width / 2;
|
|
694
|
+
const centerY = rect.top + rect.height / 2;
|
|
695
|
+
try {
|
|
696
|
+
const topEl = shadowRoot.elementFromPoint(centerX, centerY);
|
|
697
|
+
if (!topEl) return false;
|
|
698
|
+
let current = topEl;
|
|
699
|
+
while (current && current !== shadowRoot) {
|
|
700
|
+
if (current === element) return true;
|
|
701
|
+
current = current.parentElement;
|
|
702
|
+
}
|
|
703
|
+
return false;
|
|
704
|
+
} catch (e) {
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const margin = 5;
|
|
709
|
+
const checkPoints = [
|
|
710
|
+
// Initially only this was used, but it was not enough
|
|
711
|
+
{ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
|
|
712
|
+
{ x: rect.left + margin, y: rect.top + margin },
|
|
713
|
+
// top left
|
|
714
|
+
// { x: rect.right - margin, y: rect.top + margin }, // top right
|
|
715
|
+
// { x: rect.left + margin, y: rect.bottom - margin }, // bottom left
|
|
716
|
+
{ x: rect.right - margin, y: rect.bottom - margin }
|
|
717
|
+
// bottom right
|
|
718
|
+
];
|
|
719
|
+
return checkPoints.some(({ x, y }) => {
|
|
720
|
+
try {
|
|
721
|
+
const topEl = document.elementFromPoint(x, y);
|
|
722
|
+
if (!topEl) return false;
|
|
723
|
+
let current = topEl;
|
|
724
|
+
while (current && current !== document.documentElement) {
|
|
725
|
+
if (current === element) return true;
|
|
726
|
+
current = current.parentElement;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
} catch (e) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
__name(isTopElement, "isTopElement");
|
|
735
|
+
function isInExpandedViewport(element, viewportExpansion2) {
|
|
736
|
+
if (viewportExpansion2 === -1) {
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
const rects = element.getClientRects();
|
|
740
|
+
if (!rects || rects.length === 0) {
|
|
741
|
+
const boundingRect = getCachedBoundingRect(element);
|
|
742
|
+
if (!boundingRect || boundingRect.width === 0 || boundingRect.height === 0) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
return !(boundingRect.bottom < -viewportExpansion2 || boundingRect.top > window.innerHeight + viewportExpansion2 || boundingRect.right < -viewportExpansion2 || boundingRect.left > window.innerWidth + viewportExpansion2);
|
|
746
|
+
}
|
|
747
|
+
for (const rect of rects) {
|
|
748
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
749
|
+
if (!(rect.bottom < -viewportExpansion2 || rect.top > window.innerHeight + viewportExpansion2 || rect.right < -viewportExpansion2 || rect.left > window.innerWidth + viewportExpansion2)) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
__name(isInExpandedViewport, "isInExpandedViewport");
|
|
756
|
+
function isInteractiveCandidate(element) {
|
|
757
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
|
|
758
|
+
const tagName = element.tagName.toLowerCase();
|
|
759
|
+
const interactiveElements = /* @__PURE__ */ new Set([
|
|
760
|
+
"a",
|
|
761
|
+
"button",
|
|
762
|
+
"input",
|
|
763
|
+
"select",
|
|
764
|
+
"textarea",
|
|
765
|
+
"details",
|
|
766
|
+
"summary",
|
|
767
|
+
"label"
|
|
768
|
+
]);
|
|
769
|
+
if (interactiveElements.has(tagName)) return true;
|
|
770
|
+
const hasQuickInteractiveAttr = element.hasAttribute("onclick") || element.hasAttribute("role") || element.hasAttribute("tabindex") || element.hasAttribute("aria-") || element.hasAttribute("data-action") || element.getAttribute("contenteditable") === "true";
|
|
771
|
+
return hasQuickInteractiveAttr;
|
|
772
|
+
}
|
|
773
|
+
__name(isInteractiveCandidate, "isInteractiveCandidate");
|
|
774
|
+
const DISTINCT_INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
775
|
+
"a",
|
|
776
|
+
"button",
|
|
777
|
+
"input",
|
|
778
|
+
"select",
|
|
779
|
+
"textarea",
|
|
780
|
+
"summary",
|
|
781
|
+
"details",
|
|
782
|
+
"label",
|
|
783
|
+
"option"
|
|
784
|
+
]);
|
|
785
|
+
const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
786
|
+
"button",
|
|
787
|
+
"link",
|
|
788
|
+
"menuitem",
|
|
789
|
+
"menuitemradio",
|
|
790
|
+
"menuitemcheckbox",
|
|
791
|
+
"radio",
|
|
792
|
+
"checkbox",
|
|
793
|
+
"tab",
|
|
794
|
+
"switch",
|
|
795
|
+
"slider",
|
|
796
|
+
"spinbutton",
|
|
797
|
+
"combobox",
|
|
798
|
+
"searchbox",
|
|
799
|
+
"textbox",
|
|
800
|
+
"listbox",
|
|
801
|
+
"option",
|
|
802
|
+
"scrollbar"
|
|
803
|
+
]);
|
|
804
|
+
function isHeuristicallyInteractive(element) {
|
|
805
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
|
|
806
|
+
if (!isElementVisible(element)) return false;
|
|
807
|
+
const hasInteractiveAttributes = element.hasAttribute("role") || element.hasAttribute("tabindex") || element.hasAttribute("onclick") || typeof element.onclick === "function";
|
|
808
|
+
const hasInteractiveClass = /\b(btn|clickable|menu|item|entry|link)\b/i.test(
|
|
809
|
+
element.className || ""
|
|
810
|
+
);
|
|
811
|
+
const isInKnownContainer = Boolean(
|
|
812
|
+
element.closest('button,a,[role="button"],.menu,.dropdown,.list,.toolbar')
|
|
813
|
+
);
|
|
814
|
+
const hasVisibleChildren = [...element.children].some(isElementVisible);
|
|
815
|
+
const isParentBody = element.parentElement && element.parentElement.isSameNode(document.body);
|
|
816
|
+
return (isInteractiveElement(element) || hasInteractiveAttributes || hasInteractiveClass) && hasVisibleChildren && isInKnownContainer && !isParentBody;
|
|
817
|
+
}
|
|
818
|
+
__name(isHeuristicallyInteractive, "isHeuristicallyInteractive");
|
|
819
|
+
function isElementDistinctInteraction(element) {
|
|
820
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
const tagName = element.tagName.toLowerCase();
|
|
824
|
+
const role = element.getAttribute("role");
|
|
825
|
+
if (tagName === "iframe") {
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
if (DISTINCT_INTERACTIVE_TAGS.has(tagName)) {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
if (role && INTERACTIVE_ROLES.has(role)) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
if (element.isContentEditable || element.getAttribute("contenteditable") === "true") {
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
if (element.hasAttribute("data-testid") || element.hasAttribute("data-cy") || element.hasAttribute("data-test")) {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
840
|
+
if (element.hasAttribute("onclick") || typeof element.onclick === "function") {
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
const getEventListenersForNode = element?.ownerDocument?.defaultView?.getEventListenersForNode || window.getEventListenersForNode;
|
|
845
|
+
if (typeof getEventListenersForNode === "function") {
|
|
846
|
+
const listeners = getEventListenersForNode(element);
|
|
847
|
+
const interactionEvents = [
|
|
848
|
+
"click",
|
|
849
|
+
"mousedown",
|
|
850
|
+
"mouseup",
|
|
851
|
+
"keydown",
|
|
852
|
+
"keyup",
|
|
853
|
+
"submit",
|
|
854
|
+
"change",
|
|
855
|
+
"input",
|
|
856
|
+
"focus",
|
|
857
|
+
"blur"
|
|
858
|
+
];
|
|
859
|
+
for (const eventType of interactionEvents) {
|
|
860
|
+
for (const listener of listeners) {
|
|
861
|
+
if (listener.type === eventType) {
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const commonEventAttrs = [
|
|
868
|
+
"onmousedown",
|
|
869
|
+
"onmouseup",
|
|
870
|
+
"onkeydown",
|
|
871
|
+
"onkeyup",
|
|
872
|
+
"onsubmit",
|
|
873
|
+
"onchange",
|
|
874
|
+
"oninput",
|
|
875
|
+
"onfocus",
|
|
876
|
+
"onblur"
|
|
877
|
+
];
|
|
878
|
+
if (commonEventAttrs.some((attr) => element.hasAttribute(attr))) {
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
} catch (e) {
|
|
882
|
+
}
|
|
883
|
+
if (isHeuristicallyInteractive(element)) {
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
__name(isElementDistinctInteraction, "isElementDistinctInteraction");
|
|
889
|
+
function handleHighlighting(nodeData, node, parentIframe, isParentHighlighted) {
|
|
890
|
+
if (!nodeData.isInteractive) return false;
|
|
891
|
+
let shouldHighlight = false;
|
|
892
|
+
if (!isParentHighlighted) {
|
|
893
|
+
shouldHighlight = true;
|
|
894
|
+
} else {
|
|
895
|
+
if (isElementDistinctInteraction(node)) {
|
|
896
|
+
shouldHighlight = true;
|
|
897
|
+
} else {
|
|
898
|
+
shouldHighlight = false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (shouldHighlight) {
|
|
902
|
+
nodeData.isInViewport = isInExpandedViewport(node, viewportExpansion);
|
|
903
|
+
if (nodeData.isInViewport || viewportExpansion === -1) {
|
|
904
|
+
nodeData.highlightIndex = highlightIndex++;
|
|
905
|
+
if (doHighlightElements) {
|
|
906
|
+
if (focusHighlightIndex >= 0) {
|
|
907
|
+
if (focusHighlightIndex === nodeData.highlightIndex) {
|
|
908
|
+
highlightElement(node, nodeData.highlightIndex, parentIframe);
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
highlightElement(node, nodeData.highlightIndex, parentIframe);
|
|
912
|
+
}
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
__name(handleHighlighting, "handleHighlighting");
|
|
920
|
+
function buildDomTree(node, parentIframe = null, isParentHighlighted = false) {
|
|
921
|
+
if (!node || node.id === HIGHLIGHT_CONTAINER_ID || node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE) {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
if (!node || node.id === HIGHLIGHT_CONTAINER_ID) {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
if (node.dataset?.browserUseIgnore === "true") {
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
if (node === document.body) {
|
|
931
|
+
const nodeData2 = {
|
|
932
|
+
tagName: "body",
|
|
933
|
+
attributes: {},
|
|
934
|
+
xpath: "/body",
|
|
935
|
+
children: []
|
|
936
|
+
};
|
|
937
|
+
for (const child of node.childNodes) {
|
|
938
|
+
const domElement = buildDomTree(child, parentIframe, false);
|
|
939
|
+
if (domElement) nodeData2.children.push(domElement);
|
|
940
|
+
}
|
|
941
|
+
const id2 = `${ID.current++}`;
|
|
942
|
+
DOM_HASH_MAP[id2] = nodeData2;
|
|
943
|
+
return id2;
|
|
944
|
+
}
|
|
945
|
+
if (node.nodeType !== Node.ELEMENT_NODE && node.nodeType !== Node.TEXT_NODE) {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
949
|
+
const textContent = node.textContent?.trim();
|
|
950
|
+
if (!textContent) {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
const parentElement = node.parentElement;
|
|
954
|
+
if (!parentElement || parentElement.tagName.toLowerCase() === "script") {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
const id2 = `${ID.current++}`;
|
|
958
|
+
DOM_HASH_MAP[id2] = {
|
|
959
|
+
type: "TEXT_NODE",
|
|
960
|
+
text: textContent,
|
|
961
|
+
isVisible: isTextNodeVisible(node)
|
|
962
|
+
};
|
|
963
|
+
return id2;
|
|
964
|
+
}
|
|
965
|
+
if (node.nodeType === Node.ELEMENT_NODE && !isElementAccepted(node)) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
if (viewportExpansion !== -1 && !node.shadowRoot) {
|
|
969
|
+
const rect = getCachedBoundingRect(node);
|
|
970
|
+
const style = getCachedComputedStyle(node);
|
|
971
|
+
const isFixedOrSticky = style && (style.position === "fixed" || style.position === "sticky");
|
|
972
|
+
const hasSize = node.offsetWidth > 0 || node.offsetHeight > 0;
|
|
973
|
+
if (!rect || !isFixedOrSticky && !hasSize && (rect.bottom < -viewportExpansion || rect.top > window.innerHeight + viewportExpansion || rect.right < -viewportExpansion || rect.left > window.innerWidth + viewportExpansion)) {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
const nodeData = {
|
|
978
|
+
tagName: node.tagName.toLowerCase(),
|
|
979
|
+
attributes: {},
|
|
980
|
+
/**
|
|
981
|
+
* @edit no need for xpath
|
|
982
|
+
*/
|
|
983
|
+
// xpath: getXPathTree(node, true),
|
|
984
|
+
children: []
|
|
985
|
+
};
|
|
986
|
+
if (isInteractiveCandidate(node) || node.tagName.toLowerCase() === "iframe" || node.tagName.toLowerCase() === "body") {
|
|
987
|
+
const attributeNames = node.getAttributeNames?.() || [];
|
|
988
|
+
for (const name of attributeNames) {
|
|
989
|
+
const value = node.getAttribute(name);
|
|
990
|
+
nodeData.attributes[name] = value;
|
|
991
|
+
}
|
|
992
|
+
if (node.tagName.toLowerCase() === "input" && (node.type === "checkbox" || node.type === "radio")) {
|
|
993
|
+
nodeData.attributes.checked = node.checked ? "true" : "false";
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
let nodeWasHighlighted = false;
|
|
997
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
998
|
+
nodeData.isVisible = isElementVisible(node);
|
|
999
|
+
if (nodeData.isVisible) {
|
|
1000
|
+
nodeData.isTopElement = isTopElement(node);
|
|
1001
|
+
const role = node.getAttribute("role");
|
|
1002
|
+
const isMenuContainer = role === "menu" || role === "menubar" || role === "listbox";
|
|
1003
|
+
if (nodeData.isTopElement || isMenuContainer) {
|
|
1004
|
+
nodeData.isInteractive = isInteractiveElement(node);
|
|
1005
|
+
nodeWasHighlighted = handleHighlighting(nodeData, node, parentIframe, isParentHighlighted);
|
|
1006
|
+
nodeData.ref = node;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (node.tagName) {
|
|
1011
|
+
const tagName = node.tagName.toLowerCase();
|
|
1012
|
+
if (tagName === "iframe") {
|
|
1013
|
+
try {
|
|
1014
|
+
const iframeDoc = node.contentDocument || node.contentWindow?.document;
|
|
1015
|
+
if (iframeDoc) {
|
|
1016
|
+
for (const child of iframeDoc.childNodes) {
|
|
1017
|
+
const domElement = buildDomTree(child, node, false);
|
|
1018
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
} catch (e) {
|
|
1022
|
+
console.warn("Unable to access iframe:", e);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (node.isContentEditable || node.getAttribute("contenteditable") === "true" || node.id === "tinymce" || node.classList.contains("mce-content-body") || tagName === "body" && node.getAttribute("data-id")?.startsWith("mce_")) {
|
|
1025
|
+
for (const child of node.childNodes) {
|
|
1026
|
+
const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted);
|
|
1027
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1028
|
+
}
|
|
1029
|
+
} else {
|
|
1030
|
+
if (node.shadowRoot) {
|
|
1031
|
+
nodeData.shadowRoot = true;
|
|
1032
|
+
for (const child of node.shadowRoot.childNodes) {
|
|
1033
|
+
const domElement = buildDomTree(child, parentIframe, nodeWasHighlighted);
|
|
1034
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
for (const child of node.childNodes) {
|
|
1038
|
+
const passHighlightStatusToChild = nodeWasHighlighted || isParentHighlighted;
|
|
1039
|
+
const domElement = buildDomTree(child, parentIframe, passHighlightStatusToChild);
|
|
1040
|
+
if (domElement) nodeData.children.push(domElement);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (nodeData.tagName === "a" && nodeData.children.length === 0 && !nodeData.attributes.href) {
|
|
1045
|
+
const rect = getCachedBoundingRect(node);
|
|
1046
|
+
const hasSize = rect && rect.width > 0 && rect.height > 0 || node.offsetWidth > 0 || node.offsetHeight > 0;
|
|
1047
|
+
if (!hasSize) {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
nodeData.extra = extraData.get(node) || null;
|
|
1052
|
+
const id = `${ID.current++}`;
|
|
1053
|
+
DOM_HASH_MAP[id] = nodeData;
|
|
1054
|
+
return id;
|
|
1055
|
+
}
|
|
1056
|
+
__name(buildDomTree, "buildDomTree");
|
|
1057
|
+
const rootId = buildDomTree(document.body);
|
|
1058
|
+
DOM_CACHE.clearCache();
|
|
1059
|
+
return { rootId, map: DOM_HASH_MAP };
|
|
1060
|
+
}, "domTree");
|
|
1061
|
+
const newElementsCache = /* @__PURE__ */ new WeakMap();
|
|
1062
|
+
function getFlatTree(config) {
|
|
1063
|
+
const interactiveBlacklist = [];
|
|
1064
|
+
for (const item of config.interactiveBlacklist || []) {
|
|
1065
|
+
if (typeof item === "function") {
|
|
1066
|
+
interactiveBlacklist.push(item());
|
|
1067
|
+
} else {
|
|
1068
|
+
interactiveBlacklist.push(item);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const interactiveWhitelist = [];
|
|
1072
|
+
for (const item of config.interactiveWhitelist || []) {
|
|
1073
|
+
if (typeof item === "function") {
|
|
1074
|
+
interactiveWhitelist.push(item());
|
|
1075
|
+
} else {
|
|
1076
|
+
interactiveWhitelist.push(item);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const elements = domTree({
|
|
1080
|
+
doHighlightElements: true,
|
|
1081
|
+
debugMode: true,
|
|
1082
|
+
focusHighlightIndex: -1,
|
|
1083
|
+
viewportExpansion: VIEWPORT_EXPANSION,
|
|
1084
|
+
interactiveBlacklist,
|
|
1085
|
+
interactiveWhitelist,
|
|
1086
|
+
highlightOpacity: config.highlightOpacity ?? 0,
|
|
1087
|
+
highlightLabelOpacity: config.highlightLabelOpacity ?? 0.1
|
|
1088
|
+
});
|
|
1089
|
+
const currentUrl2 = window.location.href;
|
|
1090
|
+
for (const nodeId in elements.map) {
|
|
1091
|
+
const node = elements.map[nodeId];
|
|
1092
|
+
if (node.isInteractive && node.ref) {
|
|
1093
|
+
const ref = node.ref;
|
|
1094
|
+
if (!newElementsCache.has(ref)) {
|
|
1095
|
+
newElementsCache.set(ref, currentUrl2);
|
|
1096
|
+
node.isNew = true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return elements;
|
|
1101
|
+
}
|
|
1102
|
+
__name(getFlatTree, "getFlatTree");
|
|
1103
|
+
function flatTreeToString(flatTree, include_attributes) {
|
|
1104
|
+
const DEFAULT_INCLUDE_ATTRIBUTES = [
|
|
1105
|
+
"title",
|
|
1106
|
+
"type",
|
|
1107
|
+
"checked",
|
|
1108
|
+
"name",
|
|
1109
|
+
"role",
|
|
1110
|
+
"value",
|
|
1111
|
+
"placeholder",
|
|
1112
|
+
"data-date-format",
|
|
1113
|
+
"alt",
|
|
1114
|
+
"aria-label",
|
|
1115
|
+
"aria-expanded",
|
|
1116
|
+
"data-state",
|
|
1117
|
+
"aria-checked",
|
|
1118
|
+
// @edit added for better form handling
|
|
1119
|
+
"id",
|
|
1120
|
+
"for",
|
|
1121
|
+
// for jump check
|
|
1122
|
+
"target",
|
|
1123
|
+
// absolute 定位的下拉菜单
|
|
1124
|
+
"aria-haspopup",
|
|
1125
|
+
"aria-controls",
|
|
1126
|
+
"aria-owns"
|
|
1127
|
+
];
|
|
1128
|
+
const includeAttrs = [...include_attributes || [], ...DEFAULT_INCLUDE_ATTRIBUTES];
|
|
1129
|
+
const capTextLength = /* @__PURE__ */ __name((text, maxLength) => {
|
|
1130
|
+
if (text.length > maxLength) {
|
|
1131
|
+
return text.substring(0, maxLength) + "...";
|
|
1132
|
+
}
|
|
1133
|
+
return text;
|
|
1134
|
+
}, "capTextLength");
|
|
1135
|
+
const buildTreeNode = /* @__PURE__ */ __name((nodeId) => {
|
|
1136
|
+
const node = flatTree.map[nodeId];
|
|
1137
|
+
if (!node) return null;
|
|
1138
|
+
if (node.type === "TEXT_NODE") {
|
|
1139
|
+
const textNode = node;
|
|
1140
|
+
return {
|
|
1141
|
+
type: "text",
|
|
1142
|
+
text: textNode.text,
|
|
1143
|
+
isVisible: textNode.isVisible,
|
|
1144
|
+
parent: null,
|
|
1145
|
+
children: []
|
|
1146
|
+
};
|
|
1147
|
+
} else {
|
|
1148
|
+
const elementNode = node;
|
|
1149
|
+
const children = [];
|
|
1150
|
+
if (elementNode.children) {
|
|
1151
|
+
for (const childId of elementNode.children) {
|
|
1152
|
+
const child = buildTreeNode(childId);
|
|
1153
|
+
if (child) {
|
|
1154
|
+
child.parent = null;
|
|
1155
|
+
children.push(child);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
type: "element",
|
|
1161
|
+
tagName: elementNode.tagName,
|
|
1162
|
+
attributes: elementNode.attributes ?? {},
|
|
1163
|
+
isVisible: elementNode.isVisible ?? false,
|
|
1164
|
+
isInteractive: elementNode.isInteractive ?? false,
|
|
1165
|
+
isTopElement: elementNode.isTopElement ?? false,
|
|
1166
|
+
isNew: elementNode.isNew ?? false,
|
|
1167
|
+
highlightIndex: elementNode.highlightIndex,
|
|
1168
|
+
parent: null,
|
|
1169
|
+
children,
|
|
1170
|
+
extra: elementNode.extra ?? {}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
}, "buildTreeNode");
|
|
1174
|
+
const setParentReferences = /* @__PURE__ */ __name((node, parent = null) => {
|
|
1175
|
+
node.parent = parent;
|
|
1176
|
+
for (const child of node.children) {
|
|
1177
|
+
setParentReferences(child, node);
|
|
1178
|
+
}
|
|
1179
|
+
}, "setParentReferences");
|
|
1180
|
+
const rootNode = buildTreeNode(flatTree.rootId);
|
|
1181
|
+
if (!rootNode) return "";
|
|
1182
|
+
setParentReferences(rootNode);
|
|
1183
|
+
const hasParentWithHighlightIndex = /* @__PURE__ */ __name((node) => {
|
|
1184
|
+
let current = node.parent;
|
|
1185
|
+
while (current) {
|
|
1186
|
+
if (current.type === "element" && current.highlightIndex !== void 0) {
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1189
|
+
current = current.parent;
|
|
1190
|
+
}
|
|
1191
|
+
return false;
|
|
1192
|
+
}, "hasParentWithHighlightIndex");
|
|
1193
|
+
const processNode = /* @__PURE__ */ __name((node, depth, result2) => {
|
|
1194
|
+
let nextDepth = depth;
|
|
1195
|
+
const depthStr = " ".repeat(depth);
|
|
1196
|
+
if (node.type === "element") {
|
|
1197
|
+
if (node.highlightIndex !== void 0) {
|
|
1198
|
+
nextDepth += 1;
|
|
1199
|
+
const text = getAllTextTillNextClickableElement(node);
|
|
1200
|
+
let attributesHtmlStr = "";
|
|
1201
|
+
if (includeAttrs.length > 0 && node.attributes) {
|
|
1202
|
+
const attributesToInclude = {};
|
|
1203
|
+
for (const key of includeAttrs) {
|
|
1204
|
+
const value = node.attributes[key];
|
|
1205
|
+
if (value && value.trim() !== "") {
|
|
1206
|
+
attributesToInclude[key] = value.trim();
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
const orderedKeys = includeAttrs.filter((key) => key in attributesToInclude);
|
|
1210
|
+
if (orderedKeys.length > 1) {
|
|
1211
|
+
const keysToRemove = /* @__PURE__ */ new Set();
|
|
1212
|
+
const seenValues = {};
|
|
1213
|
+
for (const key of orderedKeys) {
|
|
1214
|
+
const value = attributesToInclude[key];
|
|
1215
|
+
if (value.length > 5) {
|
|
1216
|
+
if (value in seenValues) {
|
|
1217
|
+
keysToRemove.add(key);
|
|
1218
|
+
} else {
|
|
1219
|
+
seenValues[value] = key;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
for (const key of keysToRemove) {
|
|
1224
|
+
delete attributesToInclude[key];
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (attributesToInclude.role === node.tagName) {
|
|
1228
|
+
delete attributesToInclude.role;
|
|
1229
|
+
}
|
|
1230
|
+
const attrsToRemoveIfTextMatches = ["aria-label", "placeholder", "title"];
|
|
1231
|
+
for (const attr of attrsToRemoveIfTextMatches) {
|
|
1232
|
+
if (attributesToInclude[attr] && attributesToInclude[attr].toLowerCase().trim() === text.toLowerCase().trim()) {
|
|
1233
|
+
delete attributesToInclude[attr];
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
if (Object.keys(attributesToInclude).length > 0) {
|
|
1237
|
+
attributesHtmlStr = Object.entries(attributesToInclude).map(([key, value]) => `${key}=${capTextLength(value, 20)}`).join(" ");
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
const highlightIndicator = node.isNew ? `*[${node.highlightIndex}]` : `[${node.highlightIndex}]`;
|
|
1241
|
+
let line = `${depthStr}${highlightIndicator}<${node.tagName ?? ""}`;
|
|
1242
|
+
if (attributesHtmlStr) {
|
|
1243
|
+
line += ` ${attributesHtmlStr}`;
|
|
1244
|
+
}
|
|
1245
|
+
if (node.extra) {
|
|
1246
|
+
if (node.extra.scrollable) {
|
|
1247
|
+
let scrollDataText = "";
|
|
1248
|
+
if (node.extra.scrollData?.left)
|
|
1249
|
+
scrollDataText += `left=${node.extra.scrollData.left}, `;
|
|
1250
|
+
if (node.extra.scrollData?.top) scrollDataText += `top=${node.extra.scrollData.top}, `;
|
|
1251
|
+
if (node.extra.scrollData?.right)
|
|
1252
|
+
scrollDataText += `right=${node.extra.scrollData.right}, `;
|
|
1253
|
+
if (node.extra.scrollData?.bottom)
|
|
1254
|
+
scrollDataText += `bottom=${node.extra.scrollData.bottom}`;
|
|
1255
|
+
line += ` data-scrollable="${scrollDataText}"`;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (text) {
|
|
1259
|
+
const trimmedText = text.trim();
|
|
1260
|
+
if (!attributesHtmlStr) {
|
|
1261
|
+
line += " ";
|
|
1262
|
+
}
|
|
1263
|
+
line += `>${trimmedText}`;
|
|
1264
|
+
} else if (!attributesHtmlStr) {
|
|
1265
|
+
line += " ";
|
|
1266
|
+
}
|
|
1267
|
+
line += " />";
|
|
1268
|
+
result2.push(line);
|
|
1269
|
+
}
|
|
1270
|
+
for (const child of node.children) {
|
|
1271
|
+
processNode(child, nextDepth, result2);
|
|
1272
|
+
}
|
|
1273
|
+
} else if (node.type === "text") {
|
|
1274
|
+
if (hasParentWithHighlightIndex(node)) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (node.parent && node.parent.type === "element" && node.parent.isVisible && node.parent.isTopElement) {
|
|
1278
|
+
result2.push(`${depthStr}${node.text ?? ""}`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}, "processNode");
|
|
1282
|
+
const result = [];
|
|
1283
|
+
processNode(rootNode, 0, result);
|
|
1284
|
+
return result.join("\n");
|
|
1285
|
+
}
|
|
1286
|
+
__name(flatTreeToString, "flatTreeToString");
|
|
1287
|
+
const getAllTextTillNextClickableElement = /* @__PURE__ */ __name((node, maxDepth = -1) => {
|
|
1288
|
+
const textParts = [];
|
|
1289
|
+
const collectText = /* @__PURE__ */ __name((currentNode, currentDepth) => {
|
|
1290
|
+
if (maxDepth !== -1 && currentDepth > maxDepth) {
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (currentNode.type === "element" && currentNode !== node && currentNode.highlightIndex !== void 0) {
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
if (currentNode.type === "text" && currentNode.text) {
|
|
1297
|
+
textParts.push(currentNode.text);
|
|
1298
|
+
} else if (currentNode.type === "element") {
|
|
1299
|
+
for (const child of currentNode.children) {
|
|
1300
|
+
collectText(child, currentDepth + 1);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}, "collectText");
|
|
1304
|
+
collectText(node, 0);
|
|
1305
|
+
return textParts.join("\n").trim();
|
|
1306
|
+
}, "getAllTextTillNextClickableElement");
|
|
1307
|
+
function getSelectorMap(flatTree) {
|
|
1308
|
+
const selectorMap = /* @__PURE__ */ new Map();
|
|
1309
|
+
const keys = Object.keys(flatTree.map);
|
|
1310
|
+
for (const key of keys) {
|
|
1311
|
+
const node = flatTree.map[key];
|
|
1312
|
+
if (node.isInteractive && typeof node.highlightIndex === "number") {
|
|
1313
|
+
selectorMap.set(node.highlightIndex, node);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return selectorMap;
|
|
1317
|
+
}
|
|
1318
|
+
__name(getSelectorMap, "getSelectorMap");
|
|
1319
|
+
function getElementTextMap(simplifiedHTML) {
|
|
1320
|
+
const lines = simplifiedHTML.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1321
|
+
const elementTextMap = /* @__PURE__ */ new Map();
|
|
1322
|
+
for (const line of lines) {
|
|
1323
|
+
const regex = /^\[(\d+)\]<[^>]+>([^<]*)/;
|
|
1324
|
+
const match = regex.exec(line);
|
|
1325
|
+
if (match) {
|
|
1326
|
+
const index = parseInt(match[1], 10);
|
|
1327
|
+
elementTextMap.set(index, line);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return elementTextMap;
|
|
1331
|
+
}
|
|
1332
|
+
__name(getElementTextMap, "getElementTextMap");
|
|
1333
|
+
function cleanUpHighlights() {
|
|
1334
|
+
const cleanupFunctions = window._highlightCleanupFunctions || [];
|
|
1335
|
+
for (const cleanup of cleanupFunctions) {
|
|
1336
|
+
if (typeof cleanup === "function") {
|
|
1337
|
+
cleanup();
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
window._highlightCleanupFunctions = [];
|
|
1341
|
+
}
|
|
1342
|
+
__name(cleanUpHighlights, "cleanUpHighlights");
|
|
1343
|
+
window.addEventListener("popstate", () => {
|
|
1344
|
+
cleanUpHighlights();
|
|
1345
|
+
});
|
|
1346
|
+
window.addEventListener("hashchange", () => {
|
|
1347
|
+
cleanUpHighlights();
|
|
1348
|
+
});
|
|
1349
|
+
window.addEventListener("beforeunload", () => {
|
|
1350
|
+
cleanUpHighlights();
|
|
1351
|
+
});
|
|
1352
|
+
const navigation = window.navigation;
|
|
1353
|
+
if (navigation && typeof navigation.addEventListener === "function") {
|
|
1354
|
+
navigation.addEventListener("navigate", () => {
|
|
1355
|
+
cleanUpHighlights();
|
|
1356
|
+
});
|
|
1357
|
+
} else {
|
|
1358
|
+
let currentUrl2 = window.location.href;
|
|
1359
|
+
setInterval(() => {
|
|
1360
|
+
if (window.location.href !== currentUrl2) {
|
|
1361
|
+
currentUrl2 = window.location.href;
|
|
1362
|
+
cleanUpHighlights();
|
|
1363
|
+
}
|
|
1364
|
+
}, 500);
|
|
1365
|
+
}
|
|
1366
|
+
function getPageInfo() {
|
|
1367
|
+
const viewport_width = window.innerWidth;
|
|
1368
|
+
const viewport_height = window.innerHeight;
|
|
1369
|
+
const page_width = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth || 0);
|
|
1370
|
+
const page_height = Math.max(
|
|
1371
|
+
document.documentElement.scrollHeight,
|
|
1372
|
+
document.body.scrollHeight || 0
|
|
1373
|
+
);
|
|
1374
|
+
const scroll_x = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft || 0;
|
|
1375
|
+
const scroll_y = window.scrollY || window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
1376
|
+
const pixels_below = Math.max(0, page_height - (window.innerHeight + scroll_y));
|
|
1377
|
+
const pixels_right = Math.max(0, page_width - (window.innerWidth + scroll_x));
|
|
1378
|
+
return {
|
|
1379
|
+
// Current viewport dimensions
|
|
1380
|
+
viewport_width,
|
|
1381
|
+
viewport_height,
|
|
1382
|
+
// Total page dimensions
|
|
1383
|
+
page_width,
|
|
1384
|
+
page_height,
|
|
1385
|
+
// Current scroll position
|
|
1386
|
+
scroll_x,
|
|
1387
|
+
scroll_y,
|
|
1388
|
+
pixels_above: scroll_y,
|
|
1389
|
+
pixels_below,
|
|
1390
|
+
pages_above: viewport_height > 0 ? scroll_y / viewport_height : 0,
|
|
1391
|
+
pages_below: viewport_height > 0 ? pixels_below / viewport_height : 0,
|
|
1392
|
+
total_pages: viewport_height > 0 ? page_height / viewport_height : 0,
|
|
1393
|
+
current_page_position: scroll_y / Math.max(1, page_height - viewport_height),
|
|
1394
|
+
pixels_left: scroll_x,
|
|
1395
|
+
pixels_right
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
__name(getPageInfo, "getPageInfo");
|
|
1399
|
+
const zhCN = {
|
|
1400
|
+
ui: {
|
|
1401
|
+
panel: {
|
|
1402
|
+
ready: "准备就绪",
|
|
1403
|
+
thinking: "正在思考...",
|
|
1404
|
+
paused: "暂停中,稍后",
|
|
1405
|
+
taskInput: "输入新任务,详细描述步骤,回车提交",
|
|
1406
|
+
userAnswerPrompt: "请回答上面问题,回车提交",
|
|
1407
|
+
taskTerminated: "任务已终止",
|
|
1408
|
+
taskCompleted: "任务结束",
|
|
1409
|
+
continueExecution: "继续执行",
|
|
1410
|
+
userAnswer: "用户回答: {{input}}",
|
|
1411
|
+
pause: "暂停",
|
|
1412
|
+
continue: "继续",
|
|
1413
|
+
stop: "终止",
|
|
1414
|
+
expand: "展开历史",
|
|
1415
|
+
collapse: "收起历史",
|
|
1416
|
+
step: "步骤 {{number}} · {{time}}{{duration}}"
|
|
1417
|
+
},
|
|
1418
|
+
tools: {
|
|
1419
|
+
clicking: "正在点击元素 [{{index}}]...",
|
|
1420
|
+
inputting: "正在输入文本到元素 [{{index}}]...",
|
|
1421
|
+
selecting: '正在选择选项 "{{text}}"...',
|
|
1422
|
+
scrolling: "正在滚动页面...",
|
|
1423
|
+
waiting: "等待 {{seconds}} 秒...",
|
|
1424
|
+
done: "结束任务",
|
|
1425
|
+
clicked: "🖱️ 已点击元素 [{{index}}]",
|
|
1426
|
+
inputted: '⌨️ 已输入文本 "{{text}}"',
|
|
1427
|
+
selected: '☑️ 已选择选项 "{{text}}"',
|
|
1428
|
+
scrolled: "🛞 页面滚动完成",
|
|
1429
|
+
waited: "⌛️ 等待完成",
|
|
1430
|
+
executing: "正在执行 {{toolName}}..."
|
|
1431
|
+
},
|
|
1432
|
+
errors: {
|
|
1433
|
+
elementNotFound: "未找到索引为 {{index}} 的交互元素",
|
|
1434
|
+
taskRequired: "任务描述不能为空",
|
|
1435
|
+
executionFailed: "任务执行失败",
|
|
1436
|
+
notInputElement: "元素不是输入框或文本域",
|
|
1437
|
+
notSelectElement: "元素不是选择框",
|
|
1438
|
+
optionNotFound: '未找到选项 "{{text}}"'
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
const enUS = {
|
|
1443
|
+
ui: {
|
|
1444
|
+
panel: {
|
|
1445
|
+
ready: "Ready",
|
|
1446
|
+
thinking: "Thinking...",
|
|
1447
|
+
paused: "Paused",
|
|
1448
|
+
taskInput: "Enter new task, describe steps in detail, press Enter to submit",
|
|
1449
|
+
userAnswerPrompt: "Please answer the question above, press Enter to submit",
|
|
1450
|
+
taskTerminated: "Task terminated",
|
|
1451
|
+
taskCompleted: "Task completed",
|
|
1452
|
+
continueExecution: "Continue execution",
|
|
1453
|
+
userAnswer: "User answer: {{input}}",
|
|
1454
|
+
pause: "Pause",
|
|
1455
|
+
continue: "Continue",
|
|
1456
|
+
stop: "Stop",
|
|
1457
|
+
expand: "Expand history",
|
|
1458
|
+
collapse: "Collapse history",
|
|
1459
|
+
step: "Step {{number}} · {{time}}{{duration}}"
|
|
1460
|
+
},
|
|
1461
|
+
tools: {
|
|
1462
|
+
clicking: "Clicking element [{{index}}]...",
|
|
1463
|
+
inputting: "Inputting text to element [{{index}}]...",
|
|
1464
|
+
selecting: 'Selecting option "{{text}}"...',
|
|
1465
|
+
scrolling: "Scrolling page...",
|
|
1466
|
+
waiting: "Waiting {{seconds}} seconds...",
|
|
1467
|
+
done: "Task done",
|
|
1468
|
+
clicked: "🖱️ Clicked element [{{index}}]",
|
|
1469
|
+
inputted: '⌨️ Inputted text "{{text}}"',
|
|
1470
|
+
selected: '☑️ Selected option "{{text}}"',
|
|
1471
|
+
scrolled: "🛞 Page scrolled",
|
|
1472
|
+
waited: "⌛️ Wait completed",
|
|
1473
|
+
executing: "正在执行 {{toolName}}..."
|
|
1474
|
+
},
|
|
1475
|
+
errors: {
|
|
1476
|
+
elementNotFound: "No interactive element found at index {{index}}",
|
|
1477
|
+
taskRequired: "Task description is required",
|
|
1478
|
+
executionFailed: "Task execution failed",
|
|
1479
|
+
notInputElement: "Element is not an input or textarea",
|
|
1480
|
+
notSelectElement: "Element is not a select element",
|
|
1481
|
+
optionNotFound: 'Option "{{text}}" not found'
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
const locales = {
|
|
1486
|
+
"zh-CN": zhCN,
|
|
1487
|
+
"en-US": enUS
|
|
1488
|
+
};
|
|
1489
|
+
const _I18n = class _I18n {
|
|
1490
|
+
language;
|
|
1491
|
+
translations;
|
|
1492
|
+
constructor(language = "en-US") {
|
|
1493
|
+
this.language = language in locales ? language : "en-US";
|
|
1494
|
+
this.translations = locales[language];
|
|
1495
|
+
}
|
|
1496
|
+
// 类型安全的翻译方法
|
|
1497
|
+
t(key, params) {
|
|
1498
|
+
const value = this.getNestedValue(this.translations, key);
|
|
1499
|
+
if (!value) {
|
|
1500
|
+
console.warn(`Translation key "${key}" not found for language "${this.language}"`);
|
|
1501
|
+
return key;
|
|
1502
|
+
}
|
|
1503
|
+
if (params) {
|
|
1504
|
+
return this.interpolate(value, params);
|
|
1505
|
+
}
|
|
1506
|
+
return value;
|
|
1507
|
+
}
|
|
1508
|
+
getNestedValue(obj, path) {
|
|
1509
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
1510
|
+
}
|
|
1511
|
+
interpolate(template, params) {
|
|
1512
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
1513
|
+
return params[key]?.toString() || match;
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
getLanguage() {
|
|
1517
|
+
return this.language;
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
__name(_I18n, "I18n");
|
|
1521
|
+
let I18n = _I18n;
|
|
1522
|
+
function assert(condition, message, silent) {
|
|
1523
|
+
if (!condition) {
|
|
1524
|
+
const errorMessage = message ?? "Assertion failed";
|
|
1525
|
+
if (!silent) console.error(chalk.red(`❌ assert: ${errorMessage}`));
|
|
1526
|
+
throw new Error(errorMessage);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
__name(assert, "assert");
|
|
1530
|
+
const _EventBus = class _EventBus extends EventTarget {
|
|
1531
|
+
/**
|
|
1532
|
+
* Listen to built-in events
|
|
1533
|
+
*/
|
|
1534
|
+
on(event, handler) {
|
|
1535
|
+
const wrappedHandler = /* @__PURE__ */ __name((e) => {
|
|
1536
|
+
const customEvent = e;
|
|
1537
|
+
const params = customEvent.detail?.[0];
|
|
1538
|
+
return handler(params);
|
|
1539
|
+
}, "wrappedHandler");
|
|
1540
|
+
this.addEventListener(event, wrappedHandler);
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Listen to built-in events (one-time)
|
|
1544
|
+
*/
|
|
1545
|
+
once(event, handler) {
|
|
1546
|
+
const wrappedHandler = /* @__PURE__ */ __name((e) => {
|
|
1547
|
+
const customEvent = e;
|
|
1548
|
+
const params = customEvent.detail?.[0];
|
|
1549
|
+
return handler(params);
|
|
1550
|
+
}, "wrappedHandler");
|
|
1551
|
+
this.addEventListener(event, wrappedHandler, { once: true });
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Emit built-in events
|
|
1555
|
+
*/
|
|
1556
|
+
emit(event, ...args) {
|
|
1557
|
+
const customEvent = new CustomEvent(event, { detail: args });
|
|
1558
|
+
this.dispatchEvent(customEvent);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
__name(_EventBus, "EventBus");
|
|
1563
|
+
let EventBus = _EventBus;
|
|
1564
|
+
const buses = /* @__PURE__ */ new Map();
|
|
1565
|
+
function getEventBus(channel) {
|
|
1566
|
+
if (buses.has(channel)) {
|
|
1567
|
+
return buses.get(channel);
|
|
1568
|
+
}
|
|
1569
|
+
const bus = new EventBus();
|
|
1570
|
+
buses.set(channel, bus);
|
|
1571
|
+
return bus;
|
|
1572
|
+
}
|
|
1573
|
+
__name(getEventBus, "getEventBus");
|
|
1574
|
+
const _LLM = class _LLM {
|
|
1575
|
+
constructor(config, id) {
|
|
1576
|
+
__publicField(this, "config");
|
|
1577
|
+
__publicField(this, "id");
|
|
1578
|
+
__privateAdd(this, _openai);
|
|
1579
|
+
__privateAdd(this, _model);
|
|
1580
|
+
__privateAdd(this, _bus);
|
|
1581
|
+
this.config = {
|
|
1582
|
+
baseURL: DEFAULT_BASE_URL,
|
|
1583
|
+
apiKey: DEFAULT_API_KEY,
|
|
1584
|
+
modelName: DEFAULT_MODEL_NAME,
|
|
1585
|
+
maxRetries: LLM_MAX_RETRIES,
|
|
1586
|
+
...config
|
|
1587
|
+
};
|
|
1588
|
+
this.id = id;
|
|
1589
|
+
__privateSet(this, _bus, getEventBus(id));
|
|
1590
|
+
__privateSet(this, _openai, createOpenAI({ baseURL: this.config.baseURL, apiKey: this.config.apiKey }));
|
|
1591
|
+
__privateSet(this, _model, __privateGet(this, _openai).chat(this.config.modelName));
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* - call llm api *once*
|
|
1595
|
+
* - invoke tool call *once*
|
|
1596
|
+
* - return the result of the tool
|
|
1597
|
+
*/
|
|
1598
|
+
async invoke(messages, tools2, abortSignal) {
|
|
1599
|
+
const isClaude = this.config.modelName.slice(0, 8).includes("claude");
|
|
1600
|
+
this.config.modelName.slice(0, 6).includes("qwen");
|
|
1601
|
+
this.config.modelName.slice(0, 5).includes("gpt");
|
|
1602
|
+
return await withRetry(
|
|
1603
|
+
async () => {
|
|
1604
|
+
const result = await generateText({
|
|
1605
|
+
model: __privateGet(this, _model),
|
|
1606
|
+
messages,
|
|
1607
|
+
tools: tools2,
|
|
1608
|
+
abortSignal,
|
|
1609
|
+
/**
|
|
1610
|
+
* 文档中没有说明,从源码看,@facts
|
|
1611
|
+
* - 只会重试被识别为 retryable 的 API_CALL_ERROR
|
|
1612
|
+
* - 返回无法解析的 json 应该不会重试
|
|
1613
|
+
* - experimental_repairToolCall 只会执行一次,不算作重试
|
|
1614
|
+
* @facts
|
|
1615
|
+
* - 许多 proxy 过的 openAI 兼容接口返回的错误格式并不规范,通常不会被识别为 retryable
|
|
1616
|
+
* @conclusion
|
|
1617
|
+
* - 看起来并不实用,不如完全手工控制粗粒度重试
|
|
1618
|
+
*/
|
|
1619
|
+
// maxRetries: this.config.maxRetries,
|
|
1620
|
+
maxRetries: 0,
|
|
1621
|
+
// toolChoice: 'required',
|
|
1622
|
+
// @note incompatible to Claude
|
|
1623
|
+
toolChoice: isClaude ? void 0 : { type: "tool", toolName: MACRO_TOOL_NAME },
|
|
1624
|
+
/**
|
|
1625
|
+
* controlled by main loop. our method only call api once
|
|
1626
|
+
*/
|
|
1627
|
+
// stopWhen: [hasToolCall('done'), stepCountIs(100)],
|
|
1628
|
+
stopWhen: [stepCountIs(1)],
|
|
1629
|
+
// stopWhen: [hasToolCall('AgentOutput')],
|
|
1630
|
+
providerOptions: {
|
|
1631
|
+
openai: {
|
|
1632
|
+
// @note this one needs all fields in tool schema must be `required`
|
|
1633
|
+
// strictJsonSchema: true,
|
|
1634
|
+
// This way only at most one tool can be called at a time
|
|
1635
|
+
parallelToolCalls: false,
|
|
1636
|
+
reasoningEffort: "minimal",
|
|
1637
|
+
// @note not working
|
|
1638
|
+
// serviceTier: 'priority',
|
|
1639
|
+
textVerbosity: "low",
|
|
1640
|
+
// @note Optimize OpenAI model caching, should be unique per user, currently has no effect
|
|
1641
|
+
promptCacheKey: "page-agent:" + this.id
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* schema 出错时执行一次,不确定是否计入重试
|
|
1646
|
+
* 目前看起来像是会直接抛错,被 withRetry 处理
|
|
1647
|
+
* @note
|
|
1648
|
+
* 如果不提供,则 ai-sdk 会把 tool-error 加入 message 中重新调用一次,
|
|
1649
|
+
* 配合 stepCountIs 或者 hasToolCall 都会导致错误被 silent,toolResults 永远为 0
|
|
1650
|
+
* 遗憾的是,这里没有办法抛错(抛错后回到默认逻辑),只要这里 repair 不好,就会导致 silent error
|
|
1651
|
+
* 更糟糕的是,只要传入了 tools,无论 stopWhen 如何设置,都会被当作 multi-step,
|
|
1652
|
+
* 本质上就和我们 single step 的逻辑冲突
|
|
1653
|
+
* 长远来看必须删掉 ai-sdk,直接用 openAI API 实现
|
|
1654
|
+
*/
|
|
1655
|
+
// experimental_repairToolCall: (options): Promise<LanguageModelV2ToolCall | null> => {
|
|
1656
|
+
// console.error('hahhah', options)
|
|
1657
|
+
// throw options.error
|
|
1658
|
+
// },
|
|
1659
|
+
});
|
|
1660
|
+
console.log(chalk.blue.bold("LLM:invoke finished"), result);
|
|
1661
|
+
const toolError = result.content.find((part) => part.type === "tool-error");
|
|
1662
|
+
if (toolError) throw toolError.error;
|
|
1663
|
+
assert(!result.text, "Model returned text without calling done tool", true);
|
|
1664
|
+
assert(result.toolCalls.length === 1, "Model must call exactly one tool", true);
|
|
1665
|
+
assert(result.toolResults.length === 1, "Step must have exactly one tool result", true);
|
|
1666
|
+
const toolCall = result.toolCalls[0];
|
|
1667
|
+
const toolResult = result.toolResults[0];
|
|
1668
|
+
const usage = result.totalUsage;
|
|
1669
|
+
return {
|
|
1670
|
+
toolCall,
|
|
1671
|
+
toolResult,
|
|
1672
|
+
usage
|
|
1673
|
+
};
|
|
1674
|
+
},
|
|
1675
|
+
// retry settings
|
|
1676
|
+
{
|
|
1677
|
+
maxRetries: this.config.maxRetries,
|
|
1678
|
+
onRetry: /* @__PURE__ */ __name((retries) => {
|
|
1679
|
+
__privateGet(this, _bus).emit("panel:update", {
|
|
1680
|
+
type: "retry",
|
|
1681
|
+
displayText: `retry-ing (${retries} / ${this.config.maxRetries})`
|
|
1682
|
+
});
|
|
1683
|
+
}, "onRetry"),
|
|
1684
|
+
onError: /* @__PURE__ */ __name((error2, withRetry2) => {
|
|
1685
|
+
__privateGet(this, _bus).emit("panel:update", {
|
|
1686
|
+
type: "error",
|
|
1687
|
+
displayText: `step failed: ${error2.message}`
|
|
1688
|
+
});
|
|
1689
|
+
}, "onError")
|
|
1690
|
+
}
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
_openai = new WeakMap();
|
|
1695
|
+
_model = new WeakMap();
|
|
1696
|
+
_bus = new WeakMap();
|
|
1697
|
+
__name(_LLM, "LLM");
|
|
1698
|
+
let LLM = _LLM;
|
|
1699
|
+
async function withRetry(fn, settings) {
|
|
1700
|
+
let retries = 0;
|
|
1701
|
+
let lastError = null;
|
|
1702
|
+
while (retries <= settings.maxRetries) {
|
|
1703
|
+
if (retries > 0) {
|
|
1704
|
+
settings.onRetry(retries);
|
|
1705
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1706
|
+
}
|
|
1707
|
+
try {
|
|
1708
|
+
return await fn();
|
|
1709
|
+
} catch (error2) {
|
|
1710
|
+
console.error(error2);
|
|
1711
|
+
settings.onError(error2, retries < settings.maxRetries);
|
|
1712
|
+
if (error2?.name === "AbortError") throw error2;
|
|
1713
|
+
lastError = error2;
|
|
1714
|
+
retries++;
|
|
1715
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
throw lastError;
|
|
1719
|
+
}
|
|
1720
|
+
__name(withRetry, "withRetry");
|
|
1721
|
+
function patchReact(pageAgent) {
|
|
1722
|
+
const reactRootElements = document.querySelectorAll(
|
|
1723
|
+
'[data-reactroot], [data-reactid], [data-react-checksum], #root, #app, [id^="root-"], [id^="app-"], #adex-wrapper, #adex-root'
|
|
1724
|
+
);
|
|
1725
|
+
for (const element of reactRootElements) {
|
|
1726
|
+
element.setAttribute("data-page-agent-not-interactive", "true");
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
__name(patchReact, "patchReact");
|
|
1730
|
+
const SYSTEM_PROMPT = 'You are an AI agent designed to operate in an iterative loop to automate browser tasks. Your ultimate goal is accomplishing the task provided in <user_request>.\n\n<intro>\nYou excel at following tasks:\n1. Navigating complex websites and extracting precise information\n2. Automating form submissions and interactive web actions\n3. Gathering and saving information \n4. Operate effectively in an agent loop\n5. Efficiently performing diverse web tasks\n</intro>\n\n<language_settings>\n- Default working language: **中文**\n- Use the language that user is using. Return in user\'s language.\n</language_settings>\n\n<input>\nAt every step, your input will consist of: \n1. <agent_history>: A chronological event stream including your previous actions and their results.\n2. <agent_state>: Current <user_request> and <step_info>.\n3. <browser_state>: Current URL, interactive elements indexed for actions, and visible page content.\n</input>\n\n<agent_history>\nAgent history will be given as a list of step information as follows:\n\n<step_{step_number}>:\nEvaluation of Previous Step: Assessment of last action\nMemory: Your memory of this step\nNext Goal: Your goal for this step\nAction Results: Your actions and their results\n</step_{step_number}>\n\nand system messages wrapped in <sys> tag.\n</agent_history>\n\n<user_request>\nUSER REQUEST: This is your ultimate objective and always remains visible.\n- This has the highest priority. Make the user happy.\n- If the user request is very specific - then carefully follow each step and dont skip or hallucinate steps.\n- If the task is open ended you can plan yourself how to get it done.\n</user_request>\n\n<browser_state>\n1. Browser State will be given as:\n\nCurrent URL: URL of the page you are currently viewing.\nInteractive Elements: All interactive elements will be provided in format as [index]<type>text</type> where\n- index: Numeric identifier for interaction\n- type: HTML element type (button, input, etc.)\n- text: Element description\n\nExamples:\n[33]<div>User form</div>\n\\t*[35]<button aria-label=\'Submit form\'>Submit</button>\n\nNote that:\n- Only elements with numeric indexes in [] are interactive\n- (stacked) indentation (with \\t) is important and means that the element is a (html) child of the element above (with a lower index)\n- Elements tagged with `*[` are the new clickable elements that appeared on the website since the last step - if url has not changed.\n- Pure text elements without [] are not interactive.\n</browser_state>\n\n<browser_rules>\nStrictly follow these rules while using the browser and navigating the web:\n- Only interact with elements that have a numeric [index] assigned.\n- Only use indexes that are explicitly provided.\n- If the page changes after, for example, an input text action, analyze if you need to interact with new elements, e.g. selecting the right option from the list.\n- By default, only elements in the visible viewport are listed. Use scrolling actions if you suspect relevant content is offscreen which you need to interact with. Scroll ONLY if there are more pixels below or above the page.\n- You can scroll by a specific number of pages using the num_pages parameter (e.g., 0.5 for half page, 2.0 for two pages).\n- All the elements that are scrollable are marked with `data-scrollable` attribute. Including the scrollable distance in every directions. You can scroll *the element* in case some area are overflowed.\n- If a captcha appears, tell user you can not solve captcha. finished the task and ask user to solve it.\n- If expected elements are missing, try scrolling, or navigating back.\n- If the page is not fully loaded, use the `wait` action.\n- Do not repeat one action for more than 3 times unless some conditions changed.\n- If you fill an input field and your action sequence is interrupted, most often something changed e.g. suggestions popped up under the field.\n- If the <user_request> includes specific page information such as product type, rating, price, location, etc., try to apply filters to be more efficient.\n- The <user_request> is the ultimate goal. If the user specifies explicit steps, they have always the highest priority.\n- If you input_text into a field, you might need to press enter, click the search button, or select from dropdown for completion.\n- Don\'t login into a page if you don\'t have to. Don\'t login if you don\'t have the credentials. \n- There are 2 types of tasks always first think which type of request you are dealing with:\n1. Very specific step by step instructions:\n- Follow them as very precise and don\'t skip steps. Try to complete everything as requested.\n2. Open ended tasks. Plan yourself, be creative in achieving them.\n- If you get stuck e.g. with logins or captcha in open-ended tasks you can re-evaluate the task and try alternative ways, e.g. sometimes accidentally login pops up, even though there some part of the page is accessible or you get some information via web search.\n</browser_rules>\n\n<capability>\n- You can only handle single page app. Do not jump out of current page.\n- Do not click on link if it will open in a new page (etc. <a target="_blank">)\n- It is ok to fail the task.\n - User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.\n - Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It\'s encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.\n - Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.\n- If you are not clear about the request or steps. `ask_user` to clarify it.\n- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.\n</capability>\n\n<task_completion_rules>\nYou must call the `done` action in one of three cases:\n- When you have fully completed the USER REQUEST.\n- When you reach the final allowed step (`max_steps`), even if the task is incomplete.\n- When you feel stuck or unable to solve user request. Or user request is not clear or contains inappropriate content.\n- If it is ABSOLUTELY IMPOSSIBLE to continue.\n\nThe `done` action is your opportunity to terminate and share your findings with the user.\n- Set `success` to `true` only if the full USER REQUEST has been completed with no missing components.\n- If any part of the request is missing, incomplete, or uncertain, set `success` to `false`.\n- You can use the `text` field of the `done` action to communicate your findings and to provide a coherent reply to the user and fulfill the USER REQUEST.\n- You are ONLY ALLOWED to call `done` as a single action. Don\'t call it together with other actions.\n- If the user asks for specified format, such as "return JSON with following structure", "return a list of format...", MAKE sure to use the right format in your answer.\n- If the user asks for a structured output, your `done` action\'s schema may be modified. Take this schema into account when solving the task!\n</task_completion_rules>\n\n<reasoning_rules>\nExhibit the following reasoning patterns to successfully achieve the <user_request>:\n\n- Reason about <agent_history> to track progress and context toward <user_request>.\n- Analyze the most recent "Next Goal" and "Action Result" in <agent_history> and clearly state what you previously tried to achieve.\n- Analyze all relevant items in <agent_history> and <browser_state> to understand your state.\n- Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery.\n- Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help.\n- `ask_user` for help if you have any difficulty. Users want to be kept in the loop.\n- If you see information relevant to <user_request>, plan saving the information to memory.\n- Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it.\n</reasoning_rules>\n\n<examples>\nHere are examples of good output patterns. Use them as reference but never copy them directly.\n\n<evaluation_examples>\n- Positive Examples:\n"evaluation_previous_goal": "Successfully navigated to the product page and found the target information. Verdict: Success"\n"evaluation_previous_goal": "Clicked the login button and user authentication form appeared. Verdict: Success"\n</evaluation_examples>\n\n<memory_examples>\n"memory": "Found many pending reports that need to be analyzed in the main page. Successfully processed the first 2 reports on quarterly sales data and moving on to inventory analysis and customer feedback reports."\n</memory_examples>\n\n<next_goal_examples>\n"next_goal": "Click on the \'Add to Cart\' button to proceed with the purchase flow."\n"next_goal": "Extract details from the first item on the page."\n</next_goal_examples>\n</examples>\n\n<output>\nYou must ALWAYS respond with a valid JSON in this exact format:\n\n{\n "evaluation_previous_goal": "Concise one-sentence analysis of your last action. Clearly state success, failure, or uncertain.",\n "memory": "1-3 concise sentences of specific memory of this step and overall progress. You should put here everything that will help you track progress in future steps. Like counting pages visited, items found, etc.",\n "next_goal": "State the next immediate goal and action to achieve it, in one clear sentence."\n "action":{"one_action_name": {// action-specific parameter}}\n}\n</output>\n';
|
|
1731
|
+
async function waitFor(seconds) {
|
|
1732
|
+
await new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
|
|
1733
|
+
}
|
|
1734
|
+
__name(waitFor, "waitFor");
|
|
1735
|
+
let currentUrl = window.location.href;
|
|
1736
|
+
async function getSystemInfo() {
|
|
1737
|
+
if (currentUrl === window.location.href) return "";
|
|
1738
|
+
await waitFor(0.3);
|
|
1739
|
+
currentUrl = window.location.href;
|
|
1740
|
+
return `
|
|
1741
|
+
<sys> Current URL changed to: ${currentUrl} </sys>`;
|
|
1742
|
+
}
|
|
1743
|
+
__name(getSystemInfo, "getSystemInfo");
|
|
1744
|
+
async function movePointerToElement(element) {
|
|
1745
|
+
const rect = element.getBoundingClientRect();
|
|
1746
|
+
const x = rect.left + rect.width / 2;
|
|
1747
|
+
const y = rect.top + rect.height / 2;
|
|
1748
|
+
window.dispatchEvent(new CustomEvent("PageAgent::MovePointerTo", { detail: { x, y } }));
|
|
1749
|
+
await waitFor(0.3);
|
|
1750
|
+
}
|
|
1751
|
+
__name(movePointerToElement, "movePointerToElement");
|
|
1752
|
+
function getElementByIndex(pageAgent, index) {
|
|
1753
|
+
const interactiveNode = pageAgent.selectorMap.get(index);
|
|
1754
|
+
if (!interactiveNode) {
|
|
1755
|
+
throw new Error(`No interactive element found at index ${index}`);
|
|
1756
|
+
}
|
|
1757
|
+
const element = interactiveNode.ref;
|
|
1758
|
+
if (!element) {
|
|
1759
|
+
throw new Error(`Element at index ${index} does not have a reference`);
|
|
1760
|
+
}
|
|
1761
|
+
if (!(element instanceof HTMLElement)) {
|
|
1762
|
+
throw new Error(`Element at index ${index} is not an HTMLElement`);
|
|
1763
|
+
}
|
|
1764
|
+
return element;
|
|
1765
|
+
}
|
|
1766
|
+
__name(getElementByIndex, "getElementByIndex");
|
|
1767
|
+
let lastClickedElement = null;
|
|
1768
|
+
function blurLastClickedElement() {
|
|
1769
|
+
if (lastClickedElement) {
|
|
1770
|
+
lastClickedElement.blur();
|
|
1771
|
+
lastClickedElement.dispatchEvent(
|
|
1772
|
+
new MouseEvent("mouseout", { bubbles: true, cancelable: true })
|
|
1773
|
+
);
|
|
1774
|
+
lastClickedElement = null;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
__name(blurLastClickedElement, "blurLastClickedElement");
|
|
1778
|
+
async function clickElement(element) {
|
|
1779
|
+
blurLastClickedElement();
|
|
1780
|
+
lastClickedElement = element;
|
|
1781
|
+
await scrollIntoViewIfNeeded(element);
|
|
1782
|
+
await movePointerToElement(element);
|
|
1783
|
+
window.dispatchEvent(new CustomEvent("PageAgent::ClickPointer"));
|
|
1784
|
+
await waitFor(0.1);
|
|
1785
|
+
element.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true, cancelable: true }));
|
|
1786
|
+
element.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, cancelable: true }));
|
|
1787
|
+
element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
|
|
1788
|
+
element.focus();
|
|
1789
|
+
element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
|
|
1790
|
+
element.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
1791
|
+
await waitFor(0.1);
|
|
1792
|
+
}
|
|
1793
|
+
__name(clickElement, "clickElement");
|
|
1794
|
+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
|
1795
|
+
window.HTMLInputElement.prototype,
|
|
1796
|
+
"value"
|
|
1797
|
+
).set;
|
|
1798
|
+
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
|
|
1799
|
+
window.HTMLTextAreaElement.prototype,
|
|
1800
|
+
"value"
|
|
1801
|
+
).set;
|
|
1802
|
+
async function createSyntheticInputEvent(elem, key) {
|
|
1803
|
+
elem.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key }));
|
|
1804
|
+
await waitFor(0.01);
|
|
1805
|
+
if (elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement) {
|
|
1806
|
+
elem.dispatchEvent(new Event("beforeinput", { bubbles: true }));
|
|
1807
|
+
await waitFor(0.01);
|
|
1808
|
+
elem.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1809
|
+
await waitFor(0.01);
|
|
1810
|
+
}
|
|
1811
|
+
elem.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true, cancelable: true, key }));
|
|
1812
|
+
}
|
|
1813
|
+
__name(createSyntheticInputEvent, "createSyntheticInputEvent");
|
|
1814
|
+
async function inputTextElement(element, text) {
|
|
1815
|
+
if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) {
|
|
1816
|
+
throw new Error("Element is not an input or textarea");
|
|
1817
|
+
}
|
|
1818
|
+
await clickElement(element);
|
|
1819
|
+
if (element instanceof HTMLTextAreaElement) {
|
|
1820
|
+
nativeTextAreaValueSetter.call(element, text);
|
|
1821
|
+
} else {
|
|
1822
|
+
nativeInputValueSetter.call(element, text);
|
|
1823
|
+
}
|
|
1824
|
+
const inputEvent = new Event("input", { bubbles: true });
|
|
1825
|
+
element.dispatchEvent(inputEvent);
|
|
1826
|
+
await waitFor(0.1);
|
|
1827
|
+
blurLastClickedElement();
|
|
1828
|
+
}
|
|
1829
|
+
__name(inputTextElement, "inputTextElement");
|
|
1830
|
+
async function selectOptionElement(selectElement, optionText) {
|
|
1831
|
+
if (!(selectElement instanceof HTMLSelectElement)) {
|
|
1832
|
+
throw new Error("Element is not a select element");
|
|
1833
|
+
}
|
|
1834
|
+
const options = Array.from(selectElement.options);
|
|
1835
|
+
const option = options.find((opt) => opt.textContent?.trim() === optionText.trim());
|
|
1836
|
+
if (!option) {
|
|
1837
|
+
throw new Error(`Option with text "${optionText}" not found in select element`);
|
|
1838
|
+
}
|
|
1839
|
+
selectElement.value = option.value;
|
|
1840
|
+
selectElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1841
|
+
await waitFor(0.1);
|
|
1842
|
+
}
|
|
1843
|
+
__name(selectOptionElement, "selectOptionElement");
|
|
1844
|
+
async function scrollIntoViewIfNeeded(element) {
|
|
1845
|
+
const el = element;
|
|
1846
|
+
if (el.scrollIntoViewIfNeeded) {
|
|
1847
|
+
el.scrollIntoViewIfNeeded();
|
|
1848
|
+
} else {
|
|
1849
|
+
el.scrollIntoView({ behavior: "auto", block: "center", inline: "nearest" });
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
__name(scrollIntoViewIfNeeded, "scrollIntoViewIfNeeded");
|
|
1853
|
+
async function scrollVertically(down, scroll_amount, element) {
|
|
1854
|
+
if (element) {
|
|
1855
|
+
const targetElement = element;
|
|
1856
|
+
console.log(
|
|
1857
|
+
"[SCROLL DEBUG] Starting direct container scroll for element:",
|
|
1858
|
+
targetElement.tagName
|
|
1859
|
+
);
|
|
1860
|
+
let currentElement = targetElement;
|
|
1861
|
+
let scrollSuccess = false;
|
|
1862
|
+
let scrolledElement = null;
|
|
1863
|
+
let scrollDelta = 0;
|
|
1864
|
+
let attempts = 0;
|
|
1865
|
+
const dy2 = scroll_amount;
|
|
1866
|
+
while (currentElement && attempts < 10) {
|
|
1867
|
+
const computedStyle = window.getComputedStyle(currentElement);
|
|
1868
|
+
const hasScrollableY = /(auto|scroll|overlay)/.test(computedStyle.overflowY);
|
|
1869
|
+
const canScrollVertically = currentElement.scrollHeight > currentElement.clientHeight;
|
|
1870
|
+
console.log(
|
|
1871
|
+
"[SCROLL DEBUG] Checking element:",
|
|
1872
|
+
currentElement.tagName,
|
|
1873
|
+
"hasScrollableY:",
|
|
1874
|
+
hasScrollableY,
|
|
1875
|
+
"canScrollVertically:",
|
|
1876
|
+
canScrollVertically,
|
|
1877
|
+
"scrollHeight:",
|
|
1878
|
+
currentElement.scrollHeight,
|
|
1879
|
+
"clientHeight:",
|
|
1880
|
+
currentElement.clientHeight
|
|
1881
|
+
);
|
|
1882
|
+
if (hasScrollableY && canScrollVertically) {
|
|
1883
|
+
const beforeScroll = currentElement.scrollTop;
|
|
1884
|
+
const maxScroll = currentElement.scrollHeight - currentElement.clientHeight;
|
|
1885
|
+
let scrollAmount = dy2 / 3;
|
|
1886
|
+
if (scrollAmount > 0) {
|
|
1887
|
+
scrollAmount = Math.min(scrollAmount, maxScroll - beforeScroll);
|
|
1888
|
+
} else {
|
|
1889
|
+
scrollAmount = Math.max(scrollAmount, -beforeScroll);
|
|
1890
|
+
}
|
|
1891
|
+
currentElement.scrollTop = beforeScroll + scrollAmount;
|
|
1892
|
+
const afterScroll = currentElement.scrollTop;
|
|
1893
|
+
const actualScrollDelta = afterScroll - beforeScroll;
|
|
1894
|
+
console.log(
|
|
1895
|
+
"[SCROLL DEBUG] Scroll attempt:",
|
|
1896
|
+
currentElement.tagName,
|
|
1897
|
+
"before:",
|
|
1898
|
+
beforeScroll,
|
|
1899
|
+
"after:",
|
|
1900
|
+
afterScroll,
|
|
1901
|
+
"delta:",
|
|
1902
|
+
actualScrollDelta
|
|
1903
|
+
);
|
|
1904
|
+
if (Math.abs(actualScrollDelta) > 0.5) {
|
|
1905
|
+
scrollSuccess = true;
|
|
1906
|
+
scrolledElement = currentElement;
|
|
1907
|
+
scrollDelta = actualScrollDelta;
|
|
1908
|
+
console.log(
|
|
1909
|
+
"[SCROLL DEBUG] Successfully scrolled container:",
|
|
1910
|
+
currentElement.tagName,
|
|
1911
|
+
"delta:",
|
|
1912
|
+
actualScrollDelta
|
|
1913
|
+
);
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (currentElement === document.body || currentElement === document.documentElement) {
|
|
1918
|
+
break;
|
|
1919
|
+
}
|
|
1920
|
+
currentElement = currentElement.parentElement;
|
|
1921
|
+
attempts++;
|
|
1922
|
+
}
|
|
1923
|
+
if (scrollSuccess) {
|
|
1924
|
+
return `Scrolled container (${scrolledElement?.tagName}) by ${scrollDelta}px`;
|
|
1925
|
+
} else {
|
|
1926
|
+
return `No scrollable container found for element (${targetElement.tagName})`;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
const dy = scroll_amount;
|
|
1930
|
+
const bigEnough = /* @__PURE__ */ __name((el2) => el2.clientHeight >= window.innerHeight * 0.5, "bigEnough");
|
|
1931
|
+
const canScroll = /* @__PURE__ */ __name((el2) => el2 && /(auto|scroll|overlay)/.test(getComputedStyle(el2).overflowY) && el2.scrollHeight > el2.clientHeight && bigEnough(el2), "canScroll");
|
|
1932
|
+
let el = document.activeElement;
|
|
1933
|
+
while (el && !canScroll(el) && el !== document.body) el = el.parentElement;
|
|
1934
|
+
el = canScroll(el) ? el : Array.from(document.querySelectorAll("*")).find(canScroll) || document.scrollingElement || document.documentElement;
|
|
1935
|
+
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
|
1936
|
+
window.scrollBy(0, dy);
|
|
1937
|
+
return `✅ Scrolled page by ${dy}px.`;
|
|
1938
|
+
} else {
|
|
1939
|
+
el.scrollBy({ top: dy, behavior: "smooth" });
|
|
1940
|
+
await waitFor(0.1);
|
|
1941
|
+
return `✅ Scrolled container (${el.tagName}) by ${dy}px.`;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
__name(scrollVertically, "scrollVertically");
|
|
1945
|
+
async function scrollHorizontally(right, scroll_amount, element) {
|
|
1946
|
+
if (element) {
|
|
1947
|
+
const targetElement = element;
|
|
1948
|
+
console.log(
|
|
1949
|
+
"[SCROLL DEBUG] Starting direct container scroll for element:",
|
|
1950
|
+
targetElement.tagName
|
|
1951
|
+
);
|
|
1952
|
+
let currentElement = targetElement;
|
|
1953
|
+
let scrollSuccess = false;
|
|
1954
|
+
let scrolledElement = null;
|
|
1955
|
+
let scrollDelta = 0;
|
|
1956
|
+
let attempts = 0;
|
|
1957
|
+
const dx2 = right ? scroll_amount : -scroll_amount;
|
|
1958
|
+
while (currentElement && attempts < 10) {
|
|
1959
|
+
const computedStyle = window.getComputedStyle(currentElement);
|
|
1960
|
+
const hasScrollableX = /(auto|scroll|overlay)/.test(computedStyle.overflowX);
|
|
1961
|
+
const canScrollHorizontally = currentElement.scrollWidth > currentElement.clientWidth;
|
|
1962
|
+
console.log(
|
|
1963
|
+
"[SCROLL DEBUG] Checking element:",
|
|
1964
|
+
currentElement.tagName,
|
|
1965
|
+
"hasScrollableX:",
|
|
1966
|
+
hasScrollableX,
|
|
1967
|
+
"canScrollHorizontally:",
|
|
1968
|
+
canScrollHorizontally,
|
|
1969
|
+
"scrollWidth:",
|
|
1970
|
+
currentElement.scrollWidth,
|
|
1971
|
+
"clientWidth:",
|
|
1972
|
+
currentElement.clientWidth
|
|
1973
|
+
);
|
|
1974
|
+
if (hasScrollableX && canScrollHorizontally) {
|
|
1975
|
+
const beforeScroll = currentElement.scrollLeft;
|
|
1976
|
+
const maxScroll = currentElement.scrollWidth - currentElement.clientWidth;
|
|
1977
|
+
let scrollAmount = dx2 / 3;
|
|
1978
|
+
if (scrollAmount > 0) {
|
|
1979
|
+
scrollAmount = Math.min(scrollAmount, maxScroll - beforeScroll);
|
|
1980
|
+
} else {
|
|
1981
|
+
scrollAmount = Math.max(scrollAmount, -beforeScroll);
|
|
1982
|
+
}
|
|
1983
|
+
currentElement.scrollLeft = beforeScroll + scrollAmount;
|
|
1984
|
+
const afterScroll = currentElement.scrollLeft;
|
|
1985
|
+
const actualScrollDelta = afterScroll - beforeScroll;
|
|
1986
|
+
console.log(
|
|
1987
|
+
"[SCROLL DEBUG] Scroll attempt:",
|
|
1988
|
+
currentElement.tagName,
|
|
1989
|
+
"before:",
|
|
1990
|
+
beforeScroll,
|
|
1991
|
+
"after:",
|
|
1992
|
+
afterScroll,
|
|
1993
|
+
"delta:",
|
|
1994
|
+
actualScrollDelta
|
|
1995
|
+
);
|
|
1996
|
+
if (Math.abs(actualScrollDelta) > 0.5) {
|
|
1997
|
+
scrollSuccess = true;
|
|
1998
|
+
scrolledElement = currentElement;
|
|
1999
|
+
scrollDelta = actualScrollDelta;
|
|
2000
|
+
console.log(
|
|
2001
|
+
"[SCROLL DEBUG] Successfully scrolled container:",
|
|
2002
|
+
currentElement.tagName,
|
|
2003
|
+
"delta:",
|
|
2004
|
+
actualScrollDelta
|
|
2005
|
+
);
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (currentElement === document.body || currentElement === document.documentElement) {
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
2012
|
+
currentElement = currentElement.parentElement;
|
|
2013
|
+
attempts++;
|
|
2014
|
+
}
|
|
2015
|
+
if (scrollSuccess) {
|
|
2016
|
+
return `Scrolled container (${scrolledElement?.tagName}) horizontally by ${scrollDelta}px`;
|
|
2017
|
+
} else {
|
|
2018
|
+
return `No horizontally scrollable container found for element (${targetElement.tagName})`;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
const dx = right ? scroll_amount : -scroll_amount;
|
|
2022
|
+
const bigEnough = /* @__PURE__ */ __name((el2) => el2.clientWidth >= window.innerWidth * 0.5, "bigEnough");
|
|
2023
|
+
const canScroll = /* @__PURE__ */ __name((el2) => el2 && /(auto|scroll|overlay)/.test(getComputedStyle(el2).overflowX) && el2.scrollWidth > el2.clientWidth && bigEnough(el2), "canScroll");
|
|
2024
|
+
let el = document.activeElement;
|
|
2025
|
+
while (el && !canScroll(el) && el !== document.body) el = el.parentElement;
|
|
2026
|
+
el = canScroll(el) ? el : Array.from(document.querySelectorAll("*")).find(canScroll) || document.scrollingElement || document.documentElement;
|
|
2027
|
+
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
|
2028
|
+
window.scrollBy(dx, 0);
|
|
2029
|
+
return `✅ Scrolled page horizontally by ${dx}px`;
|
|
2030
|
+
} else {
|
|
2031
|
+
el.scrollBy({ left: dx, behavior: "smooth" });
|
|
2032
|
+
await waitFor(0.1);
|
|
2033
|
+
return `✅ Scrolled container (${el.tagName}) horizontally by ${dx}px`;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
__name(scrollHorizontally, "scrollHorizontally");
|
|
2037
|
+
const utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2038
|
+
__proto__: null,
|
|
2039
|
+
clickElement,
|
|
2040
|
+
createSyntheticInputEvent,
|
|
2041
|
+
getElementByIndex,
|
|
2042
|
+
getSystemInfo,
|
|
2043
|
+
inputTextElement,
|
|
2044
|
+
movePointerToElement,
|
|
2045
|
+
scrollHorizontally,
|
|
2046
|
+
scrollIntoViewIfNeeded,
|
|
2047
|
+
scrollVertically,
|
|
2048
|
+
selectOptionElement,
|
|
2049
|
+
waitFor
|
|
2050
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2051
|
+
window.utils = utils;
|
|
2052
|
+
const tools = /* @__PURE__ */ new Map();
|
|
2053
|
+
tools.set(
|
|
2054
|
+
"done",
|
|
2055
|
+
tool({
|
|
2056
|
+
description: "Complete task - provide a summary of results for the user. Set success=True if task completed successfully, false otherwise. Text should be your response to the user summarizing results.",
|
|
2057
|
+
inputSchema: zod.object({
|
|
2058
|
+
text: zod.string(),
|
|
2059
|
+
success: zod.boolean().default(true)
|
|
2060
|
+
}),
|
|
2061
|
+
execute: /* @__PURE__ */ __name(function(input2) {
|
|
2062
|
+
}, "execute")
|
|
2063
|
+
})
|
|
2064
|
+
);
|
|
2065
|
+
tools.set(
|
|
2066
|
+
"wait",
|
|
2067
|
+
tool({
|
|
2068
|
+
description: "Wait for x seconds. default 1s (max 10 seconds, min 1 second). This can be used to wait until the page or data is fully loaded.",
|
|
2069
|
+
inputSchema: zod.object({
|
|
2070
|
+
seconds: zod.number().min(1).max(10).default(1)
|
|
2071
|
+
}),
|
|
2072
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2073
|
+
const lastTimeUpdate = this.lastTimeUpdate;
|
|
2074
|
+
const actualWaitTime = Math.max(0, input2.seconds - (Date.now() - lastTimeUpdate) / 1e3);
|
|
2075
|
+
console.log(`actualWaitTime: ${actualWaitTime} seconds`);
|
|
2076
|
+
await waitFor(actualWaitTime);
|
|
2077
|
+
return `✅ Waited for ${input2.seconds} seconds.` + await getSystemInfo();
|
|
2078
|
+
}, "execute")
|
|
2079
|
+
})
|
|
2080
|
+
);
|
|
2081
|
+
tools.set(
|
|
2082
|
+
"ask_user",
|
|
2083
|
+
tool({
|
|
2084
|
+
description: "Ask the user a question and wait for their answer. Use this if you need more information or clarification.",
|
|
2085
|
+
inputSchema: zod.object({
|
|
2086
|
+
question: zod.string()
|
|
2087
|
+
}),
|
|
2088
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2089
|
+
const answer = await this.panel.askUser(input2.question);
|
|
2090
|
+
return `✅ Received user answer: ${answer}` + await getSystemInfo();
|
|
2091
|
+
}, "execute")
|
|
2092
|
+
})
|
|
2093
|
+
);
|
|
2094
|
+
tools.set(
|
|
2095
|
+
"click_element_by_index",
|
|
2096
|
+
tool({
|
|
2097
|
+
description: "Click element by index",
|
|
2098
|
+
inputSchema: zod.object({
|
|
2099
|
+
index: zod.int().min(0)
|
|
2100
|
+
}),
|
|
2101
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2102
|
+
const element = getElementByIndex(this, input2.index);
|
|
2103
|
+
const elemText = this.elementTextMap.get(input2.index);
|
|
2104
|
+
await clickElement(element);
|
|
2105
|
+
if (element instanceof HTMLAnchorElement && element.target === "_blank") {
|
|
2106
|
+
return `⚠️ Clicked link that opens in a new tab (${elemText ? elemText : input2.index}). You are not capable of reading new tabs.`;
|
|
2107
|
+
}
|
|
2108
|
+
return `✅ Clicked element (${elemText ? elemText : input2.index}).` + await getSystemInfo();
|
|
2109
|
+
}, "execute")
|
|
2110
|
+
})
|
|
2111
|
+
);
|
|
2112
|
+
tools.set(
|
|
2113
|
+
"input_text",
|
|
2114
|
+
tool({
|
|
2115
|
+
description: "Click and input text into a input interactive element",
|
|
2116
|
+
inputSchema: zod.object({
|
|
2117
|
+
index: zod.int().min(0),
|
|
2118
|
+
text: zod.string()
|
|
2119
|
+
}),
|
|
2120
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2121
|
+
const element = getElementByIndex(this, input2.index);
|
|
2122
|
+
const elemText = this.elementTextMap.get(input2.index);
|
|
2123
|
+
await inputTextElement(element, input2.text);
|
|
2124
|
+
return `✅ Input text (${input2.text}) into element (${elemText ? elemText : input2.index}).` + await getSystemInfo();
|
|
2125
|
+
}, "execute")
|
|
2126
|
+
})
|
|
2127
|
+
);
|
|
2128
|
+
tools.set(
|
|
2129
|
+
"select_dropdown_option",
|
|
2130
|
+
tool({
|
|
2131
|
+
description: "Select dropdown option for interactive element index by the text of the option you want to select",
|
|
2132
|
+
inputSchema: zod.object({
|
|
2133
|
+
index: zod.int().min(0),
|
|
2134
|
+
text: zod.string()
|
|
2135
|
+
}),
|
|
2136
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2137
|
+
const element = getElementByIndex(this, input2.index);
|
|
2138
|
+
const elemText = this.elementTextMap.get(input2.index);
|
|
2139
|
+
await selectOptionElement(element, input2.text);
|
|
2140
|
+
return `✅ Selected option (${input2.text}) in element (${elemText ? elemText : input2.index}).` + await getSystemInfo();
|
|
2141
|
+
}, "execute")
|
|
2142
|
+
})
|
|
2143
|
+
);
|
|
2144
|
+
tools.set(
|
|
2145
|
+
"scroll",
|
|
2146
|
+
tool({
|
|
2147
|
+
description: "Scroll the page by specified number of pages (set down=True to scroll down, down=False to scroll up, num_pages=number of pages to scroll like 0.5 for half page, 1.0 for one page, etc.). Optional index parameter to scroll within a specific element or its scroll container (works well for dropdowns and custom UI components). Optional pixels parameter to scroll by a specific number of pixels instead of pages.",
|
|
2148
|
+
inputSchema: zod.object({
|
|
2149
|
+
down: zod.boolean().default(true),
|
|
2150
|
+
num_pages: zod.number().min(0).max(10).optional().default(0.1),
|
|
2151
|
+
pixels: zod.number().int().min(0).optional(),
|
|
2152
|
+
index: zod.number().int().min(0).optional()
|
|
2153
|
+
}),
|
|
2154
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2155
|
+
const { down, num_pages, index, pixels } = input2;
|
|
2156
|
+
const scroll_amount = pixels ? pixels : num_pages * (down ? 1 : -1) * window.innerHeight;
|
|
2157
|
+
const element = index !== void 0 ? getElementByIndex(this, index) : null;
|
|
2158
|
+
return await scrollVertically(down, scroll_amount, element) + await getSystemInfo();
|
|
2159
|
+
}, "execute")
|
|
2160
|
+
})
|
|
2161
|
+
);
|
|
2162
|
+
tools.set(
|
|
2163
|
+
"scroll_horizontally",
|
|
2164
|
+
tool({
|
|
2165
|
+
description: "Scroll the page or element horizontally (set right=True to scroll right, right=False to scroll left, pixels=number of pixels to scroll). Optional index parameter to scroll within a specific element or its scroll container (works well for wide tables).",
|
|
2166
|
+
inputSchema: zod.object({
|
|
2167
|
+
right: zod.boolean().default(true),
|
|
2168
|
+
pixels: zod.number().int().min(0),
|
|
2169
|
+
index: zod.number().int().min(0).optional()
|
|
2170
|
+
}),
|
|
2171
|
+
execute: /* @__PURE__ */ __name(async function(input2) {
|
|
2172
|
+
const { right, pixels, index } = input2;
|
|
2173
|
+
const scroll_amount = pixels * (right ? 1 : -1);
|
|
2174
|
+
const element = index !== void 0 ? getElementByIndex(this, index) : null;
|
|
2175
|
+
return await scrollHorizontally(right, scroll_amount, element) + await getSystemInfo();
|
|
2176
|
+
}, "execute")
|
|
2177
|
+
})
|
|
2178
|
+
);
|
|
2179
|
+
async function waitUntil(check, timeout = 60 * 601e3) {
|
|
2180
|
+
if (check()) return true;
|
|
2181
|
+
return new Promise((resolve, reject) => {
|
|
2182
|
+
const start = Date.now();
|
|
2183
|
+
const interval = setInterval(() => {
|
|
2184
|
+
if (check()) {
|
|
2185
|
+
clearInterval(interval);
|
|
2186
|
+
resolve(true);
|
|
2187
|
+
} else if (Date.now() - start > timeout) {
|
|
2188
|
+
clearInterval(interval);
|
|
2189
|
+
reject(new Error("Timeout waiting for condition to become true"));
|
|
2190
|
+
}
|
|
2191
|
+
}, 100);
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
__name(waitUntil, "waitUntil");
|
|
2195
|
+
function truncate(text, maxLength) {
|
|
2196
|
+
if (text.length > maxLength) {
|
|
2197
|
+
return text.substring(0, maxLength) + "...";
|
|
2198
|
+
}
|
|
2199
|
+
return text;
|
|
2200
|
+
}
|
|
2201
|
+
__name(truncate, "truncate");
|
|
2202
|
+
function trimLines(text) {
|
|
2203
|
+
return text.split("\n").map((line) => line.trim()).join("\n");
|
|
2204
|
+
}
|
|
2205
|
+
__name(trimLines, "trimLines");
|
|
2206
|
+
function randomID(existingIDs) {
|
|
2207
|
+
let id = Math.random().toString(36).substring(2, 11);
|
|
2208
|
+
if (!existingIDs) {
|
|
2209
|
+
return id;
|
|
2210
|
+
}
|
|
2211
|
+
const MAX_TRY = 1e3;
|
|
2212
|
+
let tryCount = 0;
|
|
2213
|
+
while (existingIDs.includes(id)) {
|
|
2214
|
+
id = Math.random().toString(36).substring(2, 11);
|
|
2215
|
+
tryCount++;
|
|
2216
|
+
if (tryCount > MAX_TRY) {
|
|
2217
|
+
throw new Error("randomID: too many try");
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
return id;
|
|
2221
|
+
}
|
|
2222
|
+
__name(randomID, "randomID");
|
|
2223
|
+
if (!window.__PAGE_AGENT_IDS__) {
|
|
2224
|
+
window.__PAGE_AGENT_IDS__ = [];
|
|
2225
|
+
}
|
|
2226
|
+
const ids = window.__PAGE_AGENT_IDS__;
|
|
2227
|
+
function uid() {
|
|
2228
|
+
const id = randomID(ids);
|
|
2229
|
+
ids.push(id);
|
|
2230
|
+
return id;
|
|
2231
|
+
}
|
|
2232
|
+
__name(uid, "uid");
|
|
2233
|
+
const _UIState = class _UIState {
|
|
2234
|
+
steps = [];
|
|
2235
|
+
currentStep = null;
|
|
2236
|
+
status = "idle";
|
|
2237
|
+
stepCounter = 0;
|
|
2238
|
+
addStep(stepData) {
|
|
2239
|
+
const step = {
|
|
2240
|
+
id: this.generateId(),
|
|
2241
|
+
stepNumber: ++this.stepCounter,
|
|
2242
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2243
|
+
...stepData
|
|
2244
|
+
};
|
|
2245
|
+
this.steps.push(step);
|
|
2246
|
+
this.currentStep = step;
|
|
2247
|
+
this.updateStatus(step.type);
|
|
2248
|
+
return step;
|
|
2249
|
+
}
|
|
2250
|
+
updateCurrentStep(updates) {
|
|
2251
|
+
if (!this.currentStep) return null;
|
|
2252
|
+
Object.assign(this.currentStep, updates);
|
|
2253
|
+
return this.currentStep;
|
|
2254
|
+
}
|
|
2255
|
+
getCurrentStep() {
|
|
2256
|
+
return this.currentStep;
|
|
2257
|
+
}
|
|
2258
|
+
getAllSteps() {
|
|
2259
|
+
return [...this.steps];
|
|
2260
|
+
}
|
|
2261
|
+
getStatus() {
|
|
2262
|
+
return this.status;
|
|
2263
|
+
}
|
|
2264
|
+
reset() {
|
|
2265
|
+
this.steps = [];
|
|
2266
|
+
this.currentStep = null;
|
|
2267
|
+
this.status = "idle";
|
|
2268
|
+
this.stepCounter = 0;
|
|
2269
|
+
}
|
|
2270
|
+
updateStatus(stepType) {
|
|
2271
|
+
switch (stepType) {
|
|
2272
|
+
case "thinking":
|
|
2273
|
+
case "tool_executing":
|
|
2274
|
+
case "output":
|
|
2275
|
+
case "input":
|
|
2276
|
+
case "retry":
|
|
2277
|
+
this.status = "running";
|
|
2278
|
+
break;
|
|
2279
|
+
case "completed":
|
|
2280
|
+
this.status = "completed";
|
|
2281
|
+
break;
|
|
2282
|
+
case "error":
|
|
2283
|
+
this.status = "error";
|
|
2284
|
+
break;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
generateId() {
|
|
2288
|
+
return `step_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2291
|
+
__name(_UIState, "UIState");
|
|
2292
|
+
let UIState = _UIState;
|
|
2293
|
+
const wrapper$1 = "_wrapper_d1n0a_1";
|
|
2294
|
+
const background = "_background_d1n0a_40";
|
|
2295
|
+
const header = "_header_d1n0a_100";
|
|
2296
|
+
const pulse = "_pulse_d1n0a_1";
|
|
2297
|
+
const retryPulse = "_retryPulse_d1n0a_1";
|
|
2298
|
+
const statusTextFadeOut = "_statusTextFadeOut_d1n0a_1";
|
|
2299
|
+
const statusTextFadeIn = "_statusTextFadeIn_d1n0a_1";
|
|
2300
|
+
const statusSection = "_statusSection_d1n0a_122";
|
|
2301
|
+
const indicator = "_indicator_d1n0a_129";
|
|
2302
|
+
const thinking = "_thinking_d1n0a_138";
|
|
2303
|
+
const tool_executing = "_tool_executing_d1n0a_143";
|
|
2304
|
+
const retry = "_retry_d1n0a_148";
|
|
2305
|
+
const completed = "_completed_d1n0a_154";
|
|
2306
|
+
const input = "_input_d1n0a_155";
|
|
2307
|
+
const output = "_output_d1n0a_156";
|
|
2308
|
+
const error = "_error_d1n0a_161";
|
|
2309
|
+
const statusText = "_statusText_d1n0a_167";
|
|
2310
|
+
const fadeOut = "_fadeOut_d1n0a_179";
|
|
2311
|
+
const fadeIn = "_fadeIn_d1n0a_183";
|
|
2312
|
+
const controls = "_controls_d1n0a_189";
|
|
2313
|
+
const controlButton = "_controlButton_d1n0a_194";
|
|
2314
|
+
const pauseButton = "_pauseButton_d1n0a_213";
|
|
2315
|
+
const paused = "_paused_d1n0a_215";
|
|
2316
|
+
const stopButton = "_stopButton_d1n0a_225";
|
|
2317
|
+
const historySectionWrapper = "_historySectionWrapper_d1n0a_259";
|
|
2318
|
+
const shimmer = "_shimmer_d1n0a_1";
|
|
2319
|
+
const celebrate = "_celebrate_d1n0a_1";
|
|
2320
|
+
const expanded = "_expanded_d1n0a_291";
|
|
2321
|
+
const historySection = "_historySection_d1n0a_259";
|
|
2322
|
+
const historyItem = "_historyItem_d1n0a_310";
|
|
2323
|
+
const doneSuccess = "_doneSuccess_d1n0a_369";
|
|
2324
|
+
const historyContent = "_historyContent_d1n0a_405";
|
|
2325
|
+
const statusIcon = "_statusIcon_d1n0a_406";
|
|
2326
|
+
const doneError = "_doneError_d1n0a_415";
|
|
2327
|
+
const historyMeta = "_historyMeta_d1n0a_466";
|
|
2328
|
+
const inputSectionWrapper = "_inputSectionWrapper_d1n0a_536";
|
|
2329
|
+
const hidden = "_hidden_d1n0a_559";
|
|
2330
|
+
const inputSection = "_inputSection_d1n0a_536";
|
|
2331
|
+
const taskInput = "_taskInput_d1n0a_570";
|
|
2332
|
+
const styles$1 = {
|
|
2333
|
+
wrapper: wrapper$1,
|
|
2334
|
+
"mask-running": "_mask-running_d1n0a_1",
|
|
2335
|
+
background,
|
|
2336
|
+
header,
|
|
2337
|
+
pulse,
|
|
2338
|
+
retryPulse,
|
|
2339
|
+
statusTextFadeOut,
|
|
2340
|
+
statusTextFadeIn,
|
|
2341
|
+
statusSection,
|
|
2342
|
+
indicator,
|
|
2343
|
+
thinking,
|
|
2344
|
+
tool_executing,
|
|
2345
|
+
retry,
|
|
2346
|
+
completed,
|
|
2347
|
+
input,
|
|
2348
|
+
output,
|
|
2349
|
+
error,
|
|
2350
|
+
statusText,
|
|
2351
|
+
fadeOut,
|
|
2352
|
+
fadeIn,
|
|
2353
|
+
controls,
|
|
2354
|
+
controlButton,
|
|
2355
|
+
pauseButton,
|
|
2356
|
+
paused,
|
|
2357
|
+
stopButton,
|
|
2358
|
+
historySectionWrapper,
|
|
2359
|
+
shimmer,
|
|
2360
|
+
celebrate,
|
|
2361
|
+
expanded,
|
|
2362
|
+
historySection,
|
|
2363
|
+
historyItem,
|
|
2364
|
+
doneSuccess,
|
|
2365
|
+
historyContent,
|
|
2366
|
+
statusIcon,
|
|
2367
|
+
doneError,
|
|
2368
|
+
historyMeta,
|
|
2369
|
+
inputSectionWrapper,
|
|
2370
|
+
hidden,
|
|
2371
|
+
inputSection,
|
|
2372
|
+
taskInput
|
|
2373
|
+
};
|
|
2374
|
+
const _Panel = class _Panel {
|
|
2375
|
+
constructor(pageAgent) {
|
|
2376
|
+
__privateAdd(this, _Panel_instances);
|
|
2377
|
+
__privateAdd(this, _wrapper);
|
|
2378
|
+
__privateAdd(this, _indicator);
|
|
2379
|
+
__privateAdd(this, _statusText);
|
|
2380
|
+
__privateAdd(this, _historySection);
|
|
2381
|
+
__privateAdd(this, _expandButton);
|
|
2382
|
+
__privateAdd(this, _pauseButton);
|
|
2383
|
+
__privateAdd(this, _stopButton);
|
|
2384
|
+
__privateAdd(this, _inputSection);
|
|
2385
|
+
__privateAdd(this, _taskInput);
|
|
2386
|
+
__privateAdd(this, _bus2);
|
|
2387
|
+
__privateAdd(this, _state, new UIState());
|
|
2388
|
+
__privateAdd(this, _isExpanded, false);
|
|
2389
|
+
__privateAdd(this, _pageAgent);
|
|
2390
|
+
__privateAdd(this, _userAnswerResolver, null);
|
|
2391
|
+
__privateAdd(this, _isWaitingForUserAnswer, false);
|
|
2392
|
+
__privateSet(this, _pageAgent, pageAgent);
|
|
2393
|
+
__privateSet(this, _bus2, pageAgent.bus);
|
|
2394
|
+
__privateSet(this, _wrapper, __privateMethod(this, _Panel_instances, createWrapper_fn).call(this));
|
|
2395
|
+
__privateSet(this, _indicator, __privateGet(this, _wrapper).querySelector(`.${styles$1.indicator}`));
|
|
2396
|
+
__privateSet(this, _statusText, __privateGet(this, _wrapper).querySelector(`.${styles$1.statusText}`));
|
|
2397
|
+
__privateSet(this, _historySection, __privateGet(this, _wrapper).querySelector(`.${styles$1.historySection}`));
|
|
2398
|
+
__privateSet(this, _expandButton, __privateGet(this, _wrapper).querySelector(`.${styles$1.expandButton}`));
|
|
2399
|
+
__privateSet(this, _pauseButton, __privateGet(this, _wrapper).querySelector(`.${styles$1.pauseButton}`));
|
|
2400
|
+
__privateSet(this, _stopButton, __privateGet(this, _wrapper).querySelector(`.${styles$1.stopButton}`));
|
|
2401
|
+
__privateSet(this, _inputSection, __privateGet(this, _wrapper).querySelector(`.${styles$1.inputSectionWrapper}`));
|
|
2402
|
+
__privateSet(this, _taskInput, __privateGet(this, _wrapper).querySelector(`.${styles$1.taskInput}`));
|
|
2403
|
+
__privateMethod(this, _Panel_instances, setupEventListeners_fn).call(this);
|
|
2404
|
+
__privateMethod(this, _Panel_instances, showInputArea_fn).call(this);
|
|
2405
|
+
__privateGet(this, _bus2).on("panel:show", () => __privateMethod(this, _Panel_instances, show_fn).call(this));
|
|
2406
|
+
__privateGet(this, _bus2).on("panel:hide", () => __privateMethod(this, _Panel_instances, hide_fn).call(this));
|
|
2407
|
+
__privateGet(this, _bus2).on("panel:reset", () => __privateMethod(this, _Panel_instances, reset_fn).call(this));
|
|
2408
|
+
__privateGet(this, _bus2).on("panel:update", (stepData) => __privateMethod(this, _Panel_instances, update_fn).call(this, stepData));
|
|
2409
|
+
__privateGet(this, _bus2).on("panel:expand", () => __privateMethod(this, _Panel_instances, expand_fn).call(this));
|
|
2410
|
+
__privateGet(this, _bus2).on("panel:collapse", () => __privateMethod(this, _Panel_instances, collapse_fn).call(this));
|
|
2411
|
+
}
|
|
2412
|
+
get wrapper() {
|
|
2413
|
+
return __privateGet(this, _wrapper);
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Ask for user input
|
|
2417
|
+
*/
|
|
2418
|
+
async askUser(question) {
|
|
2419
|
+
return new Promise((resolve) => {
|
|
2420
|
+
__privateSet(this, _isWaitingForUserAnswer, true);
|
|
2421
|
+
__privateSet(this, _userAnswerResolver, resolve);
|
|
2422
|
+
__privateMethod(this, _Panel_instances, update_fn).call(this, {
|
|
2423
|
+
type: "output",
|
|
2424
|
+
displayText: `询问: ${question}`
|
|
2425
|
+
});
|
|
2426
|
+
if (!__privateGet(this, _isExpanded)) {
|
|
2427
|
+
__privateMethod(this, _Panel_instances, expand_fn).call(this);
|
|
2428
|
+
}
|
|
2429
|
+
__privateMethod(this, _Panel_instances, showInputArea_fn).call(this, __privateGet(this, _pageAgent).i18n.t("ui.panel.userAnswerPrompt"));
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Dispose panel
|
|
2434
|
+
*/
|
|
2435
|
+
dispose() {
|
|
2436
|
+
__privateSet(this, _isWaitingForUserAnswer, false);
|
|
2437
|
+
this.wrapper.remove();
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
_wrapper = new WeakMap();
|
|
2441
|
+
_indicator = new WeakMap();
|
|
2442
|
+
_statusText = new WeakMap();
|
|
2443
|
+
_historySection = new WeakMap();
|
|
2444
|
+
_expandButton = new WeakMap();
|
|
2445
|
+
_pauseButton = new WeakMap();
|
|
2446
|
+
_stopButton = new WeakMap();
|
|
2447
|
+
_inputSection = new WeakMap();
|
|
2448
|
+
_taskInput = new WeakMap();
|
|
2449
|
+
_bus2 = new WeakMap();
|
|
2450
|
+
_state = new WeakMap();
|
|
2451
|
+
_isExpanded = new WeakMap();
|
|
2452
|
+
_pageAgent = new WeakMap();
|
|
2453
|
+
_userAnswerResolver = new WeakMap();
|
|
2454
|
+
_isWaitingForUserAnswer = new WeakMap();
|
|
2455
|
+
_Panel_instances = new WeakSet();
|
|
2456
|
+
update_fn = /* @__PURE__ */ __name(async function(stepData) {
|
|
2457
|
+
const step = __privateGet(this, _state).addStep(stepData);
|
|
2458
|
+
const headerText = truncate(step.displayText, 20);
|
|
2459
|
+
if (__privateGet(this, _statusText).textContent !== headerText) {
|
|
2460
|
+
await __privateMethod(this, _Panel_instances, animateTextChange_fn).call(this, headerText);
|
|
2461
|
+
}
|
|
2462
|
+
__privateMethod(this, _Panel_instances, updateStatusIndicator_fn).call(this, step.type);
|
|
2463
|
+
__privateMethod(this, _Panel_instances, updateHistory_fn).call(this);
|
|
2464
|
+
if (step.type === "completed" || step.type === "error") {
|
|
2465
|
+
if (!__privateGet(this, _isExpanded)) {
|
|
2466
|
+
__privateMethod(this, _Panel_instances, expand_fn).call(this);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
if (__privateMethod(this, _Panel_instances, shouldShowInputArea_fn).call(this)) {
|
|
2470
|
+
__privateMethod(this, _Panel_instances, showInputArea_fn).call(this);
|
|
2471
|
+
} else {
|
|
2472
|
+
__privateMethod(this, _Panel_instances, hideInputArea_fn).call(this);
|
|
2473
|
+
}
|
|
2474
|
+
}, "#update");
|
|
2475
|
+
/**
|
|
2476
|
+
* Show panel
|
|
2477
|
+
*/
|
|
2478
|
+
show_fn = /* @__PURE__ */ __name(function() {
|
|
2479
|
+
this.wrapper.style.display = "block";
|
|
2480
|
+
void this.wrapper.offsetHeight;
|
|
2481
|
+
this.wrapper.style.opacity = "1";
|
|
2482
|
+
this.wrapper.style.transform = "translateX(-50%) translateY(0)";
|
|
2483
|
+
}, "#show");
|
|
2484
|
+
/**
|
|
2485
|
+
* 隐藏面板
|
|
2486
|
+
*/
|
|
2487
|
+
hide_fn = /* @__PURE__ */ __name(function() {
|
|
2488
|
+
this.wrapper.style.opacity = "0";
|
|
2489
|
+
this.wrapper.style.transform = "translateX(-50%) translateY(20px)";
|
|
2490
|
+
this.wrapper.style.display = "none";
|
|
2491
|
+
}, "#hide");
|
|
2492
|
+
/**
|
|
2493
|
+
* 重置状态
|
|
2494
|
+
*/
|
|
2495
|
+
reset_fn = /* @__PURE__ */ __name(function() {
|
|
2496
|
+
__privateGet(this, _state).reset();
|
|
2497
|
+
__privateGet(this, _statusText).textContent = __privateGet(this, _pageAgent).i18n.t("ui.panel.ready");
|
|
2498
|
+
__privateMethod(this, _Panel_instances, updateStatusIndicator_fn).call(this, "thinking");
|
|
2499
|
+
__privateMethod(this, _Panel_instances, updateHistory_fn).call(this);
|
|
2500
|
+
__privateMethod(this, _Panel_instances, collapse_fn).call(this);
|
|
2501
|
+
__privateGet(this, _pageAgent).paused = false;
|
|
2502
|
+
__privateMethod(this, _Panel_instances, updatePauseButton_fn).call(this);
|
|
2503
|
+
__privateSet(this, _isWaitingForUserAnswer, false);
|
|
2504
|
+
__privateSet(this, _userAnswerResolver, null);
|
|
2505
|
+
__privateMethod(this, _Panel_instances, showInputArea_fn).call(this);
|
|
2506
|
+
}, "#reset");
|
|
2507
|
+
/**
|
|
2508
|
+
* Toggle pause state
|
|
2509
|
+
*/
|
|
2510
|
+
togglePause_fn = /* @__PURE__ */ __name(function() {
|
|
2511
|
+
__privateGet(this, _pageAgent).paused = !__privateGet(this, _pageAgent).paused;
|
|
2512
|
+
__privateMethod(this, _Panel_instances, updatePauseButton_fn).call(this);
|
|
2513
|
+
if (__privateGet(this, _pageAgent).paused) {
|
|
2514
|
+
__privateGet(this, _statusText).textContent = "暂停中,稍后";
|
|
2515
|
+
__privateMethod(this, _Panel_instances, updateStatusIndicator_fn).call(this, "thinking");
|
|
2516
|
+
} else {
|
|
2517
|
+
__privateGet(this, _statusText).textContent = "继续执行";
|
|
2518
|
+
__privateMethod(this, _Panel_instances, updateStatusIndicator_fn).call(this, "tool_executing");
|
|
2519
|
+
}
|
|
2520
|
+
}, "#togglePause");
|
|
2521
|
+
/**
|
|
2522
|
+
* 更新暂停按钮状态
|
|
2523
|
+
*/
|
|
2524
|
+
updatePauseButton_fn = /* @__PURE__ */ __name(function() {
|
|
2525
|
+
if (__privateGet(this, _pageAgent).paused) {
|
|
2526
|
+
__privateGet(this, _pauseButton).textContent = "▶";
|
|
2527
|
+
__privateGet(this, _pauseButton).title = "继续";
|
|
2528
|
+
__privateGet(this, _pauseButton).classList.add(styles$1.paused);
|
|
2529
|
+
} else {
|
|
2530
|
+
__privateGet(this, _pauseButton).textContent = "⏸︎";
|
|
2531
|
+
__privateGet(this, _pauseButton).title = "暂停";
|
|
2532
|
+
__privateGet(this, _pauseButton).classList.remove(styles$1.paused);
|
|
2533
|
+
}
|
|
2534
|
+
}, "#updatePauseButton");
|
|
2535
|
+
/**
|
|
2536
|
+
* 终止 Agent
|
|
2537
|
+
*/
|
|
2538
|
+
stopAgent_fn = /* @__PURE__ */ __name(function() {
|
|
2539
|
+
__privateMethod(this, _Panel_instances, update_fn).call(this, {
|
|
2540
|
+
type: "error",
|
|
2541
|
+
displayText: "任务已终止"
|
|
2542
|
+
});
|
|
2543
|
+
__privateGet(this, _pageAgent).dispose();
|
|
2544
|
+
}, "#stopAgent");
|
|
2545
|
+
/**
|
|
2546
|
+
* 提交任务
|
|
2547
|
+
*/
|
|
2548
|
+
submitTask_fn = /* @__PURE__ */ __name(function() {
|
|
2549
|
+
const input2 = __privateGet(this, _taskInput).value.trim();
|
|
2550
|
+
if (!input2) return;
|
|
2551
|
+
__privateMethod(this, _Panel_instances, hideInputArea_fn).call(this);
|
|
2552
|
+
if (__privateGet(this, _isWaitingForUserAnswer)) {
|
|
2553
|
+
__privateMethod(this, _Panel_instances, handleUserAnswer_fn).call(this, input2);
|
|
2554
|
+
} else {
|
|
2555
|
+
__privateGet(this, _pageAgent).execute(input2);
|
|
2556
|
+
}
|
|
2557
|
+
}, "#submitTask");
|
|
2558
|
+
/**
|
|
2559
|
+
* 处理用户回答
|
|
2560
|
+
*/
|
|
2561
|
+
handleUserAnswer_fn = /* @__PURE__ */ __name(function(input2) {
|
|
2562
|
+
__privateMethod(this, _Panel_instances, update_fn).call(this, {
|
|
2563
|
+
type: "input",
|
|
2564
|
+
displayText: `用户回答: ${input2}`
|
|
2565
|
+
});
|
|
2566
|
+
__privateSet(this, _isWaitingForUserAnswer, false);
|
|
2567
|
+
if (__privateGet(this, _userAnswerResolver)) {
|
|
2568
|
+
__privateGet(this, _userAnswerResolver).call(this, input2);
|
|
2569
|
+
__privateSet(this, _userAnswerResolver, null);
|
|
2570
|
+
}
|
|
2571
|
+
}, "#handleUserAnswer");
|
|
2572
|
+
/**
|
|
2573
|
+
* 显示输入区域
|
|
2574
|
+
*/
|
|
2575
|
+
showInputArea_fn = /* @__PURE__ */ __name(function(placeholder) {
|
|
2576
|
+
__privateGet(this, _taskInput).value = "";
|
|
2577
|
+
__privateGet(this, _taskInput).placeholder = placeholder || "输入新任务,详细描述步骤,回车提交";
|
|
2578
|
+
__privateGet(this, _inputSection).classList.remove(styles$1.hidden);
|
|
2579
|
+
setTimeout(() => {
|
|
2580
|
+
__privateGet(this, _taskInput).focus();
|
|
2581
|
+
}, 100);
|
|
2582
|
+
}, "#showInputArea");
|
|
2583
|
+
/**
|
|
2584
|
+
* 隐藏输入区域
|
|
2585
|
+
*/
|
|
2586
|
+
hideInputArea_fn = /* @__PURE__ */ __name(function() {
|
|
2587
|
+
__privateGet(this, _inputSection).classList.add(styles$1.hidden);
|
|
2588
|
+
}, "#hideInputArea");
|
|
2589
|
+
/**
|
|
2590
|
+
* 检查是否应该显示输入区域
|
|
2591
|
+
*/
|
|
2592
|
+
shouldShowInputArea_fn = /* @__PURE__ */ __name(function() {
|
|
2593
|
+
if (__privateGet(this, _isWaitingForUserAnswer)) return true;
|
|
2594
|
+
const steps = __privateGet(this, _state).getAllSteps();
|
|
2595
|
+
if (steps.length === 0) {
|
|
2596
|
+
return true;
|
|
2597
|
+
}
|
|
2598
|
+
const lastStep = steps[steps.length - 1];
|
|
2599
|
+
return lastStep.type === "completed" || lastStep.type === "error";
|
|
2600
|
+
}, "#shouldShowInputArea");
|
|
2601
|
+
createWrapper_fn = /* @__PURE__ */ __name(function() {
|
|
2602
|
+
const wrapper2 = document.createElement("div");
|
|
2603
|
+
wrapper2.id = "page-agent-runtime_agent-panel";
|
|
2604
|
+
wrapper2.className = `${styles$1.wrapper} ${styles$1.collapsed}`;
|
|
2605
|
+
wrapper2.setAttribute("data-browser-use-ignore", "true");
|
|
2606
|
+
wrapper2.innerHTML = `
|
|
2607
|
+
<div class="${styles$1.background}"></div>
|
|
2608
|
+
<div class="${styles$1.historySectionWrapper}">
|
|
2609
|
+
<div class="${styles$1.historySection}">
|
|
2610
|
+
${__privateMethod(this, _Panel_instances, createHistoryItem_fn).call(this, {
|
|
2611
|
+
id: "placeholder",
|
|
2612
|
+
stepNumber: 0,
|
|
2613
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2614
|
+
type: "thinking",
|
|
2615
|
+
displayText: "等待任务开始..."
|
|
2616
|
+
})}
|
|
2617
|
+
</div>
|
|
2618
|
+
</div>
|
|
2619
|
+
<div class="${styles$1.header}">
|
|
2620
|
+
<div class="${styles$1.statusSection}">
|
|
2621
|
+
<div class="${styles$1.indicator} ${styles$1.thinking}"></div>
|
|
2622
|
+
<div class="${styles$1.statusText}">准备就绪</div>
|
|
2623
|
+
</div>
|
|
2624
|
+
<div class="${styles$1.controls}">
|
|
2625
|
+
<button class="${styles$1.controlButton} ${styles$1.expandButton}" title="展开历史">
|
|
2626
|
+
▼
|
|
2627
|
+
</button>
|
|
2628
|
+
<button class="${styles$1.controlButton} ${styles$1.pauseButton}" title="暂停">
|
|
2629
|
+
⏸︎
|
|
2630
|
+
</button>
|
|
2631
|
+
<button class="${styles$1.controlButton} ${styles$1.stopButton}" title="终止">
|
|
2632
|
+
X
|
|
2633
|
+
</button>
|
|
2634
|
+
</div>
|
|
2635
|
+
</div>
|
|
2636
|
+
<div class="${styles$1.inputSectionWrapper} ${styles$1.hidden}">
|
|
2637
|
+
<div class="${styles$1.inputSection}">
|
|
2638
|
+
<input
|
|
2639
|
+
type="text"
|
|
2640
|
+
class="${styles$1.taskInput}"
|
|
2641
|
+
maxlength="200"
|
|
2642
|
+
/>
|
|
2643
|
+
</div>
|
|
2644
|
+
</div>
|
|
2645
|
+
`;
|
|
2646
|
+
document.body.appendChild(wrapper2);
|
|
2647
|
+
return wrapper2;
|
|
2648
|
+
}, "#createWrapper");
|
|
2649
|
+
setupEventListeners_fn = /* @__PURE__ */ __name(function() {
|
|
2650
|
+
const header2 = this.wrapper.querySelector(`.${styles$1.header}`);
|
|
2651
|
+
header2.addEventListener("click", (e) => {
|
|
2652
|
+
if (e.target.closest(`.${styles$1.controlButton}`)) {
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
__privateMethod(this, _Panel_instances, toggle_fn).call(this);
|
|
2656
|
+
});
|
|
2657
|
+
__privateGet(this, _expandButton).addEventListener("click", (e) => {
|
|
2658
|
+
e.stopPropagation();
|
|
2659
|
+
__privateMethod(this, _Panel_instances, toggle_fn).call(this);
|
|
2660
|
+
});
|
|
2661
|
+
__privateGet(this, _pauseButton).addEventListener("click", (e) => {
|
|
2662
|
+
e.stopPropagation();
|
|
2663
|
+
__privateMethod(this, _Panel_instances, togglePause_fn).call(this);
|
|
2664
|
+
});
|
|
2665
|
+
__privateGet(this, _stopButton).addEventListener("click", (e) => {
|
|
2666
|
+
e.stopPropagation();
|
|
2667
|
+
__privateMethod(this, _Panel_instances, stopAgent_fn).call(this);
|
|
2668
|
+
});
|
|
2669
|
+
__privateGet(this, _taskInput).addEventListener("keydown", (e) => {
|
|
2670
|
+
if (e.isComposing) return;
|
|
2671
|
+
if (e.key === "Enter") {
|
|
2672
|
+
e.preventDefault();
|
|
2673
|
+
__privateMethod(this, _Panel_instances, submitTask_fn).call(this);
|
|
2674
|
+
}
|
|
2675
|
+
});
|
|
2676
|
+
__privateGet(this, _inputSection).addEventListener("click", (e) => {
|
|
2677
|
+
e.stopPropagation();
|
|
2678
|
+
});
|
|
2679
|
+
}, "#setupEventListeners");
|
|
2680
|
+
toggle_fn = /* @__PURE__ */ __name(function() {
|
|
2681
|
+
if (__privateGet(this, _isExpanded)) {
|
|
2682
|
+
__privateMethod(this, _Panel_instances, collapse_fn).call(this);
|
|
2683
|
+
} else {
|
|
2684
|
+
__privateMethod(this, _Panel_instances, expand_fn).call(this);
|
|
2685
|
+
}
|
|
2686
|
+
}, "#toggle");
|
|
2687
|
+
expand_fn = /* @__PURE__ */ __name(function() {
|
|
2688
|
+
__privateSet(this, _isExpanded, true);
|
|
2689
|
+
this.wrapper.classList.remove(styles$1.collapsed);
|
|
2690
|
+
this.wrapper.classList.add(styles$1.expanded);
|
|
2691
|
+
__privateGet(this, _expandButton).textContent = "▲";
|
|
2692
|
+
}, "#expand");
|
|
2693
|
+
collapse_fn = /* @__PURE__ */ __name(function() {
|
|
2694
|
+
__privateSet(this, _isExpanded, false);
|
|
2695
|
+
this.wrapper.classList.remove(styles$1.expanded);
|
|
2696
|
+
this.wrapper.classList.add(styles$1.collapsed);
|
|
2697
|
+
__privateGet(this, _expandButton).textContent = "▼";
|
|
2698
|
+
}, "#collapse");
|
|
2699
|
+
animateTextChange_fn = /* @__PURE__ */ __name(async function(newText) {
|
|
2700
|
+
return new Promise((resolve) => {
|
|
2701
|
+
__privateGet(this, _statusText).classList.add(styles$1.fadeOut);
|
|
2702
|
+
setTimeout(() => {
|
|
2703
|
+
__privateGet(this, _statusText).textContent = newText;
|
|
2704
|
+
__privateGet(this, _statusText).classList.remove(styles$1.fadeOut);
|
|
2705
|
+
__privateGet(this, _statusText).classList.add(styles$1.fadeIn);
|
|
2706
|
+
setTimeout(() => {
|
|
2707
|
+
__privateGet(this, _statusText).classList.remove(styles$1.fadeIn);
|
|
2708
|
+
resolve();
|
|
2709
|
+
}, 300);
|
|
2710
|
+
}, 150);
|
|
2711
|
+
});
|
|
2712
|
+
}, "#animateTextChange");
|
|
2713
|
+
updateStatusIndicator_fn = /* @__PURE__ */ __name(function(type) {
|
|
2714
|
+
__privateGet(this, _indicator).className = styles$1.indicator;
|
|
2715
|
+
__privateGet(this, _indicator).classList.add(styles$1[type]);
|
|
2716
|
+
}, "#updateStatusIndicator");
|
|
2717
|
+
updateHistory_fn = /* @__PURE__ */ __name(function() {
|
|
2718
|
+
const steps = __privateGet(this, _state).getAllSteps();
|
|
2719
|
+
__privateGet(this, _historySection).innerHTML = steps.slice(-10).map((step) => __privateMethod(this, _Panel_instances, createHistoryItem_fn).call(this, step)).join("");
|
|
2720
|
+
__privateMethod(this, _Panel_instances, scrollToBottom_fn).call(this);
|
|
2721
|
+
}, "#updateHistory");
|
|
2722
|
+
scrollToBottom_fn = /* @__PURE__ */ __name(function() {
|
|
2723
|
+
setTimeout(() => {
|
|
2724
|
+
__privateGet(this, _historySection).scrollTop = __privateGet(this, _historySection).scrollHeight;
|
|
2725
|
+
}, 0);
|
|
2726
|
+
}, "#scrollToBottom");
|
|
2727
|
+
createHistoryItem_fn = /* @__PURE__ */ __name(function(step) {
|
|
2728
|
+
const time = step.timestamp.toLocaleTimeString("zh-CN", {
|
|
2729
|
+
hour12: false,
|
|
2730
|
+
hour: "2-digit",
|
|
2731
|
+
minute: "2-digit",
|
|
2732
|
+
second: "2-digit"
|
|
2733
|
+
});
|
|
2734
|
+
let typeClass = "";
|
|
2735
|
+
let statusIcon2 = "";
|
|
2736
|
+
if (step.type === "completed") {
|
|
2737
|
+
if (step.toolName === "done") {
|
|
2738
|
+
const isSuccess = !step.toolResult || !step.toolResult.includes("失败") && !step.toolResult.includes("错误");
|
|
2739
|
+
typeClass = isSuccess ? styles$1.doneSuccess : styles$1.doneError;
|
|
2740
|
+
statusIcon2 = isSuccess ? "🎉" : "❌";
|
|
2741
|
+
} else {
|
|
2742
|
+
typeClass = styles$1.completed;
|
|
2743
|
+
statusIcon2 = "✅";
|
|
2744
|
+
}
|
|
2745
|
+
} else if (step.type === "error") {
|
|
2746
|
+
typeClass = styles$1.error;
|
|
2747
|
+
statusIcon2 = "❌";
|
|
2748
|
+
} else if (step.type === "tool_executing") {
|
|
2749
|
+
statusIcon2 = "⚙️";
|
|
2750
|
+
} else if (step.type === "output") {
|
|
2751
|
+
typeClass = styles$1.output;
|
|
2752
|
+
statusIcon2 = "🤖";
|
|
2753
|
+
} else if (step.type === "input") {
|
|
2754
|
+
typeClass = styles$1.input;
|
|
2755
|
+
statusIcon2 = "🎯";
|
|
2756
|
+
} else if (step.type === "retry") {
|
|
2757
|
+
typeClass = styles$1.retry;
|
|
2758
|
+
statusIcon2 = "🔄";
|
|
2759
|
+
} else {
|
|
2760
|
+
statusIcon2 = "🧠";
|
|
2761
|
+
}
|
|
2762
|
+
return `
|
|
2763
|
+
<div class="${styles$1.historyItem} ${typeClass}">
|
|
2764
|
+
<div class="${styles$1.historyContent}">
|
|
2765
|
+
<span class="${styles$1.statusIcon}">${statusIcon2}</span>
|
|
2766
|
+
<span>${step.displayText}</span>
|
|
2767
|
+
</div>
|
|
2768
|
+
<div class="${styles$1.historyMeta}">
|
|
2769
|
+
步骤 ${step.stepNumber} · ${time}
|
|
2770
|
+
${step.duration ? ` · ${step.duration}ms` : ""}
|
|
2771
|
+
</div>
|
|
2772
|
+
</div>
|
|
2773
|
+
`;
|
|
2774
|
+
}, "#createHistoryItem");
|
|
2775
|
+
__name(_Panel, "Panel");
|
|
2776
|
+
let Panel = _Panel;
|
|
2777
|
+
function getToolExecutingText(toolName, args, i18n) {
|
|
2778
|
+
switch (toolName) {
|
|
2779
|
+
case "click_element_by_index":
|
|
2780
|
+
return i18n.t("ui.tools.clicking", { index: args.index });
|
|
2781
|
+
case "input_text":
|
|
2782
|
+
return i18n.t("ui.tools.inputting", { index: args.index });
|
|
2783
|
+
case "select_dropdown_option":
|
|
2784
|
+
return i18n.t("ui.tools.selecting", { text: args.text });
|
|
2785
|
+
case "scroll":
|
|
2786
|
+
return i18n.t("ui.tools.scrolling");
|
|
2787
|
+
case "wait":
|
|
2788
|
+
return i18n.t("ui.tools.waiting", { seconds: args.seconds });
|
|
2789
|
+
case "done":
|
|
2790
|
+
return i18n.t("ui.tools.done");
|
|
2791
|
+
default:
|
|
2792
|
+
return i18n.t("ui.tools.executing", { toolName });
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
__name(getToolExecutingText, "getToolExecutingText");
|
|
2796
|
+
function getToolCompletedText(toolName, args, i18n) {
|
|
2797
|
+
switch (toolName) {
|
|
2798
|
+
case "click_element_by_index":
|
|
2799
|
+
return i18n.t("ui.tools.clicked", { index: args.index });
|
|
2800
|
+
case "input_text":
|
|
2801
|
+
return i18n.t("ui.tools.inputted", { text: args.text });
|
|
2802
|
+
case "select_dropdown_option":
|
|
2803
|
+
return i18n.t("ui.tools.selected", { text: args.text });
|
|
2804
|
+
case "scroll":
|
|
2805
|
+
return i18n.t("ui.tools.scrolled");
|
|
2806
|
+
case "wait":
|
|
2807
|
+
return i18n.t("ui.tools.waited");
|
|
2808
|
+
case "done":
|
|
2809
|
+
return null;
|
|
2810
|
+
default:
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
__name(getToolCompletedText, "getToolCompletedText");
|
|
2815
|
+
function hasDarkModeClass() {
|
|
2816
|
+
const DFEAULT_DARK_MODE_CLASSES = ["dark", "dark-mode", "theme-dark", "night", "night-mode"];
|
|
2817
|
+
const htmlElement = document.documentElement;
|
|
2818
|
+
const bodyElement = document.body;
|
|
2819
|
+
for (const className of DFEAULT_DARK_MODE_CLASSES) {
|
|
2820
|
+
if (htmlElement.classList.contains(className) || bodyElement.classList.contains(className)) {
|
|
2821
|
+
return true;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
const darkThemeAttribute = htmlElement.getAttribute("data-theme");
|
|
2825
|
+
if (darkThemeAttribute?.toLowerCase().includes("dark")) {
|
|
2826
|
+
return true;
|
|
2827
|
+
}
|
|
2828
|
+
return false;
|
|
2829
|
+
}
|
|
2830
|
+
__name(hasDarkModeClass, "hasDarkModeClass");
|
|
2831
|
+
function parseRgbColor(colorString) {
|
|
2832
|
+
const rgbMatch = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(colorString);
|
|
2833
|
+
if (!rgbMatch) {
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
return {
|
|
2837
|
+
r: parseInt(rgbMatch[1]),
|
|
2838
|
+
g: parseInt(rgbMatch[2]),
|
|
2839
|
+
b: parseInt(rgbMatch[3])
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
__name(parseRgbColor, "parseRgbColor");
|
|
2843
|
+
function isColorDark(colorString, threshold = 128) {
|
|
2844
|
+
if (!colorString || colorString === "transparent" || colorString.startsWith("rgba(0, 0, 0, 0)")) {
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
const rgb = parseRgbColor(colorString);
|
|
2848
|
+
if (!rgb) {
|
|
2849
|
+
return false;
|
|
2850
|
+
}
|
|
2851
|
+
const luminance = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
|
|
2852
|
+
return luminance < threshold;
|
|
2853
|
+
}
|
|
2854
|
+
__name(isColorDark, "isColorDark");
|
|
2855
|
+
function isBackgroundDark() {
|
|
2856
|
+
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
2857
|
+
const bodyStyle = window.getComputedStyle(document.body);
|
|
2858
|
+
const htmlBgColor = htmlStyle.backgroundColor;
|
|
2859
|
+
const bodyBgColor = bodyStyle.backgroundColor;
|
|
2860
|
+
if (isColorDark(bodyBgColor)) {
|
|
2861
|
+
return true;
|
|
2862
|
+
} else if (bodyBgColor === "transparent" || bodyBgColor.startsWith("rgba(0, 0, 0, 0)")) {
|
|
2863
|
+
return isColorDark(htmlBgColor);
|
|
2864
|
+
}
|
|
2865
|
+
return false;
|
|
2866
|
+
}
|
|
2867
|
+
__name(isBackgroundDark, "isBackgroundDark");
|
|
2868
|
+
function isPageDark() {
|
|
2869
|
+
if (hasDarkModeClass()) {
|
|
2870
|
+
return true;
|
|
2871
|
+
}
|
|
2872
|
+
if (isBackgroundDark()) {
|
|
2873
|
+
return true;
|
|
2874
|
+
}
|
|
2875
|
+
return false;
|
|
2876
|
+
}
|
|
2877
|
+
__name(isPageDark, "isPageDark");
|
|
2878
|
+
const wrapper = "_wrapper_1oy2s_1";
|
|
2879
|
+
const styles = {
|
|
2880
|
+
wrapper
|
|
2881
|
+
};
|
|
2882
|
+
const cursor = "_cursor_1vrf3_2";
|
|
2883
|
+
const cursorBorder = "_cursorBorder_1vrf3_13";
|
|
2884
|
+
const cursorFilling = "_cursorFilling_1vrf3_23";
|
|
2885
|
+
const cursorRipple = "_cursorRipple_1vrf3_31";
|
|
2886
|
+
const clicking = "_clicking_1vrf3_37";
|
|
2887
|
+
const cursorStyles = {
|
|
2888
|
+
cursor,
|
|
2889
|
+
cursorBorder,
|
|
2890
|
+
cursorFilling,
|
|
2891
|
+
cursorRipple,
|
|
2892
|
+
clicking
|
|
2893
|
+
};
|
|
2894
|
+
const _SimulatorMask = class _SimulatorMask {
|
|
2895
|
+
constructor() {
|
|
2896
|
+
__privateAdd(this, _SimulatorMask_instances);
|
|
2897
|
+
__publicField(this, "wrapper", document.createElement("div"));
|
|
2898
|
+
__publicField(this, "motion", new Motion({
|
|
2899
|
+
mode: isPageDark() ? "dark" : "light",
|
|
2900
|
+
styles: {
|
|
2901
|
+
position: "absolute",
|
|
2902
|
+
inset: "0"
|
|
2903
|
+
}
|
|
2904
|
+
}));
|
|
2905
|
+
__privateAdd(this, _cursor, document.createElement("div"));
|
|
2906
|
+
__privateAdd(this, _currentCursorX, 0);
|
|
2907
|
+
__privateAdd(this, _currentCursorY, 0);
|
|
2908
|
+
__privateAdd(this, _targetCursorX, 0);
|
|
2909
|
+
__privateAdd(this, _targetCursorY, 0);
|
|
2910
|
+
this.wrapper.id = "page-agent-runtime_simulator-mask";
|
|
2911
|
+
this.wrapper.className = styles.wrapper;
|
|
2912
|
+
this.wrapper.setAttribute("data-browser-use-ignore", "true");
|
|
2913
|
+
this.wrapper.appendChild(this.motion.element);
|
|
2914
|
+
this.motion.autoResize(this.wrapper);
|
|
2915
|
+
this.wrapper.addEventListener("click", (e) => {
|
|
2916
|
+
e.stopPropagation();
|
|
2917
|
+
e.preventDefault();
|
|
2918
|
+
});
|
|
2919
|
+
this.wrapper.addEventListener("mousedown", (e) => {
|
|
2920
|
+
e.stopPropagation();
|
|
2921
|
+
e.preventDefault();
|
|
2922
|
+
});
|
|
2923
|
+
this.wrapper.addEventListener("mouseup", (e) => {
|
|
2924
|
+
e.stopPropagation();
|
|
2925
|
+
e.preventDefault();
|
|
2926
|
+
});
|
|
2927
|
+
this.wrapper.addEventListener("mousemove", (e) => {
|
|
2928
|
+
e.stopPropagation();
|
|
2929
|
+
e.preventDefault();
|
|
2930
|
+
});
|
|
2931
|
+
this.wrapper.addEventListener("wheel", (e) => {
|
|
2932
|
+
e.stopPropagation();
|
|
2933
|
+
e.preventDefault();
|
|
2934
|
+
});
|
|
2935
|
+
this.wrapper.addEventListener("keydown", (e) => {
|
|
2936
|
+
e.stopPropagation();
|
|
2937
|
+
e.preventDefault();
|
|
2938
|
+
});
|
|
2939
|
+
this.wrapper.addEventListener("keyup", (e) => {
|
|
2940
|
+
e.stopPropagation();
|
|
2941
|
+
e.preventDefault();
|
|
2942
|
+
});
|
|
2943
|
+
__privateMethod(this, _SimulatorMask_instances, createCursor_fn).call(this);
|
|
2944
|
+
document.body.appendChild(this.wrapper);
|
|
2945
|
+
__privateMethod(this, _SimulatorMask_instances, moveCursorToTarget_fn).call(this);
|
|
2946
|
+
window.addEventListener("PageAgent::MovePointerTo", (event) => {
|
|
2947
|
+
const { x, y } = event.detail;
|
|
2948
|
+
this.setCursorPosition(x, y);
|
|
2949
|
+
});
|
|
2950
|
+
window.addEventListener("PageAgent::ClickPointer", (event) => {
|
|
2951
|
+
this.triggerClickAnimation();
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
setCursorPosition(x, y) {
|
|
2955
|
+
__privateSet(this, _targetCursorX, x);
|
|
2956
|
+
__privateSet(this, _targetCursorY, y);
|
|
2957
|
+
}
|
|
2958
|
+
triggerClickAnimation() {
|
|
2959
|
+
__privateGet(this, _cursor).classList.remove(cursorStyles.clicking);
|
|
2960
|
+
void __privateGet(this, _cursor).offsetHeight;
|
|
2961
|
+
__privateGet(this, _cursor).classList.add(cursorStyles.clicking);
|
|
2962
|
+
}
|
|
2963
|
+
show() {
|
|
2964
|
+
this.motion.start();
|
|
2965
|
+
this.motion.fadeIn();
|
|
2966
|
+
this.wrapper.style.display = "block";
|
|
2967
|
+
__privateSet(this, _currentCursorX, window.innerWidth / 2);
|
|
2968
|
+
__privateSet(this, _currentCursorY, window.innerHeight / 2);
|
|
2969
|
+
__privateSet(this, _targetCursorX, __privateGet(this, _currentCursorX));
|
|
2970
|
+
__privateSet(this, _targetCursorY, __privateGet(this, _currentCursorY));
|
|
2971
|
+
__privateGet(this, _cursor).style.left = `${__privateGet(this, _currentCursorX)}px`;
|
|
2972
|
+
__privateGet(this, _cursor).style.top = `${__privateGet(this, _currentCursorY)}px`;
|
|
2973
|
+
}
|
|
2974
|
+
hide() {
|
|
2975
|
+
this.motion.fadeOut();
|
|
2976
|
+
this.motion.pause();
|
|
2977
|
+
__privateGet(this, _cursor).classList.remove(cursorStyles.clicking);
|
|
2978
|
+
setTimeout(() => {
|
|
2979
|
+
this.wrapper.style.display = "none";
|
|
2980
|
+
}, 800);
|
|
2981
|
+
}
|
|
2982
|
+
dispose() {
|
|
2983
|
+
this.motion.dispose();
|
|
2984
|
+
this.wrapper.remove();
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
_cursor = new WeakMap();
|
|
2988
|
+
_currentCursorX = new WeakMap();
|
|
2989
|
+
_currentCursorY = new WeakMap();
|
|
2990
|
+
_targetCursorX = new WeakMap();
|
|
2991
|
+
_targetCursorY = new WeakMap();
|
|
2992
|
+
_SimulatorMask_instances = new WeakSet();
|
|
2993
|
+
createCursor_fn = /* @__PURE__ */ __name(function() {
|
|
2994
|
+
__privateGet(this, _cursor).className = cursorStyles.cursor;
|
|
2995
|
+
const rippleContainer = document.createElement("div");
|
|
2996
|
+
rippleContainer.className = cursorStyles.cursorRipple;
|
|
2997
|
+
__privateGet(this, _cursor).appendChild(rippleContainer);
|
|
2998
|
+
const fillingLayer = document.createElement("div");
|
|
2999
|
+
fillingLayer.className = cursorStyles.cursorFilling;
|
|
3000
|
+
__privateGet(this, _cursor).appendChild(fillingLayer);
|
|
3001
|
+
const borderLayer = document.createElement("div");
|
|
3002
|
+
borderLayer.className = cursorStyles.cursorBorder;
|
|
3003
|
+
__privateGet(this, _cursor).appendChild(borderLayer);
|
|
3004
|
+
this.wrapper.appendChild(__privateGet(this, _cursor));
|
|
3005
|
+
}, "#createCursor");
|
|
3006
|
+
moveCursorToTarget_fn = /* @__PURE__ */ __name(function() {
|
|
3007
|
+
const newX = __privateGet(this, _currentCursorX) + (__privateGet(this, _targetCursorX) - __privateGet(this, _currentCursorX)) * 0.2;
|
|
3008
|
+
const newY = __privateGet(this, _currentCursorY) + (__privateGet(this, _targetCursorY) - __privateGet(this, _currentCursorY)) * 0.2;
|
|
3009
|
+
const xDistance = Math.abs(newX - __privateGet(this, _targetCursorX));
|
|
3010
|
+
if (xDistance > 0) {
|
|
3011
|
+
if (xDistance < 2) {
|
|
3012
|
+
__privateSet(this, _currentCursorX, __privateGet(this, _targetCursorX));
|
|
3013
|
+
} else {
|
|
3014
|
+
__privateSet(this, _currentCursorX, newX);
|
|
3015
|
+
}
|
|
3016
|
+
__privateGet(this, _cursor).style.left = `${__privateGet(this, _currentCursorX)}px`;
|
|
3017
|
+
}
|
|
3018
|
+
const yDistance = Math.abs(newY - __privateGet(this, _targetCursorY));
|
|
3019
|
+
if (yDistance > 0) {
|
|
3020
|
+
if (yDistance < 2) {
|
|
3021
|
+
__privateSet(this, _currentCursorY, __privateGet(this, _targetCursorY));
|
|
3022
|
+
} else {
|
|
3023
|
+
__privateSet(this, _currentCursorY, newY);
|
|
3024
|
+
}
|
|
3025
|
+
__privateGet(this, _cursor).style.top = `${__privateGet(this, _currentCursorY)}px`;
|
|
3026
|
+
}
|
|
3027
|
+
requestAnimationFrame(() => __privateMethod(this, _SimulatorMask_instances, moveCursorToTarget_fn).call(this));
|
|
3028
|
+
}, "#moveCursorToTarget");
|
|
3029
|
+
__name(_SimulatorMask, "SimulatorMask");
|
|
3030
|
+
let SimulatorMask = _SimulatorMask;
|
|
3031
|
+
const _PageAgent = class _PageAgent extends EventTarget {
|
|
3032
|
+
constructor(config = {}) {
|
|
3033
|
+
super();
|
|
3034
|
+
__privateAdd(this, _PageAgent_instances);
|
|
3035
|
+
__publicField(this, "config");
|
|
3036
|
+
__publicField(this, "id", uid());
|
|
3037
|
+
__publicField(this, "bus", getEventBus(this.id));
|
|
3038
|
+
__publicField(this, "i18n");
|
|
3039
|
+
__publicField(this, "paused", false);
|
|
3040
|
+
__publicField(this, "disposed", false);
|
|
3041
|
+
__publicField(this, "task", "");
|
|
3042
|
+
__privateAdd(this, _llm);
|
|
3043
|
+
__privateAdd(this, _totalWaitTime, 0);
|
|
3044
|
+
__privateAdd(this, _abortController, new AbortController());
|
|
3045
|
+
/** Corresponds to eval_page in browser-use */
|
|
3046
|
+
__publicField(this, "flatTree", null);
|
|
3047
|
+
/**
|
|
3048
|
+
* All highlighted index-mapped interactive elements
|
|
3049
|
+
* Corresponds to DOMState.selector_map in browser-use
|
|
3050
|
+
*/
|
|
3051
|
+
__publicField(this, "selectorMap", /* @__PURE__ */ new Map());
|
|
3052
|
+
/** highlight index -> element text */
|
|
3053
|
+
__publicField(this, "elementTextMap", /* @__PURE__ */ new Map());
|
|
3054
|
+
/** Corresponds to clickable_elements_to_string in browser-use */
|
|
3055
|
+
__publicField(this, "simplifiedHTML", "<EMPTY>");
|
|
3056
|
+
/** last time the tree was updated */
|
|
3057
|
+
__publicField(this, "lastTimeUpdate", 0);
|
|
3058
|
+
/** Corresponds to actions in browser-use */
|
|
3059
|
+
__publicField(this, "tools", new Map(tools));
|
|
3060
|
+
/** Fullscreen mask */
|
|
3061
|
+
__publicField(this, "mask", new SimulatorMask());
|
|
3062
|
+
/** Interactive panel */
|
|
3063
|
+
__publicField(this, "panel", new Panel(this));
|
|
3064
|
+
/** History records */
|
|
3065
|
+
__publicField(this, "history", []);
|
|
3066
|
+
this.config = config;
|
|
3067
|
+
__privateSet(this, _llm, new LLM(this.config, this.id));
|
|
3068
|
+
this.i18n = new I18n(this.config.language);
|
|
3069
|
+
patchReact();
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* @todo maybe return something?
|
|
3073
|
+
*/
|
|
3074
|
+
async execute(task) {
|
|
3075
|
+
if (!task) throw new Error("Task is required");
|
|
3076
|
+
this.task = task;
|
|
3077
|
+
this.mask.show();
|
|
3078
|
+
this.bus.emit("panel:show");
|
|
3079
|
+
this.bus.emit("panel:reset");
|
|
3080
|
+
this.bus.emit("panel:update", {
|
|
3081
|
+
type: "input",
|
|
3082
|
+
displayText: task
|
|
3083
|
+
});
|
|
3084
|
+
if (__privateGet(this, _abortController)) {
|
|
3085
|
+
__privateGet(this, _abortController).abort();
|
|
3086
|
+
__privateSet(this, _abortController, new AbortController());
|
|
3087
|
+
}
|
|
3088
|
+
this.history = [];
|
|
3089
|
+
try {
|
|
3090
|
+
let step = 0;
|
|
3091
|
+
while (true) {
|
|
3092
|
+
console.group(`step: ${step + 1}`);
|
|
3093
|
+
if (__privateGet(this, _abortController).signal.aborted) throw new Error("AbortError");
|
|
3094
|
+
await waitUntil(() => !this.paused);
|
|
3095
|
+
console.log(chalk.blue("Thinking..."));
|
|
3096
|
+
this.bus.emit("panel:update", {
|
|
3097
|
+
type: "thinking",
|
|
3098
|
+
displayText: this.i18n.t("ui.panel.thinking")
|
|
3099
|
+
});
|
|
3100
|
+
const result = await __privateGet(this, _llm).invoke(
|
|
3101
|
+
[
|
|
3102
|
+
{
|
|
3103
|
+
role: "system",
|
|
3104
|
+
content: __privateMethod(this, _PageAgent_instances, getSystemPrompt_fn).call(this)
|
|
3105
|
+
},
|
|
3106
|
+
{
|
|
3107
|
+
role: "user",
|
|
3108
|
+
content: __privateMethod(this, _PageAgent_instances, assembleUserPrompt_fn).call(this)
|
|
3109
|
+
}
|
|
3110
|
+
],
|
|
3111
|
+
// tools,
|
|
3112
|
+
__privateMethod(this, _PageAgent_instances, packMacroTool_fn).call(this),
|
|
3113
|
+
__privateGet(this, _abortController).signal
|
|
3114
|
+
);
|
|
3115
|
+
const toolResult = result.toolResult;
|
|
3116
|
+
const input2 = toolResult.input;
|
|
3117
|
+
const output2 = toolResult.output;
|
|
3118
|
+
const brain = {
|
|
3119
|
+
thinking: input2.thinking,
|
|
3120
|
+
evaluation_previous_goal: input2.evaluation_previous_goal,
|
|
3121
|
+
memory: input2.memory,
|
|
3122
|
+
next_goal: input2.next_goal
|
|
3123
|
+
};
|
|
3124
|
+
const actionName = Object.keys(input2.action)[0];
|
|
3125
|
+
const action = {
|
|
3126
|
+
name: actionName,
|
|
3127
|
+
input: input2.action[actionName],
|
|
3128
|
+
output: output2
|
|
3129
|
+
};
|
|
3130
|
+
this.history.push({
|
|
3131
|
+
brain,
|
|
3132
|
+
action,
|
|
3133
|
+
usage: result.usage
|
|
3134
|
+
});
|
|
3135
|
+
console.log(chalk.green("Step finished:"), actionName);
|
|
3136
|
+
console.groupEnd();
|
|
3137
|
+
step++;
|
|
3138
|
+
if (step > MAX_STEPS) {
|
|
3139
|
+
__privateMethod(this, _PageAgent_instances, onDone_fn).call(this, "Step count exceeded maximum limit", false);
|
|
3140
|
+
return {
|
|
3141
|
+
success: false,
|
|
3142
|
+
data: "Step count exceeded maximum limit",
|
|
3143
|
+
history: this.history
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
if (actionName === "done") {
|
|
3147
|
+
const success = action.input.success || false;
|
|
3148
|
+
const text = action.input.text || "no text provided";
|
|
3149
|
+
console.log(chalk.green.bold("Task completed"), success, text);
|
|
3150
|
+
__privateMethod(this, _PageAgent_instances, onDone_fn).call(this, text, success);
|
|
3151
|
+
return {
|
|
3152
|
+
success,
|
|
3153
|
+
data: text,
|
|
3154
|
+
history: this.history
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
} catch (error2) {
|
|
3159
|
+
console.error("Task failed", error2);
|
|
3160
|
+
__privateMethod(this, _PageAgent_instances, onDone_fn).call(this, String(error2), false);
|
|
3161
|
+
return {
|
|
3162
|
+
success: false,
|
|
3163
|
+
data: String(error2),
|
|
3164
|
+
history: this.history
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
dispose() {
|
|
3169
|
+
console.log("Disposing PageAgent...");
|
|
3170
|
+
this.disposed = true;
|
|
3171
|
+
cleanUpHighlights();
|
|
3172
|
+
this.flatTree = null;
|
|
3173
|
+
this.selectorMap.clear();
|
|
3174
|
+
this.elementTextMap.clear();
|
|
3175
|
+
this.panel.dispose();
|
|
3176
|
+
this.mask.dispose();
|
|
3177
|
+
this.history = [];
|
|
3178
|
+
__privateGet(this, _abortController).abort("PageAgent disposed");
|
|
3179
|
+
}
|
|
3180
|
+
};
|
|
3181
|
+
_llm = new WeakMap();
|
|
3182
|
+
_totalWaitTime = new WeakMap();
|
|
3183
|
+
_abortController = new WeakMap();
|
|
3184
|
+
_PageAgent_instances = new WeakSet();
|
|
3185
|
+
/**
|
|
3186
|
+
* Merge all tools into a single MacroTool with the following input:
|
|
3187
|
+
* - thinking: string
|
|
3188
|
+
* - evaluation_previous_goal: string
|
|
3189
|
+
* - memory: string
|
|
3190
|
+
* - next_goal: string
|
|
3191
|
+
* - action: { toolName: toolInput }
|
|
3192
|
+
* where action must be selected from tools defined in this.tools
|
|
3193
|
+
*
|
|
3194
|
+
* @topic 要不要合并成一个 tool?
|
|
3195
|
+
* @facts
|
|
3196
|
+
* - 我们需要模型每步返回 evaluation/memory/goal 等思考过程
|
|
3197
|
+
* - browser use 合并成一个巨大的 tool
|
|
3198
|
+
* ```json
|
|
3199
|
+
* {
|
|
3200
|
+
* "memory": "...",
|
|
3201
|
+
* "goal": "...",
|
|
3202
|
+
* "actions": [
|
|
3203
|
+
* {
|
|
3204
|
+
* "name": "...",
|
|
3205
|
+
* "args": "..."
|
|
3206
|
+
* }
|
|
3207
|
+
* // ...
|
|
3208
|
+
* ]
|
|
3209
|
+
* }
|
|
3210
|
+
* ```
|
|
3211
|
+
* - qwen 目前必须指定 function name 来确保 tool call
|
|
3212
|
+
* @reasoning
|
|
3213
|
+
* - 不能为了 qwen 的缺陷而设计系统
|
|
3214
|
+
* - 更复杂的 tool 更容易出错
|
|
3215
|
+
* - 分散的 tool 更容易利用 ai-sdk 的重试机制,也更容易处理错误
|
|
3216
|
+
* - 不能用额外的步骤生成这些数据,不仅性能过差,而且 goal 之类的必须和 call 一起生成
|
|
3217
|
+
* @options
|
|
3218
|
+
* - Plan @A
|
|
3219
|
+
* - 和 browser use 使用完全一致的做法,合并成一个大 tool,要求每次调用
|
|
3220
|
+
* - 会把 tool 定义变得非常复杂,增加出错率
|
|
3221
|
+
* - Plan @B
|
|
3222
|
+
* - 每次调用两个 tool,其中一个用来输出思考
|
|
3223
|
+
* - 很难用提示词 enforce 这么复杂的规则
|
|
3224
|
+
* - Plan @C
|
|
3225
|
+
* - 自动为每个 tool 增加固定的 reasoning/memory/goal 等输入,并自动拦截提取这些数据
|
|
3226
|
+
* - 会让 tool 定义变得很长
|
|
3227
|
+
* @conclusion
|
|
3228
|
+
* - 使用 @A
|
|
3229
|
+
*/
|
|
3230
|
+
packMacroTool_fn = /* @__PURE__ */ __name(function() {
|
|
3231
|
+
const tools2 = this.tools;
|
|
3232
|
+
const actionSchemas = Array.from(tools2.entries()).map(([toolName, tool2]) => {
|
|
3233
|
+
return zod.object({
|
|
3234
|
+
[toolName]: tool2.inputSchema
|
|
3235
|
+
});
|
|
3236
|
+
});
|
|
3237
|
+
const actionSchema = zod.union(actionSchemas);
|
|
3238
|
+
return {
|
|
3239
|
+
[MACRO_TOOL_NAME]: tool({
|
|
3240
|
+
// description: 'Output the result of the agent',
|
|
3241
|
+
inputSchema: zod.object({
|
|
3242
|
+
// thinking: zod.string().optional(),
|
|
3243
|
+
evaluation_previous_goal: zod.string().optional(),
|
|
3244
|
+
memory: zod.string().optional(),
|
|
3245
|
+
next_goal: zod.string().optional(),
|
|
3246
|
+
action: actionSchema
|
|
3247
|
+
}),
|
|
3248
|
+
execute: /* @__PURE__ */ __name(async (input2, options) => {
|
|
3249
|
+
if (__privateGet(this, _abortController).signal.aborted) throw new Error("AbortError");
|
|
3250
|
+
await waitUntil(() => !this.paused);
|
|
3251
|
+
console.log(chalk.blue.bold("MacroTool execute"), input2);
|
|
3252
|
+
const action = input2.action;
|
|
3253
|
+
const toolName = Object.keys(action)[0];
|
|
3254
|
+
const toolInput = action[toolName];
|
|
3255
|
+
const brain = trimLines(`✅: ${input2.evaluation_previous_goal}
|
|
3256
|
+
💾: ${input2.memory}
|
|
3257
|
+
🎯: ${input2.next_goal}
|
|
3258
|
+
`);
|
|
3259
|
+
console.log(brain);
|
|
3260
|
+
this.bus.emit("panel:update", {
|
|
3261
|
+
type: "thinking",
|
|
3262
|
+
displayText: brain
|
|
3263
|
+
});
|
|
3264
|
+
const tool2 = tools2.get(toolName);
|
|
3265
|
+
assert(tool2, `Tool ${toolName} not found. (@note should have been caught before this!!!)`);
|
|
3266
|
+
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput, options);
|
|
3267
|
+
this.bus.emit("panel:update", {
|
|
3268
|
+
type: "tool_executing",
|
|
3269
|
+
toolName,
|
|
3270
|
+
toolArgs: toolInput,
|
|
3271
|
+
displayText: getToolExecutingText(toolName, toolInput, this.i18n)
|
|
3272
|
+
});
|
|
3273
|
+
const startTime = Date.now();
|
|
3274
|
+
let result = await tool2.execute.bind(this)(toolInput, options);
|
|
3275
|
+
const duration = Date.now() - startTime;
|
|
3276
|
+
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result);
|
|
3277
|
+
if (toolName === "wait") {
|
|
3278
|
+
__privateSet(this, _totalWaitTime, __privateGet(this, _totalWaitTime) + Math.round(toolInput.seconds + duration / 1e3));
|
|
3279
|
+
result += `
|
|
3280
|
+
<sys> You have waited ${__privateGet(this, _totalWaitTime)} seconds accumulatively.`;
|
|
3281
|
+
if (__privateGet(this, _totalWaitTime) >= 3)
|
|
3282
|
+
result += "\nDo NOT wait any longer unless you have a good reason.\n";
|
|
3283
|
+
result += "</sys>";
|
|
3284
|
+
} else {
|
|
3285
|
+
__privateSet(this, _totalWaitTime, 0);
|
|
3286
|
+
}
|
|
3287
|
+
const displayResult = getToolCompletedText(toolName, toolInput, this.i18n);
|
|
3288
|
+
if (displayResult)
|
|
3289
|
+
this.bus.emit("panel:update", {
|
|
3290
|
+
type: "tool_executing",
|
|
3291
|
+
toolName,
|
|
3292
|
+
toolArgs: toolInput,
|
|
3293
|
+
toolResult: result,
|
|
3294
|
+
displayText: displayResult,
|
|
3295
|
+
duration
|
|
3296
|
+
});
|
|
3297
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3298
|
+
return result;
|
|
3299
|
+
}, "execute")
|
|
3300
|
+
})
|
|
3301
|
+
};
|
|
3302
|
+
}, "#packMacroTool");
|
|
3303
|
+
/**
|
|
3304
|
+
* Get system prompt, dynamically replace language settings based on configured language
|
|
3305
|
+
*/
|
|
3306
|
+
getSystemPrompt_fn = /* @__PURE__ */ __name(function() {
|
|
3307
|
+
let systemPrompt = SYSTEM_PROMPT;
|
|
3308
|
+
const targetLanguage = this.config.language === "zh-CN" ? "中文" : "English";
|
|
3309
|
+
systemPrompt = systemPrompt.replace(
|
|
3310
|
+
/Default working language: \*\*.*?\*\*/,
|
|
3311
|
+
`Default working language: **${targetLanguage}**`
|
|
3312
|
+
);
|
|
3313
|
+
return systemPrompt;
|
|
3314
|
+
}, "#getSystemPrompt");
|
|
3315
|
+
assembleUserPrompt_fn = /* @__PURE__ */ __name(function() {
|
|
3316
|
+
let prompt = "";
|
|
3317
|
+
prompt += "<agent_history>\n";
|
|
3318
|
+
this.history.forEach((history, index) => {
|
|
3319
|
+
prompt += `<step_${index + 1}>
|
|
3320
|
+
Evaluation of Previous Step: ${history.brain.evaluation_previous_goal}
|
|
3321
|
+
Memory: ${history.brain.memory}
|
|
3322
|
+
Next Goal: ${history.brain.next_goal}
|
|
3323
|
+
Action Results: ${history.action.output}
|
|
3324
|
+
</step_${index + 1}>
|
|
3325
|
+
`;
|
|
3326
|
+
});
|
|
3327
|
+
prompt += "</agent_history>\n\n";
|
|
3328
|
+
prompt += `<agent_state>
|
|
3329
|
+
<user_request>
|
|
3330
|
+
${this.task}
|
|
3331
|
+
</user_request>
|
|
3332
|
+
<step_info>
|
|
3333
|
+
Step ${this.history.length + 1} of ${MAX_STEPS} max possible steps
|
|
3334
|
+
Current date and time: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3335
|
+
</step_info>
|
|
3336
|
+
</agent_state>
|
|
3337
|
+
`;
|
|
3338
|
+
prompt += __privateMethod(this, _PageAgent_instances, getBrowserState_fn).call(this);
|
|
3339
|
+
return trimLines(prompt);
|
|
3340
|
+
}, "#assembleUserPrompt");
|
|
3341
|
+
onDone_fn = /* @__PURE__ */ __name(function(text, success = true) {
|
|
3342
|
+
cleanUpHighlights();
|
|
3343
|
+
this.bus.emit("panel:update", {
|
|
3344
|
+
type: success ? "output" : "error",
|
|
3345
|
+
displayText: text
|
|
3346
|
+
});
|
|
3347
|
+
this.bus.emit("panel:update", {
|
|
3348
|
+
type: "completed",
|
|
3349
|
+
displayText: this.i18n.t("ui.panel.taskCompleted")
|
|
3350
|
+
});
|
|
3351
|
+
this.mask.hide();
|
|
3352
|
+
__privateGet(this, _abortController).abort();
|
|
3353
|
+
}, "#onDone");
|
|
3354
|
+
getBrowserState_fn = /* @__PURE__ */ __name(function() {
|
|
3355
|
+
const pageUrl = window.location.href;
|
|
3356
|
+
const pageTitle = document.title;
|
|
3357
|
+
const pi = getPageInfo();
|
|
3358
|
+
__privateMethod(this, _PageAgent_instances, updateTree_fn).call(this);
|
|
3359
|
+
let prompt = trimLines(`<browser_state>
|
|
3360
|
+
Current Page: [${pageTitle}](${pageUrl})
|
|
3361
|
+
|
|
3362
|
+
Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page
|
|
3363
|
+
|
|
3364
|
+
${"Interactive elements from top layer of the current page (full page):"}
|
|
3365
|
+
|
|
3366
|
+
`);
|
|
3367
|
+
{
|
|
3368
|
+
prompt += `[Start of page]
|
|
3369
|
+
`;
|
|
3370
|
+
}
|
|
3371
|
+
prompt += this.simplifiedHTML;
|
|
3372
|
+
prompt += `
|
|
3373
|
+
`;
|
|
3374
|
+
{
|
|
3375
|
+
prompt += `[End of page]
|
|
3376
|
+
`;
|
|
3377
|
+
}
|
|
3378
|
+
prompt += `</browser_state>
|
|
3379
|
+
`;
|
|
3380
|
+
return prompt;
|
|
3381
|
+
}, "#getBrowserState");
|
|
3382
|
+
/**
|
|
3383
|
+
* Update document tree
|
|
3384
|
+
*/
|
|
3385
|
+
updateTree_fn = /* @__PURE__ */ __name(function() {
|
|
3386
|
+
this.dispatchEvent(new Event("beforeUpdate"));
|
|
3387
|
+
this.lastTimeUpdate = Date.now();
|
|
3388
|
+
cleanUpHighlights();
|
|
3389
|
+
this.mask.wrapper.style.pointerEvents = "none";
|
|
3390
|
+
this.flatTree = getFlatTree({
|
|
3391
|
+
...this.config,
|
|
3392
|
+
interactiveBlacklist: [
|
|
3393
|
+
...this.config.interactiveBlacklist || [],
|
|
3394
|
+
...document.querySelectorAll("[data-page-agent-not-interactive]").values()
|
|
3395
|
+
]
|
|
3396
|
+
});
|
|
3397
|
+
this.mask.wrapper.style.pointerEvents = "auto";
|
|
3398
|
+
this.simplifiedHTML = flatTreeToString(this.flatTree, this.config.include_attributes);
|
|
3399
|
+
this.selectorMap.clear();
|
|
3400
|
+
this.selectorMap = getSelectorMap(this.flatTree);
|
|
3401
|
+
this.elementTextMap.clear();
|
|
3402
|
+
this.elementTextMap = getElementTextMap(this.simplifiedHTML);
|
|
3403
|
+
this.dispatchEvent(new Event("afterUpdate"));
|
|
3404
|
+
}, "#updateTree");
|
|
3405
|
+
__name(_PageAgent, "PageAgent");
|
|
3406
|
+
let PageAgent = _PageAgent;
|
|
3407
|
+
export {
|
|
3408
|
+
PageAgent
|
|
3409
|
+
};
|
|
3410
|
+
//# sourceMappingURL=page-agent.js.map
|