@coolkiller007/my-page-agent 0.1.13 → 0.1.14
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/README.md +0 -1
- package/dist/index.d.ts +225 -196
- package/dist/index.js +422 -291
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("._wrapper_ur79v_1 {\n position: fixed;\n inset: 0;\n z-index: 2147483641; /* 确保在所有元素之上,除了 Agent UI */\n cursor: wait;\n overflow: hidden;\n\n display: none;\n}\n\n._wrapper_ur79v_1._visible_ur79v_11 {\n display: block;\n}\n/* AI 光标样式 */\n._cursor_eh09s_2 {\n position: absolute;\n width: var(--cursor-size, 48px);\n height: var(--cursor-size, 48px);\n pointer-events: none;\n z-index: 10000;\n}\n\n._cursorBorder_eh09s_10 {\n position: absolute;\n width: 100%;\n height: 100%;\n background: linear-gradient(45deg, rgb(57, 182, 255), rgb(189, 69, 251));\n mask-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%20fill='none'%3e%3cpath%20d='M287.232%20128l514.752%20387.008L574.912%20576l149.376%20299.52-103.04%2051.392L472.32%20628.544l-185.856%20143.424z'%20stroke='%23000000'%20stroke-width='56'%20stroke-linejoin='miter'/%3e%3c/svg%3e\");\n mask-size: 100% 100%;\n mask-repeat: no-repeat;\n margin-left: -16px;\n margin-top: -7px;\n}\n\n._cursorFilling_eh09s_22 {\n position: absolute;\n width: 100%;\n height: 100%;\n background: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%3e%3cpath%20d='M287.232%20128l514.752%20387.008L574.912%20576l149.376%20299.52-103.04%2051.392L472.32%20628.544l-185.856%20143.424z'%20fill='%23ffffff'/%3e%3c/svg%3e\");\n background-size: 100% 100%;\n background-repeat: no-repeat;\n margin-left: -16px;\n margin-top: -7px;\n}\n\n._cursorRipple_eh09s_33 {\n position: absolute;\n width: 100%;\n height: 100%;\n pointer-events: none;\n margin-left: -50%;\n margin-top: -50%;\n\n &::after {\n content: '';\n opacity: 0;\n position: absolute;\n inset: 0;\n border: 4px solid rgba(57, 182, 255, 1);\n border-radius: 50%;\n }\n}\n\n._cursor_eh09s_2._clicking_eh09s_51 ._cursorRipple_eh09s_33::after {\n animation: _cursor-ripple_eh09s_1 300ms ease-out forwards;\n}\n\n@keyframes _cursor-ripple_eh09s_1 {\n 0% {\n transform: scale(0);\n opacity: 1;\n }\n 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n._wrapper_h452n_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 /* 普通流布局:历史弹窗、面板依次纵向排列,靠子元素自身尺寸撑开,\n * 不再用 position:absolute + 手算偏移量拼位置(这套算法之前连续出过两次 bug:\n * 弹窗被状态条盖住、状态条运行态时下方角没跟着圆)。 */\n display: flex;\n flex-direction: column;\n align-items: center;\n\n * {\n box-sizing: border-box;\n }\n\n /* 宽度取窗口 40%,用 clamp 防止超窄/超宽窗口下过小或过大 */\n --width: clamp(280px, 40vw, 640px);\n --height: 40px; /* 状态条高度 */\n --input-height: 80px; /* 输入区高度 */\n --border-radius: 16px;\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\n transition:\n opacity 0.3s ease-in-out,\n transform 0.3s ease-in-out;\n\n /* 响应式设计 */\n @media (max-width: 480px) {\n width: calc(100vw - 40px);\n --width: calc(100vw - 40px);\n }\n}\n\n@keyframes _mask-running_h452n_1 {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(100%);\n }\n}\n\n/* 状态条 + 输入区的面板容器;.background 光效挂在这一层,.panelInner 负责裁出圆角 */\n._panel_h452n_57 {\n position: relative;\n width: 100%;\n}\n\n._background_h452n_57 {\n position: absolute;\n inset: -2px;\n border-radius: calc(var(--border-radius) + 4px);\n filter: blur(4px);\n overflow: hidden;\n pointer-events: none;\n\n &::before {\n content: '';\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-1),\n var(--color-2),\n var(--color-1)\n );\n animation: _mask-running_h452n_1 2s linear infinite;\n }\n &::after {\n content: '';\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_h452n_1 2s linear infinite;\n animation-delay: 1s;\n }\n}\n\n/* 面板可视区域:靠 overflow:hidden + border-radius 统一裁角,\n * header 单独存在(运行中输入区隐藏)或 header+输入区一起存在时,四角都自动是圆的,\n * 不用再像以前那样给 header 写一条\"输入区隐藏时补圆角\"的特判规则。 */\n._panelInner_h452n_57 {\n position: relative;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border-radius: var(--border-radius);\n border: 2px solid rgba(255, 255, 255, 0.4);\n box-shadow:\n 0 0 5px 1px rgba(255, 255, 255, 0.3),\n 0 4px 20px rgba(0, 0, 0, 0.5);\n}\n\n/* 控制栏 */\n._header_h452n_124 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n user-select: none;\n\n height: var(--height);\n flex-shrink: 0; /* 防止 header 被压缩 */\n\n cursor: pointer;\n\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(10px);\n background-clip: padding-box;\n\n ._statusSection_h452n_140 {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-height: 24px; /* 确保垂直居中 */\n\n ._indicator_h452n_147 {\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_h452n_156 {\n background: rgb(57, 182, 255);\n animation: _pulse_h452n_1 0.8s ease-in-out infinite;\n }\n\n &._tool_executing_h452n_161 {\n background: rgb(189, 69, 251);\n animation: _pulse_h452n_1 0.6s ease-in-out infinite;\n }\n\n &._retry_h452n_166 {\n background: rgb(255, 214, 0);\n animation: _retryPulse_h452n_1 1s ease-in-out infinite;\n }\n\n /* 静止状态 - 无动画 */\n &._completed_h452n_172,\n &._input_h452n_173,\n &._output_h452n_174 {\n background: rgb(34, 197, 94);\n animation: none;\n }\n\n &._error_h452n_179 {\n background: rgb(239, 68, 68);\n animation: none;\n }\n }\n\n ._statusText_h452n_185 {\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_h452n_197 {\n animation: _statusTextFadeOut_h452n_1 0.3s ease forwards;\n }\n\n &._fadeIn_h452n_201 {\n animation: _statusTextFadeIn_h452n_1 0.3s ease forwards;\n }\n }\n }\n\n ._controls_h452n_207 {\n display: flex;\n align-items: center;\n gap: 4px;\n\n ._controlButton_h452n_212 {\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 ._stopButton_h452n_231 {\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_h452n_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_h452n_1 {\n 0% {\n opacity: 1;\n transform: translateY(0);\n }\n 100% {\n opacity: 0;\n transform: translateY(-5px);\n }\n}\n\n/* 历史记录弹窗:普通流里排在面板前面(视觉上在上方),默认收起为 0 高度,\n * 展开时靠 JS 量出的真实内容高度做 max-height 动画(不用固定封顶值),\n * 撑开多高、要不要出现,跟面板本身完全无关,不用再算\"面板一共多高、弹窗该挂在哪个 bottom 偏移\"。 */\n._historySectionWrapper_h452n_268 {\n width: 100%;\n overflow: hidden;\n max-height: 0;\n margin-bottom: 0;\n\n background: rgba(2, 0, 20, 0.5);\n backdrop-filter: blur(10px);\n\n text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);\n\n border-radius: calc(var(--border-radius) + 4px);\n border: 0 solid rgba(255, 255, 255, 0);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0);\n\n transition:\n max-height 0.2s,\n margin-bottom 0.2s,\n border-width 0.2s,\n border-color 0.2s,\n box-shadow 0.2s;\n\n ._wrapper_h452n_1._expanded_h452n_290 & {\n margin-bottom: 8px;\n border-width: 2px;\n border-color: rgba(255, 255, 255, 0.4);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);\n }\n\n ._historySection_h452n_268 {\n max-height: min(\n 500px,\n calc(100vh - 200px - var(--height))\n ); /* 列表本身的滚动上限,跟展开/收起动画解耦 */\n overflow-y: auto;\n overscroll-behavior: contain;\n scrollbar-width: none;\n /* 底部留白用 padding 撑,不依赖最后一项 margin-bottom:\n * overflow:auto 容器会裁掉末尾子元素的 margin,导致最后一条记录贴底 */\n padding: 8px;\n\n ._historyItem_h452n_309 {\n /* backdrop-filter: blur(10px); */\n padding: 8px 10px;\n margin-bottom: 8px;\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 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 &._completed_h452n_172,\n &._input_h452n_173,\n &._output_h452n_174 {\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_h452n_179 {\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_h452n_166 {\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 &._observation_h452n_363 {\n border-left-color: rgb(147, 51, 234);\n background: linear-gradient(135deg, rgba(147, 51, 234, 0.1), rgba(147, 51, 234, 0.05));\n }\n\n &._question_h452n_368 {\n border-left-color: rgb(255, 159, 67);\n background: linear-gradient(135deg, rgba(255, 159, 67, 0.15), rgba(255, 159, 67, 0.08));\n }\n\n /* 突出显示 done 成功结果 */\n &._doneSuccess_h452n_374 {\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: 2px 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_h452n_1 2s ease-in-out infinite;\n }\n\n ._historyContent_h452n_410 {\n ._statusIcon_h452n_411 {\n font-size: 16px;\n animation: _celebrate_h452n_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_h452n_420 {\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: 2px 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_h452n_410 {\n ._statusIcon_h452n_411 {\n font-size: 16px;\n filter: drop-shadow(0 2px 4px rgba(239, 68, 68, 0.5));\n }\n }\n }\n\n ._historyContent_h452n_410 {\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_h452n_411 {\n font-size: 12px;\n flex-shrink: 0;\n line-height: 1;\n transition: all 0.3s ease;\n }\n\n ._reflectionLines_h452n_470 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n }\n\n ._historyMeta_h452n_477 {\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_h452n_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_h452n_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_h452n_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_h452n_1 {\n 0% {\n left: -100%;\n }\n 100% {\n left: 100%;\n }\n}\n\n/* 输入区域样式 */\n._inputSectionWrapper_h452n_547 {\n width: 100%;\n margin: 0 auto;\n\n overflow: hidden;\n height: var(--input-height);\n\n transition: height 0.2s;\n\n /* 与 header 保持同色调,让面板看起来是一体的深色玻璃卡片 */\n background: rgba(0, 0, 0, 0.4);\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 &._hidden_h452n_563 {\n height: 0;\n }\n\n ._inputSection_h452n_547 {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 100%;\n padding: 8px; /* 与上方状态条 padding 保持一致 */\n\n ._taskInput_h452n_574 {\n flex: 1;\n height: 100%;\n background: rgba(255, 255, 255, 0.08);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 12px;\n padding: 8px 14px;\n color: rgb(240, 240, 245);\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n outline: none;\n resize: none;\n overflow-y: auto;\n transition: all 0.2s ease;\n\n &::placeholder {\n color: rgba(255, 255, 255, 0.45);\n }\n\n &:focus {\n background: rgba(255, 255, 255, 0.14);\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/*$vite$:1*/")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
|
|
1
|
+
(function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("._wrapper_ur79v_1 {\n position: fixed;\n inset: 0;\n z-index: 2147483641; /* 确保在所有元素之上,除了 Agent UI */\n cursor: wait;\n overflow: hidden;\n\n display: none;\n}\n\n._wrapper_ur79v_1._visible_ur79v_11 {\n display: block;\n}\n/* AI 光标样式 */\n._cursor_eh09s_2 {\n position: absolute;\n width: var(--cursor-size, 48px);\n height: var(--cursor-size, 48px);\n pointer-events: none;\n z-index: 10000;\n}\n\n._cursorBorder_eh09s_10 {\n position: absolute;\n width: 100%;\n height: 100%;\n background: linear-gradient(45deg, rgb(57, 182, 255), rgb(189, 69, 251));\n mask-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%20fill='none'%3e%3cpath%20d='M287.232%20128l514.752%20387.008L574.912%20576l149.376%20299.52-103.04%2051.392L472.32%20628.544l-185.856%20143.424z'%20stroke='%23000000'%20stroke-width='56'%20stroke-linejoin='miter'/%3e%3c/svg%3e\");\n mask-size: 100% 100%;\n mask-repeat: no-repeat;\n margin-left: -16px;\n margin-top: -7px;\n}\n\n._cursorFilling_eh09s_22 {\n position: absolute;\n width: 100%;\n height: 100%;\n background: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%3e%3cpath%20d='M287.232%20128l514.752%20387.008L574.912%20576l149.376%20299.52-103.04%2051.392L472.32%20628.544l-185.856%20143.424z'%20fill='%23ffffff'/%3e%3c/svg%3e\");\n background-size: 100% 100%;\n background-repeat: no-repeat;\n margin-left: -16px;\n margin-top: -7px;\n}\n\n._cursorRipple_eh09s_33 {\n position: absolute;\n width: 100%;\n height: 100%;\n pointer-events: none;\n margin-left: -50%;\n margin-top: -50%;\n\n &::after {\n content: '';\n opacity: 0;\n position: absolute;\n inset: 0;\n border: 4px solid rgba(57, 182, 255, 1);\n border-radius: 50%;\n }\n}\n\n._cursor_eh09s_2._clicking_eh09s_51 ._cursorRipple_eh09s_33::after {\n animation: _cursor-ripple_eh09s_1 300ms ease-out forwards;\n}\n\n@keyframes _cursor-ripple_eh09s_1 {\n 0% {\n transform: scale(0);\n opacity: 1;\n }\n 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n._wrapper_18lru_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 /* 普通流布局:历史弹窗、面板依次纵向排列,靠子元素自身尺寸撑开,\n * 不再用 position:absolute + 手算偏移量拼位置(这套算法之前连续出过两次 bug:\n * 弹窗被状态条盖住、状态条运行态时下方角没跟着圆)。 */\n display: flex;\n flex-direction: column;\n align-items: center;\n\n * {\n box-sizing: border-box;\n }\n\n /* 宽度取窗口 40%,用 clamp 防止超窄/超宽窗口下过小或过大 */\n --width: clamp(280px, 40vw, 640px);\n --height: 40px; /* 状态条高度 */\n --input-height: 80px; /* 输入区高度 */\n --border-radius: 16px;\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\n transition:\n opacity 0.3s ease-in-out,\n transform 0.3s ease-in-out;\n\n /* 响应式设计 */\n @media (max-width: 480px) {\n width: calc(100vw - 40px);\n --width: calc(100vw - 40px);\n }\n}\n\n@keyframes _mask-running_18lru_1 {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(100%);\n }\n}\n\n/* 状态条 + 输入区的外层容器;.background 光效挂在这一层,.shellInner 负责裁出圆角 */\n._shell_18lru_57 {\n position: relative;\n width: 100%;\n}\n\n._background_18lru_57 {\n position: absolute;\n inset: -2px;\n border-radius: calc(var(--border-radius) + 4px);\n filter: blur(4px);\n overflow: hidden;\n pointer-events: none;\n\n &::before {\n content: '';\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-1),\n var(--color-2),\n var(--color-1)\n );\n animation: _mask-running_18lru_1 2s linear infinite;\n }\n &::after {\n content: '';\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_18lru_1 2s linear infinite;\n animation-delay: 1s;\n }\n}\n\n/* 可视区域:靠 overflow:hidden + border-radius 统一裁角,\n * header 单独存在(运行中输入区隐藏)或 header+输入区一起存在时,四角都自动是圆的,\n * 不用再像以前那样给 header 写一条\"输入区隐藏时补圆角\"的特判规则。 */\n._shellInner_18lru_57 {\n position: relative;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border-radius: var(--border-radius);\n border: 2px solid rgba(255, 255, 255, 0.4);\n box-shadow:\n 0 0 5px 1px rgba(255, 255, 255, 0.3),\n 0 4px 20px rgba(0, 0, 0, 0.5);\n}\n\n/* 控制栏 */\n._header_18lru_124 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n user-select: none;\n\n height: var(--height);\n flex-shrink: 0; /* 防止 header 被压缩 */\n\n cursor: pointer;\n\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(10px);\n background-clip: padding-box;\n\n ._statusSection_18lru_140 {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-height: 24px; /* 确保垂直居中 */\n\n ._indicator_18lru_147 {\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_18lru_156 {\n background: rgb(57, 182, 255);\n animation: _pulse_18lru_1 0.8s ease-in-out infinite;\n }\n\n &._tool_executing_18lru_161 {\n background: rgb(189, 69, 251);\n animation: _pulse_18lru_1 0.6s ease-in-out infinite;\n }\n\n &._retry_18lru_166 {\n background: rgb(255, 214, 0);\n animation: _retryPulse_18lru_1 1s ease-in-out infinite;\n }\n\n /* 静止状态 - 无动画 */\n &._completed_18lru_172,\n &._input_18lru_173,\n &._output_18lru_174 {\n background: rgb(34, 197, 94);\n animation: none;\n }\n\n &._error_18lru_179 {\n background: rgb(239, 68, 68);\n animation: none;\n }\n }\n\n ._statusText_18lru_185 {\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_18lru_197 {\n animation: _statusTextFadeOut_18lru_1 0.3s ease forwards;\n }\n\n &._fadeIn_18lru_201 {\n animation: _statusTextFadeIn_18lru_1 0.3s ease forwards;\n }\n }\n }\n\n ._controls_18lru_207 {\n display: flex;\n align-items: center;\n gap: 4px;\n\n ._controlButton_18lru_212 {\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 ._stopButton_18lru_231 {\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_18lru_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_18lru_1 {\n 0% {\n opacity: 1;\n transform: translateY(0);\n }\n 100% {\n opacity: 0;\n transform: translateY(-5px);\n }\n}\n\n/* 历史记录弹窗:普通流里排在面板前面(视觉上在上方),默认收起为 0 高度,\n * 展开时靠 JS 量出的真实内容高度做 max-height 动画(不用固定封顶值),\n * 撑开多高、要不要出现,跟面板本身完全无关,不用再算\"面板一共多高、弹窗该挂在哪个 bottom 偏移\"。 */\n._historySectionWrapper_18lru_268 {\n width: 100%;\n overflow: hidden;\n max-height: 0;\n margin-bottom: 0;\n\n background: rgba(2, 0, 20, 0.5);\n backdrop-filter: blur(10px);\n\n text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);\n\n border-radius: calc(var(--border-radius) + 4px);\n border: 0 solid rgba(255, 255, 255, 0);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0);\n\n transition:\n max-height 0.2s,\n margin-bottom 0.2s,\n border-width 0.2s,\n border-color 0.2s,\n box-shadow 0.2s;\n\n ._wrapper_18lru_1._expanded_18lru_290 & {\n margin-bottom: 8px;\n border-width: 2px;\n border-color: rgba(255, 255, 255, 0.4);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);\n }\n\n ._historySection_18lru_268 {\n max-height: min(\n 500px,\n calc(100vh - 200px - var(--height))\n ); /* 列表本身的滚动上限,跟展开/收起动画解耦 */\n overflow-y: auto;\n overscroll-behavior: contain;\n scrollbar-width: none;\n /* 底部留白用 padding 撑,不依赖最后一项 margin-bottom:\n * overflow:auto 容器会裁掉末尾子元素的 margin,导致最后一条记录贴底 */\n padding: 8px;\n\n ._historyItem_18lru_309 {\n /* backdrop-filter: blur(10px); */\n padding: 8px 10px;\n margin-bottom: 8px;\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 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 &._completed_18lru_172,\n &._input_18lru_173,\n &._output_18lru_174 {\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_18lru_179 {\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_18lru_166 {\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 &._observation_18lru_363 {\n border-left-color: rgb(147, 51, 234);\n background: linear-gradient(135deg, rgba(147, 51, 234, 0.1), rgba(147, 51, 234, 0.05));\n }\n\n &._question_18lru_368 {\n border-left-color: rgb(255, 159, 67);\n background: linear-gradient(135deg, rgba(255, 159, 67, 0.15), rgba(255, 159, 67, 0.08));\n }\n\n /* 突出显示 done 成功结果 */\n &._doneSuccess_18lru_374 {\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: 2px 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_18lru_1 2s ease-in-out infinite;\n }\n\n ._historyContent_18lru_410 {\n ._statusIcon_18lru_411 {\n font-size: 16px;\n animation: _celebrate_18lru_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_18lru_420 {\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: 2px 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_18lru_410 {\n ._statusIcon_18lru_411 {\n font-size: 16px;\n filter: drop-shadow(0 2px 4px rgba(239, 68, 68, 0.5));\n }\n }\n }\n\n ._historyContent_18lru_410 {\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_18lru_411 {\n font-size: 12px;\n flex-shrink: 0;\n line-height: 1;\n transition: all 0.3s ease;\n }\n\n ._reflectionLines_18lru_470 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n }\n\n ._historyMeta_18lru_477 {\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_18lru_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_18lru_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_18lru_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_18lru_1 {\n 0% {\n left: -100%;\n }\n 100% {\n left: 100%;\n }\n}\n\n/* 输入区域样式 */\n._inputSectionWrapper_18lru_547 {\n width: 100%;\n margin: 0 auto;\n\n overflow: hidden;\n height: var(--input-height);\n\n transition: height 0.2s;\n\n /* 与 header 保持同色调,让面板看起来是一体的深色玻璃卡片 */\n background: rgba(0, 0, 0, 0.4);\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 &._hidden_18lru_563 {\n height: 0;\n }\n\n ._inputSection_18lru_547 {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 100%;\n padding: 8px; /* 与上方状态条 padding 保持一致 */\n\n ._taskInput_18lru_574 {\n flex: 1;\n height: 100%;\n background: rgba(255, 255, 255, 0.08);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 12px;\n padding: 8px 14px;\n color: rgb(240, 240, 245);\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n outline: none;\n resize: none;\n overflow-y: auto;\n transition: all 0.2s ease;\n\n &::placeholder {\n color: rgba(255, 255, 255, 0.45);\n }\n\n &:focus {\n background: rgba(255, 255, 255, 0.14);\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/*$vite$:1*/")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
|
|
2
2
|
import { Motion } from "ai-motion";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
//#region \0rolldown/runtime.js
|
|
@@ -22,22 +22,27 @@ var __exportAll = (all, no_symbols) => {
|
|
|
22
22
|
};
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/browser/utils/index.ts
|
|
25
|
+
/** 判断值是否为 HTMLElement(type guard)。 */
|
|
25
26
|
function isHTMLElement(el) {
|
|
26
27
|
return !!el && el.nodeType === 1;
|
|
27
28
|
}
|
|
29
|
+
/** 判断元素是否为 <input>(type guard)。 */
|
|
28
30
|
function isInputElement(el) {
|
|
29
31
|
return el?.nodeType === 1 && el.tagName === "INPUT";
|
|
30
32
|
}
|
|
33
|
+
/** 判断元素是否为 <textarea>(type guard)。 */
|
|
31
34
|
function isTextAreaElement(el) {
|
|
32
35
|
return el?.nodeType === 1 && el.tagName === "TEXTAREA";
|
|
33
36
|
}
|
|
37
|
+
/** 判断元素是否为 <select>(type guard)。 */
|
|
34
38
|
function isSelectElement(el) {
|
|
35
39
|
return el?.nodeType === 1 && el.tagName === "SELECT";
|
|
36
40
|
}
|
|
41
|
+
/** 判断元素是否为 <a>(type guard)。 */
|
|
37
42
|
function isAnchorElement(el) {
|
|
38
43
|
return el?.nodeType === 1 && el.tagName === "A";
|
|
39
44
|
}
|
|
40
|
-
/**
|
|
45
|
+
/** 用于将元素坐标换算为顶层视口坐标的 iframe 偏移量。 */
|
|
41
46
|
function getIframeOffset(element) {
|
|
42
47
|
const frame = element.ownerDocument.defaultView?.frameElement;
|
|
43
48
|
if (!frame) return {
|
|
@@ -51,19 +56,20 @@ function getIframeOffset(element) {
|
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
/**
|
|
54
|
-
*
|
|
55
|
-
* @note
|
|
59
|
+
* 从元素自身的原型上获取原生 value setter(iframe 安全)。
|
|
60
|
+
* @note 用于 React
|
|
56
61
|
*/
|
|
57
62
|
function getNativeValueSetter(element) {
|
|
58
63
|
return Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), "value").set;
|
|
59
64
|
}
|
|
65
|
+
/** 等待指定的秒数。 */
|
|
60
66
|
async function waitFor$1(seconds) {
|
|
61
67
|
await new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
|
|
62
68
|
}
|
|
63
69
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @param x - x
|
|
66
|
-
* @param y - y
|
|
70
|
+
* 将视觉指针移动到元素内的某个位置。
|
|
71
|
+
* @param x - 元素所属文档视口内的 x 坐标
|
|
72
|
+
* @param y - 元素所属文档视口内的 y 坐标
|
|
67
73
|
*/
|
|
68
74
|
async function movePointerToElement(element, x, y) {
|
|
69
75
|
const offset = getIframeOffset(element);
|
|
@@ -73,20 +79,23 @@ async function movePointerToElement(element, x, y) {
|
|
|
73
79
|
} }));
|
|
74
80
|
await waitFor$1(.3);
|
|
75
81
|
}
|
|
82
|
+
/** 触发一次模拟光标点击动画。 */
|
|
76
83
|
async function clickPointer() {
|
|
77
84
|
window.dispatchEvent(new CustomEvent("PageAgent::ClickPointer"));
|
|
78
85
|
}
|
|
86
|
+
/** 开启遮罩穿透,鼠标事件不再被遮罩拦截。 */
|
|
79
87
|
async function enablePassThrough() {
|
|
80
88
|
window.dispatchEvent(new CustomEvent("PageAgent::EnablePassThrough"));
|
|
81
89
|
}
|
|
90
|
+
/** 关闭遮罩穿透,遮罩重新拦截鼠标事件。 */
|
|
82
91
|
async function disablePassThrough() {
|
|
83
92
|
window.dispatchEvent(new CustomEvent("PageAgent::DisablePassThrough"));
|
|
84
93
|
}
|
|
85
94
|
//#endregion
|
|
86
95
|
//#region src/browser/actions.ts
|
|
87
96
|
/**
|
|
88
|
-
*
|
|
89
|
-
* @private
|
|
97
|
+
* 根据索引从 selectorMap 中获取对应的 HTMLElement。
|
|
98
|
+
* @private 内部方法,随时可能变更。
|
|
90
99
|
*/
|
|
91
100
|
function getElementByIndex(selectorMap, index) {
|
|
92
101
|
const interactiveNode = selectorMap.get(index);
|
|
@@ -97,6 +106,9 @@ function getElementByIndex(selectorMap, index) {
|
|
|
97
106
|
return element;
|
|
98
107
|
}
|
|
99
108
|
var lastClickedElement = null;
|
|
109
|
+
/**
|
|
110
|
+
* 触发上次点击元素的失焦相关事件(pointerout/pointerleave/mouseout/mouseleave/blur)并清空记录。
|
|
111
|
+
*/
|
|
100
112
|
function blurLastClickedElement() {
|
|
101
113
|
if (lastClickedElement) {
|
|
102
114
|
lastClickedElement.dispatchEvent(new PointerEvent("pointerout", { bubbles: true }));
|
|
@@ -108,11 +120,11 @@ function blurLastClickedElement() {
|
|
|
108
120
|
}
|
|
109
121
|
}
|
|
110
122
|
/**
|
|
111
|
-
*
|
|
123
|
+
* 模拟一次完整的点击,遵循 W3C Pointer Events + UI Events 规范顺序:
|
|
112
124
|
* pointerover/enter → mouseover/enter → pointerdown → mousedown → [focus] →
|
|
113
125
|
* pointerup → mouseup → click
|
|
114
126
|
*
|
|
115
|
-
* @private
|
|
127
|
+
* @private 内部方法,随时可能变更。
|
|
116
128
|
*/
|
|
117
129
|
async function clickElement(element) {
|
|
118
130
|
blurLastClickedElement();
|
|
@@ -164,7 +176,8 @@ async function clickElement(element) {
|
|
|
164
176
|
await waitFor$1(.1);
|
|
165
177
|
}
|
|
166
178
|
/**
|
|
167
|
-
*
|
|
179
|
+
* 向输入类元素(input / textarea / contenteditable)输入文本,并触发相应的输入事件。
|
|
180
|
+
* @private 内部方法,随时可能变更。
|
|
168
181
|
*/
|
|
169
182
|
async function inputTextElement(element, text) {
|
|
170
183
|
const isContentEditable = element.isContentEditable;
|
|
@@ -214,8 +227,9 @@ async function inputTextElement(element, text) {
|
|
|
214
227
|
blurLastClickedElement();
|
|
215
228
|
}
|
|
216
229
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @
|
|
230
|
+
* 在下拉选择框(select)中按文本匹配并选中对应选项,然后触发 change 事件。
|
|
231
|
+
* @todo browser-use 版本非常复杂且支持菜单标签,需要跟进
|
|
232
|
+
* @private 内部方法,随时可能变更。
|
|
219
233
|
*/
|
|
220
234
|
async function selectOptionElement(selectElement, optionText) {
|
|
221
235
|
if (!isSelectElement(selectElement)) throw new Error("Element is not a select element");
|
|
@@ -226,7 +240,8 @@ async function selectOptionElement(selectElement, optionText) {
|
|
|
226
240
|
await waitFor$1(.05);
|
|
227
241
|
}
|
|
228
242
|
/**
|
|
229
|
-
*
|
|
243
|
+
* 将元素滚动到可视区域内;优先使用原生 scrollIntoViewIfNeeded,不支持时回退为 scrollIntoView。
|
|
244
|
+
* @private 内部方法,随时可能变更。
|
|
230
245
|
*/
|
|
231
246
|
async function scrollIntoViewIfNeeded(element) {
|
|
232
247
|
const el = element;
|
|
@@ -237,6 +252,12 @@ async function scrollIntoViewIfNeeded(element) {
|
|
|
237
252
|
inline: "nearest"
|
|
238
253
|
});
|
|
239
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* 垂直滚动页面或指定元素容器。
|
|
257
|
+
* @param scroll_amount - 滚动像素数(正数向下,负数向上)
|
|
258
|
+
* @param element - 可选;若传入,则优先滚动该元素及其祖先中最近的可纵向滚动容器,否则滚动页面
|
|
259
|
+
* @returns 滚动结果的描述消息
|
|
260
|
+
*/
|
|
240
261
|
async function scrollVertically(scroll_amount, element) {
|
|
241
262
|
if (element) {
|
|
242
263
|
const targetElement = element;
|
|
@@ -310,6 +331,12 @@ async function scrollVertically(scroll_amount, element) {
|
|
|
310
331
|
return `✅ ${warningMsg} Scrolled container (${el.tagName}) by ${scrolled}px.`;
|
|
311
332
|
}
|
|
312
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* 水平滚动页面或指定元素容器。
|
|
336
|
+
* @param scroll_amount - 滚动像素数(正数向右,负数向左)
|
|
337
|
+
* @param element - 可选;若传入,则优先滚动该元素及其祖先中最近的可横向滚动容器,否则滚动页面
|
|
338
|
+
* @returns 滚动结果的描述消息
|
|
339
|
+
*/
|
|
313
340
|
async function scrollHorizontally(scroll_amount, element) {
|
|
314
341
|
if (element) {
|
|
315
342
|
const targetElement = element;
|
|
@@ -1426,13 +1453,13 @@ var dom_tree_default = (args = {
|
|
|
1426
1453
|
//#endregion
|
|
1427
1454
|
//#region src/browser/dom/index.ts
|
|
1428
1455
|
/**
|
|
1429
|
-
*
|
|
1430
|
-
* -1
|
|
1431
|
-
* 0
|
|
1432
|
-
*
|
|
1456
|
+
* DOM 树提取时的视口扩展。
|
|
1457
|
+
* -1 表示整页(不受视口限制)
|
|
1458
|
+
* 0 表示仅视口
|
|
1459
|
+
* 正数表示在视口基础上额外扩展的像素数
|
|
1433
1460
|
*
|
|
1434
|
-
* @note
|
|
1435
|
-
*
|
|
1461
|
+
* @note 由于 isTopElement 依赖 elementFromPoint,
|
|
1462
|
+
* 当元素在视口外时会返回 null,该特性实际用处不大,仅在 -1 和 0 之间有区别
|
|
1436
1463
|
*/
|
|
1437
1464
|
var DEFAULT_VIEWPORT_EXPANSION = -1;
|
|
1438
1465
|
function resolveViewportExpansion(viewportExpansion) {
|
|
@@ -1780,13 +1807,13 @@ function patchReact(_browserController) {
|
|
|
1780
1807
|
for (const element of reactRootElements) element.setAttribute("data-page-agent-not-interactive", "true");
|
|
1781
1808
|
}
|
|
1782
1809
|
/**
|
|
1783
|
-
* @todo
|
|
1810
|
+
* @todo (较重,可能有漏判)交互检测:如果元素宽高等于 body 的 offsetWidth/Height,则视为根元素并标记为不可交互(React 经常把大量事件绑定在根元素上,导致误判为可交互)
|
|
1784
1811
|
*/
|
|
1785
1812
|
//#endregion
|
|
1786
1813
|
//#region src/browser/visual/checkDarkMode.ts
|
|
1787
1814
|
/**
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1815
|
+
* 判断页面当前是否处于深色主题的综合函数。
|
|
1816
|
+
* 启发式检测,只覆盖常见模式,默认返回 false。
|
|
1790
1817
|
*/
|
|
1791
1818
|
function isPageDark() {
|
|
1792
1819
|
try {
|
|
@@ -1803,7 +1830,7 @@ function isPageDark() {
|
|
|
1803
1830
|
}
|
|
1804
1831
|
}
|
|
1805
1832
|
/**
|
|
1806
|
-
*
|
|
1833
|
+
* 检查 html 或 body 元素上常见的深色模式 CSS 类名。
|
|
1807
1834
|
*/
|
|
1808
1835
|
function hasDarkModeClass() {
|
|
1809
1836
|
const DEFAULT_DARK_MODE_CLASSES = [
|
|
@@ -1819,7 +1846,7 @@ function hasDarkModeClass() {
|
|
|
1819
1846
|
return false;
|
|
1820
1847
|
}
|
|
1821
1848
|
/**
|
|
1822
|
-
*
|
|
1849
|
+
* 某些 UI 框架使用 data 属性来表示主题
|
|
1823
1850
|
*/
|
|
1824
1851
|
function hasDarkModeDataAttribute() {
|
|
1825
1852
|
const htmlElement = document.documentElement;
|
|
@@ -1837,8 +1864,8 @@ function hasDarkModeDataAttribute() {
|
|
|
1837
1864
|
return false;
|
|
1838
1865
|
}
|
|
1839
1866
|
/**
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1867
|
+
* 检查 CSS 的 `color-scheme` 属性和 `<meta name="color-scheme">` 标签。
|
|
1868
|
+
* 只有 "dark"/"only dark" 才视为深色;"light dark" 存在歧义,忽略。
|
|
1842
1869
|
*/
|
|
1843
1870
|
function isColorSchemeDark() {
|
|
1844
1871
|
const metaContent = document.querySelector("meta[name=\"color-scheme\"]")?.content.toLowerCase();
|
|
@@ -1847,7 +1874,7 @@ function isColorSchemeDark() {
|
|
|
1847
1874
|
return colorScheme === "dark" || colorScheme === "only dark";
|
|
1848
1875
|
}
|
|
1849
1876
|
/**
|
|
1850
|
-
*
|
|
1877
|
+
* 检查 body 元素的背景色以判断页面是否为深色。
|
|
1851
1878
|
*/
|
|
1852
1879
|
function isBackgroundDark() {
|
|
1853
1880
|
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
@@ -1859,18 +1886,18 @@ function isBackgroundDark() {
|
|
|
1859
1886
|
return false;
|
|
1860
1887
|
}
|
|
1861
1888
|
/**
|
|
1862
|
-
*
|
|
1889
|
+
* 检查 body 上的文字颜色是否为浅色,浅色文字通常意味着深色背景。
|
|
1863
1890
|
*/
|
|
1864
1891
|
function isTextColorLight() {
|
|
1865
|
-
/**
|
|
1892
|
+
/** 亮度(0-255)超过该值时认为 body 文字为浅色 */
|
|
1866
1893
|
const LIGHT_TEXT_LUMINANCE = 200;
|
|
1867
1894
|
const luminance = getLuminance(window.getComputedStyle(document.body || document.documentElement).color);
|
|
1868
1895
|
return luminance !== null && luminance > LIGHT_TEXT_LUMINANCE;
|
|
1869
1896
|
}
|
|
1870
1897
|
/**
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
* <body>
|
|
1898
|
+
* 检查主要布局元素(#app、#root 等)的背景色。
|
|
1899
|
+
* 很多 SPA 会把内容渲染到某个容器中,该容器可能有自己的深色背景,
|
|
1900
|
+
* 而 <body> 仍然是透明的。
|
|
1874
1901
|
*/
|
|
1875
1902
|
function isMainContentBackgroundDark() {
|
|
1876
1903
|
const { innerWidth: vw, innerHeight: vh } = window;
|
|
@@ -1889,8 +1916,8 @@ function isMainContentBackgroundDark() {
|
|
|
1889
1916
|
return false;
|
|
1890
1917
|
}
|
|
1891
1918
|
/**
|
|
1892
|
-
*
|
|
1893
|
-
* @param {string} colorString -
|
|
1919
|
+
* 解析 RGB 或 RGBA 颜色字符串,返回包含 r、g、b 属性的对象。
|
|
1920
|
+
* @param {string} colorString - 例如 "rgb(34, 34, 34)" 或 "rgba(0, 0, 0, 0.5)"
|
|
1894
1921
|
* @returns {{r: number, g: number, b: number}|null}
|
|
1895
1922
|
*/
|
|
1896
1923
|
function parseRgbColor(colorString) {
|
|
@@ -1903,9 +1930,9 @@ function parseRgbColor(colorString) {
|
|
|
1903
1930
|
};
|
|
1904
1931
|
}
|
|
1905
1932
|
/**
|
|
1906
|
-
*
|
|
1907
|
-
* @param {string} colorString -
|
|
1908
|
-
* @returns {number|null} -
|
|
1933
|
+
* 计算 CSS 颜色字符串的感知亮度(0-255)。
|
|
1934
|
+
* @param {string} colorString - 例如 "rgb(50, 50, 50)" 或 "rgba(0, 0, 0, 0.5)"
|
|
1935
|
+
* @returns {number|null} - 亮度值,若颜色为透明或无法解析则返回 null。
|
|
1909
1936
|
*/
|
|
1910
1937
|
function getLuminance(colorString) {
|
|
1911
1938
|
if (!colorString || colorString === "transparent" || colorString.startsWith("rgba(0, 0, 0, 0)")) return null;
|
|
@@ -1914,9 +1941,9 @@ function getLuminance(colorString) {
|
|
|
1914
1941
|
return .299 * rgb.r + .587 * rgb.g + .114 * rgb.b;
|
|
1915
1942
|
}
|
|
1916
1943
|
/**
|
|
1917
|
-
*
|
|
1918
|
-
* @param {string} colorString -
|
|
1919
|
-
* @param {number} threshold -
|
|
1944
|
+
* 根据计算出的亮度判断颜色是否为“深色”。
|
|
1945
|
+
* @param {string} colorString - CSS 颜色字符串(例如 "rgb(50, 50, 50)")。
|
|
1946
|
+
* @param {number} threshold - 0 到 255 之间的值。亮度低于该值的颜色视为深色。默认为 128。
|
|
1920
1947
|
*/
|
|
1921
1948
|
function isColorDark(colorString, threshold = 128) {
|
|
1922
1949
|
const luminance = getLuminance(colorString);
|
|
@@ -1970,6 +1997,9 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
1970
1997
|
#currentCursorY = 0;
|
|
1971
1998
|
#targetCursorX = 0;
|
|
1972
1999
|
#targetCursorY = 0;
|
|
2000
|
+
/**
|
|
2001
|
+
* 构造函数:初始化遮罩 DOM、创建 ai-motion 动效、拦截底层事件,并注册全局事件监听。
|
|
2002
|
+
*/
|
|
1973
2003
|
constructor() {
|
|
1974
2004
|
super();
|
|
1975
2005
|
this.wrapper.id = "page-agent-runtime_simulator-mask";
|
|
@@ -2048,6 +2078,7 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2048
2078
|
window.removeEventListener("PageAgent::DisablePassThrough", disablePassThroughListener);
|
|
2049
2079
|
});
|
|
2050
2080
|
}
|
|
2081
|
+
/** 创建模拟光标的 DOM 结构(包含波纹、填充层、边框层)。 */
|
|
2051
2082
|
#createCursor() {
|
|
2052
2083
|
this.#cursor.className = cursor_module_default.cursor;
|
|
2053
2084
|
const rippleContainer = document.createElement("div");
|
|
@@ -2061,6 +2092,7 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2061
2092
|
this.#cursor.appendChild(borderLayer);
|
|
2062
2093
|
this.wrapper.appendChild(this.#cursor);
|
|
2063
2094
|
}
|
|
2095
|
+
/** 每帧将光标向目标位置移动 20% 距离,实现平滑跟随动画。 */
|
|
2064
2096
|
#moveCursorToTarget() {
|
|
2065
2097
|
if (this.#disposed) return;
|
|
2066
2098
|
const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * .2;
|
|
@@ -2079,17 +2111,24 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2079
2111
|
}
|
|
2080
2112
|
requestAnimationFrame(() => this.#moveCursorToTarget());
|
|
2081
2113
|
}
|
|
2114
|
+
/**
|
|
2115
|
+
* 设置光标的目标坐标(由外部通过事件触发调用)。
|
|
2116
|
+
* @param x - 目标 X 坐标
|
|
2117
|
+
* @param y - 目标 Y 坐标
|
|
2118
|
+
*/
|
|
2082
2119
|
setCursorPosition(x, y) {
|
|
2083
2120
|
if (this.#disposed) return;
|
|
2084
2121
|
this.#targetCursorX = x;
|
|
2085
2122
|
this.#targetCursorY = y;
|
|
2086
2123
|
}
|
|
2124
|
+
/** 触发一次光标点击动画(通过重放 clicking class 实现)。 */
|
|
2087
2125
|
triggerClickAnimation() {
|
|
2088
2126
|
if (this.#disposed) return;
|
|
2089
2127
|
this.#cursor.classList.remove(cursor_module_default.clicking);
|
|
2090
2128
|
this.#cursor.offsetHeight;
|
|
2091
2129
|
this.#cursor.classList.add(cursor_module_default.clicking);
|
|
2092
2130
|
}
|
|
2131
|
+
/** 显示遮罩及模拟光标。 */
|
|
2093
2132
|
show() {
|
|
2094
2133
|
if (this.shown || this.#disposed) return;
|
|
2095
2134
|
this.shown = true;
|
|
@@ -2103,6 +2142,7 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2103
2142
|
this.#cursor.style.left = `${this.#currentCursorX}px`;
|
|
2104
2143
|
this.#cursor.style.top = `${this.#currentCursorY}px`;
|
|
2105
2144
|
}
|
|
2145
|
+
/** 隐藏遮罩及模拟光标。 */
|
|
2106
2146
|
hide() {
|
|
2107
2147
|
if (!this.shown || this.#disposed) return;
|
|
2108
2148
|
this.shown = false;
|
|
@@ -2113,6 +2153,7 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2113
2153
|
this.wrapper.classList.remove(SimulatorMask_module_default.visible);
|
|
2114
2154
|
}, 800);
|
|
2115
2155
|
}
|
|
2156
|
+
/** 销毁遮罩:释放动效、移除 DOM 并触发 dispose 事件。 */
|
|
2116
2157
|
dispose() {
|
|
2117
2158
|
this.#disposed = true;
|
|
2118
2159
|
this.motion?.dispose();
|
|
@@ -2124,44 +2165,46 @@ var init_SimulatorMask = __esmMin((() => {
|
|
|
2124
2165
|
//#endregion
|
|
2125
2166
|
//#region src/browser/BrowserController.ts
|
|
2126
2167
|
/**
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2168
|
+
* 版权所有 (C) 2025 Alibaba Group Holding Limited
|
|
2169
|
+
* 保留所有权利。
|
|
2129
2170
|
*
|
|
2130
|
-
* BrowserController -
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
2171
|
+
* BrowserController - 管理 DOM 操作和元素交互。
|
|
2172
|
+
* 设计上独立于 LLM,可在单元测试中直接测试。
|
|
2173
|
+
* 所有公开方法均为异步,以支持潜在的远程调用。
|
|
2133
2174
|
*/
|
|
2134
2175
|
/**
|
|
2135
|
-
* BrowserController
|
|
2136
|
-
*
|
|
2176
|
+
* BrowserController 管理 DOM 状态和元素交互。
|
|
2177
|
+
* 它为所有 DOM 操作提供异步方法,并保持状态隔离。
|
|
2137
2178
|
*
|
|
2138
2179
|
* @lifecycle
|
|
2139
|
-
* - beforeUpdate:
|
|
2140
|
-
* - afterUpdate:
|
|
2180
|
+
* - beforeUpdate: 在 DOM 树更新之前触发。
|
|
2181
|
+
* - afterUpdate: 在 DOM 树更新之后触发。
|
|
2141
2182
|
*/
|
|
2142
2183
|
var BrowserController = class extends EventTarget {
|
|
2143
2184
|
config;
|
|
2144
|
-
/**
|
|
2185
|
+
/** 对应 browser-use 中的 eval_page */
|
|
2145
2186
|
flatTree = null;
|
|
2146
2187
|
/**
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
2188
|
+
* 所有已索引的高亮交互元素
|
|
2189
|
+
* 对应 browser-use 中的 DOMState.selector_map
|
|
2149
2190
|
*/
|
|
2150
2191
|
selectorMap = /* @__PURE__ */ new Map();
|
|
2151
|
-
/**
|
|
2192
|
+
/** 索引 -> 元素文本描述映射 */
|
|
2152
2193
|
elementTextMap = /* @__PURE__ */ new Map();
|
|
2153
2194
|
/**
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2195
|
+
* 供 LLM 消费的简化 HTML。
|
|
2196
|
+
* 对应 browser-use 中的 clickable_elements_to_string
|
|
2156
2197
|
*/
|
|
2157
2198
|
simplifiedHTML = "<EMPTY>";
|
|
2158
|
-
/**
|
|
2199
|
+
/** 树最后一次更新的时间 */
|
|
2159
2200
|
lastTimeUpdate = 0;
|
|
2160
|
-
/**
|
|
2201
|
+
/** 树是否至少被索引过一次 */
|
|
2161
2202
|
isIndexed = false;
|
|
2162
|
-
/**
|
|
2203
|
+
/** 用于在自动化期间阻止用户交互的视觉遮罩层 */
|
|
2163
2204
|
mask = null;
|
|
2205
|
+
/** mask 异步初始化完成的 Promise,用于在调用前等待初始化完成 */
|
|
2164
2206
|
maskReady = null;
|
|
2207
|
+
/** 是否已释放资源 */
|
|
2165
2208
|
disposed = false;
|
|
2166
2209
|
constructor(config = {}) {
|
|
2167
2210
|
super();
|
|
@@ -2170,7 +2213,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2170
2213
|
if (config.enableMask) this.initMask();
|
|
2171
2214
|
}
|
|
2172
2215
|
/**
|
|
2173
|
-
*
|
|
2216
|
+
* 异步初始化遮罩层(动态导入以避免在 Node 中加载 CSS)
|
|
2174
2217
|
*/
|
|
2175
2218
|
initMask() {
|
|
2176
2219
|
if (this.disposed || this.maskReady !== null) return;
|
|
@@ -2186,20 +2229,20 @@ var BrowserController = class extends EventTarget {
|
|
|
2186
2229
|
})();
|
|
2187
2230
|
}
|
|
2188
2231
|
/**
|
|
2189
|
-
*
|
|
2232
|
+
* 获取当前页面 URL
|
|
2190
2233
|
*/
|
|
2191
2234
|
async getCurrentUrl() {
|
|
2192
2235
|
return window.location.href;
|
|
2193
2236
|
}
|
|
2194
2237
|
/**
|
|
2195
|
-
*
|
|
2238
|
+
* 获取树最后一次更新的时间戳
|
|
2196
2239
|
*/
|
|
2197
2240
|
async getLastUpdateTime() {
|
|
2198
2241
|
return this.lastTimeUpdate;
|
|
2199
2242
|
}
|
|
2200
2243
|
/**
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2244
|
+
* 获取供 LLM 消费的结构化浏览器状态。
|
|
2245
|
+
* 自动调用 updateTree() 刷新 DOM 状态。
|
|
2203
2246
|
*/
|
|
2204
2247
|
async getBrowserState() {
|
|
2205
2248
|
const url = window.location.href;
|
|
@@ -2217,9 +2260,9 @@ var BrowserController = class extends EventTarget {
|
|
|
2217
2260
|
};
|
|
2218
2261
|
}
|
|
2219
2262
|
/**
|
|
2220
|
-
*
|
|
2221
|
-
*
|
|
2222
|
-
*
|
|
2263
|
+
* 更新 DOM 树,返回供 LLM 使用的简化 HTML。
|
|
2264
|
+
* 这是刷新页面状态的主要方法。
|
|
2265
|
+
* 若已启用遮罩,DOM 提取期间会自动绕过遮罩。
|
|
2223
2266
|
*/
|
|
2224
2267
|
async updateTree() {
|
|
2225
2268
|
this.dispatchEvent(new Event("beforeUpdate"));
|
|
@@ -2242,21 +2285,21 @@ var BrowserController = class extends EventTarget {
|
|
|
2242
2285
|
return this.simplifiedHTML;
|
|
2243
2286
|
}
|
|
2244
2287
|
/**
|
|
2245
|
-
*
|
|
2288
|
+
* 清理所有元素高亮
|
|
2246
2289
|
*/
|
|
2247
2290
|
async cleanUpHighlights() {
|
|
2248
2291
|
console.log("[BrowserController] cleanUpHighlights");
|
|
2249
2292
|
cleanUpHighlights();
|
|
2250
2293
|
}
|
|
2251
2294
|
/**
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
2295
|
+
* 在任何基于索引的操作之前确保树已被索引。
|
|
2296
|
+
* 若尚未调用 updateTree() 则抛出异常。
|
|
2254
2297
|
*/
|
|
2255
2298
|
assertIndexed() {
|
|
2256
2299
|
if (!this.isIndexed) throw new Error("DOM tree not indexed yet. Can not perform actions on elements.");
|
|
2257
2300
|
}
|
|
2258
2301
|
/**
|
|
2259
|
-
*
|
|
2302
|
+
* 根据索引点击元素
|
|
2260
2303
|
*/
|
|
2261
2304
|
async clickElement(index) {
|
|
2262
2305
|
try {
|
|
@@ -2280,7 +2323,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2280
2323
|
}
|
|
2281
2324
|
}
|
|
2282
2325
|
/**
|
|
2283
|
-
*
|
|
2326
|
+
* 根据索引向元素输入文本
|
|
2284
2327
|
*/
|
|
2285
2328
|
async inputText(index, text) {
|
|
2286
2329
|
try {
|
|
@@ -2300,7 +2343,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2300
2343
|
}
|
|
2301
2344
|
}
|
|
2302
2345
|
/**
|
|
2303
|
-
*
|
|
2346
|
+
* 根据索引和选项文本在下拉框中选中选项
|
|
2304
2347
|
*/
|
|
2305
2348
|
async selectOption(index, optionText) {
|
|
2306
2349
|
try {
|
|
@@ -2320,7 +2363,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2320
2363
|
}
|
|
2321
2364
|
}
|
|
2322
2365
|
/**
|
|
2323
|
-
*
|
|
2366
|
+
* 垂直滚动
|
|
2324
2367
|
*/
|
|
2325
2368
|
async scroll(options) {
|
|
2326
2369
|
try {
|
|
@@ -2338,7 +2381,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2338
2381
|
}
|
|
2339
2382
|
}
|
|
2340
2383
|
/**
|
|
2341
|
-
*
|
|
2384
|
+
* 水平滚动
|
|
2342
2385
|
*/
|
|
2343
2386
|
async scrollHorizontally(options) {
|
|
2344
2387
|
try {
|
|
@@ -2356,9 +2399,8 @@ var BrowserController = class extends EventTarget {
|
|
|
2356
2399
|
}
|
|
2357
2400
|
}
|
|
2358
2401
|
/**
|
|
2359
|
-
*
|
|
2360
|
-
*
|
|
2361
|
-
* can abort promptly when the task is stopped.
|
|
2402
|
+
* 在页面上执行任意 JavaScript。
|
|
2403
|
+
* 可选的 `signal` 会暴露到脚本作用域中,使协作代码能在任务被中止时及时退出。
|
|
2362
2404
|
*/
|
|
2363
2405
|
async executeJavascript(script, signal) {
|
|
2364
2406
|
try {
|
|
@@ -2374,8 +2416,8 @@ var BrowserController = class extends EventTarget {
|
|
|
2374
2416
|
}
|
|
2375
2417
|
}
|
|
2376
2418
|
/**
|
|
2377
|
-
*
|
|
2378
|
-
*
|
|
2419
|
+
* 显示视觉遮罩层。
|
|
2420
|
+
* 仅在遮罩初始化完成后可用。
|
|
2379
2421
|
*/
|
|
2380
2422
|
async showMask() {
|
|
2381
2423
|
if (this.disposed) return;
|
|
@@ -2384,8 +2426,8 @@ var BrowserController = class extends EventTarget {
|
|
|
2384
2426
|
this.mask?.show();
|
|
2385
2427
|
}
|
|
2386
2428
|
/**
|
|
2387
|
-
*
|
|
2388
|
-
*
|
|
2429
|
+
* 隐藏视觉遮罩层。
|
|
2430
|
+
* 仅在遮罩初始化完成后可用。
|
|
2389
2431
|
*/
|
|
2390
2432
|
async hideMask() {
|
|
2391
2433
|
if (this.disposed) return;
|
|
@@ -2394,7 +2436,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2394
2436
|
this.mask?.hide();
|
|
2395
2437
|
}
|
|
2396
2438
|
/**
|
|
2397
|
-
*
|
|
2439
|
+
* 释放资源并清理
|
|
2398
2440
|
*/
|
|
2399
2441
|
dispose() {
|
|
2400
2442
|
if (this.disposed) return;
|
|
@@ -2421,6 +2463,7 @@ var BrowserController = class extends EventTarget {
|
|
|
2421
2463
|
if (errors.length > 1) throw new AggregateError(errors, "BrowserController disposal failed");
|
|
2422
2464
|
}
|
|
2423
2465
|
};
|
|
2466
|
+
/** 语言包集合:键为语言代码,值为对应翻译表 */
|
|
2424
2467
|
var locales = {
|
|
2425
2468
|
"en-US": { ui: {
|
|
2426
2469
|
ready: "Ready",
|
|
@@ -2525,81 +2568,85 @@ var I18n = class {
|
|
|
2525
2568
|
if (params) return this.interpolate(value, params);
|
|
2526
2569
|
return value;
|
|
2527
2570
|
}
|
|
2571
|
+
/** 根据点分路径从翻译对象中取值 */
|
|
2528
2572
|
getNestedValue(obj, path) {
|
|
2529
2573
|
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
2530
2574
|
}
|
|
2575
|
+
/** 将模板中的 {{key}} 占位符替换为参数值 */
|
|
2531
2576
|
interpolate(template, params) {
|
|
2532
2577
|
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
2533
2578
|
return params[key] != null ? params[key].toString() : match;
|
|
2534
2579
|
});
|
|
2535
2580
|
}
|
|
2581
|
+
/** 获取当前语言代码 */
|
|
2536
2582
|
getLanguage() {
|
|
2537
2583
|
return this.language;
|
|
2538
2584
|
}
|
|
2539
2585
|
};
|
|
2540
2586
|
//#endregion
|
|
2541
2587
|
//#region src/ui/utils.ts
|
|
2588
|
+
/** 截断字符串,超过最大长度时以省略号结尾 */
|
|
2542
2589
|
function truncate$1(text, maxLength) {
|
|
2543
2590
|
if (text.length > maxLength) return text.substring(0, maxLength) + "...";
|
|
2544
2591
|
return text;
|
|
2545
2592
|
}
|
|
2546
2593
|
/**
|
|
2547
|
-
*
|
|
2594
|
+
* 转义 HTML 特殊字符,防止 XSS 及渲染问题
|
|
2548
2595
|
*/
|
|
2549
2596
|
function escapeHtml(text) {
|
|
2550
2597
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2551
2598
|
}
|
|
2552
2599
|
var UI_module_default = {
|
|
2553
|
-
wrapper: "
|
|
2554
|
-
|
|
2555
|
-
background: "
|
|
2556
|
-
"mask-running": "_mask-
|
|
2557
|
-
|
|
2558
|
-
header: "
|
|
2559
|
-
pulse: "
|
|
2560
|
-
retryPulse: "
|
|
2561
|
-
statusTextFadeOut: "
|
|
2562
|
-
statusTextFadeIn: "
|
|
2563
|
-
statusSection: "
|
|
2564
|
-
indicator: "
|
|
2565
|
-
thinking: "
|
|
2566
|
-
tool_executing: "
|
|
2567
|
-
retry: "
|
|
2568
|
-
completed: "
|
|
2569
|
-
input: "
|
|
2570
|
-
output: "
|
|
2571
|
-
error: "
|
|
2572
|
-
statusText: "
|
|
2573
|
-
fadeOut: "
|
|
2574
|
-
fadeIn: "
|
|
2575
|
-
controls: "
|
|
2576
|
-
controlButton: "
|
|
2577
|
-
stopButton: "
|
|
2578
|
-
historySectionWrapper: "
|
|
2579
|
-
shimmer: "
|
|
2580
|
-
celebrate: "
|
|
2581
|
-
expanded: "
|
|
2582
|
-
historySection: "
|
|
2583
|
-
historyItem: "
|
|
2584
|
-
observation: "
|
|
2585
|
-
question: "
|
|
2586
|
-
doneSuccess: "
|
|
2587
|
-
historyContent: "
|
|
2588
|
-
statusIcon: "
|
|
2589
|
-
doneError: "
|
|
2590
|
-
reflectionLines: "
|
|
2591
|
-
historyMeta: "
|
|
2592
|
-
inputSectionWrapper: "
|
|
2593
|
-
hidden: "
|
|
2594
|
-
inputSection: "
|
|
2595
|
-
taskInput: "
|
|
2600
|
+
wrapper: "_wrapper_18lru_1",
|
|
2601
|
+
shell: "_shell_18lru_57",
|
|
2602
|
+
background: "_background_18lru_57",
|
|
2603
|
+
"mask-running": "_mask-running_18lru_1",
|
|
2604
|
+
shellInner: "_shellInner_18lru_57",
|
|
2605
|
+
header: "_header_18lru_124",
|
|
2606
|
+
pulse: "_pulse_18lru_1",
|
|
2607
|
+
retryPulse: "_retryPulse_18lru_1",
|
|
2608
|
+
statusTextFadeOut: "_statusTextFadeOut_18lru_1",
|
|
2609
|
+
statusTextFadeIn: "_statusTextFadeIn_18lru_1",
|
|
2610
|
+
statusSection: "_statusSection_18lru_140",
|
|
2611
|
+
indicator: "_indicator_18lru_147",
|
|
2612
|
+
thinking: "_thinking_18lru_156",
|
|
2613
|
+
tool_executing: "_tool_executing_18lru_161",
|
|
2614
|
+
retry: "_retry_18lru_166",
|
|
2615
|
+
completed: "_completed_18lru_172",
|
|
2616
|
+
input: "_input_18lru_173",
|
|
2617
|
+
output: "_output_18lru_174",
|
|
2618
|
+
error: "_error_18lru_179",
|
|
2619
|
+
statusText: "_statusText_18lru_185",
|
|
2620
|
+
fadeOut: "_fadeOut_18lru_197",
|
|
2621
|
+
fadeIn: "_fadeIn_18lru_201",
|
|
2622
|
+
controls: "_controls_18lru_207",
|
|
2623
|
+
controlButton: "_controlButton_18lru_212",
|
|
2624
|
+
stopButton: "_stopButton_18lru_231",
|
|
2625
|
+
historySectionWrapper: "_historySectionWrapper_18lru_268",
|
|
2626
|
+
shimmer: "_shimmer_18lru_1",
|
|
2627
|
+
celebrate: "_celebrate_18lru_1",
|
|
2628
|
+
expanded: "_expanded_18lru_290",
|
|
2629
|
+
historySection: "_historySection_18lru_268",
|
|
2630
|
+
historyItem: "_historyItem_18lru_309",
|
|
2631
|
+
observation: "_observation_18lru_363",
|
|
2632
|
+
question: "_question_18lru_368",
|
|
2633
|
+
doneSuccess: "_doneSuccess_18lru_374",
|
|
2634
|
+
historyContent: "_historyContent_18lru_410",
|
|
2635
|
+
statusIcon: "_statusIcon_18lru_411",
|
|
2636
|
+
doneError: "_doneError_18lru_420",
|
|
2637
|
+
reflectionLines: "_reflectionLines_18lru_470",
|
|
2638
|
+
historyMeta: "_historyMeta_18lru_477",
|
|
2639
|
+
inputSectionWrapper: "_inputSectionWrapper_18lru_547",
|
|
2640
|
+
hidden: "_hidden_18lru_563",
|
|
2641
|
+
inputSection: "_inputSection_18lru_547",
|
|
2642
|
+
taskInput: "_taskInput_18lru_574"
|
|
2596
2643
|
};
|
|
2597
2644
|
//#endregion
|
|
2598
2645
|
//#region src/ui/cards.ts
|
|
2599
2646
|
/**
|
|
2600
|
-
*
|
|
2647
|
+
* UI 的卡片 HTML 生成工具
|
|
2601
2648
|
*/
|
|
2602
|
-
/**
|
|
2649
|
+
/** 创建单条历史卡片 */
|
|
2603
2650
|
function createCard({ icon, content, meta, type }) {
|
|
2604
2651
|
const typeClass = type ? UI_module_default[type] : "";
|
|
2605
2652
|
const contentHtml = Array.isArray(content) ? `<div class="${UI_module_default.reflectionLines}">${content.map((line) => `<span>${escapeHtml(line)}</span>`).join("")}</div>` : `<span>${escapeHtml(content)}</span>`;
|
|
@@ -2613,7 +2660,7 @@ function createCard({ icon, content, meta, type }) {
|
|
|
2613
2660
|
</div>
|
|
2614
2661
|
`;
|
|
2615
2662
|
}
|
|
2616
|
-
/**
|
|
2663
|
+
/** 从反思对象生成反思行 */
|
|
2617
2664
|
function createReflectionLines(reflection) {
|
|
2618
2665
|
const lines = [];
|
|
2619
2666
|
if (reflection.evaluation_previous_goal) lines.push(`🔍 ${reflection.evaluation_previous_goal}`);
|
|
@@ -2624,14 +2671,14 @@ function createReflectionLines(reflection) {
|
|
|
2624
2671
|
//#endregion
|
|
2625
2672
|
//#region src/ui/UI.ts
|
|
2626
2673
|
/**
|
|
2627
|
-
* Agent
|
|
2674
|
+
* Agent 控制 UI
|
|
2628
2675
|
*
|
|
2629
|
-
*
|
|
2630
|
-
* -
|
|
2631
|
-
* -
|
|
2676
|
+
* 架构:
|
|
2677
|
+
* - 历史列表:直接从 agent.history 渲染(历史事件)
|
|
2678
|
+
* - 头部栏:展示活动事件(瞬时状态)和 agent 状态
|
|
2632
2679
|
*
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
2680
|
+
* 这种分离保证了数据一致性 - 历史是已完成内容的唯一数据源,
|
|
2681
|
+
* 而活动状态展示的是当前正在发生的事情。
|
|
2635
2682
|
*/
|
|
2636
2683
|
var UI = class {
|
|
2637
2684
|
#wrapper;
|
|
@@ -2662,13 +2709,14 @@ var UI = class {
|
|
|
2662
2709
|
#onActivity = (e) => this.#handleActivity(e.detail);
|
|
2663
2710
|
#onAgentDispose = () => this.dispose();
|
|
2664
2711
|
#askUserCallback = (question, options) => this.#askUser(question, options?.signal);
|
|
2712
|
+
/** 获取 UI 的根容器元素 */
|
|
2665
2713
|
get wrapper() {
|
|
2666
2714
|
return this.#wrapper;
|
|
2667
2715
|
}
|
|
2668
2716
|
/**
|
|
2669
|
-
*
|
|
2670
|
-
* @param agent -
|
|
2671
|
-
* @param config -
|
|
2717
|
+
* 创建绑定到某个 agent 的 UI
|
|
2718
|
+
* @param agent - 实现 UIAdapter 的 Agent 实例
|
|
2719
|
+
* @param config - 可选的 UI 配置
|
|
2672
2720
|
*/
|
|
2673
2721
|
constructor(agent, config = {}) {
|
|
2674
2722
|
this.#agent = agent;
|
|
@@ -2693,7 +2741,7 @@ var UI = class {
|
|
|
2693
2741
|
this.#showInputArea();
|
|
2694
2742
|
this.hide();
|
|
2695
2743
|
}
|
|
2696
|
-
/**
|
|
2744
|
+
/** 处理 agent 状态变化 */
|
|
2697
2745
|
#handleStatusChange() {
|
|
2698
2746
|
const status = this.#agent.status;
|
|
2699
2747
|
const failed = status === "completed" && this.#agent.lastResult?.success === false;
|
|
@@ -2714,13 +2762,13 @@ var UI = class {
|
|
|
2714
2762
|
if (this.#shouldShowInputArea()) this.#showInputArea();
|
|
2715
2763
|
}
|
|
2716
2764
|
}
|
|
2717
|
-
/**
|
|
2765
|
+
/** 处理 agent 历史变化 - 根据 agent.history 重新渲染历史列表 */
|
|
2718
2766
|
#handleHistoryChange() {
|
|
2719
2767
|
this.#renderHistory();
|
|
2720
2768
|
}
|
|
2721
2769
|
/**
|
|
2722
|
-
*
|
|
2723
|
-
*
|
|
2770
|
+
* 处理 agent 活动 - 用于即时 UI 反馈的瞬时状态
|
|
2771
|
+
* 活动事件不会持久化到历史中,仅用于头部栏更新
|
|
2724
2772
|
*/
|
|
2725
2773
|
#handleActivity(activity) {
|
|
2726
2774
|
switch (activity.type) {
|
|
@@ -2745,9 +2793,9 @@ var UI = class {
|
|
|
2745
2793
|
}
|
|
2746
2794
|
}
|
|
2747
2795
|
/**
|
|
2748
|
-
*
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2796
|
+
* 请求用户输入(内部方法,由 agent 通过 onAskUser 调用)。
|
|
2797
|
+
* 当 `signal` 中止(任务被停止或销毁)时拒绝该请求,
|
|
2798
|
+
* 并清理问题卡片和挂起状态,以便 agent 循环能够收尾。
|
|
2751
2799
|
*/
|
|
2752
2800
|
#askUser(question, signal) {
|
|
2753
2801
|
if (this.#disposed) return Promise.reject(this.#createAbortError());
|
|
@@ -2777,9 +2825,11 @@ var UI = class {
|
|
|
2777
2825
|
}
|
|
2778
2826
|
});
|
|
2779
2827
|
}
|
|
2828
|
+
/** 创建标准的中止异常(AbortError) */
|
|
2780
2829
|
#createAbortError() {
|
|
2781
2830
|
return new DOMException("The operation was aborted.", "AbortError");
|
|
2782
2831
|
}
|
|
2832
|
+
/** 清空用户回答相关状态并移除临时卡片 */
|
|
2783
2833
|
#clearQuestionState() {
|
|
2784
2834
|
if (this.#userAnswerSignal && this.#userAnswerAbortHandler) this.#userAnswerSignal.removeEventListener("abort", this.#userAnswerAbortHandler);
|
|
2785
2835
|
this.#userAnswerSignal = null;
|
|
@@ -2789,30 +2839,34 @@ var UI = class {
|
|
|
2789
2839
|
this.#isWaitingForUserAnswer = false;
|
|
2790
2840
|
this.#removeTempCards();
|
|
2791
2841
|
}
|
|
2842
|
+
/** 拒绝挂起的用户问题(存在待回答问题时) */
|
|
2792
2843
|
#rejectPendingQuestion() {
|
|
2793
2844
|
const reject = this.#userAnswerRejecter;
|
|
2794
2845
|
if (!reject) return;
|
|
2795
2846
|
this.#clearQuestionState();
|
|
2796
2847
|
reject(this.#createAbortError());
|
|
2797
2848
|
}
|
|
2798
|
-
/**
|
|
2849
|
+
/** 移除临时问题卡片(为安全起见仅移除直接子节点) */
|
|
2799
2850
|
#removeTempCards() {
|
|
2800
2851
|
Array.from(this.#historySection.children).forEach((child) => {
|
|
2801
2852
|
if (child.getAttribute("data-temp-card") === "true") child.remove();
|
|
2802
2853
|
});
|
|
2803
2854
|
this.#syncHistoryHeight();
|
|
2804
2855
|
}
|
|
2856
|
+
/** 显示 UI(淡入 + 平移效果) */
|
|
2805
2857
|
show() {
|
|
2806
2858
|
this.wrapper.style.display = "block";
|
|
2807
2859
|
this.wrapper.offsetHeight;
|
|
2808
2860
|
this.wrapper.style.opacity = "1";
|
|
2809
2861
|
this.wrapper.style.transform = "translateX(-50%) translateY(0)";
|
|
2810
2862
|
}
|
|
2863
|
+
/** 隐藏 UI(淡出 + 下移效果) */
|
|
2811
2864
|
hide() {
|
|
2812
2865
|
this.wrapper.style.opacity = "0";
|
|
2813
2866
|
this.wrapper.style.transform = "translateX(-50%) translateY(20px)";
|
|
2814
2867
|
this.wrapper.style.display = "none";
|
|
2815
2868
|
}
|
|
2869
|
+
/** 重置 UI 状态(拒绝挂起问题、清空状态文本、收起历史并显示输入框) */
|
|
2816
2870
|
reset() {
|
|
2817
2871
|
this.#rejectPendingQuestion();
|
|
2818
2872
|
this.#statusText.textContent = this.#i18n.t("ui.ready");
|
|
@@ -2821,14 +2875,16 @@ var UI = class {
|
|
|
2821
2875
|
this.#collapse();
|
|
2822
2876
|
this.#showInputArea();
|
|
2823
2877
|
}
|
|
2878
|
+
/** 展开历史弹窗(公开方法) */
|
|
2824
2879
|
expand() {
|
|
2825
2880
|
this.#expand();
|
|
2826
2881
|
}
|
|
2882
|
+
/** 收起历史弹窗(公开方法) */
|
|
2827
2883
|
collapse() {
|
|
2828
2884
|
this.#collapse();
|
|
2829
2885
|
}
|
|
2830
2886
|
/**
|
|
2831
|
-
*
|
|
2887
|
+
* 销毁 UI 并清理事件监听器
|
|
2832
2888
|
*/
|
|
2833
2889
|
dispose() {
|
|
2834
2890
|
if (this.#disposed) return;
|
|
@@ -2845,6 +2901,7 @@ var UI = class {
|
|
|
2845
2901
|
this.#timers.clear();
|
|
2846
2902
|
this.wrapper.remove();
|
|
2847
2903
|
}
|
|
2904
|
+
/** 根据工具名称和参数生成"正在执行"提示文本 */
|
|
2848
2905
|
#getToolExecutingText(toolName, args) {
|
|
2849
2906
|
const a = args;
|
|
2850
2907
|
switch (toolName) {
|
|
@@ -2859,14 +2916,14 @@ var UI = class {
|
|
|
2859
2916
|
}
|
|
2860
2917
|
}
|
|
2861
2918
|
/**
|
|
2862
|
-
*
|
|
2919
|
+
* 动作按钮处理:运行中则停止,空闲则关闭(销毁)
|
|
2863
2920
|
*/
|
|
2864
2921
|
#handleActionButton() {
|
|
2865
2922
|
if (this.#agent.status === "running") this.#agent.stop();
|
|
2866
2923
|
else this.#agent.dispose();
|
|
2867
2924
|
}
|
|
2868
2925
|
/**
|
|
2869
|
-
*
|
|
2926
|
+
* 提交任务
|
|
2870
2927
|
*/
|
|
2871
2928
|
#submitTask() {
|
|
2872
2929
|
const input = this.#taskInput.value.trim();
|
|
@@ -2880,7 +2937,7 @@ var UI = class {
|
|
|
2880
2937
|
});
|
|
2881
2938
|
}
|
|
2882
2939
|
/**
|
|
2883
|
-
*
|
|
2940
|
+
* 处理用户回答
|
|
2884
2941
|
*/
|
|
2885
2942
|
#handleUserAnswer(input) {
|
|
2886
2943
|
this.#removeTempCards();
|
|
@@ -2889,7 +2946,7 @@ var UI = class {
|
|
|
2889
2946
|
resolve?.(input);
|
|
2890
2947
|
}
|
|
2891
2948
|
/**
|
|
2892
|
-
*
|
|
2949
|
+
* 显示输入区域
|
|
2893
2950
|
*/
|
|
2894
2951
|
#showInputArea(placeholder) {
|
|
2895
2952
|
this.#taskInput.value = "";
|
|
@@ -2900,13 +2957,13 @@ var UI = class {
|
|
|
2900
2957
|
}, 100);
|
|
2901
2958
|
}
|
|
2902
2959
|
/**
|
|
2903
|
-
*
|
|
2960
|
+
* 隐藏输入区域
|
|
2904
2961
|
*/
|
|
2905
2962
|
#hideInputArea() {
|
|
2906
2963
|
this.#inputSection.classList.add(UI_module_default.hidden);
|
|
2907
2964
|
}
|
|
2908
2965
|
/**
|
|
2909
|
-
*
|
|
2966
|
+
* 检查是否应显示输入区域
|
|
2910
2967
|
*/
|
|
2911
2968
|
#shouldShowInputArea() {
|
|
2912
2969
|
if (this.#isWaitingForUserAnswer) return true;
|
|
@@ -2915,6 +2972,7 @@ var UI = class {
|
|
|
2915
2972
|
if (status === "completed" || status === "error" || status === "stopped") return this.#config.promptForNextTask ?? true;
|
|
2916
2973
|
return false;
|
|
2917
2974
|
}
|
|
2975
|
+
/** 创建 UI 根元素并挂载到 body,返回根容器 */
|
|
2918
2976
|
#createWrapper() {
|
|
2919
2977
|
const taskInputMaxLength = 1e3;
|
|
2920
2978
|
const wrapper = document.createElement("div");
|
|
@@ -2933,9 +2991,9 @@ var UI = class {
|
|
|
2933
2991
|
</div>
|
|
2934
2992
|
</div>
|
|
2935
2993
|
</div>
|
|
2936
|
-
<div class="${UI_module_default.
|
|
2994
|
+
<div class="${UI_module_default.shell}">
|
|
2937
2995
|
<div class="${UI_module_default.background}"></div>
|
|
2938
|
-
<div class="${UI_module_default.
|
|
2996
|
+
<div class="${UI_module_default.shellInner}">
|
|
2939
2997
|
<div class="${UI_module_default.header}">
|
|
2940
2998
|
<div class="${UI_module_default.statusSection}">
|
|
2941
2999
|
<div class="${UI_module_default.indicator} ${UI_module_default.thinking}"></div>
|
|
@@ -2965,6 +3023,7 @@ var UI = class {
|
|
|
2965
3023
|
document.body.appendChild(wrapper);
|
|
2966
3024
|
return wrapper;
|
|
2967
3025
|
}
|
|
3026
|
+
/** 绑定所有 DOM 事件监听器(头部点击、展开按钮、动作按钮、回车提交等) */
|
|
2968
3027
|
#setupEventListeners() {
|
|
2969
3028
|
this.wrapper.querySelector(`.${UI_module_default.header}`).addEventListener("click", (e) => {
|
|
2970
3029
|
if (e.target.closest(`.${UI_module_default.controlButton}`)) return;
|
|
@@ -2989,17 +3048,18 @@ var UI = class {
|
|
|
2989
3048
|
e.stopPropagation();
|
|
2990
3049
|
});
|
|
2991
3050
|
}
|
|
3051
|
+
/** 在展开/收起状态之间切换 */
|
|
2992
3052
|
#toggle() {
|
|
2993
3053
|
if (this.#isExpanded) this.#collapse();
|
|
2994
3054
|
else this.#expand();
|
|
2995
3055
|
}
|
|
2996
3056
|
/**
|
|
2997
|
-
*
|
|
3057
|
+
* 展开历史弹窗。
|
|
2998
3058
|
*
|
|
2999
|
-
* `max-height`
|
|
3000
|
-
*
|
|
3001
|
-
*
|
|
3002
|
-
*
|
|
3059
|
+
* `max-height` 由 JS 测量(而非动画到固定的 CSS 上限),因此
|
|
3060
|
+
* 过渡距离始终匹配真实内容高度 — 否则展开/收起会给人
|
|
3061
|
+
* 速度不一致的感觉(展开会提前达到真实高度并停滞,
|
|
3062
|
+
* 而收起每次都按完整上限做动画)。
|
|
3003
3063
|
*/
|
|
3004
3064
|
#expand() {
|
|
3005
3065
|
this.#isExpanded = true;
|
|
@@ -3007,19 +3067,20 @@ var UI = class {
|
|
|
3007
3067
|
this.#expandButton.textContent = "▲";
|
|
3008
3068
|
this.#syncHistoryHeight();
|
|
3009
3069
|
}
|
|
3070
|
+
/** 收起历史弹窗 */
|
|
3010
3071
|
#collapse() {
|
|
3011
3072
|
this.#isExpanded = false;
|
|
3012
3073
|
this.wrapper.classList.remove(UI_module_default.expanded);
|
|
3013
3074
|
this.#expandButton.textContent = "▼";
|
|
3014
3075
|
this.#historySectionWrapper.style.maxHeight = "0px";
|
|
3015
3076
|
}
|
|
3016
|
-
/**
|
|
3077
|
+
/** 将历史弹窗的 max-height 同步为当前(已封顶的)内容高度 */
|
|
3017
3078
|
#syncHistoryHeight() {
|
|
3018
3079
|
if (!this.#isExpanded) return;
|
|
3019
3080
|
this.#historySectionWrapper.style.maxHeight = `${this.#historySectionWrapper.scrollHeight}px`;
|
|
3020
3081
|
}
|
|
3021
3082
|
/**
|
|
3022
|
-
*
|
|
3083
|
+
* 启动周期性的头部更新循环
|
|
3023
3084
|
*/
|
|
3024
3085
|
#startHeaderUpdateLoop() {
|
|
3025
3086
|
this.#headerUpdateTimer = setInterval(() => {
|
|
@@ -3027,7 +3088,7 @@ var UI = class {
|
|
|
3027
3088
|
}, 450);
|
|
3028
3089
|
}
|
|
3029
3090
|
/**
|
|
3030
|
-
*
|
|
3091
|
+
* 停止周期性的头部更新循环
|
|
3031
3092
|
*/
|
|
3032
3093
|
#stopHeaderUpdateLoop() {
|
|
3033
3094
|
if (this.#headerUpdateTimer) {
|
|
@@ -3035,6 +3096,7 @@ var UI = class {
|
|
|
3035
3096
|
this.#headerUpdateTimer = null;
|
|
3036
3097
|
}
|
|
3037
3098
|
}
|
|
3099
|
+
/** 设置一个受控延时定时器(销毁后自动忽略回调) */
|
|
3038
3100
|
#setTimer(callback, delay) {
|
|
3039
3101
|
const timer = setTimeout(() => {
|
|
3040
3102
|
this.#timers.delete(timer);
|
|
@@ -3043,7 +3105,7 @@ var UI = class {
|
|
|
3043
3105
|
this.#timers.add(timer);
|
|
3044
3106
|
}
|
|
3045
3107
|
/**
|
|
3046
|
-
*
|
|
3108
|
+
* 检查头部是否需要更新,若当前未在动画中则触发动画
|
|
3047
3109
|
*/
|
|
3048
3110
|
#checkAndUpdateHeader() {
|
|
3049
3111
|
if (!this.#pendingHeaderText || this.#isAnimating) return;
|
|
@@ -3056,7 +3118,7 @@ var UI = class {
|
|
|
3056
3118
|
this.#animateTextChange(textToShow);
|
|
3057
3119
|
}
|
|
3058
3120
|
/**
|
|
3059
|
-
*
|
|
3121
|
+
* 以淡出/淡入效果动画切换文本
|
|
3060
3122
|
*/
|
|
3061
3123
|
#animateTextChange(newText) {
|
|
3062
3124
|
this.#isAnimating = true;
|
|
@@ -3071,24 +3133,26 @@ var UI = class {
|
|
|
3071
3133
|
}, 300);
|
|
3072
3134
|
}, 150);
|
|
3073
3135
|
}
|
|
3136
|
+
/** 更新状态指示器的样式类 */
|
|
3074
3137
|
#updateStatusIndicator(type) {
|
|
3075
3138
|
const variant = type === "running" ? "thinking" : type === "executing" ? "tool_executing" : type === "retrying" ? "retry" : type;
|
|
3076
3139
|
this.#indicator.className = UI_module_default.indicator;
|
|
3077
3140
|
if (variant !== "idle" && variant !== "stopped") this.#indicator.classList.add(UI_module_default[variant]);
|
|
3078
3141
|
}
|
|
3142
|
+
/** 将历史区域滚动到底部 */
|
|
3079
3143
|
#scrollToBottom() {
|
|
3080
3144
|
this.#setTimer(() => {
|
|
3081
3145
|
this.#historySection.scrollTop = this.#historySection.scrollHeight;
|
|
3082
3146
|
}, 0);
|
|
3083
3147
|
}
|
|
3084
3148
|
/**
|
|
3085
|
-
*
|
|
3149
|
+
* 直接从 agent.history 渲染历史
|
|
3086
3150
|
*
|
|
3087
|
-
*
|
|
3088
|
-
* 1.
|
|
3089
|
-
* 2.
|
|
3090
|
-
* 3.
|
|
3091
|
-
* 4.
|
|
3151
|
+
* 渲染内容:
|
|
3152
|
+
* 1. 任务(第一项,来自 agent.task)
|
|
3153
|
+
* 2. 反思卡片(评估、记忆、下一步目标)
|
|
3154
|
+
* 3. 工具执行及输出
|
|
3155
|
+
* 4. 观察
|
|
3092
3156
|
*/
|
|
3093
3157
|
#renderHistory() {
|
|
3094
3158
|
const items = [];
|
|
@@ -3100,6 +3164,7 @@ var UI = class {
|
|
|
3100
3164
|
this.#scrollToBottom();
|
|
3101
3165
|
this.#syncHistoryHeight();
|
|
3102
3166
|
}
|
|
3167
|
+
/** 生成任务卡片 HTML */
|
|
3103
3168
|
#createTaskCard(task) {
|
|
3104
3169
|
return createCard({
|
|
3105
3170
|
icon: "🎯",
|
|
@@ -3107,7 +3172,7 @@ var UI = class {
|
|
|
3107
3172
|
type: "input"
|
|
3108
3173
|
});
|
|
3109
3174
|
}
|
|
3110
|
-
/**
|
|
3175
|
+
/** 为一条历史事件生成卡片 */
|
|
3111
3176
|
#createHistoryCards(event) {
|
|
3112
3177
|
const cards = [];
|
|
3113
3178
|
const meta = event.type === "step" && event.stepIndex !== void 0 ? this.#i18n.t("ui.step", { number: (event.stepIndex + 1).toString() }) : void 0;
|
|
@@ -3150,7 +3215,7 @@ var UI = class {
|
|
|
3150
3215
|
}));
|
|
3151
3216
|
return cards;
|
|
3152
3217
|
}
|
|
3153
|
-
/**
|
|
3218
|
+
/** 为一个动作生成卡片 */
|
|
3154
3219
|
#createActionCards(action, meta) {
|
|
3155
3220
|
const cards = [];
|
|
3156
3221
|
if (action.name === "done") {
|
|
@@ -3197,7 +3262,7 @@ var UI = class {
|
|
|
3197
3262
|
//#endregion
|
|
3198
3263
|
//#region src/llm/errors.ts
|
|
3199
3264
|
/**
|
|
3200
|
-
*
|
|
3265
|
+
* LLM 调用相关的错误类型与错误处理。
|
|
3201
3266
|
*/
|
|
3202
3267
|
var InvokeErrorTypes = {
|
|
3203
3268
|
NETWORK_ERROR: "network_error",
|
|
@@ -3250,19 +3315,30 @@ var colorCodes = {
|
|
|
3250
3315
|
red: 31,
|
|
3251
3316
|
yellow: 33
|
|
3252
3317
|
};
|
|
3253
|
-
/**
|
|
3318
|
+
/**
|
|
3319
|
+
* 使用标准 ANSI 终端颜色转义序列格式化文本。
|
|
3320
|
+
* 给文本添加标准的 ANSI 终端颜色转义序列,使文本在终端中以指定颜色显示。
|
|
3321
|
+
* @param text 需要上色的原始文本
|
|
3322
|
+
* @param color 终端颜色名称
|
|
3323
|
+
* @param bold 是否加粗显示,默认 false
|
|
3324
|
+
* @returns 带 ANSI 颜色转义序列的文本
|
|
3325
|
+
*/
|
|
3254
3326
|
function formatTerminalText(text, color, bold = false) {
|
|
3255
3327
|
return `${bold ? "\x1B[1m" : ""}\u001B[${colorCodes[color]}m${text}\u001B[0m`;
|
|
3256
3328
|
}
|
|
3257
3329
|
//#endregion
|
|
3258
3330
|
//#region src/llm/utils.ts
|
|
3259
3331
|
/**
|
|
3260
|
-
*
|
|
3332
|
+
* LLM 集成的工具函数
|
|
3261
3333
|
*/
|
|
3262
3334
|
var debug = console.debug.bind(console, formatTerminalText("[LLM]", "gray"));
|
|
3263
3335
|
/**
|
|
3264
|
-
*
|
|
3265
|
-
*
|
|
3336
|
+
* 将 Zod schema 转换为 OpenAI 工具格式
|
|
3337
|
+
* 使用 Zod 4 原生 z.toJSONSchema()
|
|
3338
|
+
*
|
|
3339
|
+
* @param name - 工具名称
|
|
3340
|
+
* @param tool - 工具定义
|
|
3341
|
+
* @returns OpenAI 格式的工具定义
|
|
3266
3342
|
*/
|
|
3267
3343
|
function zodToOpenAITool(name, tool) {
|
|
3268
3344
|
return {
|
|
@@ -3275,17 +3351,21 @@ function zodToOpenAITool(name, tool) {
|
|
|
3275
3351
|
};
|
|
3276
3352
|
}
|
|
3277
3353
|
/**
|
|
3278
|
-
*
|
|
3354
|
+
* 为特定模型打补丁。仅修补已知的模型。
|
|
3279
3355
|
*
|
|
3280
3356
|
* @purpose
|
|
3281
|
-
* -
|
|
3282
|
-
* -
|
|
3283
|
-
* -
|
|
3284
|
-
* -
|
|
3285
|
-
* @note
|
|
3357
|
+
* - 调和各模型所接受的参数 schema 之间的差异。
|
|
3358
|
+
* - 禁用思考/推理,或在无法完全禁用时将其降到最低。
|
|
3359
|
+
* - 尽量减少返回的 token。
|
|
3360
|
+
* - 对已知的较小模型提高 temperature,以提升自动恢复的几率。
|
|
3361
|
+
* @note 若用户显式设置了 temperature 则尊重该设置
|
|
3362
|
+
*
|
|
3363
|
+
* @todo 需要针对不同厂商的补丁。
|
|
3364
|
+
* 本地托管和第三方托管的模型可能有不同的 schema。
|
|
3286
3365
|
*
|
|
3287
|
-
* @
|
|
3288
|
-
*
|
|
3366
|
+
* @param body - 请求体对象(会就地修改)
|
|
3367
|
+
* @param baseURL - 模型服务的基础 URL,用于识别 provider
|
|
3368
|
+
* @returns 修改后的请求体对象
|
|
3289
3369
|
*/
|
|
3290
3370
|
function modelPatch(body, baseURL) {
|
|
3291
3371
|
const model = body.model || "";
|
|
@@ -3402,19 +3482,25 @@ function modelPatch(body, baseURL) {
|
|
|
3402
3482
|
return body;
|
|
3403
3483
|
}
|
|
3404
3484
|
/**
|
|
3405
|
-
*
|
|
3485
|
+
* 检查给定的模型 ID 是否符合某个具体模型名称
|
|
3406
3486
|
*
|
|
3407
3487
|
* @note
|
|
3408
|
-
*
|
|
3409
|
-
*
|
|
3488
|
+
* 不同的模型服务商可能对同一模型使用不同的模型 ID。
|
|
3489
|
+
* 例如,OpenAI 的 `gpt-5.2` 可能被命名为:
|
|
3410
3490
|
*
|
|
3411
3491
|
* - `gpt-5.2-version`
|
|
3412
3492
|
* - `gpt-5_2-date`
|
|
3413
3493
|
* - `GPT-52-version-date`
|
|
3414
3494
|
* - `openai/gpt-5.2-chat`
|
|
3415
3495
|
*
|
|
3416
|
-
*
|
|
3417
|
-
*
|
|
3496
|
+
* 它们应被视为同一模型。
|
|
3497
|
+
* 将其归一化为 `gpt-52`
|
|
3498
|
+
*/
|
|
3499
|
+
/**
|
|
3500
|
+
* 归一化模型名称,将不同厂商对同一模型的命名统一为同一形式(如 `gpt-5.2` -> `gpt52`)
|
|
3501
|
+
*
|
|
3502
|
+
* @param modelName - 原始模型名称
|
|
3503
|
+
* @returns 归一化后的模型名称(小写、去掉前缀与特殊字符)
|
|
3418
3504
|
*/
|
|
3419
3505
|
function normalizeModelName(modelName) {
|
|
3420
3506
|
let normalizedName = modelName.toLowerCase();
|
|
@@ -3423,6 +3509,12 @@ function normalizeModelName(modelName) {
|
|
|
3423
3509
|
normalizedName = normalizedName.replace(/\./g, "");
|
|
3424
3510
|
return normalizedName;
|
|
3425
3511
|
}
|
|
3512
|
+
/**
|
|
3513
|
+
* 根据 baseURL 判断服务提供商(目前仅识别 openrouter)
|
|
3514
|
+
*
|
|
3515
|
+
* @param baseURL - 模型服务的基础 URL
|
|
3516
|
+
* @returns 'openrouter' 或 undefined
|
|
3517
|
+
*/
|
|
3426
3518
|
function getProvider(baseURL) {
|
|
3427
3519
|
if (!baseURL) return void 0;
|
|
3428
3520
|
try {
|
|
@@ -3435,10 +3527,10 @@ function getProvider(baseURL) {
|
|
|
3435
3527
|
//#endregion
|
|
3436
3528
|
//#region src/llm/OpenAICompatibleClient.ts
|
|
3437
3529
|
/**
|
|
3438
|
-
* OpenAI
|
|
3530
|
+
* OpenAI 客户端实现
|
|
3439
3531
|
*/
|
|
3440
3532
|
/**
|
|
3441
|
-
*
|
|
3533
|
+
* 兼容 OpenAI 的 API 客户端
|
|
3442
3534
|
*/
|
|
3443
3535
|
var OpenAICompatibleClient = class {
|
|
3444
3536
|
config;
|
|
@@ -3447,6 +3539,16 @@ var OpenAICompatibleClient = class {
|
|
|
3447
3539
|
this.config = config;
|
|
3448
3540
|
this.fetch = config.customFetch;
|
|
3449
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* 调用 LLM 接口并执行模型请求的工具
|
|
3544
|
+
* 流程:转换工具定义 -> 构建并转换请求体 -> 调用 API -> 处理 HTTP 错误 -> 解析并校验响应 -> 执行工具
|
|
3545
|
+
*
|
|
3546
|
+
* @param messages - 对话消息列表
|
|
3547
|
+
* @param tools - 可用工具集合
|
|
3548
|
+
* @param abortSignal - 中止信号,用于取消请求
|
|
3549
|
+
* @param options - 可选的调用选项
|
|
3550
|
+
* @returns 工具调用结果
|
|
3551
|
+
*/
|
|
3450
3552
|
async invoke(messages, tools, abortSignal, options) {
|
|
3451
3553
|
abortSignal?.throwIfAborted();
|
|
3452
3554
|
const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t));
|
|
@@ -3564,7 +3666,7 @@ var OpenAICompatibleClient = class {
|
|
|
3564
3666
|
//#endregion
|
|
3565
3667
|
//#region src/llm/LLM.ts
|
|
3566
3668
|
/**
|
|
3567
|
-
* LLM
|
|
3669
|
+
* LLM 模块
|
|
3568
3670
|
*/
|
|
3569
3671
|
var LLM = class extends EventTarget {
|
|
3570
3672
|
config;
|
|
@@ -3575,9 +3677,9 @@ var LLM = class extends EventTarget {
|
|
|
3575
3677
|
this.client = new OpenAICompatibleClient(this.config);
|
|
3576
3678
|
}
|
|
3577
3679
|
/**
|
|
3578
|
-
* -
|
|
3579
|
-
* -
|
|
3580
|
-
* -
|
|
3680
|
+
* - 调用一次 LLM API
|
|
3681
|
+
* - 调用一次工具
|
|
3682
|
+
* - 返回工具执行结果
|
|
3581
3683
|
*/
|
|
3582
3684
|
async invoke(messages, tools, abortSignal, options) {
|
|
3583
3685
|
return await withRetry(async () => this.client.invoke(messages, tools, abortSignal, options), {
|
|
@@ -3593,7 +3695,7 @@ var LLM = class extends EventTarget {
|
|
|
3593
3695
|
}
|
|
3594
3696
|
};
|
|
3595
3697
|
/**
|
|
3596
|
-
*
|
|
3698
|
+
* 重试某个函数,直到成功或达到最大重试次数。
|
|
3597
3699
|
*/
|
|
3598
3700
|
async function withRetry(fn, settings) {
|
|
3599
3701
|
let attempt = 0;
|
|
@@ -3609,6 +3711,12 @@ async function withRetry(fn, settings) {
|
|
|
3609
3711
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3610
3712
|
}
|
|
3611
3713
|
}
|
|
3714
|
+
/**
|
|
3715
|
+
* 解析并校验 LLM 配置,填充默认值后返回完整配置
|
|
3716
|
+
*
|
|
3717
|
+
* @param config - 用户传入的 LLM 配置
|
|
3718
|
+
* @returns 解析后的完整配置
|
|
3719
|
+
*/
|
|
3612
3720
|
function parseLLMConfig(config) {
|
|
3613
3721
|
if (!config.baseURL || !config.model) throw new Error("[PageAgent] LLM configuration required. Please provide: baseURL, model. See: https://alibaba.github.io/page-agent/docs/features/models");
|
|
3614
3722
|
if (config.temperature !== void 0) console.warn("[PageAgent] LLMConfig.temperature is deprecated and will be removed in a future version. Use transformRequestBody to set it only for models you have verified accept it.");
|
|
@@ -3630,16 +3738,16 @@ var system_prompt_default = "You are an AI agent designed to operate in an itera
|
|
|
3630
3738
|
//#region src/agent/utils/autoFixer.ts
|
|
3631
3739
|
var log = console.log.bind(console, formatTerminalText("[autoFixer]", "yellow"));
|
|
3632
3740
|
/**
|
|
3633
|
-
*
|
|
3741
|
+
* 规范化 LLM 响应并修复常见的格式问题。
|
|
3634
3742
|
*
|
|
3635
|
-
*
|
|
3636
|
-
* -
|
|
3637
|
-
* -
|
|
3638
|
-
* -
|
|
3639
|
-
* -
|
|
3640
|
-
* -
|
|
3641
|
-
* -
|
|
3642
|
-
* -
|
|
3743
|
+
* 处理:
|
|
3744
|
+
* - 没有 tool_calls 但 message.content 中含 JSON(回退)
|
|
3745
|
+
* - 模型将动作名作为工具调用返回而非 AgentOutput
|
|
3746
|
+
* - 参数被双重 JSON 字符串化
|
|
3747
|
+
* - 嵌套的函数调用格式
|
|
3748
|
+
* - 缺少 action 字段(回退为 wait)
|
|
3749
|
+
* - 单字段工具的原始动作输入(如 `{"click_element_by_index": 2}`)
|
|
3750
|
+
* - 等等
|
|
3643
3751
|
*/
|
|
3644
3752
|
function normalizeResponse(response, tools) {
|
|
3645
3753
|
let resolvedArguments;
|
|
@@ -3698,11 +3806,11 @@ function normalizeResponse(response, tools) {
|
|
|
3698
3806
|
};
|
|
3699
3807
|
}
|
|
3700
3808
|
/**
|
|
3701
|
-
*
|
|
3702
|
-
*
|
|
3809
|
+
* 根据工具 schema 校验动作。提供清晰的错误信息,
|
|
3810
|
+
* 而不是让联合 schema 产生难以阅读的错误。
|
|
3703
3811
|
*
|
|
3704
|
-
*
|
|
3705
|
-
*
|
|
3812
|
+
* 同时为单字段工具强制转换原始输入:
|
|
3813
|
+
* 如 `{"click_element_by_index": 2}` → `{"click_element_by_index": {"index": 2}}`
|
|
3706
3814
|
*/
|
|
3707
3815
|
function validateAction(action, tools) {
|
|
3708
3816
|
if (typeof action !== "object" || action === null) return action;
|
|
@@ -3727,7 +3835,7 @@ function validateAction(action, tools) {
|
|
|
3727
3835
|
return { [toolName]: result.data };
|
|
3728
3836
|
}
|
|
3729
3837
|
/**
|
|
3730
|
-
*
|
|
3838
|
+
* 安全解析 JSON,如果不是 JSON 则返回原始输入。
|
|
3731
3839
|
*/
|
|
3732
3840
|
function safeJsonParse(input) {
|
|
3733
3841
|
if (typeof input === "string") try {
|
|
@@ -3738,9 +3846,9 @@ function safeJsonParse(input) {
|
|
|
3738
3846
|
return input;
|
|
3739
3847
|
}
|
|
3740
3848
|
/**
|
|
3741
|
-
*
|
|
3742
|
-
* -
|
|
3743
|
-
* -
|
|
3849
|
+
* 从字符串中提取并解析 JSON。
|
|
3850
|
+
* - 将第一个 `{` 与最后一个 `}` 之间的内容视为 JSON。
|
|
3851
|
+
* - 尝试将该内容解析为 JSON 并返回解析后的值(对象/数组/原始值),成功则返回,否则返回 null。
|
|
3744
3852
|
*/
|
|
3745
3853
|
function retrieveJsonFromString(str) {
|
|
3746
3854
|
try {
|
|
@@ -3754,8 +3862,8 @@ function retrieveJsonFromString(str) {
|
|
|
3754
3862
|
//#endregion
|
|
3755
3863
|
//#region src/agent/utils/index.ts
|
|
3756
3864
|
/**
|
|
3757
|
-
*
|
|
3758
|
-
*
|
|
3865
|
+
* 等待 `seconds` 秒。若提供了 `signal`,则等待可被取消:
|
|
3866
|
+
* 中止时会以 signal 的 reason(一个 `AbortError`)reject。
|
|
3759
3867
|
*/
|
|
3760
3868
|
async function waitFor(seconds, signal) {
|
|
3761
3869
|
if (!signal) {
|
|
@@ -3795,8 +3903,8 @@ var _global = globalThis;
|
|
|
3795
3903
|
if (!_global.__PAGE_AGENT_IDS__) _global.__PAGE_AGENT_IDS__ = [];
|
|
3796
3904
|
var ids = _global.__PAGE_AGENT_IDS__;
|
|
3797
3905
|
/**
|
|
3798
|
-
*
|
|
3799
|
-
* @note
|
|
3906
|
+
* 生成一个随机 ID。
|
|
3907
|
+
* @note 在当前窗口内唯一。
|
|
3800
3908
|
*/
|
|
3801
3909
|
function uid() {
|
|
3802
3910
|
const id = randomID(ids);
|
|
@@ -3804,7 +3912,7 @@ function uid() {
|
|
|
3804
3912
|
return id;
|
|
3805
3913
|
}
|
|
3806
3914
|
var llmsTxtCache = /* @__PURE__ */ new Map();
|
|
3807
|
-
/**
|
|
3915
|
+
/** 获取某个 URL 源的 /llms.txt。按源缓存,`null` 表示已尝试过但未找到。 */
|
|
3808
3916
|
async function fetchLlmsTxt(url) {
|
|
3809
3917
|
let origin;
|
|
3810
3918
|
try {
|
|
@@ -3834,10 +3942,10 @@ async function fetchLlmsTxt(url) {
|
|
|
3834
3942
|
return result;
|
|
3835
3943
|
}
|
|
3836
3944
|
/**
|
|
3837
|
-
*
|
|
3838
|
-
* @param condition -
|
|
3839
|
-
* @param message -
|
|
3840
|
-
* @throws
|
|
3945
|
+
* 简单的断言函数:条件为假时抛出错误
|
|
3946
|
+
* @param condition - 要断言的条件
|
|
3947
|
+
* @param message - 可选的错误信息
|
|
3948
|
+
* @throws 条件为假时抛出错误
|
|
3841
3949
|
*/
|
|
3842
3950
|
function assert(condition, message, silent) {
|
|
3843
3951
|
if (!condition) {
|
|
@@ -3847,7 +3955,7 @@ function assert(condition, message, silent) {
|
|
|
3847
3955
|
}
|
|
3848
3956
|
}
|
|
3849
3957
|
/**
|
|
3850
|
-
*
|
|
3958
|
+
* 抑制函数产生的错误。
|
|
3851
3959
|
*/
|
|
3852
3960
|
async function suppress(fn) {
|
|
3853
3961
|
try {
|
|
@@ -3860,15 +3968,16 @@ async function suppress(fn) {
|
|
|
3860
3968
|
//#endregion
|
|
3861
3969
|
//#region src/agent/tools/index.ts
|
|
3862
3970
|
/**
|
|
3863
|
-
*
|
|
3864
|
-
* @note
|
|
3971
|
+
* PageAgent 的内部工具。
|
|
3972
|
+
* @note 改编自 browser-use
|
|
3865
3973
|
*/
|
|
3974
|
+
/** 创建一个工具定义(直接透传 options) */
|
|
3866
3975
|
function tool(options) {
|
|
3867
3976
|
return options;
|
|
3868
3977
|
}
|
|
3869
3978
|
/**
|
|
3870
|
-
*
|
|
3871
|
-
*
|
|
3979
|
+
* PageAgent 的内部工具。
|
|
3980
|
+
* 注:使用 any 以允许每个工具拥有不同的参数类型
|
|
3872
3981
|
*/
|
|
3873
3982
|
var tools = /* @__PURE__ */ new Map();
|
|
3874
3983
|
tools.set("done", tool({
|
|
@@ -3929,7 +4038,7 @@ tools.set("select_dropdown_option", tool({
|
|
|
3929
4038
|
}
|
|
3930
4039
|
}));
|
|
3931
4040
|
/**
|
|
3932
|
-
* @note
|
|
4041
|
+
* @note 参考自 browser-use
|
|
3933
4042
|
*/
|
|
3934
4043
|
tools.set("scroll", tool({
|
|
3935
4044
|
description: "Scroll vertically. Without index: scrolls the document. With index: scrolls the container at that index (or its nearest scrollable ancestor). Use index of a data-scrollable element to scroll a specific area.",
|
|
@@ -3947,7 +4056,7 @@ tools.set("scroll", tool({
|
|
|
3947
4056
|
}
|
|
3948
4057
|
}));
|
|
3949
4058
|
/**
|
|
3950
|
-
* @todo
|
|
4059
|
+
* @todo 表格需要专门的解析器来提取结构化数据。这个工具有点鸡肋。
|
|
3951
4060
|
*/
|
|
3952
4061
|
tools.set("scroll_horizontally", tool({
|
|
3953
4062
|
description: "Scroll horizontally. Without index: scrolls the document. With index: scrolls the container at that index (or its nearest scrollable ancestor). Use index of a data-scrollable element to scroll a specific area.",
|
|
@@ -3972,76 +4081,84 @@ tools.set("execute_javascript", tool({
|
|
|
3972
4081
|
//#endregion
|
|
3973
4082
|
//#region src/agent/AgentRuntime.ts
|
|
3974
4083
|
/**
|
|
3975
|
-
* AI
|
|
4084
|
+
* 用于浏览器自动化的 AI 智能体。
|
|
3976
4085
|
*
|
|
3977
4086
|
* @remarks
|
|
3978
|
-
* ##
|
|
3979
|
-
* -
|
|
3980
|
-
* -
|
|
3981
|
-
* -
|
|
3982
|
-
* -
|
|
3983
|
-
* -
|
|
3984
|
-
* -
|
|
3985
|
-
* -
|
|
4087
|
+
* ## 反应式 Agent 循环
|
|
4088
|
+
* - 步骤
|
|
4089
|
+
* - 观察(收集当前环境与上下文信息)
|
|
4090
|
+
* - 思考(调用 LLM)
|
|
4091
|
+
* - 反思(评估历史、生成记忆、短期规划)
|
|
4092
|
+
* - 行动(给出接近下一个目标的动作)
|
|
4093
|
+
* - 执行(执行动作)
|
|
4094
|
+
* - 循环
|
|
3986
4095
|
*
|
|
3987
|
-
* ##
|
|
3988
|
-
* - `statuschange` - Agent
|
|
3989
|
-
* - `historychange` -
|
|
3990
|
-
* - `activity` -
|
|
3991
|
-
* - `dispose` - Agent
|
|
4096
|
+
* ## 事件系统
|
|
4097
|
+
* - `statuschange` - Agent 状态变更(idle → running → completed/error/stopped)
|
|
4098
|
+
* - `historychange` - 历史事件更新(持久化,属于 Agent 记忆的一部分)
|
|
4099
|
+
* - `activity` - 实时活动反馈(瞬态,仅用于 UI)
|
|
4100
|
+
* - `dispose` - 触发 Agent 清理
|
|
3992
4101
|
*
|
|
3993
|
-
* ##
|
|
3994
|
-
* 1.
|
|
3995
|
-
* -
|
|
3996
|
-
* -
|
|
3997
|
-
* -
|
|
4102
|
+
* ## 信息流
|
|
4103
|
+
* 1. **历史事件**(`history` 数组)
|
|
4104
|
+
* - 构成 Agent 记忆的持久事件流
|
|
4105
|
+
* - 跨步骤包含在 LLM 上下文中
|
|
4106
|
+
* - 类型:步骤、观察、用户接管、LLM 错误
|
|
3998
4107
|
*
|
|
3999
|
-
* 2.
|
|
4000
|
-
* -
|
|
4001
|
-
* -
|
|
4002
|
-
* -
|
|
4108
|
+
* 2. **活动事件**(通过 `activity` 事件)
|
|
4109
|
+
* - 任务执行期间的瞬时 UI 反馈
|
|
4110
|
+
* - 不包含在 LLM 上下文中
|
|
4111
|
+
* - 类型:thinking、executing、executed、retrying、error
|
|
4003
4112
|
*/
|
|
4004
4113
|
var AgentRuntime = class extends EventTarget {
|
|
4114
|
+
/** Agent 唯一 ID */
|
|
4005
4115
|
id = uid();
|
|
4116
|
+
/** 合并后的 Agent 配置(含最大步数上限) */
|
|
4006
4117
|
config;
|
|
4118
|
+
/** 内部工具集合(按工具名索引) */
|
|
4007
4119
|
tools;
|
|
4008
|
-
/**
|
|
4120
|
+
/** 用于 DOM 操作的 BrowserController */
|
|
4009
4121
|
browserController;
|
|
4122
|
+
/** 当前任务描述 */
|
|
4010
4123
|
task = "";
|
|
4124
|
+
/** 当前任务 ID */
|
|
4011
4125
|
taskId = "";
|
|
4012
|
-
/**
|
|
4126
|
+
/** 历史事件 */
|
|
4013
4127
|
history = [];
|
|
4014
|
-
/**
|
|
4128
|
+
/** 该 Agent 是否已被销毁 */
|
|
4015
4129
|
disposed = false;
|
|
4016
4130
|
/**
|
|
4017
|
-
*
|
|
4018
|
-
*
|
|
4019
|
-
*
|
|
4131
|
+
* 当 Agent 需要向用户提问时调用。
|
|
4132
|
+
* 若未设置,`ask_user` 工具将被禁用。
|
|
4133
|
+
* 实现应在 `signal` 中止时 reject 该 Promise。
|
|
4020
4134
|
* @example onAskUser: (q) => window.prompt(q) || ''
|
|
4021
4135
|
*/
|
|
4022
4136
|
onAskUser;
|
|
4137
|
+
/** 当前 Agent 状态 */
|
|
4023
4138
|
#status = "idle";
|
|
4139
|
+
/** LLM 实例(负责模型调用) */
|
|
4024
4140
|
#llm;
|
|
4025
4141
|
/**
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4028
|
-
*
|
|
4029
|
-
* stays a standard `AbortError`.
|
|
4142
|
+
* 任务取消原语:其 signal 会传递给 LLM fetch、工具(通过 `ctx.signal`)和异步回调。
|
|
4143
|
+
* 仅由 `stop`/`dispose`(任务进行中)或任务初始化时中止,且始终不带 reason,
|
|
4144
|
+
* 这样 `signal.reason` 保持为标准的 `AbortError`。
|
|
4030
4145
|
*/
|
|
4031
4146
|
#abortController = new AbortController();
|
|
4147
|
+
/** 本轮任务收集的观察消息(暂存,稍后统一写入历史) */
|
|
4032
4148
|
#observations = [];
|
|
4033
|
-
/**
|
|
4149
|
+
/** 当前一次运行完全结束时 resolve。由 `stop()` 等待。 */
|
|
4034
4150
|
#running = Promise.resolve();
|
|
4035
4151
|
#lastResult = null;
|
|
4036
|
-
/**
|
|
4152
|
+
/** 单次任务执行期间的内部状态 */
|
|
4037
4153
|
#states = {
|
|
4038
|
-
/**
|
|
4154
|
+
/** 累计等待时间(秒) */
|
|
4039
4155
|
totalWaitTime: 0,
|
|
4040
|
-
/**
|
|
4156
|
+
/** 用于检测页面导航 */
|
|
4041
4157
|
lastURL: "",
|
|
4042
|
-
/**
|
|
4158
|
+
/** 浏览器状态 */
|
|
4043
4159
|
browserState: null
|
|
4044
4160
|
};
|
|
4161
|
+
/** 构造函数:初始化配置、LLM、工具集,并注册重试事件监听与注入自定义工具 */
|
|
4045
4162
|
constructor(config) {
|
|
4046
4163
|
super();
|
|
4047
4164
|
this.config = {
|
|
@@ -4080,31 +4197,31 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4080
4197
|
}
|
|
4081
4198
|
if (!this.config.experimentalScriptExecutionTool) this.tools.delete("execute_javascript");
|
|
4082
4199
|
}
|
|
4083
|
-
/**
|
|
4200
|
+
/** 获取当前 Agent 状态 */
|
|
4084
4201
|
get status() {
|
|
4085
4202
|
return this.#status;
|
|
4086
4203
|
}
|
|
4087
|
-
/**
|
|
4204
|
+
/** 最近一次运行的结果,首次运行完成前为 `null`。 */
|
|
4088
4205
|
get lastResult() {
|
|
4089
4206
|
return this.#lastResult;
|
|
4090
4207
|
}
|
|
4091
|
-
/**
|
|
4208
|
+
/** 触发 statuschange 事件 */
|
|
4092
4209
|
#emitStatusChange() {
|
|
4093
4210
|
this.dispatchEvent(new Event("statuschange"));
|
|
4094
4211
|
}
|
|
4095
|
-
/**
|
|
4212
|
+
/** 触发 historychange 事件 */
|
|
4096
4213
|
#emitHistoryChange(pushHistoricalEvent) {
|
|
4097
4214
|
if (pushHistoricalEvent) this.history.push(pushHistoricalEvent);
|
|
4098
4215
|
this.dispatchEvent(new Event("historychange"));
|
|
4099
4216
|
}
|
|
4100
4217
|
/**
|
|
4101
|
-
*
|
|
4102
|
-
* @param activity -
|
|
4218
|
+
* 触发 activity 事件 - 用于瞬态的 UI 反馈
|
|
4219
|
+
* @param activity - 当前的 Agent 活动
|
|
4103
4220
|
*/
|
|
4104
4221
|
#emitActivity(activity) {
|
|
4105
4222
|
this.dispatchEvent(new CustomEvent("activity", { detail: activity }));
|
|
4106
4223
|
}
|
|
4107
|
-
/**
|
|
4224
|
+
/** 更新状态并触发事件 */
|
|
4108
4225
|
#setStatus(status) {
|
|
4109
4226
|
if (this.#status !== status) {
|
|
4110
4227
|
this.#status = status;
|
|
@@ -4112,17 +4229,17 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4112
4229
|
}
|
|
4113
4230
|
}
|
|
4114
4231
|
/**
|
|
4115
|
-
*
|
|
4116
|
-
*
|
|
4232
|
+
* 向历史事件流推送一条观察消息。
|
|
4233
|
+
* 它会出现在 <agent_history> 中,并在跨步骤的记忆中保持持久。
|
|
4117
4234
|
* @experimental @internal
|
|
4118
|
-
* @note
|
|
4235
|
+
* @note 历史变更会在下一步开始前触发
|
|
4119
4236
|
*/
|
|
4120
4237
|
pushObservation(content) {
|
|
4121
4238
|
this.#observations.push(content);
|
|
4122
4239
|
}
|
|
4123
4240
|
/**
|
|
4124
|
-
*
|
|
4125
|
-
* @note
|
|
4241
|
+
* 停止当前任务,并等待运行完全结束(包括生命周期钩子)。
|
|
4242
|
+
* @note 切勿在生命周期钩子中 await .stop()。
|
|
4126
4243
|
*/
|
|
4127
4244
|
async stop() {
|
|
4128
4245
|
if (this.#status !== "running") return;
|
|
@@ -4130,8 +4247,8 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4130
4247
|
await this.#running;
|
|
4131
4248
|
}
|
|
4132
4249
|
/**
|
|
4133
|
-
*
|
|
4134
|
-
*
|
|
4250
|
+
* 外部错误(前置检查/配置/钩子)会直接抛出;
|
|
4251
|
+
* Agent 内部错误会被捕获并加入历史,同时返回失败结果
|
|
4135
4252
|
*/
|
|
4136
4253
|
async execute(task) {
|
|
4137
4254
|
if (this.disposed) throw new Error("PageAgent has been disposed. Create a new instance.");
|
|
@@ -4289,13 +4406,13 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4289
4406
|
}
|
|
4290
4407
|
}
|
|
4291
4408
|
/**
|
|
4292
|
-
*
|
|
4409
|
+
* 将所有工具合并为单个 MacroTool,其输入如下:
|
|
4293
4410
|
* - thinking: string
|
|
4294
4411
|
* - evaluation_previous_goal: string
|
|
4295
4412
|
* - memory: string
|
|
4296
4413
|
* - next_goal: string
|
|
4297
4414
|
* - action: { toolName: toolInput }
|
|
4298
|
-
*
|
|
4415
|
+
* 其中 action 必须从 this.tools 中定义的工具中选择
|
|
4299
4416
|
*/
|
|
4300
4417
|
#packMacroTool() {
|
|
4301
4418
|
const tools = this.tools;
|
|
@@ -4354,7 +4471,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4354
4471
|
};
|
|
4355
4472
|
}
|
|
4356
4473
|
/**
|
|
4357
|
-
*
|
|
4474
|
+
* 获取系统提示词,根据配置的语言动态替换语言设置
|
|
4358
4475
|
*/
|
|
4359
4476
|
#getSystemPrompt() {
|
|
4360
4477
|
if (this.config.customSystemPrompt) return this.config.customSystemPrompt;
|
|
@@ -4362,7 +4479,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4362
4479
|
return system_prompt_default.replace(/Default working language: \*\*.*?\*\*/, `Default working language: **${targetLanguage}**`);
|
|
4363
4480
|
}
|
|
4364
4481
|
/**
|
|
4365
|
-
*
|
|
4482
|
+
* 从配置中获取指令
|
|
4366
4483
|
*/
|
|
4367
4484
|
async #getInstructions() {
|
|
4368
4485
|
const { instructions, experimentalLlmsTxt } = this.config;
|
|
@@ -4384,8 +4501,8 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4384
4501
|
return result;
|
|
4385
4502
|
}
|
|
4386
4503
|
/**
|
|
4387
|
-
*
|
|
4388
|
-
* @todo
|
|
4504
|
+
* 在每一步之前生成系统观察
|
|
4505
|
+
* @todo 控制台错误
|
|
4389
4506
|
*/
|
|
4390
4507
|
async #handleObservations(step) {
|
|
4391
4508
|
if (this.#states.totalWaitTime >= 3) this.pushObservation(`You have waited ${this.#states.totalWaitTime} seconds accumulatively. DO NOT wait any longer unless you have a good reason.`);
|
|
@@ -4415,6 +4532,9 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4415
4532
|
this.#emitHistoryChange();
|
|
4416
4533
|
}
|
|
4417
4534
|
}
|
|
4535
|
+
/**
|
|
4536
|
+
* 组装发给 LLM 的完整用户提示词(含指令、Agent 状态、历史、浏览器状态)
|
|
4537
|
+
*/
|
|
4418
4538
|
async #assembleUserPrompt() {
|
|
4419
4539
|
const browserState = this.#states.browserState;
|
|
4420
4540
|
let prompt = "";
|
|
@@ -4457,6 +4577,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4457
4577
|
prompt += "</browser_state>\n\n";
|
|
4458
4578
|
return prompt;
|
|
4459
4579
|
}
|
|
4580
|
+
/** 销毁 Agent:中止任务、释放浏览器控制器,并触发 dispose 事件(一次性) */
|
|
4460
4581
|
dispose() {
|
|
4461
4582
|
if (this.disposed) return;
|
|
4462
4583
|
this.disposed = true;
|
|
@@ -4479,8 +4600,13 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4479
4600
|
};
|
|
4480
4601
|
//#endregion
|
|
4481
4602
|
//#region src/agent/MyPageAgent.ts
|
|
4603
|
+
/**
|
|
4604
|
+
* 完整页面智能体:组合了 AgentRuntime、BrowserController 与 UI
|
|
4605
|
+
*/
|
|
4482
4606
|
var MyPageAgent = class extends AgentRuntime {
|
|
4607
|
+
/** UI 实例(用于用户交互与反馈) */
|
|
4483
4608
|
ui;
|
|
4609
|
+
/** 构造函数:创建浏览器控制器、父类运行时与 UI 实例 */
|
|
4484
4610
|
constructor(config) {
|
|
4485
4611
|
const browserController = new BrowserController({
|
|
4486
4612
|
...config,
|
|
@@ -4499,6 +4625,11 @@ var MyPageAgent = class extends AgentRuntime {
|
|
|
4499
4625
|
};
|
|
4500
4626
|
//#endregion
|
|
4501
4627
|
//#region src/index.ts
|
|
4628
|
+
/**
|
|
4629
|
+
* 创建智能体实例的快捷函数,内部直接实例化 MyPageAgent。
|
|
4630
|
+
* @param config 智能体的配置对象
|
|
4631
|
+
* @returns 创建好的 MyPageAgent 实例
|
|
4632
|
+
*/
|
|
4502
4633
|
function createAgent(config) {
|
|
4503
4634
|
return new MyPageAgent(config);
|
|
4504
4635
|
}
|