@nexface/tools 0.1.10-alpha.0 → 0.1.11-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/controller.js +8 -27
- package/dist/browser/controller.js.map +1 -1
- package/dist/browser/feedback.d.ts +2 -8
- package/dist/browser/feedback.js +2 -11
- package/dist/browser/feedback.js.map +1 -1
- package/dist/browser/locator.d.ts +0 -1
- package/dist/browser/locator.js +1 -1
- package/dist/browser/locator.js.map +1 -1
- package/dist/browser/toolset.d.ts +1 -3
- package/dist/browser/toolset.js +6 -9
- package/dist/browser/toolset.js.map +1 -1
- package/dist/internal-react.d.ts +3 -4
- package/dist/internal-react.js.map +1 -1
- package/dist/operation-runtime.d.ts +0 -1
- package/dist/operation-runtime.js +8 -12
- package/dist/operation-runtime.js.map +1 -1
- package/dist/operation-surface.d.ts +2 -2
- package/dist/operation-surface.js +99 -77
- package/dist/operation-surface.js.map +1 -1
- package/dist/operation-types.d.ts +8 -18
- package/dist/operation-types.js.map +1 -1
- package/dist/operation-view.d.ts +13 -4
- package/dist/operation-view.js +348 -147
- package/dist/operation-view.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js.map +1 -1
- package/dist/runtime.d.ts +6 -27
- package/dist/runtime.js +23 -35
- package/dist/runtime.js.map +1 -1
- package/dist/styles.css +7 -1
- package/package.json +1 -1
package/dist/operation-view.js
CHANGED
|
@@ -1,190 +1,391 @@
|
|
|
1
1
|
import { CursorIcon as Cursor } from "@phosphor-icons/react/Cursor";
|
|
2
2
|
import { SparkleIcon as Sparkle } from "@phosphor-icons/react/Sparkle";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { createPortal } from "react-dom";
|
|
6
|
-
import { PageCommandError } from "./browser/locator.js";
|
|
4
|
+
import { useLayoutEffect, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { createPortal, flushSync } from "react-dom";
|
|
7
6
|
import { lockOperationSurface } from "./operation-lock.js";
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const reached = () => pointer.isConnected && target.isConnected && distance(pointer.getBoundingClientRect(), center(target.getBoundingClientRect())) < 2;
|
|
23
|
-
const onTransition = (event) => {
|
|
24
|
-
if (event.target === pointer && event.propertyName === "transform" && reached())
|
|
25
|
-
finish();
|
|
26
|
-
};
|
|
27
|
-
const onAbort = () => finish(interrupted());
|
|
28
|
-
const timer = setTimeout(() => reached() ? finish() : finish(new PageCommandError({
|
|
29
|
-
code: "ACTION_TIMEOUT", message: "Page action pointer did not reach its target in time.",
|
|
30
|
-
})), 750);
|
|
31
|
-
pointer.addEventListener("transitionend", onTransition);
|
|
32
|
-
presentation.signal?.addEventListener("abort", onAbort, { once: true });
|
|
33
|
-
queueMicrotask(() => { if (presentation.signal?.aborted)
|
|
34
|
-
onAbort();
|
|
35
|
-
else if (reached())
|
|
36
|
-
finish(); });
|
|
37
|
-
return () => finish(interrupted());
|
|
7
|
+
const BROWSER_TOOL_NAMES = new Set(["nxf_getSnapshot", "nxf_executeAction", "nxf_applyAction"]);
|
|
8
|
+
const CENTER_BROWSER_TOOL_NAMES = new Set(["nxf_getSnapshot", "nxf_executeAction"]);
|
|
9
|
+
const POINTER_EXIT_DURATION_MS = 120;
|
|
10
|
+
const CONTROLLER_EXIT_DURATION_MS = 300;
|
|
11
|
+
function operationProgressLabel(operation) {
|
|
12
|
+
const progressLabel = operation.progressLabel?.trim().replace(/\s+/g, " ");
|
|
13
|
+
if (progressLabel)
|
|
14
|
+
return progressLabel.slice(0, 48);
|
|
15
|
+
return BROWSER_TOOL_NAMES.has(operation.name)
|
|
16
|
+
? "正在浏览网页"
|
|
17
|
+
: "正在处理你的请求";
|
|
18
|
+
}
|
|
19
|
+
function operationEffect(operation) {
|
|
20
|
+
return operation.effect ?? "action";
|
|
38
21
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
feedback.effect === "read" ? React.createElement("span", { className: "agent-operation__pointer-read-scan" },
|
|
43
|
-
React.createElement("i", null),
|
|
44
|
-
React.createElement("i", null),
|
|
45
|
-
React.createElement("i", null),
|
|
46
|
-
React.createElement("i", null),
|
|
47
|
-
React.createElement("i", null)) : null,
|
|
48
|
-
feedback.effect === "navigate" ? React.createElement("span", { className: "agent-operation__pointer-navigation-trail" }) : null,
|
|
49
|
-
React.createElement("span", { className: "agent-operation__pointer-label-text" }, feedback.label));
|
|
22
|
+
function normalizedLabel(label) {
|
|
23
|
+
const normalized = label?.trim().replace(/\s+/g, " ");
|
|
24
|
+
return normalized || undefined;
|
|
50
25
|
}
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
return
|
|
57
|
-
"--agent-operation-pointer-y": `${current.top}px` } },
|
|
58
|
-
React.createElement(FeedbackLabel, { feedback: feedback })) : null;
|
|
26
|
+
function operationTargetLabel(operation, target) {
|
|
27
|
+
const label = target?.label;
|
|
28
|
+
if (!label || label === operation.name)
|
|
29
|
+
return undefined;
|
|
30
|
+
const rawNamePrefix = `${operation.name} · `;
|
|
31
|
+
return label.startsWith(rawNamePrefix) ? label.slice(rawNamePrefix.length) : label;
|
|
59
32
|
}
|
|
60
|
-
function
|
|
61
|
-
const
|
|
33
|
+
function pointerMoveDuration(from, to) {
|
|
34
|
+
const distance = Math.hypot(to.left - from.left, to.top - from.top);
|
|
35
|
+
if (distance < 2)
|
|
36
|
+
return 0;
|
|
37
|
+
return Math.round(Math.min(360, Math.max(180, 180 + distance * 0.18)));
|
|
38
|
+
}
|
|
39
|
+
function pointerReachedTarget(element, target) {
|
|
40
|
+
const rect = element.getBoundingClientRect();
|
|
41
|
+
return Math.hypot(rect.left - target.left, rect.top - target.top) < 2;
|
|
42
|
+
}
|
|
43
|
+
function AgentPointer({ active, initialPosition, label, labelActive, operation, targetElement, targetPosition, visible, onPointerReady }) {
|
|
44
|
+
const pointerRef = useRef(null);
|
|
45
|
+
const activeOperationRef = useRef(undefined);
|
|
46
|
+
const activeTargetRef = useRef(undefined);
|
|
47
|
+
const exitTimerRef = useRef(undefined);
|
|
48
|
+
const movementTimerRef = useRef(undefined);
|
|
49
|
+
const motionRevisionRef = useRef(0);
|
|
50
|
+
const latestActiveRef = useRef(active);
|
|
51
|
+
const latestOperationRef = useRef(operation);
|
|
62
52
|
const [motion, setMotion] = useState();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
latestActiveRef.current = active;
|
|
54
|
+
latestOperationRef.current = operation;
|
|
55
|
+
const clearExitTimer = () => {
|
|
56
|
+
if (!exitTimerRef.current)
|
|
57
|
+
return;
|
|
58
|
+
clearTimeout(exitTimerRef.current);
|
|
59
|
+
exitTimerRef.current = undefined;
|
|
60
|
+
};
|
|
61
|
+
const clearMovementTimer = () => {
|
|
62
|
+
if (!movementTimerRef.current)
|
|
63
|
+
return;
|
|
64
|
+
clearTimeout(movementTimerRef.current);
|
|
65
|
+
movementTimerRef.current = undefined;
|
|
66
|
+
};
|
|
67
|
+
const finishMovement = (revision, toolCallId) => {
|
|
68
|
+
const latestOperation = latestOperationRef.current;
|
|
69
|
+
if (!latestActiveRef.current
|
|
70
|
+
|| motionRevisionRef.current !== revision
|
|
71
|
+
|| latestOperation.toolCallId !== toolCallId)
|
|
72
|
+
return;
|
|
73
|
+
clearMovementTimer();
|
|
74
|
+
const settle = () => setMotion((current) => current?.revision === revision ? { ...current, phase: "resting" } : current);
|
|
75
|
+
if (latestOperation.name !== "nxf_applyAction" || !onPointerReady) {
|
|
76
|
+
settle();
|
|
67
77
|
return;
|
|
68
78
|
}
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
queueMicrotask(() => {
|
|
80
|
+
if (!latestActiveRef.current || motionRevisionRef.current !== revision || latestOperationRef.current.toolCallId !== toolCallId)
|
|
81
|
+
return;
|
|
82
|
+
flushSync(settle);
|
|
83
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
84
|
+
if (latestActiveRef.current && motionRevisionRef.current === revision
|
|
85
|
+
&& latestOperationRef.current.toolCallId === toolCallId && activeTargetRef.current?.isConnected)
|
|
86
|
+
onPointerReady(toolCallId);
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
useEffect(() => () => {
|
|
91
|
+
clearExitTimer();
|
|
92
|
+
clearMovementTimer();
|
|
93
|
+
}, []);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!visible || !initialPosition) {
|
|
96
|
+
if (!motion || motion.phase === "hidden")
|
|
97
|
+
return;
|
|
98
|
+
activeOperationRef.current = undefined;
|
|
99
|
+
activeTargetRef.current = undefined;
|
|
100
|
+
clearMovementTimer();
|
|
101
|
+
const revision = ++motionRevisionRef.current;
|
|
102
|
+
setMotion((current) => current
|
|
103
|
+
? { ...current, durationMs: POINTER_EXIT_DURATION_MS, phase: "hidden", revision }
|
|
104
|
+
: current);
|
|
105
|
+
clearExitTimer();
|
|
106
|
+
exitTimerRef.current = setTimeout(() => {
|
|
107
|
+
if (motionRevisionRef.current === revision)
|
|
108
|
+
setMotion(undefined);
|
|
109
|
+
}, POINTER_EXIT_DURATION_MS);
|
|
75
110
|
return;
|
|
76
111
|
}
|
|
112
|
+
clearExitTimer();
|
|
77
113
|
if (!motion) {
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
activeOperationRef.current = operation.toolCallId;
|
|
115
|
+
activeTargetRef.current = undefined;
|
|
116
|
+
const revision = ++motionRevisionRef.current;
|
|
117
|
+
setMotion({
|
|
118
|
+
durationMs: 0,
|
|
119
|
+
phase: "resting",
|
|
120
|
+
position: initialPosition,
|
|
121
|
+
revision,
|
|
122
|
+
toolCallId: operation.toolCallId,
|
|
123
|
+
});
|
|
80
124
|
return;
|
|
81
125
|
}
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
if (!active) {
|
|
127
|
+
activeOperationRef.current = undefined;
|
|
128
|
+
activeTargetRef.current = undefined;
|
|
129
|
+
clearMovementTimer();
|
|
130
|
+
if (motion.phase !== "resting") {
|
|
131
|
+
const revision = ++motionRevisionRef.current;
|
|
132
|
+
setMotion((current) => current
|
|
133
|
+
? { ...current, phase: "resting", revision }
|
|
134
|
+
: current);
|
|
135
|
+
}
|
|
85
136
|
return;
|
|
86
137
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
138
|
+
const operationChanged = activeOperationRef.current !== operation.toolCallId;
|
|
139
|
+
if (operationChanged) {
|
|
140
|
+
activeOperationRef.current = operation.toolCallId;
|
|
141
|
+
activeTargetRef.current = undefined;
|
|
142
|
+
clearMovementTimer();
|
|
143
|
+
}
|
|
144
|
+
else if (motion.phase === "hidden") {
|
|
145
|
+
setMotion((current) => current
|
|
146
|
+
? { ...current, durationMs: 0, phase: "resting" }
|
|
147
|
+
: current);
|
|
148
|
+
}
|
|
149
|
+
if (!targetElement || !targetPosition) {
|
|
150
|
+
const positionChanged = CENTER_BROWSER_TOOL_NAMES.has(operation.name)
|
|
151
|
+
&& (motion.position.left !== initialPosition.left || motion.position.top !== initialPosition.top);
|
|
152
|
+
if (operationChanged || activeTargetRef.current || positionChanged) {
|
|
153
|
+
activeTargetRef.current = undefined;
|
|
154
|
+
clearMovementTimer();
|
|
155
|
+
const revision = ++motionRevisionRef.current;
|
|
156
|
+
const pointerRect = pointerRef.current?.getBoundingClientRect();
|
|
157
|
+
setMotion((current) => current
|
|
158
|
+
? {
|
|
159
|
+
...current,
|
|
160
|
+
durationMs: 0,
|
|
161
|
+
phase: "resting",
|
|
162
|
+
position: operationChanged || positionChanged ? initialPosition : pointerRect
|
|
163
|
+
? { left: pointerRect.left, top: pointerRect.top }
|
|
164
|
+
: current.position,
|
|
165
|
+
revision,
|
|
166
|
+
toolCallId: operation.toolCallId,
|
|
167
|
+
}
|
|
168
|
+
: current);
|
|
169
|
+
}
|
|
95
170
|
return;
|
|
96
|
-
|
|
97
|
-
|
|
171
|
+
}
|
|
172
|
+
const targetChanged = operationChanged || activeTargetRef.current !== targetElement;
|
|
173
|
+
if (!targetChanged) {
|
|
174
|
+
if (motion.position.left === targetPosition.left && motion.position.top === targetPosition.top)
|
|
175
|
+
return;
|
|
176
|
+
clearMovementTimer();
|
|
177
|
+
const revision = ++motionRevisionRef.current;
|
|
178
|
+
const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? 0 : 180;
|
|
179
|
+
setMotion((current) => current
|
|
180
|
+
? { ...current, durationMs, phase: "moving", position: targetPosition, revision }
|
|
181
|
+
: current);
|
|
182
|
+
if (durationMs === 0)
|
|
183
|
+
finishMovement(revision, operation.toolCallId);
|
|
184
|
+
else
|
|
185
|
+
movementTimerRef.current = setTimeout(() => finishMovement(revision, operation.toolCallId), durationMs + 50);
|
|
98
186
|
return;
|
|
99
187
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
188
|
+
activeTargetRef.current = targetElement;
|
|
189
|
+
clearMovementTimer();
|
|
190
|
+
const revision = ++motionRevisionRef.current;
|
|
191
|
+
const pointerRect = pointerRef.current?.getBoundingClientRect();
|
|
192
|
+
const currentPosition = pointerRect
|
|
193
|
+
? { left: pointerRect.left, top: pointerRect.top }
|
|
194
|
+
: motion.position;
|
|
195
|
+
const prefersReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
196
|
+
const durationMs = prefersReducedMotion ? 0 : pointerMoveDuration(currentPosition, targetPosition);
|
|
197
|
+
setMotion((current) => current
|
|
198
|
+
? {
|
|
199
|
+
...current,
|
|
200
|
+
durationMs,
|
|
201
|
+
phase: "moving",
|
|
202
|
+
position: targetPosition,
|
|
203
|
+
revision,
|
|
204
|
+
toolCallId: operation.toolCallId,
|
|
205
|
+
}
|
|
206
|
+
: current);
|
|
207
|
+
if (durationMs === 0)
|
|
208
|
+
finishMovement(revision, operation.toolCallId);
|
|
209
|
+
else {
|
|
210
|
+
// A first-frame position update may not dispatch transitionend.
|
|
211
|
+
movementTimerRef.current = setTimeout(() => finishMovement(revision, operation.toolCallId), durationMs + 50);
|
|
212
|
+
}
|
|
213
|
+
}, [
|
|
214
|
+
active,
|
|
215
|
+
initialPosition?.left,
|
|
216
|
+
initialPosition?.top,
|
|
217
|
+
motion,
|
|
218
|
+
operation.toolCallId,
|
|
219
|
+
targetElement,
|
|
220
|
+
targetPosition?.left,
|
|
221
|
+
targetPosition?.top,
|
|
222
|
+
visible,
|
|
223
|
+
]);
|
|
224
|
+
if (!motion)
|
|
225
|
+
return null;
|
|
226
|
+
const effect = operationEffect(operation);
|
|
227
|
+
const activeEffect = active ? effect : undefined;
|
|
228
|
+
const browserTool = CENTER_BROWSER_TOOL_NAMES.has(operation.name);
|
|
229
|
+
return (React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": active ? "true" : "false", "data-operation-effect": activeEffect, "data-phase": motion.phase, onTransitionEnd: (event) => {
|
|
230
|
+
if (event.currentTarget !== event.target
|
|
231
|
+
|| event.propertyName !== "transform"
|
|
232
|
+
|| motion.phase !== "moving"
|
|
233
|
+
|| !pointerReachedTarget(event.currentTarget, motion.position))
|
|
234
|
+
return;
|
|
235
|
+
finishMovement(motion.revision, motion.toolCallId);
|
|
236
|
+
}, ref: pointerRef, style: {
|
|
237
|
+
"--agent-operation-pointer-duration": `${motion.durationMs}ms`,
|
|
238
|
+
"--agent-operation-pointer-x": `${motion.position.left}px`,
|
|
239
|
+
"--agent-operation-pointer-y": `${motion.position.top}px`,
|
|
240
|
+
} },
|
|
241
|
+
active && visible && motion.toolCallId === operation.toolCallId && browserTool && effect === "action" && motion.phase !== "hidden" ? (React.createElement(React.Fragment, null,
|
|
242
|
+
React.createElement("span", { className: "agent-operation__browser-halo" }),
|
|
243
|
+
labelActive ? React.createElement("span", { className: "agent-operation__browser-label", title: label }, label) : null)) : null,
|
|
244
|
+
active && visible && motion.toolCallId === operation.toolCallId && !browserTool && effect === "action" && motion.phase !== "hidden" ? (React.createElement(React.Fragment, null,
|
|
245
|
+
React.createElement("span", { className: "agent-operation__pointer-cursor" },
|
|
246
|
+
React.createElement(Cursor, { className: "agent-operation__pointer", size: 24, weight: "fill" })))) : null,
|
|
247
|
+
active && (!browserTool || (effect === "read" && visible && motion.toolCallId === operation.toolCallId))
|
|
248
|
+
&& motion.phase !== "hidden" && (labelActive || effect !== "action") ? (React.createElement("span", { className: labelActive ? "agent-operation__pointer-label" : "agent-operation__pointer-indicator", "data-browser-tool": browserTool ? "true" : undefined, "data-operation-effect": activeEffect, title: labelActive ? label : undefined },
|
|
249
|
+
activeEffect === "processing" ? React.createElement("span", { className: "agent-operation__pointer-processing" }) : null,
|
|
250
|
+
activeEffect === "read" ? (React.createElement("span", { className: "agent-operation__pointer-read-scan" },
|
|
251
|
+
React.createElement("i", null),
|
|
252
|
+
React.createElement("i", null),
|
|
253
|
+
React.createElement("i", null),
|
|
254
|
+
React.createElement("i", null),
|
|
255
|
+
React.createElement("i", null))) : null,
|
|
256
|
+
activeEffect === "navigate" ? React.createElement("span", { className: "agent-operation__pointer-navigation-trail" }) : null,
|
|
257
|
+
labelActive ? React.createElement("span", { className: "agent-operation__pointer-label-text" }, label) : null)) : null));
|
|
108
258
|
}
|
|
109
|
-
export function OperationOverlayView({ operation,
|
|
259
|
+
export function OperationOverlayView({ operation, progressHistory = [], running, feedbackActive = running, labelActive = feedbackActive, humanHasControl = false, cancelling = false, onCancel, resolveSurface, resolveTarget, fallbackPosition, onPointerReady }) {
|
|
110
260
|
const [, setGeometry] = useState("");
|
|
261
|
+
useLayoutEffect(() => {
|
|
262
|
+
const rect = resolveSurface?.()?.getBoundingClientRect();
|
|
263
|
+
if (rect)
|
|
264
|
+
setGeometry([rect.left, rect.top, rect.width, rect.height].join(","));
|
|
265
|
+
}, [resolveSurface]);
|
|
111
266
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
112
267
|
const [historyId] = useState(() => `nexface-operation-history-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`);
|
|
268
|
+
const clearTimerRef = useRef(undefined);
|
|
113
269
|
const cancelButtonRef = useRef(null);
|
|
114
|
-
const
|
|
115
|
-
const [
|
|
270
|
+
const controllerTimerRef = useRef(undefined);
|
|
271
|
+
const [visibleOperation, setVisibleOperation] = useState(operation);
|
|
272
|
+
const [controllerMounted, setControllerMounted] = useState(Boolean(operation
|
|
273
|
+
&& operation.status !== "cancelled"
|
|
274
|
+
&& (running || humanHasControl || operation.status === "failed")));
|
|
275
|
+
const [controllerExiting, setControllerExiting] = useState(false);
|
|
276
|
+
useLayoutEffect(() => {
|
|
277
|
+
if (clearTimerRef.current)
|
|
278
|
+
clearTimeout(clearTimerRef.current);
|
|
279
|
+
if (operation)
|
|
280
|
+
setVisibleOperation(operation);
|
|
281
|
+
if (operation?.status === "cancelled") {
|
|
282
|
+
clearTimerRef.current = setTimeout(() => setVisibleOperation(undefined), 600);
|
|
283
|
+
}
|
|
284
|
+
return () => { if (clearTimerRef.current)
|
|
285
|
+
clearTimeout(clearTimerRef.current); };
|
|
286
|
+
}, [operation, running]);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if ((!running && !feedbackActive && !humanHasControl) || !visibleOperation || visibleOperation.status === "cancelled")
|
|
289
|
+
return;
|
|
290
|
+
let frame = requestAnimationFrame(function update() {
|
|
291
|
+
const surface = resolveSurface?.();
|
|
292
|
+
const target = resolveTarget?.(visibleOperation)?.element;
|
|
293
|
+
const geometry = [surface, target].map((element) => {
|
|
294
|
+
if (!element?.isConnected)
|
|
295
|
+
return "";
|
|
296
|
+
const rect = element.getBoundingClientRect();
|
|
297
|
+
return [rect.left, rect.top, rect.width, rect.height].join(",");
|
|
298
|
+
}).join(";");
|
|
299
|
+
setGeometry(geometry);
|
|
300
|
+
frame = requestAnimationFrame(update);
|
|
301
|
+
});
|
|
302
|
+
return () => cancelAnimationFrame(frame);
|
|
303
|
+
}, [visibleOperation?.toolCallId, visibleOperation?.status, running, feedbackActive, humanHasControl, resolveSurface, resolveTarget]);
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (progressHistory.length === 0)
|
|
306
|
+
setHistoryOpen(false);
|
|
307
|
+
}, [progressHistory.length]);
|
|
308
|
+
const controllerShouldBeVisible = Boolean(visibleOperation
|
|
309
|
+
&& visibleOperation.status !== "cancelled"
|
|
310
|
+
&& (running || humanHasControl || visibleOperation.status === "failed"));
|
|
116
311
|
useEffect(() => {
|
|
312
|
+
if (controllerTimerRef.current)
|
|
313
|
+
clearTimeout(controllerTimerRef.current);
|
|
117
314
|
if (controllerShouldBeVisible) {
|
|
118
|
-
|
|
315
|
+
setControllerMounted(true);
|
|
316
|
+
setControllerExiting(false);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!controllerMounted) {
|
|
320
|
+
setControllerExiting(false);
|
|
119
321
|
return;
|
|
120
322
|
}
|
|
121
323
|
setHistoryOpen(false);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
324
|
+
setControllerExiting(true);
|
|
325
|
+
controllerTimerRef.current = setTimeout(() => {
|
|
326
|
+
setControllerMounted(false);
|
|
327
|
+
setControllerExiting(false);
|
|
328
|
+
}, CONTROLLER_EXIT_DURATION_MS);
|
|
329
|
+
return () => {
|
|
330
|
+
if (controllerTimerRef.current)
|
|
331
|
+
clearTimeout(controllerTimerRef.current);
|
|
332
|
+
};
|
|
333
|
+
}, [controllerMounted, controllerShouldBeVisible]);
|
|
128
334
|
useLayoutEffect(() => {
|
|
129
335
|
const surface = resolveSurface?.();
|
|
130
336
|
if (!surface || !running || humanHasControl)
|
|
131
337
|
return;
|
|
132
338
|
return lockOperationSurface(surface, () => cancelButtonRef.current?.focus());
|
|
133
339
|
}, [humanHasControl, resolveSurface, running]);
|
|
134
|
-
|
|
135
|
-
const surface = resolveSurface?.();
|
|
136
|
-
const resolvedTarget = feedback?.resolveTarget ? feedback.resolveTarget() : feedback?.target;
|
|
137
|
-
const target = resolvedTarget?.isConnected && surface?.contains(resolvedTarget) ? resolvedTarget : undefined;
|
|
138
|
-
return {
|
|
139
|
-
surfaceRect: surface?.isConnected ? surface.getBoundingClientRect() : undefined,
|
|
140
|
-
target,
|
|
141
|
-
targetRect: target?.getBoundingClientRect(),
|
|
142
|
-
};
|
|
143
|
-
}, [resolveSurface, feedback?.target, feedback?.resolveTarget]);
|
|
144
|
-
useLayoutEffect(() => {
|
|
145
|
-
const update = () => {
|
|
146
|
-
const { surfaceRect, targetRect } = readGeometry();
|
|
147
|
-
const geometry = [surfaceRect, targetRect].map((rect) => {
|
|
148
|
-
if (!rect)
|
|
149
|
-
return "";
|
|
150
|
-
return [rect.left, rect.top, rect.width, rect.height].join(",");
|
|
151
|
-
}).join(";");
|
|
152
|
-
setGeometry(geometry);
|
|
153
|
-
};
|
|
154
|
-
update();
|
|
155
|
-
if (!running && !humanHasControl)
|
|
156
|
-
return;
|
|
157
|
-
let frame = requestAnimationFrame(function track() { update(); frame = requestAnimationFrame(track); });
|
|
158
|
-
return () => cancelAnimationFrame(frame);
|
|
159
|
-
}, [readGeometry, operation, running, humanHasControl]);
|
|
160
|
-
if (typeof document === "undefined")
|
|
340
|
+
if (!visibleOperation || typeof document === "undefined")
|
|
161
341
|
return null;
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
342
|
+
const resolvedTarget = resolveTarget?.(visibleOperation);
|
|
343
|
+
const surfaceElement = resolveSurface?.();
|
|
344
|
+
const target = resolvedTarget && (!surfaceElement || surfaceElement.contains(resolvedTarget.element))
|
|
345
|
+
? resolvedTarget
|
|
346
|
+
: undefined;
|
|
347
|
+
const structuredTargetLost = visibleOperation.name === "nxf_applyAction" && !target;
|
|
348
|
+
const progressLabel = operationProgressLabel(visibleOperation);
|
|
349
|
+
const effect = operationEffect(visibleOperation);
|
|
350
|
+
const targetLabel = operationTargetLabel(visibleOperation, target);
|
|
351
|
+
const pointerLabel = normalizedLabel(visibleOperation.progressLabel) ?? targetLabel ?? progressLabel;
|
|
352
|
+
const controllerLabel = humanHasControl ? undefined : progressLabel;
|
|
173
353
|
const statusLabel = humanHasControl ? "等待你完成操作" : {
|
|
174
|
-
running: "Agent 正在控制",
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
354
|
+
running: "Agent 正在控制",
|
|
355
|
+
completed: running ? "工具已完成,任务继续中" : "操作已完成",
|
|
356
|
+
failed: running ? "Agent 正在处理错误" : "操作失败",
|
|
357
|
+
cancelled: "任务已终止",
|
|
358
|
+
}[visibleOperation.status];
|
|
359
|
+
const compactStatusLabel = humanHasControl
|
|
360
|
+
? "等待你完成操作"
|
|
361
|
+
: progressLabel;
|
|
362
|
+
const rect = target?.element.isConnected ? target.element.getBoundingClientRect() : undefined;
|
|
363
|
+
const highlightedRect = effect === "navigate" ? undefined : rect;
|
|
364
|
+
const surfaceRect = surfaceElement?.isConnected ? surfaceElement.getBoundingClientRect() : undefined;
|
|
365
|
+
const agentControlsSurface = running && !humanHasControl;
|
|
366
|
+
const operationFeedbackActive = feedbackActive && !structuredTargetLost && !humanHasControl && visibleOperation.status !== "cancelled";
|
|
367
|
+
const pointerVisible = Boolean(surfaceRect) && operationFeedbackActive;
|
|
368
|
+
const pointerPosition = rect ? {
|
|
369
|
+
left: rect.left + rect.width / 2,
|
|
370
|
+
top: rect.top + rect.height / 2,
|
|
371
|
+
} : undefined;
|
|
372
|
+
const cancel = onCancel ? () => {
|
|
373
|
+
onCancel();
|
|
374
|
+
} : undefined;
|
|
375
|
+
return createPortal(React.createElement("div", { className: "agent-operation", "data-nexface-ignore": true, "data-feedback-active": operationFeedbackActive, "data-structured-target-lost": structuredTargetLost, "data-human-control": humanHasControl, "data-operation-effect": effect, "data-run-active": running, "data-status": visibleOperation.status },
|
|
179
376
|
agentControlsSurface && surfaceRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
|
|
180
|
-
...(highlightedRect ? {
|
|
377
|
+
...(highlightedRect ? {
|
|
378
|
+
"--agent-operation-cutout-height": `${highlightedRect.height}px`,
|
|
181
379
|
"--agent-operation-cutout-left": `${highlightedRect.left - surfaceRect.left}px`,
|
|
182
|
-
"--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`,
|
|
380
|
+
"--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`,
|
|
381
|
+
"--agent-operation-cutout-width": `${highlightedRect.width}px`,
|
|
382
|
+
} : {}),
|
|
183
383
|
height: surfaceRect.height, left: surfaceRect.left, top: surfaceRect.top, width: surfaceRect.width,
|
|
184
384
|
} }) : null,
|
|
185
|
-
|
|
186
|
-
React.createElement(AgentPointer, { active:
|
|
187
|
-
|
|
385
|
+
operationFeedbackActive && highlightedRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__target", "data-operation-effect": effect, style: { height: highlightedRect.height, left: highlightedRect.left, top: highlightedRect.top, width: highlightedRect.width } }) : null,
|
|
386
|
+
React.createElement(AgentPointer, { active: operationFeedbackActive, initialPosition: visibleOperation.name === "nxf_applyAction" && surfaceRect
|
|
387
|
+
? { left: surfaceRect.left + surfaceRect.width / 2, top: surfaceRect.top + surfaceRect.height / 2 }
|
|
388
|
+
: pointerPosition ?? fallbackPosition ?? (surfaceRect ? { left: surfaceRect.left + surfaceRect.width / 2, top: surfaceRect.top + surfaceRect.height / 2 } : undefined), label: pointerLabel, labelActive: labelActive, operation: visibleOperation, targetElement: target?.element, targetPosition: pointerPosition, visible: pointerVisible, ...(onPointerReady ? { onPointerReady } : {}) }),
|
|
188
389
|
controllerMounted ? React.createElement("section", { "aria-live": "polite", className: "agent-operation__controller", "data-visibility": controllerExiting ? "exiting" : "visible", style: surfaceRect ? { left: surfaceRect.left + surfaceRect.width / 2, right: "auto", "--agent-operation-controller-x": "-50%", maxWidth: Math.max(0, surfaceRect.width - 32) } : undefined },
|
|
189
390
|
historyOpen ? React.createElement("aside", { "aria-label": "\u672C\u6B21\u8FD0\u884C\u7684\u64CD\u4F5C\u8BB0\u5F55", className: "agent-operation__history", id: historyId },
|
|
190
391
|
React.createElement("header", null,
|
|
@@ -206,7 +407,7 @@ export function OperationOverlayView({ operation, feedback, progressHistory = []
|
|
|
206
407
|
React.createElement("strong", null, controllerLabel)),
|
|
207
408
|
statusLabel ? React.createElement("small", { className: "agent-operation__description" },
|
|
208
409
|
React.createElement("span", { className: "agent-operation__status" }, statusLabel)) : null),
|
|
209
|
-
(running || humanHasControl) &&
|
|
210
|
-
cancel && (running || humanHasControl) &&
|
|
410
|
+
(running || humanHasControl) && visibleOperation.status !== "cancelled" ? React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel) : null,
|
|
411
|
+
cancel && (running || humanHasControl) && visibleOperation.status !== "cancelled" ? React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务") : null) : null), document.body);
|
|
211
412
|
}
|
|
212
413
|
//# sourceMappingURL=operation-view.js.map
|