@coolkiller007/my-page-agent 0.1.13 → 0.1.15
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 +245 -198
- package/dist/index.js +449 -304
- 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;
|
|
@@ -2708,19 +2756,20 @@ var UI = class {
|
|
|
2708
2756
|
if (status === "running") {
|
|
2709
2757
|
this.show();
|
|
2710
2758
|
this.#hideInputArea();
|
|
2759
|
+
if (!this.#isExpanded) this.#expand();
|
|
2711
2760
|
}
|
|
2712
2761
|
if (status === "completed" || status === "error" || status === "stopped") {
|
|
2713
2762
|
if (!this.#isExpanded) this.#expand();
|
|
2714
2763
|
if (this.#shouldShowInputArea()) this.#showInputArea();
|
|
2715
2764
|
}
|
|
2716
2765
|
}
|
|
2717
|
-
/**
|
|
2766
|
+
/** 处理 agent 历史变化 - 根据 agent.history 重新渲染历史列表 */
|
|
2718
2767
|
#handleHistoryChange() {
|
|
2719
2768
|
this.#renderHistory();
|
|
2720
2769
|
}
|
|
2721
2770
|
/**
|
|
2722
|
-
*
|
|
2723
|
-
*
|
|
2771
|
+
* 处理 agent 活动 - 用于即时 UI 反馈的瞬时状态
|
|
2772
|
+
* 活动事件不会持久化到历史中,仅用于头部栏更新
|
|
2724
2773
|
*/
|
|
2725
2774
|
#handleActivity(activity) {
|
|
2726
2775
|
switch (activity.type) {
|
|
@@ -2745,9 +2794,9 @@ var UI = class {
|
|
|
2745
2794
|
}
|
|
2746
2795
|
}
|
|
2747
2796
|
/**
|
|
2748
|
-
*
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2797
|
+
* 请求用户输入(内部方法,由 agent 通过 onAskUser 调用)。
|
|
2798
|
+
* 当 `signal` 中止(任务被停止或销毁)时拒绝该请求,
|
|
2799
|
+
* 并清理问题卡片和挂起状态,以便 agent 循环能够收尾。
|
|
2751
2800
|
*/
|
|
2752
2801
|
#askUser(question, signal) {
|
|
2753
2802
|
if (this.#disposed) return Promise.reject(this.#createAbortError());
|
|
@@ -2777,9 +2826,11 @@ var UI = class {
|
|
|
2777
2826
|
}
|
|
2778
2827
|
});
|
|
2779
2828
|
}
|
|
2829
|
+
/** 创建标准的中止异常(AbortError) */
|
|
2780
2830
|
#createAbortError() {
|
|
2781
2831
|
return new DOMException("The operation was aborted.", "AbortError");
|
|
2782
2832
|
}
|
|
2833
|
+
/** 清空用户回答相关状态并移除临时卡片 */
|
|
2783
2834
|
#clearQuestionState() {
|
|
2784
2835
|
if (this.#userAnswerSignal && this.#userAnswerAbortHandler) this.#userAnswerSignal.removeEventListener("abort", this.#userAnswerAbortHandler);
|
|
2785
2836
|
this.#userAnswerSignal = null;
|
|
@@ -2789,30 +2840,34 @@ var UI = class {
|
|
|
2789
2840
|
this.#isWaitingForUserAnswer = false;
|
|
2790
2841
|
this.#removeTempCards();
|
|
2791
2842
|
}
|
|
2843
|
+
/** 拒绝挂起的用户问题(存在待回答问题时) */
|
|
2792
2844
|
#rejectPendingQuestion() {
|
|
2793
2845
|
const reject = this.#userAnswerRejecter;
|
|
2794
2846
|
if (!reject) return;
|
|
2795
2847
|
this.#clearQuestionState();
|
|
2796
2848
|
reject(this.#createAbortError());
|
|
2797
2849
|
}
|
|
2798
|
-
/**
|
|
2850
|
+
/** 移除临时问题卡片(为安全起见仅移除直接子节点) */
|
|
2799
2851
|
#removeTempCards() {
|
|
2800
2852
|
Array.from(this.#historySection.children).forEach((child) => {
|
|
2801
2853
|
if (child.getAttribute("data-temp-card") === "true") child.remove();
|
|
2802
2854
|
});
|
|
2803
2855
|
this.#syncHistoryHeight();
|
|
2804
2856
|
}
|
|
2857
|
+
/** 显示 UI(淡入 + 平移效果) */
|
|
2805
2858
|
show() {
|
|
2806
2859
|
this.wrapper.style.display = "block";
|
|
2807
2860
|
this.wrapper.offsetHeight;
|
|
2808
2861
|
this.wrapper.style.opacity = "1";
|
|
2809
2862
|
this.wrapper.style.transform = "translateX(-50%) translateY(0)";
|
|
2810
2863
|
}
|
|
2864
|
+
/** 隐藏 UI(淡出 + 下移效果) */
|
|
2811
2865
|
hide() {
|
|
2812
2866
|
this.wrapper.style.opacity = "0";
|
|
2813
2867
|
this.wrapper.style.transform = "translateX(-50%) translateY(20px)";
|
|
2814
2868
|
this.wrapper.style.display = "none";
|
|
2815
2869
|
}
|
|
2870
|
+
/** 重置 UI 状态(拒绝挂起问题、清空状态文本、收起历史并显示输入框) */
|
|
2816
2871
|
reset() {
|
|
2817
2872
|
this.#rejectPendingQuestion();
|
|
2818
2873
|
this.#statusText.textContent = this.#i18n.t("ui.ready");
|
|
@@ -2821,14 +2876,16 @@ var UI = class {
|
|
|
2821
2876
|
this.#collapse();
|
|
2822
2877
|
this.#showInputArea();
|
|
2823
2878
|
}
|
|
2879
|
+
/** 展开历史弹窗(公开方法) */
|
|
2824
2880
|
expand() {
|
|
2825
2881
|
this.#expand();
|
|
2826
2882
|
}
|
|
2883
|
+
/** 收起历史弹窗(公开方法) */
|
|
2827
2884
|
collapse() {
|
|
2828
2885
|
this.#collapse();
|
|
2829
2886
|
}
|
|
2830
2887
|
/**
|
|
2831
|
-
*
|
|
2888
|
+
* 销毁 UI 并清理事件监听器
|
|
2832
2889
|
*/
|
|
2833
2890
|
dispose() {
|
|
2834
2891
|
if (this.#disposed) return;
|
|
@@ -2845,6 +2902,7 @@ var UI = class {
|
|
|
2845
2902
|
this.#timers.clear();
|
|
2846
2903
|
this.wrapper.remove();
|
|
2847
2904
|
}
|
|
2905
|
+
/** 根据工具名称和参数生成"正在执行"提示文本 */
|
|
2848
2906
|
#getToolExecutingText(toolName, args) {
|
|
2849
2907
|
const a = args;
|
|
2850
2908
|
switch (toolName) {
|
|
@@ -2859,14 +2917,14 @@ var UI = class {
|
|
|
2859
2917
|
}
|
|
2860
2918
|
}
|
|
2861
2919
|
/**
|
|
2862
|
-
*
|
|
2920
|
+
* 动作按钮处理:运行中则停止,空闲则关闭(销毁)
|
|
2863
2921
|
*/
|
|
2864
2922
|
#handleActionButton() {
|
|
2865
2923
|
if (this.#agent.status === "running") this.#agent.stop();
|
|
2866
2924
|
else this.#agent.dispose();
|
|
2867
2925
|
}
|
|
2868
2926
|
/**
|
|
2869
|
-
*
|
|
2927
|
+
* 提交任务
|
|
2870
2928
|
*/
|
|
2871
2929
|
#submitTask() {
|
|
2872
2930
|
const input = this.#taskInput.value.trim();
|
|
@@ -2880,7 +2938,7 @@ var UI = class {
|
|
|
2880
2938
|
});
|
|
2881
2939
|
}
|
|
2882
2940
|
/**
|
|
2883
|
-
*
|
|
2941
|
+
* 处理用户回答
|
|
2884
2942
|
*/
|
|
2885
2943
|
#handleUserAnswer(input) {
|
|
2886
2944
|
this.#removeTempCards();
|
|
@@ -2889,7 +2947,7 @@ var UI = class {
|
|
|
2889
2947
|
resolve?.(input);
|
|
2890
2948
|
}
|
|
2891
2949
|
/**
|
|
2892
|
-
*
|
|
2950
|
+
* 显示输入区域
|
|
2893
2951
|
*/
|
|
2894
2952
|
#showInputArea(placeholder) {
|
|
2895
2953
|
this.#taskInput.value = "";
|
|
@@ -2900,13 +2958,13 @@ var UI = class {
|
|
|
2900
2958
|
}, 100);
|
|
2901
2959
|
}
|
|
2902
2960
|
/**
|
|
2903
|
-
*
|
|
2961
|
+
* 隐藏输入区域
|
|
2904
2962
|
*/
|
|
2905
2963
|
#hideInputArea() {
|
|
2906
2964
|
this.#inputSection.classList.add(UI_module_default.hidden);
|
|
2907
2965
|
}
|
|
2908
2966
|
/**
|
|
2909
|
-
*
|
|
2967
|
+
* 检查是否应显示输入区域
|
|
2910
2968
|
*/
|
|
2911
2969
|
#shouldShowInputArea() {
|
|
2912
2970
|
if (this.#isWaitingForUserAnswer) return true;
|
|
@@ -2915,6 +2973,7 @@ var UI = class {
|
|
|
2915
2973
|
if (status === "completed" || status === "error" || status === "stopped") return this.#config.promptForNextTask ?? true;
|
|
2916
2974
|
return false;
|
|
2917
2975
|
}
|
|
2976
|
+
/** 创建 UI 根元素并挂载到 body,返回根容器 */
|
|
2918
2977
|
#createWrapper() {
|
|
2919
2978
|
const taskInputMaxLength = 1e3;
|
|
2920
2979
|
const wrapper = document.createElement("div");
|
|
@@ -2933,9 +2992,9 @@ var UI = class {
|
|
|
2933
2992
|
</div>
|
|
2934
2993
|
</div>
|
|
2935
2994
|
</div>
|
|
2936
|
-
<div class="${UI_module_default.
|
|
2995
|
+
<div class="${UI_module_default.shell}">
|
|
2937
2996
|
<div class="${UI_module_default.background}"></div>
|
|
2938
|
-
<div class="${UI_module_default.
|
|
2997
|
+
<div class="${UI_module_default.shellInner}">
|
|
2939
2998
|
<div class="${UI_module_default.header}">
|
|
2940
2999
|
<div class="${UI_module_default.statusSection}">
|
|
2941
3000
|
<div class="${UI_module_default.indicator} ${UI_module_default.thinking}"></div>
|
|
@@ -2965,6 +3024,7 @@ var UI = class {
|
|
|
2965
3024
|
document.body.appendChild(wrapper);
|
|
2966
3025
|
return wrapper;
|
|
2967
3026
|
}
|
|
3027
|
+
/** 绑定所有 DOM 事件监听器(头部点击、展开按钮、动作按钮、回车提交等) */
|
|
2968
3028
|
#setupEventListeners() {
|
|
2969
3029
|
this.wrapper.querySelector(`.${UI_module_default.header}`).addEventListener("click", (e) => {
|
|
2970
3030
|
if (e.target.closest(`.${UI_module_default.controlButton}`)) return;
|
|
@@ -2989,17 +3049,18 @@ var UI = class {
|
|
|
2989
3049
|
e.stopPropagation();
|
|
2990
3050
|
});
|
|
2991
3051
|
}
|
|
3052
|
+
/** 在展开/收起状态之间切换 */
|
|
2992
3053
|
#toggle() {
|
|
2993
3054
|
if (this.#isExpanded) this.#collapse();
|
|
2994
3055
|
else this.#expand();
|
|
2995
3056
|
}
|
|
2996
3057
|
/**
|
|
2997
|
-
*
|
|
3058
|
+
* 展开历史弹窗。
|
|
2998
3059
|
*
|
|
2999
|
-
* `max-height`
|
|
3000
|
-
*
|
|
3001
|
-
*
|
|
3002
|
-
*
|
|
3060
|
+
* `max-height` 由 JS 测量(而非动画到固定的 CSS 上限),因此
|
|
3061
|
+
* 过渡距离始终匹配真实内容高度 — 否则展开/收起会给人
|
|
3062
|
+
* 速度不一致的感觉(展开会提前达到真实高度并停滞,
|
|
3063
|
+
* 而收起每次都按完整上限做动画)。
|
|
3003
3064
|
*/
|
|
3004
3065
|
#expand() {
|
|
3005
3066
|
this.#isExpanded = true;
|
|
@@ -3007,19 +3068,20 @@ var UI = class {
|
|
|
3007
3068
|
this.#expandButton.textContent = "▲";
|
|
3008
3069
|
this.#syncHistoryHeight();
|
|
3009
3070
|
}
|
|
3071
|
+
/** 收起历史弹窗 */
|
|
3010
3072
|
#collapse() {
|
|
3011
3073
|
this.#isExpanded = false;
|
|
3012
3074
|
this.wrapper.classList.remove(UI_module_default.expanded);
|
|
3013
3075
|
this.#expandButton.textContent = "▼";
|
|
3014
3076
|
this.#historySectionWrapper.style.maxHeight = "0px";
|
|
3015
3077
|
}
|
|
3016
|
-
/**
|
|
3078
|
+
/** 将历史弹窗的 max-height 同步为当前(已封顶的)内容高度 */
|
|
3017
3079
|
#syncHistoryHeight() {
|
|
3018
3080
|
if (!this.#isExpanded) return;
|
|
3019
3081
|
this.#historySectionWrapper.style.maxHeight = `${this.#historySectionWrapper.scrollHeight}px`;
|
|
3020
3082
|
}
|
|
3021
3083
|
/**
|
|
3022
|
-
*
|
|
3084
|
+
* 启动周期性的头部更新循环
|
|
3023
3085
|
*/
|
|
3024
3086
|
#startHeaderUpdateLoop() {
|
|
3025
3087
|
this.#headerUpdateTimer = setInterval(() => {
|
|
@@ -3027,7 +3089,7 @@ var UI = class {
|
|
|
3027
3089
|
}, 450);
|
|
3028
3090
|
}
|
|
3029
3091
|
/**
|
|
3030
|
-
*
|
|
3092
|
+
* 停止周期性的头部更新循环
|
|
3031
3093
|
*/
|
|
3032
3094
|
#stopHeaderUpdateLoop() {
|
|
3033
3095
|
if (this.#headerUpdateTimer) {
|
|
@@ -3035,6 +3097,7 @@ var UI = class {
|
|
|
3035
3097
|
this.#headerUpdateTimer = null;
|
|
3036
3098
|
}
|
|
3037
3099
|
}
|
|
3100
|
+
/** 设置一个受控延时定时器(销毁后自动忽略回调) */
|
|
3038
3101
|
#setTimer(callback, delay) {
|
|
3039
3102
|
const timer = setTimeout(() => {
|
|
3040
3103
|
this.#timers.delete(timer);
|
|
@@ -3043,7 +3106,7 @@ var UI = class {
|
|
|
3043
3106
|
this.#timers.add(timer);
|
|
3044
3107
|
}
|
|
3045
3108
|
/**
|
|
3046
|
-
*
|
|
3109
|
+
* 检查头部是否需要更新,若当前未在动画中则触发动画
|
|
3047
3110
|
*/
|
|
3048
3111
|
#checkAndUpdateHeader() {
|
|
3049
3112
|
if (!this.#pendingHeaderText || this.#isAnimating) return;
|
|
@@ -3056,7 +3119,7 @@ var UI = class {
|
|
|
3056
3119
|
this.#animateTextChange(textToShow);
|
|
3057
3120
|
}
|
|
3058
3121
|
/**
|
|
3059
|
-
*
|
|
3122
|
+
* 以淡出/淡入效果动画切换文本
|
|
3060
3123
|
*/
|
|
3061
3124
|
#animateTextChange(newText) {
|
|
3062
3125
|
this.#isAnimating = true;
|
|
@@ -3071,35 +3134,37 @@ var UI = class {
|
|
|
3071
3134
|
}, 300);
|
|
3072
3135
|
}, 150);
|
|
3073
3136
|
}
|
|
3137
|
+
/** 更新状态指示器的样式类 */
|
|
3074
3138
|
#updateStatusIndicator(type) {
|
|
3075
3139
|
const variant = type === "running" ? "thinking" : type === "executing" ? "tool_executing" : type === "retrying" ? "retry" : type;
|
|
3076
3140
|
this.#indicator.className = UI_module_default.indicator;
|
|
3077
3141
|
if (variant !== "idle" && variant !== "stopped") this.#indicator.classList.add(UI_module_default[variant]);
|
|
3078
3142
|
}
|
|
3143
|
+
/** 将历史区域滚动到底部 */
|
|
3079
3144
|
#scrollToBottom() {
|
|
3080
3145
|
this.#setTimer(() => {
|
|
3081
3146
|
this.#historySection.scrollTop = this.#historySection.scrollHeight;
|
|
3082
3147
|
}, 0);
|
|
3083
3148
|
}
|
|
3084
3149
|
/**
|
|
3085
|
-
*
|
|
3150
|
+
* 直接从 agent.history 渲染历史
|
|
3086
3151
|
*
|
|
3087
|
-
*
|
|
3088
|
-
* 1.
|
|
3089
|
-
* 2.
|
|
3090
|
-
* 3.
|
|
3091
|
-
* 4.
|
|
3152
|
+
* 渲染内容:
|
|
3153
|
+
* 1. 任务卡片(每个 task_start 事件对应一张,history 跨任务持久保留,因此可展示历次任务)
|
|
3154
|
+
* 2. 反思卡片(评估、记忆、下一步目标)
|
|
3155
|
+
* 3. 工具执行及输出
|
|
3156
|
+
* 4. 观察
|
|
3092
3157
|
*/
|
|
3093
3158
|
#renderHistory() {
|
|
3094
3159
|
const items = [];
|
|
3095
|
-
const task = this.#agent.task;
|
|
3096
|
-
if (task) items.push(this.#createTaskCard(task));
|
|
3097
3160
|
const history = this.#agent.history;
|
|
3161
|
+
if (history.length === 0 && this.#agent.task) items.push(this.#createTaskCard(this.#agent.task));
|
|
3098
3162
|
for (const event of history) items.push(...this.#createHistoryCards(event));
|
|
3099
3163
|
this.#historySection.innerHTML = items.join("");
|
|
3100
3164
|
this.#scrollToBottom();
|
|
3101
3165
|
this.#syncHistoryHeight();
|
|
3102
3166
|
}
|
|
3167
|
+
/** 生成任务卡片 HTML */
|
|
3103
3168
|
#createTaskCard(task) {
|
|
3104
3169
|
return createCard({
|
|
3105
3170
|
icon: "🎯",
|
|
@@ -3107,11 +3172,13 @@ 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;
|
|
3114
|
-
if (event.type === "
|
|
3179
|
+
if (event.type === "task_start") {
|
|
3180
|
+
if (event.task) cards.push(this.#createTaskCard(event.task));
|
|
3181
|
+
} else if (event.type === "step") {
|
|
3115
3182
|
if (event.reflection) {
|
|
3116
3183
|
const lines = createReflectionLines(event.reflection);
|
|
3117
3184
|
if (lines.length > 0) cards.push(createCard({
|
|
@@ -3150,7 +3217,7 @@ var UI = class {
|
|
|
3150
3217
|
}));
|
|
3151
3218
|
return cards;
|
|
3152
3219
|
}
|
|
3153
|
-
/**
|
|
3220
|
+
/** 为一个动作生成卡片 */
|
|
3154
3221
|
#createActionCards(action, meta) {
|
|
3155
3222
|
const cards = [];
|
|
3156
3223
|
if (action.name === "done") {
|
|
@@ -3197,7 +3264,7 @@ var UI = class {
|
|
|
3197
3264
|
//#endregion
|
|
3198
3265
|
//#region src/llm/errors.ts
|
|
3199
3266
|
/**
|
|
3200
|
-
*
|
|
3267
|
+
* LLM 调用相关的错误类型与错误处理。
|
|
3201
3268
|
*/
|
|
3202
3269
|
var InvokeErrorTypes = {
|
|
3203
3270
|
NETWORK_ERROR: "network_error",
|
|
@@ -3250,19 +3317,30 @@ var colorCodes = {
|
|
|
3250
3317
|
red: 31,
|
|
3251
3318
|
yellow: 33
|
|
3252
3319
|
};
|
|
3253
|
-
/**
|
|
3320
|
+
/**
|
|
3321
|
+
* 使用标准 ANSI 终端颜色转义序列格式化文本。
|
|
3322
|
+
* 给文本添加标准的 ANSI 终端颜色转义序列,使文本在终端中以指定颜色显示。
|
|
3323
|
+
* @param text 需要上色的原始文本
|
|
3324
|
+
* @param color 终端颜色名称
|
|
3325
|
+
* @param bold 是否加粗显示,默认 false
|
|
3326
|
+
* @returns 带 ANSI 颜色转义序列的文本
|
|
3327
|
+
*/
|
|
3254
3328
|
function formatTerminalText(text, color, bold = false) {
|
|
3255
3329
|
return `${bold ? "\x1B[1m" : ""}\u001B[${colorCodes[color]}m${text}\u001B[0m`;
|
|
3256
3330
|
}
|
|
3257
3331
|
//#endregion
|
|
3258
3332
|
//#region src/llm/utils.ts
|
|
3259
3333
|
/**
|
|
3260
|
-
*
|
|
3334
|
+
* LLM 集成的工具函数
|
|
3261
3335
|
*/
|
|
3262
3336
|
var debug = console.debug.bind(console, formatTerminalText("[LLM]", "gray"));
|
|
3263
3337
|
/**
|
|
3264
|
-
*
|
|
3265
|
-
*
|
|
3338
|
+
* 将 Zod schema 转换为 OpenAI 工具格式
|
|
3339
|
+
* 使用 Zod 4 原生 z.toJSONSchema()
|
|
3340
|
+
*
|
|
3341
|
+
* @param name - 工具名称
|
|
3342
|
+
* @param tool - 工具定义
|
|
3343
|
+
* @returns OpenAI 格式的工具定义
|
|
3266
3344
|
*/
|
|
3267
3345
|
function zodToOpenAITool(name, tool) {
|
|
3268
3346
|
return {
|
|
@@ -3275,17 +3353,21 @@ function zodToOpenAITool(name, tool) {
|
|
|
3275
3353
|
};
|
|
3276
3354
|
}
|
|
3277
3355
|
/**
|
|
3278
|
-
*
|
|
3356
|
+
* 为特定模型打补丁。仅修补已知的模型。
|
|
3279
3357
|
*
|
|
3280
3358
|
* @purpose
|
|
3281
|
-
* -
|
|
3282
|
-
* -
|
|
3283
|
-
* -
|
|
3284
|
-
* -
|
|
3285
|
-
* @note
|
|
3359
|
+
* - 调和各模型所接受的参数 schema 之间的差异。
|
|
3360
|
+
* - 禁用思考/推理,或在无法完全禁用时将其降到最低。
|
|
3361
|
+
* - 尽量减少返回的 token。
|
|
3362
|
+
* - 对已知的较小模型提高 temperature,以提升自动恢复的几率。
|
|
3363
|
+
* @note 若用户显式设置了 temperature 则尊重该设置
|
|
3286
3364
|
*
|
|
3287
|
-
* @todo
|
|
3288
|
-
*
|
|
3365
|
+
* @todo 需要针对不同厂商的补丁。
|
|
3366
|
+
* 本地托管和第三方托管的模型可能有不同的 schema。
|
|
3367
|
+
*
|
|
3368
|
+
* @param body - 请求体对象(会就地修改)
|
|
3369
|
+
* @param baseURL - 模型服务的基础 URL,用于识别 provider
|
|
3370
|
+
* @returns 修改后的请求体对象
|
|
3289
3371
|
*/
|
|
3290
3372
|
function modelPatch(body, baseURL) {
|
|
3291
3373
|
const model = body.model || "";
|
|
@@ -3402,19 +3484,25 @@ function modelPatch(body, baseURL) {
|
|
|
3402
3484
|
return body;
|
|
3403
3485
|
}
|
|
3404
3486
|
/**
|
|
3405
|
-
*
|
|
3487
|
+
* 检查给定的模型 ID 是否符合某个具体模型名称
|
|
3406
3488
|
*
|
|
3407
3489
|
* @note
|
|
3408
|
-
*
|
|
3409
|
-
*
|
|
3490
|
+
* 不同的模型服务商可能对同一模型使用不同的模型 ID。
|
|
3491
|
+
* 例如,OpenAI 的 `gpt-5.2` 可能被命名为:
|
|
3410
3492
|
*
|
|
3411
3493
|
* - `gpt-5.2-version`
|
|
3412
3494
|
* - `gpt-5_2-date`
|
|
3413
3495
|
* - `GPT-52-version-date`
|
|
3414
3496
|
* - `openai/gpt-5.2-chat`
|
|
3415
3497
|
*
|
|
3416
|
-
*
|
|
3417
|
-
*
|
|
3498
|
+
* 它们应被视为同一模型。
|
|
3499
|
+
* 将其归一化为 `gpt-52`
|
|
3500
|
+
*/
|
|
3501
|
+
/**
|
|
3502
|
+
* 归一化模型名称,将不同厂商对同一模型的命名统一为同一形式(如 `gpt-5.2` -> `gpt52`)
|
|
3503
|
+
*
|
|
3504
|
+
* @param modelName - 原始模型名称
|
|
3505
|
+
* @returns 归一化后的模型名称(小写、去掉前缀与特殊字符)
|
|
3418
3506
|
*/
|
|
3419
3507
|
function normalizeModelName(modelName) {
|
|
3420
3508
|
let normalizedName = modelName.toLowerCase();
|
|
@@ -3423,6 +3511,12 @@ function normalizeModelName(modelName) {
|
|
|
3423
3511
|
normalizedName = normalizedName.replace(/\./g, "");
|
|
3424
3512
|
return normalizedName;
|
|
3425
3513
|
}
|
|
3514
|
+
/**
|
|
3515
|
+
* 根据 baseURL 判断服务提供商(目前仅识别 openrouter)
|
|
3516
|
+
*
|
|
3517
|
+
* @param baseURL - 模型服务的基础 URL
|
|
3518
|
+
* @returns 'openrouter' 或 undefined
|
|
3519
|
+
*/
|
|
3426
3520
|
function getProvider(baseURL) {
|
|
3427
3521
|
if (!baseURL) return void 0;
|
|
3428
3522
|
try {
|
|
@@ -3435,10 +3529,10 @@ function getProvider(baseURL) {
|
|
|
3435
3529
|
//#endregion
|
|
3436
3530
|
//#region src/llm/OpenAICompatibleClient.ts
|
|
3437
3531
|
/**
|
|
3438
|
-
* OpenAI
|
|
3532
|
+
* OpenAI 客户端实现
|
|
3439
3533
|
*/
|
|
3440
3534
|
/**
|
|
3441
|
-
*
|
|
3535
|
+
* 兼容 OpenAI 的 API 客户端
|
|
3442
3536
|
*/
|
|
3443
3537
|
var OpenAICompatibleClient = class {
|
|
3444
3538
|
config;
|
|
@@ -3447,6 +3541,16 @@ var OpenAICompatibleClient = class {
|
|
|
3447
3541
|
this.config = config;
|
|
3448
3542
|
this.fetch = config.customFetch;
|
|
3449
3543
|
}
|
|
3544
|
+
/**
|
|
3545
|
+
* 调用 LLM 接口并执行模型请求的工具
|
|
3546
|
+
* 流程:转换工具定义 -> 构建并转换请求体 -> 调用 API -> 处理 HTTP 错误 -> 解析并校验响应 -> 执行工具
|
|
3547
|
+
*
|
|
3548
|
+
* @param messages - 对话消息列表
|
|
3549
|
+
* @param tools - 可用工具集合
|
|
3550
|
+
* @param abortSignal - 中止信号,用于取消请求
|
|
3551
|
+
* @param options - 可选的调用选项
|
|
3552
|
+
* @returns 工具调用结果
|
|
3553
|
+
*/
|
|
3450
3554
|
async invoke(messages, tools, abortSignal, options) {
|
|
3451
3555
|
abortSignal?.throwIfAborted();
|
|
3452
3556
|
const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t));
|
|
@@ -3564,7 +3668,7 @@ var OpenAICompatibleClient = class {
|
|
|
3564
3668
|
//#endregion
|
|
3565
3669
|
//#region src/llm/LLM.ts
|
|
3566
3670
|
/**
|
|
3567
|
-
* LLM
|
|
3671
|
+
* LLM 模块
|
|
3568
3672
|
*/
|
|
3569
3673
|
var LLM = class extends EventTarget {
|
|
3570
3674
|
config;
|
|
@@ -3575,9 +3679,9 @@ var LLM = class extends EventTarget {
|
|
|
3575
3679
|
this.client = new OpenAICompatibleClient(this.config);
|
|
3576
3680
|
}
|
|
3577
3681
|
/**
|
|
3578
|
-
* -
|
|
3579
|
-
* -
|
|
3580
|
-
* -
|
|
3682
|
+
* - 调用一次 LLM API
|
|
3683
|
+
* - 调用一次工具
|
|
3684
|
+
* - 返回工具执行结果
|
|
3581
3685
|
*/
|
|
3582
3686
|
async invoke(messages, tools, abortSignal, options) {
|
|
3583
3687
|
return await withRetry(async () => this.client.invoke(messages, tools, abortSignal, options), {
|
|
@@ -3593,7 +3697,7 @@ var LLM = class extends EventTarget {
|
|
|
3593
3697
|
}
|
|
3594
3698
|
};
|
|
3595
3699
|
/**
|
|
3596
|
-
*
|
|
3700
|
+
* 重试某个函数,直到成功或达到最大重试次数。
|
|
3597
3701
|
*/
|
|
3598
3702
|
async function withRetry(fn, settings) {
|
|
3599
3703
|
let attempt = 0;
|
|
@@ -3609,6 +3713,12 @@ async function withRetry(fn, settings) {
|
|
|
3609
3713
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3610
3714
|
}
|
|
3611
3715
|
}
|
|
3716
|
+
/**
|
|
3717
|
+
* 解析并校验 LLM 配置,填充默认值后返回完整配置
|
|
3718
|
+
*
|
|
3719
|
+
* @param config - 用户传入的 LLM 配置
|
|
3720
|
+
* @returns 解析后的完整配置
|
|
3721
|
+
*/
|
|
3612
3722
|
function parseLLMConfig(config) {
|
|
3613
3723
|
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
3724
|
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 +3740,16 @@ var system_prompt_default = "You are an AI agent designed to operate in an itera
|
|
|
3630
3740
|
//#region src/agent/utils/autoFixer.ts
|
|
3631
3741
|
var log = console.log.bind(console, formatTerminalText("[autoFixer]", "yellow"));
|
|
3632
3742
|
/**
|
|
3633
|
-
*
|
|
3743
|
+
* 规范化 LLM 响应并修复常见的格式问题。
|
|
3634
3744
|
*
|
|
3635
|
-
*
|
|
3636
|
-
* -
|
|
3637
|
-
* -
|
|
3638
|
-
* -
|
|
3639
|
-
* -
|
|
3640
|
-
* -
|
|
3641
|
-
* -
|
|
3642
|
-
* -
|
|
3745
|
+
* 处理:
|
|
3746
|
+
* - 没有 tool_calls 但 message.content 中含 JSON(回退)
|
|
3747
|
+
* - 模型将动作名作为工具调用返回而非 AgentOutput
|
|
3748
|
+
* - 参数被双重 JSON 字符串化
|
|
3749
|
+
* - 嵌套的函数调用格式
|
|
3750
|
+
* - 缺少 action 字段(回退为 wait)
|
|
3751
|
+
* - 单字段工具的原始动作输入(如 `{"click_element_by_index": 2}`)
|
|
3752
|
+
* - 等等
|
|
3643
3753
|
*/
|
|
3644
3754
|
function normalizeResponse(response, tools) {
|
|
3645
3755
|
let resolvedArguments;
|
|
@@ -3698,11 +3808,11 @@ function normalizeResponse(response, tools) {
|
|
|
3698
3808
|
};
|
|
3699
3809
|
}
|
|
3700
3810
|
/**
|
|
3701
|
-
*
|
|
3702
|
-
*
|
|
3811
|
+
* 根据工具 schema 校验动作。提供清晰的错误信息,
|
|
3812
|
+
* 而不是让联合 schema 产生难以阅读的错误。
|
|
3703
3813
|
*
|
|
3704
|
-
*
|
|
3705
|
-
*
|
|
3814
|
+
* 同时为单字段工具强制转换原始输入:
|
|
3815
|
+
* 如 `{"click_element_by_index": 2}` → `{"click_element_by_index": {"index": 2}}`
|
|
3706
3816
|
*/
|
|
3707
3817
|
function validateAction(action, tools) {
|
|
3708
3818
|
if (typeof action !== "object" || action === null) return action;
|
|
@@ -3727,7 +3837,7 @@ function validateAction(action, tools) {
|
|
|
3727
3837
|
return { [toolName]: result.data };
|
|
3728
3838
|
}
|
|
3729
3839
|
/**
|
|
3730
|
-
*
|
|
3840
|
+
* 安全解析 JSON,如果不是 JSON 则返回原始输入。
|
|
3731
3841
|
*/
|
|
3732
3842
|
function safeJsonParse(input) {
|
|
3733
3843
|
if (typeof input === "string") try {
|
|
@@ -3738,9 +3848,9 @@ function safeJsonParse(input) {
|
|
|
3738
3848
|
return input;
|
|
3739
3849
|
}
|
|
3740
3850
|
/**
|
|
3741
|
-
*
|
|
3742
|
-
* -
|
|
3743
|
-
* -
|
|
3851
|
+
* 从字符串中提取并解析 JSON。
|
|
3852
|
+
* - 将第一个 `{` 与最后一个 `}` 之间的内容视为 JSON。
|
|
3853
|
+
* - 尝试将该内容解析为 JSON 并返回解析后的值(对象/数组/原始值),成功则返回,否则返回 null。
|
|
3744
3854
|
*/
|
|
3745
3855
|
function retrieveJsonFromString(str) {
|
|
3746
3856
|
try {
|
|
@@ -3754,8 +3864,8 @@ function retrieveJsonFromString(str) {
|
|
|
3754
3864
|
//#endregion
|
|
3755
3865
|
//#region src/agent/utils/index.ts
|
|
3756
3866
|
/**
|
|
3757
|
-
*
|
|
3758
|
-
*
|
|
3867
|
+
* 等待 `seconds` 秒。若提供了 `signal`,则等待可被取消:
|
|
3868
|
+
* 中止时会以 signal 的 reason(一个 `AbortError`)reject。
|
|
3759
3869
|
*/
|
|
3760
3870
|
async function waitFor(seconds, signal) {
|
|
3761
3871
|
if (!signal) {
|
|
@@ -3795,8 +3905,8 @@ var _global = globalThis;
|
|
|
3795
3905
|
if (!_global.__PAGE_AGENT_IDS__) _global.__PAGE_AGENT_IDS__ = [];
|
|
3796
3906
|
var ids = _global.__PAGE_AGENT_IDS__;
|
|
3797
3907
|
/**
|
|
3798
|
-
*
|
|
3799
|
-
* @note
|
|
3908
|
+
* 生成一个随机 ID。
|
|
3909
|
+
* @note 在当前窗口内唯一。
|
|
3800
3910
|
*/
|
|
3801
3911
|
function uid() {
|
|
3802
3912
|
const id = randomID(ids);
|
|
@@ -3804,7 +3914,7 @@ function uid() {
|
|
|
3804
3914
|
return id;
|
|
3805
3915
|
}
|
|
3806
3916
|
var llmsTxtCache = /* @__PURE__ */ new Map();
|
|
3807
|
-
/**
|
|
3917
|
+
/** 获取某个 URL 源的 /llms.txt。按源缓存,`null` 表示已尝试过但未找到。 */
|
|
3808
3918
|
async function fetchLlmsTxt(url) {
|
|
3809
3919
|
let origin;
|
|
3810
3920
|
try {
|
|
@@ -3834,10 +3944,10 @@ async function fetchLlmsTxt(url) {
|
|
|
3834
3944
|
return result;
|
|
3835
3945
|
}
|
|
3836
3946
|
/**
|
|
3837
|
-
*
|
|
3838
|
-
* @param condition -
|
|
3839
|
-
* @param message -
|
|
3840
|
-
* @throws
|
|
3947
|
+
* 简单的断言函数:条件为假时抛出错误
|
|
3948
|
+
* @param condition - 要断言的条件
|
|
3949
|
+
* @param message - 可选的错误信息
|
|
3950
|
+
* @throws 条件为假时抛出错误
|
|
3841
3951
|
*/
|
|
3842
3952
|
function assert(condition, message, silent) {
|
|
3843
3953
|
if (!condition) {
|
|
@@ -3847,7 +3957,7 @@ function assert(condition, message, silent) {
|
|
|
3847
3957
|
}
|
|
3848
3958
|
}
|
|
3849
3959
|
/**
|
|
3850
|
-
*
|
|
3960
|
+
* 抑制函数产生的错误。
|
|
3851
3961
|
*/
|
|
3852
3962
|
async function suppress(fn) {
|
|
3853
3963
|
try {
|
|
@@ -3860,15 +3970,16 @@ async function suppress(fn) {
|
|
|
3860
3970
|
//#endregion
|
|
3861
3971
|
//#region src/agent/tools/index.ts
|
|
3862
3972
|
/**
|
|
3863
|
-
*
|
|
3864
|
-
* @note
|
|
3973
|
+
* PageAgent 的内部工具。
|
|
3974
|
+
* @note 改编自 browser-use
|
|
3865
3975
|
*/
|
|
3976
|
+
/** 创建一个工具定义(直接透传 options) */
|
|
3866
3977
|
function tool(options) {
|
|
3867
3978
|
return options;
|
|
3868
3979
|
}
|
|
3869
3980
|
/**
|
|
3870
|
-
*
|
|
3871
|
-
*
|
|
3981
|
+
* PageAgent 的内部工具。
|
|
3982
|
+
* 注:使用 any 以允许每个工具拥有不同的参数类型
|
|
3872
3983
|
*/
|
|
3873
3984
|
var tools = /* @__PURE__ */ new Map();
|
|
3874
3985
|
tools.set("done", tool({
|
|
@@ -3929,7 +4040,7 @@ tools.set("select_dropdown_option", tool({
|
|
|
3929
4040
|
}
|
|
3930
4041
|
}));
|
|
3931
4042
|
/**
|
|
3932
|
-
* @note
|
|
4043
|
+
* @note 参考自 browser-use
|
|
3933
4044
|
*/
|
|
3934
4045
|
tools.set("scroll", tool({
|
|
3935
4046
|
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 +4058,7 @@ tools.set("scroll", tool({
|
|
|
3947
4058
|
}
|
|
3948
4059
|
}));
|
|
3949
4060
|
/**
|
|
3950
|
-
* @todo
|
|
4061
|
+
* @todo 表格需要专门的解析器来提取结构化数据。这个工具有点鸡肋。
|
|
3951
4062
|
*/
|
|
3952
4063
|
tools.set("scroll_horizontally", tool({
|
|
3953
4064
|
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,81 +4083,92 @@ tools.set("execute_javascript", tool({
|
|
|
3972
4083
|
//#endregion
|
|
3973
4084
|
//#region src/agent/AgentRuntime.ts
|
|
3974
4085
|
/**
|
|
3975
|
-
* AI
|
|
4086
|
+
* 用于浏览器自动化的 AI 智能体。
|
|
3976
4087
|
*
|
|
3977
4088
|
* @remarks
|
|
3978
|
-
* ##
|
|
3979
|
-
* -
|
|
3980
|
-
* -
|
|
3981
|
-
* -
|
|
3982
|
-
* -
|
|
3983
|
-
* -
|
|
3984
|
-
* -
|
|
3985
|
-
* -
|
|
4089
|
+
* ## 反应式 Agent 循环
|
|
4090
|
+
* - 步骤
|
|
4091
|
+
* - 观察(收集当前环境与上下文信息)
|
|
4092
|
+
* - 思考(调用 LLM)
|
|
4093
|
+
* - 反思(评估历史、生成记忆、短期规划)
|
|
4094
|
+
* - 行动(给出接近下一个目标的动作)
|
|
4095
|
+
* - 执行(执行动作)
|
|
4096
|
+
* - 循环
|
|
3986
4097
|
*
|
|
3987
|
-
* ##
|
|
3988
|
-
* - `statuschange` - Agent
|
|
3989
|
-
* - `historychange` -
|
|
3990
|
-
* - `activity` -
|
|
3991
|
-
* - `dispose` - Agent
|
|
4098
|
+
* ## 事件系统
|
|
4099
|
+
* - `statuschange` - Agent 状态变更(idle → running → completed/error/stopped)
|
|
4100
|
+
* - `historychange` - 历史事件更新(持久化,属于 Agent 记忆的一部分)
|
|
4101
|
+
* - `activity` - 实时活动反馈(瞬态,仅用于 UI)
|
|
4102
|
+
* - `dispose` - 触发 Agent 清理
|
|
3992
4103
|
*
|
|
3993
|
-
* ##
|
|
3994
|
-
* 1.
|
|
3995
|
-
* -
|
|
3996
|
-
* -
|
|
3997
|
-
* -
|
|
4104
|
+
* ## 信息流
|
|
4105
|
+
* 1. **历史事件**(`history` 数组)
|
|
4106
|
+
* - 构成 Agent 记忆的持久事件流
|
|
4107
|
+
* - 跨步骤包含在 LLM 上下文中
|
|
4108
|
+
* - 类型:步骤、观察、用户接管、LLM 错误
|
|
3998
4109
|
*
|
|
3999
|
-
* 2.
|
|
4000
|
-
* -
|
|
4001
|
-
* -
|
|
4002
|
-
* -
|
|
4110
|
+
* 2. **活动事件**(通过 `activity` 事件)
|
|
4111
|
+
* - 任务执行期间的瞬时 UI 反馈
|
|
4112
|
+
* - 不包含在 LLM 上下文中
|
|
4113
|
+
* - 类型:thinking、executing、executed、retrying、error
|
|
4003
4114
|
*/
|
|
4004
4115
|
var AgentRuntime = class extends EventTarget {
|
|
4116
|
+
/** Agent 唯一 ID */
|
|
4005
4117
|
id = uid();
|
|
4118
|
+
/** 合并后的 Agent 配置(含最大步数上限) */
|
|
4006
4119
|
config;
|
|
4120
|
+
/** 内部工具集合(按工具名索引) */
|
|
4007
4121
|
tools;
|
|
4008
|
-
/**
|
|
4122
|
+
/** 用于 DOM 操作的 BrowserController */
|
|
4009
4123
|
browserController;
|
|
4124
|
+
/** 当前任务描述 */
|
|
4010
4125
|
task = "";
|
|
4126
|
+
/** 当前任务 ID */
|
|
4011
4127
|
taskId = "";
|
|
4012
|
-
/**
|
|
4128
|
+
/** 历史事件 */
|
|
4013
4129
|
history = [];
|
|
4014
|
-
/**
|
|
4130
|
+
/** 该 Agent 是否已被销毁 */
|
|
4015
4131
|
disposed = false;
|
|
4016
4132
|
/**
|
|
4017
|
-
*
|
|
4018
|
-
*
|
|
4019
|
-
*
|
|
4133
|
+
* 当 Agent 需要向用户提问时调用。
|
|
4134
|
+
* 若未设置,`ask_user` 工具将被禁用。
|
|
4135
|
+
* 实现应在 `signal` 中止时 reject 该 Promise。
|
|
4020
4136
|
* @example onAskUser: (q) => window.prompt(q) || ''
|
|
4021
4137
|
*/
|
|
4022
4138
|
onAskUser;
|
|
4139
|
+
/** 当前 Agent 状态 */
|
|
4023
4140
|
#status = "idle";
|
|
4141
|
+
/** LLM 实例(负责模型调用) */
|
|
4024
4142
|
#llm;
|
|
4025
4143
|
/**
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4028
|
-
*
|
|
4029
|
-
* stays a standard `AbortError`.
|
|
4144
|
+
* 任务取消原语:其 signal 会传递给 LLM fetch、工具(通过 `ctx.signal`)和异步回调。
|
|
4145
|
+
* 仅由 `stop`/`dispose`(任务进行中)或任务初始化时中止,且始终不带 reason,
|
|
4146
|
+
* 这样 `signal.reason` 保持为标准的 `AbortError`。
|
|
4030
4147
|
*/
|
|
4031
4148
|
#abortController = new AbortController();
|
|
4149
|
+
/** 本轮任务收集的观察消息(暂存,稍后统一写入历史) */
|
|
4032
4150
|
#observations = [];
|
|
4033
|
-
/**
|
|
4151
|
+
/** 当前任务在 history 数组中的起始下标(用于跨任务保留历史时,把 prompt/循环检测限定在当前任务范围内) */
|
|
4152
|
+
#taskHistoryStart = 0;
|
|
4153
|
+
/** 当前一次运行完全结束时 resolve。由 `stop()` 等待。 */
|
|
4034
4154
|
#running = Promise.resolve();
|
|
4035
4155
|
#lastResult = null;
|
|
4036
|
-
/**
|
|
4156
|
+
/** 单次任务执行期间的内部状态 */
|
|
4037
4157
|
#states = {
|
|
4038
|
-
/**
|
|
4158
|
+
/** 累计等待时间(秒) */
|
|
4039
4159
|
totalWaitTime: 0,
|
|
4040
|
-
/**
|
|
4160
|
+
/** 用于检测页面导航 */
|
|
4041
4161
|
lastURL: "",
|
|
4042
|
-
/**
|
|
4162
|
+
/** 浏览器状态 */
|
|
4043
4163
|
browserState: null
|
|
4044
4164
|
};
|
|
4165
|
+
/** 构造函数:初始化配置、LLM、工具集,并注册重试事件监听与注入自定义工具 */
|
|
4045
4166
|
constructor(config) {
|
|
4046
4167
|
super();
|
|
4047
4168
|
this.config = {
|
|
4048
4169
|
...config,
|
|
4049
|
-
maxSteps: config.maxSteps ?? 40
|
|
4170
|
+
maxSteps: config.maxSteps ?? 40,
|
|
4171
|
+
historyWindow: config.historyWindow ?? 5
|
|
4050
4172
|
};
|
|
4051
4173
|
this.#llm = new LLM(this.config);
|
|
4052
4174
|
this.tools = new Map(tools);
|
|
@@ -4080,31 +4202,35 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4080
4202
|
}
|
|
4081
4203
|
if (!this.config.experimentalScriptExecutionTool) this.tools.delete("execute_javascript");
|
|
4082
4204
|
}
|
|
4083
|
-
/**
|
|
4205
|
+
/** 获取当前 Agent 状态 */
|
|
4084
4206
|
get status() {
|
|
4085
4207
|
return this.#status;
|
|
4086
4208
|
}
|
|
4087
|
-
/**
|
|
4209
|
+
/** 最近一次运行的结果,首次运行完成前为 `null`。 */
|
|
4088
4210
|
get lastResult() {
|
|
4089
4211
|
return this.#lastResult;
|
|
4090
4212
|
}
|
|
4091
|
-
/**
|
|
4213
|
+
/** 当前任务自身的历史事件(不含之前任务留下的记录),用于 prompt 拼装、循环检测等按任务隔离的场景 */
|
|
4214
|
+
get #currentTaskHistory() {
|
|
4215
|
+
return this.history.slice(this.#taskHistoryStart);
|
|
4216
|
+
}
|
|
4217
|
+
/** 触发 statuschange 事件 */
|
|
4092
4218
|
#emitStatusChange() {
|
|
4093
4219
|
this.dispatchEvent(new Event("statuschange"));
|
|
4094
4220
|
}
|
|
4095
|
-
/**
|
|
4221
|
+
/** 触发 historychange 事件 */
|
|
4096
4222
|
#emitHistoryChange(pushHistoricalEvent) {
|
|
4097
4223
|
if (pushHistoricalEvent) this.history.push(pushHistoricalEvent);
|
|
4098
4224
|
this.dispatchEvent(new Event("historychange"));
|
|
4099
4225
|
}
|
|
4100
4226
|
/**
|
|
4101
|
-
*
|
|
4102
|
-
* @param activity -
|
|
4227
|
+
* 触发 activity 事件 - 用于瞬态的 UI 反馈
|
|
4228
|
+
* @param activity - 当前的 Agent 活动
|
|
4103
4229
|
*/
|
|
4104
4230
|
#emitActivity(activity) {
|
|
4105
4231
|
this.dispatchEvent(new CustomEvent("activity", { detail: activity }));
|
|
4106
4232
|
}
|
|
4107
|
-
/**
|
|
4233
|
+
/** 更新状态并触发事件 */
|
|
4108
4234
|
#setStatus(status) {
|
|
4109
4235
|
if (this.#status !== status) {
|
|
4110
4236
|
this.#status = status;
|
|
@@ -4112,17 +4238,17 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4112
4238
|
}
|
|
4113
4239
|
}
|
|
4114
4240
|
/**
|
|
4115
|
-
*
|
|
4116
|
-
*
|
|
4241
|
+
* 向历史事件流推送一条观察消息。
|
|
4242
|
+
* 它会出现在 <agent_history> 中,并在跨步骤的记忆中保持持久。
|
|
4117
4243
|
* @experimental @internal
|
|
4118
|
-
* @note
|
|
4244
|
+
* @note 历史变更会在下一步开始前触发
|
|
4119
4245
|
*/
|
|
4120
4246
|
pushObservation(content) {
|
|
4121
4247
|
this.#observations.push(content);
|
|
4122
4248
|
}
|
|
4123
4249
|
/**
|
|
4124
|
-
*
|
|
4125
|
-
* @note
|
|
4250
|
+
* 停止当前任务,并等待运行完全结束(包括生命周期钩子)。
|
|
4251
|
+
* @note 切勿在生命周期钩子中 await .stop()。
|
|
4126
4252
|
*/
|
|
4127
4253
|
async stop() {
|
|
4128
4254
|
if (this.#status !== "running") return;
|
|
@@ -4130,8 +4256,8 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4130
4256
|
await this.#running;
|
|
4131
4257
|
}
|
|
4132
4258
|
/**
|
|
4133
|
-
*
|
|
4134
|
-
*
|
|
4259
|
+
* 外部错误(前置检查/配置/钩子)会直接抛出;
|
|
4260
|
+
* Agent 内部错误会被捕获并加入历史,同时返回失败结果
|
|
4135
4261
|
*/
|
|
4136
4262
|
async execute(task) {
|
|
4137
4263
|
if (this.disposed) throw new Error("PageAgent has been disposed. Create a new instance.");
|
|
@@ -4139,7 +4265,11 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4139
4265
|
if (!task) throw new Error("Task is required");
|
|
4140
4266
|
this.task = task;
|
|
4141
4267
|
this.taskId = uid();
|
|
4142
|
-
this
|
|
4268
|
+
this.#taskHistoryStart = this.history.length;
|
|
4269
|
+
this.#emitHistoryChange({
|
|
4270
|
+
type: "task_start",
|
|
4271
|
+
task
|
|
4272
|
+
});
|
|
4143
4273
|
this.#observations = [];
|
|
4144
4274
|
this.#states = {
|
|
4145
4275
|
totalWaitTime: 0,
|
|
@@ -4180,7 +4310,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4180
4310
|
taskResult = {
|
|
4181
4311
|
success: false,
|
|
4182
4312
|
data: message,
|
|
4183
|
-
history: this
|
|
4313
|
+
history: this.#currentTaskHistory
|
|
4184
4314
|
};
|
|
4185
4315
|
this.#lastResult = taskResult;
|
|
4186
4316
|
finalStatus = "error";
|
|
@@ -4238,7 +4368,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4238
4368
|
taskResult = {
|
|
4239
4369
|
success,
|
|
4240
4370
|
data,
|
|
4241
|
-
history: this
|
|
4371
|
+
history: this.#currentTaskHistory
|
|
4242
4372
|
};
|
|
4243
4373
|
this.#lastResult = taskResult;
|
|
4244
4374
|
finalStatus = "completed";
|
|
@@ -4260,14 +4390,14 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4260
4390
|
taskResult = {
|
|
4261
4391
|
success: false,
|
|
4262
4392
|
data: message,
|
|
4263
|
-
history: this
|
|
4393
|
+
history: this.#currentTaskHistory
|
|
4264
4394
|
};
|
|
4265
4395
|
this.#lastResult = taskResult;
|
|
4266
4396
|
finalStatus = isAbortError ? "stopped" : "error";
|
|
4267
4397
|
break;
|
|
4268
4398
|
} finally {
|
|
4269
4399
|
console.groupEnd();
|
|
4270
|
-
await onAfterStep?.(this, this
|
|
4400
|
+
await onAfterStep?.(this, this.#currentTaskHistory);
|
|
4271
4401
|
}
|
|
4272
4402
|
step++;
|
|
4273
4403
|
}
|
|
@@ -4289,13 +4419,13 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4289
4419
|
}
|
|
4290
4420
|
}
|
|
4291
4421
|
/**
|
|
4292
|
-
*
|
|
4422
|
+
* 将所有工具合并为单个 MacroTool,其输入如下:
|
|
4293
4423
|
* - thinking: string
|
|
4294
4424
|
* - evaluation_previous_goal: string
|
|
4295
4425
|
* - memory: string
|
|
4296
4426
|
* - next_goal: string
|
|
4297
4427
|
* - action: { toolName: toolInput }
|
|
4298
|
-
*
|
|
4428
|
+
* 其中 action 必须从 this.tools 中定义的工具中选择
|
|
4299
4429
|
*/
|
|
4300
4430
|
#packMacroTool() {
|
|
4301
4431
|
const tools = this.tools;
|
|
@@ -4354,7 +4484,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4354
4484
|
};
|
|
4355
4485
|
}
|
|
4356
4486
|
/**
|
|
4357
|
-
*
|
|
4487
|
+
* 获取系统提示词,根据配置的语言动态替换语言设置
|
|
4358
4488
|
*/
|
|
4359
4489
|
#getSystemPrompt() {
|
|
4360
4490
|
if (this.config.customSystemPrompt) return this.config.customSystemPrompt;
|
|
@@ -4362,7 +4492,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4362
4492
|
return system_prompt_default.replace(/Default working language: \*\*.*?\*\*/, `Default working language: **${targetLanguage}**`);
|
|
4363
4493
|
}
|
|
4364
4494
|
/**
|
|
4365
|
-
*
|
|
4495
|
+
* 从配置中获取指令
|
|
4366
4496
|
*/
|
|
4367
4497
|
async #getInstructions() {
|
|
4368
4498
|
const { instructions, experimentalLlmsTxt } = this.config;
|
|
@@ -4384,12 +4514,12 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4384
4514
|
return result;
|
|
4385
4515
|
}
|
|
4386
4516
|
/**
|
|
4387
|
-
*
|
|
4388
|
-
* @todo
|
|
4517
|
+
* 在每一步之前生成系统观察
|
|
4518
|
+
* @todo 控制台错误
|
|
4389
4519
|
*/
|
|
4390
4520
|
async #handleObservations(step) {
|
|
4391
4521
|
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.`);
|
|
4392
|
-
const recentSteps = this.
|
|
4522
|
+
const recentSteps = this.#currentTaskHistory.filter((e) => e.type === "step").slice(-3);
|
|
4393
4523
|
if (recentSteps.length === 3) {
|
|
4394
4524
|
const isSameAction = (a, b) => a.action.name === b.action.name && JSON.stringify(a.action.input) === JSON.stringify(b.action.input);
|
|
4395
4525
|
if (isSameAction(recentSteps[0], recentSteps[1]) && isSameAction(recentSteps[1], recentSteps[2])) this.pushObservation(`⚠️ Loop detected: you repeated the exact same action ("${recentSteps[0].action.name}" with identical input) 3 times in a row with no new result. STOP repeating it. If the target genuinely does not exist (e.g. search returns empty), report this explicitly and call \`done\`, or try a clearly different action/strategy instead.`);
|
|
@@ -4415,11 +4545,15 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4415
4545
|
this.#emitHistoryChange();
|
|
4416
4546
|
}
|
|
4417
4547
|
}
|
|
4548
|
+
/**
|
|
4549
|
+
* 组装发给 LLM 的完整用户提示词(含指令、Agent 状态、历史、浏览器状态)
|
|
4550
|
+
*/
|
|
4418
4551
|
async #assembleUserPrompt() {
|
|
4419
4552
|
const browserState = this.#states.browserState;
|
|
4420
4553
|
let prompt = "";
|
|
4421
4554
|
prompt += await this.#getInstructions();
|
|
4422
|
-
const
|
|
4555
|
+
const currentTaskHistory = this.#currentTaskHistory;
|
|
4556
|
+
const stepCount = currentTaskHistory.filter((e) => e.type === "step").length;
|
|
4423
4557
|
prompt += "<agent_state>\n";
|
|
4424
4558
|
prompt += "<user_request>\n";
|
|
4425
4559
|
prompt += `${this.task}\n`;
|
|
@@ -4429,10 +4563,10 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4429
4563
|
prompt += `Current time: ${(/* @__PURE__ */ new Date()).toLocaleString()}\n`;
|
|
4430
4564
|
prompt += "</step_info>\n";
|
|
4431
4565
|
prompt += "</agent_state>\n\n";
|
|
4432
|
-
const windowStart = Math.max(0, stepCount -
|
|
4566
|
+
const windowStart = Math.max(0, stepCount - this.config.historyWindow);
|
|
4433
4567
|
prompt += "<agent_history>\n";
|
|
4434
4568
|
let stepIndex = 0;
|
|
4435
|
-
for (const event of
|
|
4569
|
+
for (const event of currentTaskHistory) if (event.type === "step") {
|
|
4436
4570
|
stepIndex++;
|
|
4437
4571
|
if (stepIndex <= windowStart) {
|
|
4438
4572
|
prompt += `<step_${stepIndex}>Action: ${event.action.name} — Next Goal: ${event.reflection.next_goal}</step_${stepIndex}>\n`;
|
|
@@ -4457,6 +4591,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4457
4591
|
prompt += "</browser_state>\n\n";
|
|
4458
4592
|
return prompt;
|
|
4459
4593
|
}
|
|
4594
|
+
/** 销毁 Agent:中止任务、释放浏览器控制器,并触发 dispose 事件(一次性) */
|
|
4460
4595
|
dispose() {
|
|
4461
4596
|
if (this.disposed) return;
|
|
4462
4597
|
this.disposed = true;
|
|
@@ -4479,8 +4614,13 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4479
4614
|
};
|
|
4480
4615
|
//#endregion
|
|
4481
4616
|
//#region src/agent/MyPageAgent.ts
|
|
4617
|
+
/**
|
|
4618
|
+
* 完整页面智能体:组合了 AgentRuntime、BrowserController 与 UI
|
|
4619
|
+
*/
|
|
4482
4620
|
var MyPageAgent = class extends AgentRuntime {
|
|
4621
|
+
/** UI 实例(用于用户交互与反馈) */
|
|
4483
4622
|
ui;
|
|
4623
|
+
/** 构造函数:创建浏览器控制器、父类运行时与 UI 实例 */
|
|
4484
4624
|
constructor(config) {
|
|
4485
4625
|
const browserController = new BrowserController({
|
|
4486
4626
|
...config,
|
|
@@ -4499,6 +4639,11 @@ var MyPageAgent = class extends AgentRuntime {
|
|
|
4499
4639
|
};
|
|
4500
4640
|
//#endregion
|
|
4501
4641
|
//#region src/index.ts
|
|
4642
|
+
/**
|
|
4643
|
+
* 创建智能体实例的快捷函数,内部直接实例化 MyPageAgent。
|
|
4644
|
+
* @param config 智能体的配置对象
|
|
4645
|
+
* @returns 创建好的 MyPageAgent 实例
|
|
4646
|
+
*/
|
|
4502
4647
|
function createAgent(config) {
|
|
4503
4648
|
return new MyPageAgent(config);
|
|
4504
4649
|
}
|