@coolkiller007/my-page-agent 0.2.1 → 0.2.3
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/dist/index.d.ts +31 -2
- package/dist/index.js +223 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -254,11 +254,27 @@ declare class AgentRuntime extends EventTarget {
|
|
|
254
254
|
* 当 Agent 需要向用户提问时调用。
|
|
255
255
|
* 若未设置,`ask_user` 工具将被禁用。
|
|
256
256
|
* 实现应在 `signal` 中止时 reject 该 Promise。
|
|
257
|
+
* 返回值可以是纯文本回答,也可以附带回答时用户上传的附件(图片等)。
|
|
257
258
|
* @example onAskUser: (q) => window.prompt(q) || ''
|
|
258
259
|
*/
|
|
259
260
|
onAskUser?: (question: string, options?: {
|
|
260
261
|
signal: AbortSignal;
|
|
261
|
-
}) => Promise<string
|
|
262
|
+
}) => Promise<string | {
|
|
263
|
+
text: string;
|
|
264
|
+
attachments?: Attachment[];
|
|
265
|
+
}>;
|
|
266
|
+
/**
|
|
267
|
+
* `ask_user` 工具收到带附件的回答后暂存于此,供下一步 `#assembleUserPrompt` 注入 LLM prompt 后清空。
|
|
268
|
+
* 非私有字段:需要被 `tools/index.ts` 中的 `ask_user` 工具从外部写入。
|
|
269
|
+
*/
|
|
270
|
+
pendingAnswerAttachments: Attachment[];
|
|
271
|
+
/**
|
|
272
|
+
* 图片附件缓存(按附件名索引):所有出现过的图片附件在此登记原始 dataUrl,
|
|
273
|
+
* 使原图在 prompt 里只出现一次之后,仍可被 `image_understand` 工具按名称"按需重传"重新查看,
|
|
274
|
+
* 而不必每步都把原图重新塞进主对话历史(节省 token)。
|
|
275
|
+
* 非私有字段:需要被 `tools/index.ts` 中的 `image_understand` 工具从外部读取。
|
|
276
|
+
*/
|
|
277
|
+
imageAttachmentCache: Map<string, Attachment>;
|
|
262
278
|
/** 构造函数:初始化配置、LLM、工具集,并注册重试事件监听与注入自定义工具 */
|
|
263
279
|
constructor(config: AgentRuntimeConfig);
|
|
264
280
|
/** 获取当前 Agent 状态 */
|
|
@@ -276,6 +292,15 @@ declare class AgentRuntime extends EventTarget {
|
|
|
276
292
|
* Agent 内部错误会被捕获并加入历史,同时返回失败结果
|
|
277
293
|
*/
|
|
278
294
|
execute(task: string, attachments?: Attachment[]): Promise<ExecutionResult>;
|
|
295
|
+
/**
|
|
296
|
+
* 针对已缓存的图片附件发起一次独立的视觉问答,不进入主对话历史、不携带主链路的历史上下文。
|
|
297
|
+
* 供 `image_understand` 工具使用:模型需要复看图片细节时按需调用,而不是每步都重发原图。
|
|
298
|
+
* @param attachmentName - 附件名(即注入 prompt 时标注的名称)
|
|
299
|
+
* @param query - 需要从图片中确认的具体问题
|
|
300
|
+
* @param signal - 取消信号
|
|
301
|
+
* @returns 视觉模型针对 query 给出的文字答案;附件不存在时返回错误提示
|
|
302
|
+
*/
|
|
303
|
+
describeImage(attachmentName: string, query: string, signal: AbortSignal): Promise<string>;
|
|
279
304
|
/** 销毁 Agent:中止任务、释放浏览器控制器,并触发 dispose 事件(一次性) */
|
|
280
305
|
dispose(): void;
|
|
281
306
|
}
|
|
@@ -918,10 +943,14 @@ declare interface UIAdapter extends EventTarget {
|
|
|
918
943
|
* 如果未设置,`ask_user` 工具将被禁用。
|
|
919
944
|
* UI 会设置该回调以便通过自身的界面处理用户提问。
|
|
920
945
|
* 可选参数 `signal` 在任务被停止或销毁时触发中止。
|
|
946
|
+
* 返回值为纯文本回答,或附带回答时上传的附件(图片等)。
|
|
921
947
|
*/
|
|
922
948
|
onAskUser?: (question: string, options?: {
|
|
923
949
|
signal: AbortSignal;
|
|
924
|
-
}) => Promise<string
|
|
950
|
+
}) => Promise<string | {
|
|
951
|
+
text: string;
|
|
952
|
+
attachments?: Attachment_2[];
|
|
953
|
+
}>;
|
|
925
954
|
/** 执行一个任务;attachments 为随任务一起上传的图片/文本附件(可选) */
|
|
926
955
|
execute(task: string, attachments?: Attachment_2[]): Promise<unknown>;
|
|
927
956
|
/** 停止当前任务(agent 仍可复用) */
|
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_194hg_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_194hg_1 {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(100%);\n }\n}\n\n/* 状态条 + 输入区的外层容器;.background 光效挂在这一层,.shellInner 负责裁出圆角 */\n._shell_194hg_57 {\n position: relative;\n width: 100%;\n}\n\n._background_194hg_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_194hg_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_194hg_1 2s linear infinite;\n animation-delay: 1s;\n }\n}\n\n/* 可视区域:靠 overflow:hidden + border-radius 统一裁角,\n * header 单独存在(运行中输入区隐藏)或 header+输入区一起存在时,四角都自动是圆的,\n * 不用再像以前那样给 header 写一条\"输入区隐藏时补圆角\"的特判规则。 */\n._shellInner_194hg_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_194hg_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_194hg_140 {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-height: 24px; /* 确保垂直居中 */\n\n ._indicator_194hg_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_194hg_156 {\n background: rgb(57, 182, 255);\n animation: _pulse_194hg_1 0.8s ease-in-out infinite;\n }\n\n &._tool_executing_194hg_161 {\n background: rgb(189, 69, 251);\n animation: _pulse_194hg_1 0.6s ease-in-out infinite;\n }\n\n &._retry_194hg_166 {\n background: rgb(255, 214, 0);\n animation: _retryPulse_194hg_1 1s ease-in-out infinite;\n }\n\n /* 静止状态 - 无动画 */\n &._completed_194hg_172,\n &._input_194hg_173,\n &._output_194hg_174 {\n background: rgb(34, 197, 94);\n animation: none;\n }\n\n &._error_194hg_179 {\n background: rgb(239, 68, 68);\n animation: none;\n }\n }\n\n ._statusText_194hg_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_194hg_197 {\n animation: _statusTextFadeOut_194hg_1 0.3s ease forwards;\n }\n\n &._fadeIn_194hg_201 {\n animation: _statusTextFadeIn_194hg_1 0.3s ease forwards;\n }\n }\n }\n\n ._controls_194hg_207 {\n display: flex;\n align-items: center;\n gap: 4px;\n\n ._controlButton_194hg_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_194hg_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_194hg_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_194hg_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_194hg_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_194hg_1._expanded_194hg_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_194hg_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_194hg_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_194hg_172,\n &._input_194hg_173,\n &._output_194hg_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_194hg_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_194hg_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_194hg_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_194hg_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_194hg_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_194hg_1 2s ease-in-out infinite;\n }\n\n ._historyContent_194hg_410 {\n ._statusIcon_194hg_411 {\n font-size: 16px;\n animation: _celebrate_194hg_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_194hg_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_194hg_410 {\n ._statusIcon_194hg_411 {\n font-size: 16px;\n filter: drop-shadow(0 2px 4px rgba(239, 68, 68, 0.5));\n }\n }\n }\n\n ._historyContent_194hg_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_194hg_411 {\n font-size: 12px;\n flex-shrink: 0;\n line-height: 1;\n transition: all 0.3s ease;\n }\n\n ._reflectionLines_194hg_470 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n }\n\n ._historyMeta_194hg_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_194hg_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_194hg_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_194hg_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_194hg_1 {\n 0% {\n left: -100%;\n }\n 100% {\n left: 100%;\n }\n}\n\n/* 输入区域样式 */\n._inputSectionWrapper_194hg_547 {\n width: 100%;\n margin: 0 auto;\n\n overflow: hidden;\n max-height: 0; /* 由 JS 同步为真实内容高度(含附件条、错误提示),见 #syncInputHeight */\n\n transition: max-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_194hg_563 {\n max-height: 0;\n }\n\n ._inputSection_194hg_547 {\n position: relative;\n display: flex;\n align-items: center;\n height: var(--input-height);\n padding: 8px; /* 与上方状态条 padding 保持一致 */\n\n ._attachButton_194hg_574 {\n position: absolute;\n right: 14px;\n bottom: 12px;\n z-index: 1;\n width: 26px;\n height: 26px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: rgba(255, 255, 255, 0.85);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n }\n\n ._taskInput_194hg_590 {\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 40px 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 ._attachmentsBar_194hg_618 {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 8px 0;\n\n &:empty {\n display: none;\n }\n }\n\n ._attachmentChip_194hg_629 {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n max-width: 160px;\n padding: 3px 4px 3px 8px;\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.1);\n color: rgba(255, 255, 255, 0.85);\n font-size: 12px;\n white-space: nowrap;\n\n span {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n\n ._attachmentChipRemove_194hg_647 {\n flex-shrink: 0;\n border: none;\n background: transparent;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n font-size: 13px;\n line-height: 1;\n padding: 0 4px;\n\n &:hover {\n color: rgba(255, 255, 255, 0.95);\n }\n }\n\n ._attachError_194hg_662 {\n padding: 4px 8px 0;\n color: rgb(255, 138, 138);\n font-size: 12px;\n\n &:empty {\n display: none;\n padding: 0;\n }\n }\n\n ._fileInput_194hg_673 {\n display: none;\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_1utdd_1 {\n position: fixed;\n inset: 0;\n z-index: 10000; /* 层级:本层(10000) < Agent UI(20000) < 图片预览(99999),上限 99999 */\n cursor: wait;\n overflow: hidden;\n\n display: none;\n}\n\n._wrapper_1utdd_1._visible_1utdd_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_1svu5_1 {\n position: fixed;\n bottom: 100px;\n left: 50%;\n transform: translateX(-50%) translateY(20px);\n opacity: 0;\n z-index: 20000; /* 层级:SimulatorMask(10000) < 本层(20000),上限 99999 */\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_1svu5_1 {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(100%);\n }\n}\n\n/* 状态条 + 输入区的外层容器;.background 光效挂在这一层,.shellInner 负责裁出圆角 */\n._shell_1svu5_57 {\n position: relative;\n width: 100%;\n}\n\n._background_1svu5_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_1svu5_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_1svu5_1 2s linear infinite;\n animation-delay: 1s;\n }\n}\n\n/* 可视区域:靠 overflow:hidden + border-radius 统一裁角,\n * header 单独存在(运行中输入区隐藏)或 header+输入区一起存在时,四角都自动是圆的,\n * 不用再像以前那样给 header 写一条\"输入区隐藏时补圆角\"的特判规则。 */\n._shellInner_1svu5_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_1svu5_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_1svu5_140 {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n min-height: 24px; /* 确保垂直居中 */\n\n ._indicator_1svu5_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_1svu5_156 {\n background: rgb(57, 182, 255);\n animation: _pulse_1svu5_1 0.8s ease-in-out infinite;\n }\n\n &._tool_executing_1svu5_161 {\n background: rgb(189, 69, 251);\n animation: _pulse_1svu5_1 0.6s ease-in-out infinite;\n }\n\n &._retry_1svu5_166 {\n background: rgb(255, 214, 0);\n animation: _retryPulse_1svu5_1 1s ease-in-out infinite;\n }\n\n /* 静止状态 - 无动画 */\n &._completed_1svu5_172,\n &._input_1svu5_173,\n &._output_1svu5_174 {\n background: rgb(34, 197, 94);\n animation: none;\n }\n\n &._error_1svu5_179 {\n background: rgb(239, 68, 68);\n animation: none;\n }\n }\n\n ._statusText_1svu5_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_1svu5_197 {\n animation: _statusTextFadeOut_1svu5_1 0.3s ease forwards;\n }\n\n &._fadeIn_1svu5_201 {\n animation: _statusTextFadeIn_1svu5_1 0.3s ease forwards;\n }\n }\n }\n\n ._controls_1svu5_207 {\n display: flex;\n align-items: center;\n gap: 4px;\n\n ._controlButton_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_1._expanded_1svu5_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_1svu5_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_1svu5_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_1svu5_172,\n &._input_1svu5_173,\n &._output_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_1 2s ease-in-out infinite;\n }\n\n ._historyContent_1svu5_410 {\n ._statusIcon_1svu5_411 {\n font-size: 16px;\n animation: _celebrate_1svu5_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_1svu5_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_1svu5_410 {\n ._statusIcon_1svu5_411 {\n font-size: 16px;\n filter: drop-shadow(0 2px 4px rgba(239, 68, 68, 0.5));\n }\n }\n }\n\n ._historyContent_1svu5_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_1svu5_411 {\n font-size: 12px;\n flex-shrink: 0;\n line-height: 1;\n transition: all 0.3s ease;\n }\n\n ._reflectionLines_1svu5_470 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n }\n\n ._historyMeta_1svu5_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_1svu5_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_1svu5_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_1svu5_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_1svu5_1 {\n 0% {\n left: -100%;\n }\n 100% {\n left: 100%;\n }\n}\n\n/* 输入区域样式 */\n._inputSectionWrapper_1svu5_547 {\n width: 100%;\n margin: 0 auto;\n\n overflow: hidden;\n max-height: 0; /* 由 JS 同步为真实内容高度(含附件条、错误提示),见 #syncInputHeight */\n\n transition: max-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_1svu5_563 {\n max-height: 0;\n }\n\n ._inputSection_1svu5_547 {\n position: relative;\n display: flex;\n align-items: center;\n height: var(--input-height);\n padding: 8px; /* 与上方状态条 padding 保持一致 */\n\n ._attachButton_1svu5_574 {\n position: absolute;\n right: 14px;\n bottom: 12px;\n z-index: 1;\n width: 26px;\n height: 26px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: rgba(255, 255, 255, 0.85);\n font-size: 13px;\n line-height: 1;\n cursor: pointer;\n }\n\n ._taskInput_1svu5_590 {\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 40px 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 ._attachmentsBar_1svu5_618 {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 8px 0;\n\n &:empty {\n display: none;\n }\n }\n\n ._attachmentChip_1svu5_629 {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n max-width: 160px;\n padding: 3px 4px 3px 8px;\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.1);\n color: rgba(255, 255, 255, 0.85);\n font-size: 12px;\n white-space: nowrap;\n\n span {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n\n ._attachmentChipRemove_1svu5_647 {\n flex-shrink: 0;\n border: none;\n background: transparent;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n font-size: 13px;\n line-height: 1;\n padding: 0 4px;\n\n &:hover {\n color: rgba(255, 255, 255, 0.95);\n }\n }\n\n ._attachError_1svu5_662 {\n padding: 4px 8px 0;\n color: rgb(255, 138, 138);\n font-size: 12px;\n\n &:empty {\n display: none;\n padding: 0;\n }\n }\n\n ._fileInput_1svu5_673 {\n display: none;\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
|
|
@@ -555,7 +555,7 @@ var dom_tree_default = (args = {
|
|
|
555
555
|
/**
|
|
556
556
|
* @edit smaller zIndex for highlight
|
|
557
557
|
*/
|
|
558
|
-
container.style.zIndex = "
|
|
558
|
+
container.style.zIndex = "5000";
|
|
559
559
|
container.style.backgroundColor = "transparent";
|
|
560
560
|
document.body.appendChild(container);
|
|
561
561
|
}
|
|
@@ -1954,8 +1954,8 @@ var init_checkDarkMode = __esmMin((() => {}));
|
|
|
1954
1954
|
//#region src/browser/visual/SimulatorMask.module.css
|
|
1955
1955
|
var wrapper$1, visible, SimulatorMask_module_default;
|
|
1956
1956
|
var init_SimulatorMask_module = __esmMin((() => {
|
|
1957
|
-
wrapper$1 = "
|
|
1958
|
-
visible = "
|
|
1957
|
+
wrapper$1 = "_wrapper_1utdd_1";
|
|
1958
|
+
visible = "_visible_1utdd_11";
|
|
1959
1959
|
SimulatorMask_module_default = {
|
|
1960
1960
|
wrapper: wrapper$1,
|
|
1961
1961
|
visible
|
|
@@ -2607,55 +2607,55 @@ function escapeHtml(text) {
|
|
|
2607
2607
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2608
2608
|
}
|
|
2609
2609
|
var UI_module_default = {
|
|
2610
|
-
wrapper: "
|
|
2611
|
-
shell: "
|
|
2612
|
-
background: "
|
|
2613
|
-
"mask-running": "_mask-
|
|
2614
|
-
shellInner: "
|
|
2615
|
-
header: "
|
|
2616
|
-
pulse: "
|
|
2617
|
-
retryPulse: "
|
|
2618
|
-
statusTextFadeOut: "
|
|
2619
|
-
statusTextFadeIn: "
|
|
2620
|
-
statusSection: "
|
|
2621
|
-
indicator: "
|
|
2622
|
-
thinking: "
|
|
2623
|
-
tool_executing: "
|
|
2624
|
-
retry: "
|
|
2625
|
-
completed: "
|
|
2626
|
-
input: "
|
|
2627
|
-
output: "
|
|
2628
|
-
error: "
|
|
2629
|
-
statusText: "
|
|
2630
|
-
fadeOut: "
|
|
2631
|
-
fadeIn: "
|
|
2632
|
-
controls: "
|
|
2633
|
-
controlButton: "
|
|
2634
|
-
stopButton: "
|
|
2635
|
-
historySectionWrapper: "
|
|
2636
|
-
shimmer: "
|
|
2637
|
-
celebrate: "
|
|
2638
|
-
expanded: "
|
|
2639
|
-
historySection: "
|
|
2640
|
-
historyItem: "
|
|
2641
|
-
observation: "
|
|
2642
|
-
question: "
|
|
2643
|
-
doneSuccess: "
|
|
2644
|
-
historyContent: "
|
|
2645
|
-
statusIcon: "
|
|
2646
|
-
doneError: "
|
|
2647
|
-
reflectionLines: "
|
|
2648
|
-
historyMeta: "
|
|
2649
|
-
inputSectionWrapper: "
|
|
2650
|
-
hidden: "
|
|
2651
|
-
inputSection: "
|
|
2652
|
-
attachButton: "
|
|
2653
|
-
taskInput: "
|
|
2654
|
-
attachmentsBar: "
|
|
2655
|
-
attachmentChip: "
|
|
2656
|
-
attachmentChipRemove: "
|
|
2657
|
-
attachError: "
|
|
2658
|
-
fileInput: "
|
|
2610
|
+
wrapper: "_wrapper_1svu5_1",
|
|
2611
|
+
shell: "_shell_1svu5_57",
|
|
2612
|
+
background: "_background_1svu5_57",
|
|
2613
|
+
"mask-running": "_mask-running_1svu5_1",
|
|
2614
|
+
shellInner: "_shellInner_1svu5_57",
|
|
2615
|
+
header: "_header_1svu5_124",
|
|
2616
|
+
pulse: "_pulse_1svu5_1",
|
|
2617
|
+
retryPulse: "_retryPulse_1svu5_1",
|
|
2618
|
+
statusTextFadeOut: "_statusTextFadeOut_1svu5_1",
|
|
2619
|
+
statusTextFadeIn: "_statusTextFadeIn_1svu5_1",
|
|
2620
|
+
statusSection: "_statusSection_1svu5_140",
|
|
2621
|
+
indicator: "_indicator_1svu5_147",
|
|
2622
|
+
thinking: "_thinking_1svu5_156",
|
|
2623
|
+
tool_executing: "_tool_executing_1svu5_161",
|
|
2624
|
+
retry: "_retry_1svu5_166",
|
|
2625
|
+
completed: "_completed_1svu5_172",
|
|
2626
|
+
input: "_input_1svu5_173",
|
|
2627
|
+
output: "_output_1svu5_174",
|
|
2628
|
+
error: "_error_1svu5_179",
|
|
2629
|
+
statusText: "_statusText_1svu5_185",
|
|
2630
|
+
fadeOut: "_fadeOut_1svu5_197",
|
|
2631
|
+
fadeIn: "_fadeIn_1svu5_201",
|
|
2632
|
+
controls: "_controls_1svu5_207",
|
|
2633
|
+
controlButton: "_controlButton_1svu5_212",
|
|
2634
|
+
stopButton: "_stopButton_1svu5_231",
|
|
2635
|
+
historySectionWrapper: "_historySectionWrapper_1svu5_268",
|
|
2636
|
+
shimmer: "_shimmer_1svu5_1",
|
|
2637
|
+
celebrate: "_celebrate_1svu5_1",
|
|
2638
|
+
expanded: "_expanded_1svu5_290",
|
|
2639
|
+
historySection: "_historySection_1svu5_268",
|
|
2640
|
+
historyItem: "_historyItem_1svu5_309",
|
|
2641
|
+
observation: "_observation_1svu5_363",
|
|
2642
|
+
question: "_question_1svu5_368",
|
|
2643
|
+
doneSuccess: "_doneSuccess_1svu5_374",
|
|
2644
|
+
historyContent: "_historyContent_1svu5_410",
|
|
2645
|
+
statusIcon: "_statusIcon_1svu5_411",
|
|
2646
|
+
doneError: "_doneError_1svu5_420",
|
|
2647
|
+
reflectionLines: "_reflectionLines_1svu5_470",
|
|
2648
|
+
historyMeta: "_historyMeta_1svu5_477",
|
|
2649
|
+
inputSectionWrapper: "_inputSectionWrapper_1svu5_547",
|
|
2650
|
+
hidden: "_hidden_1svu5_563",
|
|
2651
|
+
inputSection: "_inputSection_1svu5_547",
|
|
2652
|
+
attachButton: "_attachButton_1svu5_574",
|
|
2653
|
+
taskInput: "_taskInput_1svu5_590",
|
|
2654
|
+
attachmentsBar: "_attachmentsBar_1svu5_618",
|
|
2655
|
+
attachmentChip: "_attachmentChip_1svu5_629",
|
|
2656
|
+
attachmentChipRemove: "_attachmentChipRemove_1svu5_647",
|
|
2657
|
+
attachError: "_attachError_1svu5_662",
|
|
2658
|
+
fileInput: "_fileInput_1svu5_673"
|
|
2659
2659
|
};
|
|
2660
2660
|
//#endregion
|
|
2661
2661
|
//#region src/ui/cards.ts
|
|
@@ -2998,13 +2998,19 @@ var UI = class {
|
|
|
2998
2998
|
this.#renderAttachments();
|
|
2999
2999
|
}
|
|
3000
3000
|
/**
|
|
3001
|
-
*
|
|
3001
|
+
* 处理用户回答;若回答时附带了附件,一并传给 resolve 回调
|
|
3002
3002
|
*/
|
|
3003
3003
|
#handleUserAnswer(input) {
|
|
3004
3004
|
this.#removeTempCards();
|
|
3005
|
+
const attachments = this.#pendingAttachments;
|
|
3006
|
+
this.#pendingAttachments = [];
|
|
3007
|
+
this.#renderAttachments();
|
|
3005
3008
|
const resolve = this.#userAnswerResolver;
|
|
3006
3009
|
this.#clearQuestionState();
|
|
3007
|
-
resolve?.(
|
|
3010
|
+
resolve?.(attachments.length > 0 ? {
|
|
3011
|
+
text: input,
|
|
3012
|
+
attachments
|
|
3013
|
+
} : input);
|
|
3008
3014
|
}
|
|
3009
3015
|
/**
|
|
3010
3016
|
* 显示输入区域
|
|
@@ -3013,7 +3019,6 @@ var UI = class {
|
|
|
3013
3019
|
this.#taskInput.value = "";
|
|
3014
3020
|
this.#taskInput.placeholder = placeholder || this.#i18n.t("ui.taskInput");
|
|
3015
3021
|
this.#inputSection.classList.remove(UI_module_default.hidden);
|
|
3016
|
-
this.#attachButton.style.display = this.#isWaitingForUserAnswer ? "none" : "";
|
|
3017
3022
|
this.#syncInputHeight();
|
|
3018
3023
|
this.#setTimer(() => {
|
|
3019
3024
|
this.#taskInput.focus();
|
|
@@ -3213,6 +3218,12 @@ var UI = class {
|
|
|
3213
3218
|
if (files?.length) this.#handleFileSelection(files);
|
|
3214
3219
|
this.#fileInput.value = "";
|
|
3215
3220
|
});
|
|
3221
|
+
this.#taskInput.addEventListener("paste", (e) => {
|
|
3222
|
+
const files = e.clipboardData?.files;
|
|
3223
|
+
if (!files?.length) return;
|
|
3224
|
+
e.preventDefault();
|
|
3225
|
+
this.#handleFileSelection(files);
|
|
3226
|
+
});
|
|
3216
3227
|
this.#attachmentsBar.addEventListener("click", (e) => {
|
|
3217
3228
|
const removeButton = e.target.closest(`.${UI_module_default.attachmentChipRemove}`);
|
|
3218
3229
|
if (!(removeButton instanceof HTMLElement)) return;
|
|
@@ -4037,6 +4048,47 @@ function retrieveJsonFromString(str) {
|
|
|
4037
4048
|
}
|
|
4038
4049
|
}
|
|
4039
4050
|
//#endregion
|
|
4051
|
+
//#region src/agent/utils/image.ts
|
|
4052
|
+
/**
|
|
4053
|
+
* 图片附件处理工具(纯浏览器 Canvas 实现,无额外依赖)
|
|
4054
|
+
*/
|
|
4055
|
+
/**
|
|
4056
|
+
* 按需缩小图片:仅当长边超过 maxDimension 时才缩放并重新编码为 JPEG,
|
|
4057
|
+
* 用于降低多模态请求里图片消耗的 token;非浏览器环境或解码/绘制失败时原样返回。
|
|
4058
|
+
* @param dataUrl - 原始图片的 base64 data URL
|
|
4059
|
+
* @param maxDimension - 长边允许的最大像素
|
|
4060
|
+
* @param quality - 重新编码为 JPEG 时的质量(0-1)
|
|
4061
|
+
* @returns 缩放后的 data URL,未超阈值或失败时返回原 dataUrl
|
|
4062
|
+
*/
|
|
4063
|
+
async function resizeImageDataUrl(dataUrl, maxDimension, quality) {
|
|
4064
|
+
if (typeof Image === "undefined" || typeof document === "undefined") return dataUrl;
|
|
4065
|
+
try {
|
|
4066
|
+
const img = await loadImage(dataUrl);
|
|
4067
|
+
const { naturalWidth: width, naturalHeight: height } = img;
|
|
4068
|
+
if (!width || !height || width <= maxDimension && height <= maxDimension) return dataUrl;
|
|
4069
|
+
const scale = maxDimension / Math.max(width, height);
|
|
4070
|
+
const targetWidth = Math.round(width * scale);
|
|
4071
|
+
const targetHeight = Math.round(height * scale);
|
|
4072
|
+
const canvas = document.createElement("canvas");
|
|
4073
|
+
canvas.width = targetWidth;
|
|
4074
|
+
canvas.height = targetHeight;
|
|
4075
|
+
const ctx = canvas.getContext("2d");
|
|
4076
|
+
if (!ctx) return dataUrl;
|
|
4077
|
+
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
|
4078
|
+
return canvas.toDataURL("image/jpeg", quality);
|
|
4079
|
+
} catch {
|
|
4080
|
+
return dataUrl;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
function loadImage(dataUrl) {
|
|
4084
|
+
return new Promise((resolve, reject) => {
|
|
4085
|
+
const img = new Image();
|
|
4086
|
+
img.onload = () => resolve(img);
|
|
4087
|
+
img.onerror = () => reject(/* @__PURE__ */ new Error("Failed to load image"));
|
|
4088
|
+
img.src = dataUrl;
|
|
4089
|
+
});
|
|
4090
|
+
}
|
|
4091
|
+
//#endregion
|
|
4040
4092
|
//#region src/agent/utils/index.ts
|
|
4041
4093
|
/**
|
|
4042
4094
|
* 等待 `seconds` 秒。若提供了 `signal`,则等待可被取消:
|
|
@@ -4184,7 +4236,21 @@ tools.set("ask_user", tool({
|
|
|
4184
4236
|
inputSchema: z.object({ question: z.string() }),
|
|
4185
4237
|
execute: async function(input, { signal }) {
|
|
4186
4238
|
if (!this.onAskUser) throw new Error("ask_user tool requires onAskUser callback to be set");
|
|
4187
|
-
|
|
4239
|
+
const raw = await this.onAskUser(input.question, { signal });
|
|
4240
|
+
const answer = typeof raw === "string" ? raw : raw.text;
|
|
4241
|
+
const attachments = typeof raw === "string" ? void 0 : raw.attachments;
|
|
4242
|
+
if (attachments?.length) this.pendingAnswerAttachments = attachments;
|
|
4243
|
+
return `User answered: ${answer}`;
|
|
4244
|
+
}
|
|
4245
|
+
}));
|
|
4246
|
+
tools.set("image_understand", tool({
|
|
4247
|
+
description: "Re-examine a previously shown image attachment to answer a specific visual question about it. Images are only sent once — use this instead of assuming you can still \"see\" an image from an earlier step. Only call this when the info is NOT already recorded in your memory/history; do not call it repeatedly for the same question.",
|
|
4248
|
+
inputSchema: z.object({
|
|
4249
|
+
attachment_name: z.string(),
|
|
4250
|
+
query: z.string()
|
|
4251
|
+
}),
|
|
4252
|
+
execute: async function(input, { signal }) {
|
|
4253
|
+
return await this.describeImage(input.attachment_name, input.query, signal);
|
|
4188
4254
|
}
|
|
4189
4255
|
}));
|
|
4190
4256
|
tools.set("click_element_by_index", tool({
|
|
@@ -4259,6 +4325,14 @@ tools.set("execute_javascript", tool({
|
|
|
4259
4325
|
//#region src/agent/AgentRuntime.ts
|
|
4260
4326
|
/** 文本类附件注入 prompt 时的最大字符数,避免超大文件把上下文撑爆 */
|
|
4261
4327
|
var ATTACHMENT_TEXT_MAX_LENGTH = 2e4;
|
|
4328
|
+
/** 图片长边超过该像素才压缩(OpenAI vision 高清分块阈值附近,兼顾 token 成本与截图文字可读性) */
|
|
4329
|
+
var ATTACHMENT_IMAGE_MAX_DIMENSION = 1568;
|
|
4330
|
+
/** 图片压缩重编码为 JPEG 时的质量 */
|
|
4331
|
+
var ATTACHMENT_IMAGE_JPEG_QUALITY = .85;
|
|
4332
|
+
/** 单次注入里最多内联发送的图片张数,超出的仅登记缓存,需模型主动调用 image_understand 查看 */
|
|
4333
|
+
var MAX_INLINE_IMAGES = 4;
|
|
4334
|
+
/** 历史任务 done 文本注入 prompt 时的最大字符数 */
|
|
4335
|
+
var PREVIOUS_TASK_TEXT_MAX_LENGTH = 300;
|
|
4262
4336
|
/**
|
|
4263
4337
|
* 用于浏览器自动化的 AI 智能体。
|
|
4264
4338
|
*
|
|
@@ -4310,6 +4384,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4310
4384
|
* 当 Agent 需要向用户提问时调用。
|
|
4311
4385
|
* 若未设置,`ask_user` 工具将被禁用。
|
|
4312
4386
|
* 实现应在 `signal` 中止时 reject 该 Promise。
|
|
4387
|
+
* 返回值可以是纯文本回答,也可以附带回答时用户上传的附件(图片等)。
|
|
4313
4388
|
* @example onAskUser: (q) => window.prompt(q) || ''
|
|
4314
4389
|
*/
|
|
4315
4390
|
onAskUser;
|
|
@@ -4327,6 +4402,18 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4327
4402
|
#observations = [];
|
|
4328
4403
|
/** 本次任务上传的附件;仅在任务第一步注入 LLM prompt,之后步骤不重复携带原文件以节省 token */
|
|
4329
4404
|
#taskAttachments = [];
|
|
4405
|
+
/**
|
|
4406
|
+
* `ask_user` 工具收到带附件的回答后暂存于此,供下一步 `#assembleUserPrompt` 注入 LLM prompt 后清空。
|
|
4407
|
+
* 非私有字段:需要被 `tools/index.ts` 中的 `ask_user` 工具从外部写入。
|
|
4408
|
+
*/
|
|
4409
|
+
pendingAnswerAttachments = [];
|
|
4410
|
+
/**
|
|
4411
|
+
* 图片附件缓存(按附件名索引):所有出现过的图片附件在此登记原始 dataUrl,
|
|
4412
|
+
* 使原图在 prompt 里只出现一次之后,仍可被 `image_understand` 工具按名称"按需重传"重新查看,
|
|
4413
|
+
* 而不必每步都把原图重新塞进主对话历史(节省 token)。
|
|
4414
|
+
* 非私有字段:需要被 `tools/index.ts` 中的 `image_understand` 工具从外部读取。
|
|
4415
|
+
*/
|
|
4416
|
+
imageAttachmentCache = /* @__PURE__ */ new Map();
|
|
4330
4417
|
/** 当前任务在 history 数组中的起始下标(用于跨任务保留历史时,把 prompt/循环检测限定在当前任务范围内) */
|
|
4331
4418
|
#taskHistoryStart = 0;
|
|
4332
4419
|
/** 当前一次运行完全结束时 resolve。由 `stop()` 等待。 */
|
|
@@ -4445,6 +4532,8 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4445
4532
|
this.task = task;
|
|
4446
4533
|
this.taskId = uid();
|
|
4447
4534
|
this.#taskAttachments = attachments;
|
|
4535
|
+
this.pendingAnswerAttachments = [];
|
|
4536
|
+
this.imageAttachmentCache = /* @__PURE__ */ new Map();
|
|
4448
4537
|
this.#taskHistoryStart = this.history.length;
|
|
4449
4538
|
this.#emitHistoryChange({
|
|
4450
4539
|
type: "task_start",
|
|
@@ -4695,6 +4784,35 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4695
4784
|
return result;
|
|
4696
4785
|
}
|
|
4697
4786
|
/**
|
|
4787
|
+
* 汇总当前任务之前、同一 AgentRuntime 实例已完成过的历史任务,生成一段纯文字摘要。
|
|
4788
|
+
* 只带文字结论(任务描述 + done 文本 + 涉及的附件文件名),不携带图片原始数据 ——
|
|
4789
|
+
* 图片附件与 `imageAttachmentCache` 一样按任务隔离,跨任务需要复看时应提示用户重新上传。
|
|
4790
|
+
* 只取最近 `PREVIOUS_TASKS_SUMMARY_LIMIT` 个任务,避免任务数增多后 prompt 无限增长。
|
|
4791
|
+
*/
|
|
4792
|
+
#buildPreviousTasksSummary() {
|
|
4793
|
+
const priorHistory = this.history.slice(0, this.#taskHistoryStart);
|
|
4794
|
+
if (priorHistory.length === 0) return "";
|
|
4795
|
+
const summaries = [];
|
|
4796
|
+
let current = null;
|
|
4797
|
+
for (const event of priorHistory) if (event.type === "task_start") {
|
|
4798
|
+
current = {
|
|
4799
|
+
task: event.task,
|
|
4800
|
+
attachmentNames: event.attachmentNames
|
|
4801
|
+
};
|
|
4802
|
+
summaries.push(current);
|
|
4803
|
+
} else if (event.type === "step" && event.action.name === "done" && current) {
|
|
4804
|
+
const input = event.action.input;
|
|
4805
|
+
current.result = `${input?.success === false ? "❌" : "✅"} ${input?.text ?? ""}`;
|
|
4806
|
+
}
|
|
4807
|
+
const recent = summaries.slice(-5);
|
|
4808
|
+
if (recent.length === 0) return "";
|
|
4809
|
+
return `<previous_tasks_summary>\n${recent.map((s) => {
|
|
4810
|
+
const attachmentNote = s.attachmentNames?.length ? ` [attachments uploaded then: ${s.attachmentNames.join(", ")} — NOT available in this task, ask the user to re-upload if you need to see them again]` : "";
|
|
4811
|
+
const result = s.result ? truncate(s.result, PREVIOUS_TASK_TEXT_MAX_LENGTH) : "(no result recorded — task may have been interrupted)";
|
|
4812
|
+
return `- Task: "${s.task}"${attachmentNote}\n Result: ${result}`;
|
|
4813
|
+
}).join("\n")}\n</previous_tasks_summary>\n\n`;
|
|
4814
|
+
}
|
|
4815
|
+
/**
|
|
4698
4816
|
* 在每一步之前生成系统观察
|
|
4699
4817
|
* @todo 控制台错误
|
|
4700
4818
|
*/
|
|
@@ -4735,6 +4853,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4735
4853
|
prompt += await this.#getInstructions();
|
|
4736
4854
|
const currentTaskHistory = this.#currentTaskHistory;
|
|
4737
4855
|
const stepCount = currentTaskHistory.filter((e) => e.type === "step").length;
|
|
4856
|
+
if (stepCount === 0) prompt += this.#buildPreviousTasksSummary();
|
|
4738
4857
|
prompt += "<agent_state>\n";
|
|
4739
4858
|
prompt += "<user_request>\n";
|
|
4740
4859
|
prompt += `${this.task}\n`;
|
|
@@ -4757,6 +4876,7 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4757
4876
|
prompt += `Evaluation of Previous Step: ${event.reflection.evaluation_previous_goal}\n`;
|
|
4758
4877
|
prompt += `Memory: ${event.reflection.memory}\n`;
|
|
4759
4878
|
prompt += `Next Goal: ${event.reflection.next_goal}\n`;
|
|
4879
|
+
prompt += `Action: ${event.action.name}(${JSON.stringify(event.action.input)})\n`;
|
|
4760
4880
|
prompt += `Action Results: ${event.action.output}\n`;
|
|
4761
4881
|
prompt += `</step_${stepIndex}>\n`;
|
|
4762
4882
|
} else if (event.type === "observation") prompt += `<sys>${event.content}</sys>\n`;
|
|
@@ -4771,20 +4891,38 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4771
4891
|
prompt += browserState.footer + "\n\n";
|
|
4772
4892
|
prompt += "</browser_state>\n\n";
|
|
4773
4893
|
if (stepCount === 0 && this.#taskAttachments.length > 0) return await this.#injectAttachments(prompt, this.#taskAttachments);
|
|
4894
|
+
if (this.pendingAnswerAttachments.length > 0) {
|
|
4895
|
+
const attachments = this.pendingAnswerAttachments;
|
|
4896
|
+
this.pendingAnswerAttachments = [];
|
|
4897
|
+
return await this.#injectAttachments(prompt, attachments);
|
|
4898
|
+
}
|
|
4774
4899
|
return prompt;
|
|
4775
4900
|
}
|
|
4776
4901
|
/**
|
|
4777
4902
|
* 把附件内容注入 prompt:图片转为 image_url 片段;文本类文件解码后截断拼进文字部分。
|
|
4778
4903
|
* 仅当存在图片附件时才返回多模态数组,纯文本附件保持返回字符串(不影响不支持 vision 的模型)。
|
|
4904
|
+
* 图片附件会先登记进 `imageAttachmentCache`(存原图,供 `image_understand` 复看细节用),
|
|
4905
|
+
* 内联发给模型的图片先按 `ATTACHMENT_IMAGE_MAX_DIMENSION` 压缩降 token;
|
|
4906
|
+
* 单次超过 `MAX_INLINE_IMAGES` 张时,多出的图片只登记缓存 + 文字提示名称,不内联发送。
|
|
4907
|
+
* 之后步骤不再重复下发原图,需要复看细节时改由模型调用 `image_understand` 按需重传。
|
|
4779
4908
|
*/
|
|
4780
4909
|
async #injectAttachments(prompt, attachments) {
|
|
4781
4910
|
const textSegments = [prompt];
|
|
4782
4911
|
const imageParts = [];
|
|
4912
|
+
let inlinedImageCount = 0;
|
|
4783
4913
|
for (const attachment of attachments) {
|
|
4784
4914
|
if (attachment.mimeType.startsWith("image/")) {
|
|
4915
|
+
this.imageAttachmentCache.set(attachment.name, attachment);
|
|
4916
|
+
if (inlinedImageCount >= MAX_INLINE_IMAGES) {
|
|
4917
|
+
textSegments.push(`[Image attachment "${attachment.name}" was NOT shown inline (too many images in this batch). Call image_understand with attachment_name="${attachment.name}" if you need to see it.]`);
|
|
4918
|
+
continue;
|
|
4919
|
+
}
|
|
4920
|
+
inlinedImageCount++;
|
|
4921
|
+
const resizedDataUrl = await resizeImageDataUrl(attachment.dataUrl, ATTACHMENT_IMAGE_MAX_DIMENSION, ATTACHMENT_IMAGE_JPEG_QUALITY);
|
|
4922
|
+
textSegments.push(`[Image attachment "${attachment.name}" is shown below. It will NOT be resent in later steps — note only the key facts you need into memory, don't transcribe it verbatim. If you need to re-examine its visual details (e.g. exact small text) in a later step, call image_understand with attachment_name="${attachment.name}".]`);
|
|
4785
4923
|
imageParts.push({
|
|
4786
4924
|
type: "image_url",
|
|
4787
|
-
image_url: { url:
|
|
4925
|
+
image_url: { url: resizedDataUrl }
|
|
4788
4926
|
});
|
|
4789
4927
|
continue;
|
|
4790
4928
|
}
|
|
@@ -4797,6 +4935,33 @@ var AgentRuntime = class extends EventTarget {
|
|
|
4797
4935
|
text: textSegments.join("\n\n")
|
|
4798
4936
|
}, ...imageParts];
|
|
4799
4937
|
}
|
|
4938
|
+
/**
|
|
4939
|
+
* 针对已缓存的图片附件发起一次独立的视觉问答,不进入主对话历史、不携带主链路的历史上下文。
|
|
4940
|
+
* 供 `image_understand` 工具使用:模型需要复看图片细节时按需调用,而不是每步都重发原图。
|
|
4941
|
+
* @param attachmentName - 附件名(即注入 prompt 时标注的名称)
|
|
4942
|
+
* @param query - 需要从图片中确认的具体问题
|
|
4943
|
+
* @param signal - 取消信号
|
|
4944
|
+
* @returns 视觉模型针对 query 给出的文字答案;附件不存在时返回错误提示
|
|
4945
|
+
*/
|
|
4946
|
+
async describeImage(attachmentName, query, signal) {
|
|
4947
|
+
const attachment = this.imageAttachmentCache.get(attachmentName);
|
|
4948
|
+
if (!attachment) return `❌ Attachment "${attachmentName}" not found. Available image attachments: ${[...this.imageAttachmentCache.keys()].join(", ") || "none"}`;
|
|
4949
|
+
const answerTool = {
|
|
4950
|
+
description: "Answer the question about the image as precisely and completely as possible.",
|
|
4951
|
+
inputSchema: z.object({ answer: z.string() }),
|
|
4952
|
+
execute: async (args) => args
|
|
4953
|
+
};
|
|
4954
|
+
return (await this.#llm.invoke([{
|
|
4955
|
+
role: "user",
|
|
4956
|
+
content: [{
|
|
4957
|
+
type: "text",
|
|
4958
|
+
text: query
|
|
4959
|
+
}, {
|
|
4960
|
+
type: "image_url",
|
|
4961
|
+
image_url: { url: attachment.dataUrl }
|
|
4962
|
+
}]
|
|
4963
|
+
}], { answer_image_question: answerTool }, signal, { toolChoiceName: "answer_image_question" })).toolResult.answer;
|
|
4964
|
+
}
|
|
4800
4965
|
/** 销毁 Agent:中止任务、释放浏览器控制器,并触发 dispose 事件(一次性) */
|
|
4801
4966
|
dispose() {
|
|
4802
4967
|
if (this.disposed) return;
|