@nexface/tools 0.1.11-alpha.0 → 0.1.11

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.
@@ -1,391 +1,190 @@
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 { useLayoutEffect, useEffect, useRef, useState } from "react";
5
- import { createPortal, flushSync } from "react-dom";
4
+ import { useCallback, useLayoutEffect, useEffect, useRef, useState } from "react";
5
+ import { createPortal } from "react-dom";
6
+ import { PageCommandError } from "./browser/locator.js";
6
7
  import { lockOperationSurface } from "./operation-lock.js";
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";
21
- }
22
- function normalizedLabel(label) {
23
- const normalized = label?.trim().replace(/\s+/g, " ");
24
- return normalized || undefined;
25
- }
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;
8
+ const center = (rect) => ({ left: rect.left + rect.width / 2, top: rect.top + rect.height / 2 });
9
+ const distance = (a, b) => Math.hypot(a.left - b.left, a.top - b.top);
10
+ const interrupted = () => new PageCommandError({ code: "EXECUTION_CANCELLED", message: "Page action presentation was interrupted." });
11
+ function presentPointer(pointer, target, presentation) {
12
+ let finished = false;
13
+ const finish = (error) => {
14
+ if (finished)
15
+ return;
16
+ finished = true;
17
+ clearTimeout(timer);
18
+ pointer.removeEventListener("transitionend", onTransition);
19
+ presentation.signal?.removeEventListener("abort", onAbort);
20
+ presentation.finish(error);
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());
32
38
  }
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)));
39
+ function FeedbackLabel({ feedback }) {
40
+ return React.createElement("span", { className: "agent-operation__pointer-label", "data-browser-tool": feedback.centered ? "true" : undefined, "data-operation-effect": feedback.effect, title: feedback.label },
41
+ feedback.effect === "processing" ? React.createElement("span", { className: "agent-operation__pointer-processing" }) : null,
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));
38
50
  }
39
- function pointerReachedTarget(element, target) {
40
- const rect = element.getBoundingClientRect();
41
- return Math.hypot(rect.left - target.left, rect.top - target.top) < 2;
51
+ function FeedbackIndicator({ feedback, position }) {
52
+ const [lastPosition, setLastPosition] = useState(position ?? feedback.position);
53
+ useLayoutEffect(() => { if (position)
54
+ setLastPosition(position); }, [position?.left, position?.top]);
55
+ const current = position ?? lastPosition;
56
+ return current ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__pointer-effect", "data-active": "true", "data-operation-effect": feedback.effect, style: { "--agent-operation-pointer-duration": "0ms", "--agent-operation-pointer-x": `${current.left}px`,
57
+ "--agent-operation-pointer-y": `${current.top}px` } },
58
+ React.createElement(FeedbackLabel, { feedback: feedback })) : null;
42
59
  }
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);
60
+ function AgentPointer({ active, feedback, targetPosition, surfaceCenter }) {
61
+ const pointer = useRef(null);
52
62
  const [motion, setMotion] = useState();
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();
63
+ const action = feedback?.effect === "action" && (!feedback.requiresTarget || Boolean(targetPosition));
64
+ useLayoutEffect(() => {
65
+ if (!active) {
66
+ setMotion(undefined);
77
67
  return;
78
68
  }
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);
69
+ if (!action || !targetPosition) {
70
+ if (action && !feedback?.requiresTarget && motion && pointer.current) {
71
+ const rect = pointer.current.getBoundingClientRect();
72
+ if (distance(rect, motion.position) >= 1)
73
+ setMotion({ ...motion, position: rect, durationMs: 0 });
74
+ }
110
75
  return;
111
76
  }
112
- clearExitTimer();
113
77
  if (!motion) {
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
- });
78
+ setMotion({ position: feedback?.persistentCursor && surfaceCenter ? surfaceCenter : targetPosition,
79
+ durationMs: 0, persistent: Boolean(feedback?.persistentCursor) });
124
80
  return;
125
81
  }
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
- }
82
+ if (distance(motion.position, targetPosition) < 1) {
83
+ if (feedback?.persistentCursor && !motion.persistent)
84
+ setMotion({ ...motion, persistent: true });
136
85
  return;
137
86
  }
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
- }
87
+ const from = pointer.current?.getBoundingClientRect() ?? motion.position;
88
+ const durationMs = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? 0
89
+ : Math.round(Math.min(360, Math.max(180, 180 + distance(from, targetPosition) * 0.18)));
90
+ setMotion({ position: targetPosition, durationMs, persistent: motion.persistent || Boolean(feedback?.persistentCursor) });
91
+ }, [active, action, feedback?.id, targetPosition?.left, targetPosition?.top, motion]);
92
+ const show = Boolean(active && motion && (motion.persistent || action));
93
+ useLayoutEffect(() => {
94
+ if (!feedback?.presentation)
170
95
  return;
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);
96
+ if (!active || !feedback.target?.isConnected || !targetPosition) {
97
+ feedback.presentation.finish(interrupted());
186
98
  return;
187
99
  }
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));
100
+ if (show && pointer.current)
101
+ return presentPointer(pointer.current, feedback.target, feedback.presentation);
102
+ }, [active, show, feedback?.presentation, feedback?.target, Boolean(targetPosition)]);
103
+ return show && motion ? React.createElement("div", { "aria-hidden": true, ref: pointer, className: "agent-operation__pointer-effect", "data-active": action, "data-operation-effect": action ? "action" : undefined, style: { "--agent-operation-pointer-duration": `${motion.durationMs}ms`,
104
+ "--agent-operation-pointer-x": `${motion.position.left}px`, "--agent-operation-pointer-y": `${motion.position.top}px` } },
105
+ React.createElement("span", { className: "agent-operation__pointer-cursor" },
106
+ React.createElement(Cursor, { className: "agent-operation__pointer", size: 24, weight: "fill" })),
107
+ action && feedback ? React.createElement(FeedbackLabel, { feedback: feedback }) : null) : null;
258
108
  }
259
- export function OperationOverlayView({ operation, progressHistory = [], running, feedbackActive = running, labelActive = feedbackActive, humanHasControl = false, cancelling = false, onCancel, resolveSurface, resolveTarget, fallbackPosition, onPointerReady }) {
109
+ export function OperationOverlayView({ operation, feedback, progressHistory = [], running, humanHasControl = false, cancelling = false, onCancel: cancel, resolveSurface }) {
260
110
  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]);
266
111
  const [historyOpen, setHistoryOpen] = useState(false);
267
112
  const [historyId] = useState(() => `nexface-operation-history-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`);
268
- const clearTimerRef = useRef(undefined);
269
113
  const cancelButtonRef = useRef(null);
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"));
114
+ const controllerShouldBeVisible = operation.status !== "cancelled" && (running || humanHasControl || operation.status === "failed");
115
+ const [controllerVisibility, setControllerVisibility] = useState(controllerShouldBeVisible ? "visible" : undefined);
311
116
  useEffect(() => {
312
- if (controllerTimerRef.current)
313
- clearTimeout(controllerTimerRef.current);
314
117
  if (controllerShouldBeVisible) {
315
- setControllerMounted(true);
316
- setControllerExiting(false);
317
- return;
318
- }
319
- if (!controllerMounted) {
320
- setControllerExiting(false);
118
+ setControllerVisibility("visible");
321
119
  return;
322
120
  }
323
121
  setHistoryOpen(false);
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]);
122
+ setControllerVisibility((current) => current ? "exiting" : undefined);
123
+ const timer = setTimeout(() => setControllerVisibility(undefined), 300);
124
+ return () => clearTimeout(timer);
125
+ }, [controllerShouldBeVisible]);
126
+ useEffect(() => { if (!progressHistory.length)
127
+ setHistoryOpen(false); }, [progressHistory.length]);
334
128
  useLayoutEffect(() => {
335
129
  const surface = resolveSurface?.();
336
130
  if (!surface || !running || humanHasControl)
337
131
  return;
338
132
  return lockOperationSurface(surface, () => cancelButtonRef.current?.focus());
339
133
  }, [humanHasControl, resolveSurface, running]);
340
- if (!visibleOperation || typeof document === "undefined")
134
+ const readGeometry = useCallback(() => {
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")
341
161
  return null;
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;
353
- const statusLabel = humanHasControl ? "等待你完成操作" : {
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;
162
+ const { surfaceRect, target, targetRect: rect } = readGeometry();
163
+ const structuredTargetLost = Boolean(feedback?.requiresTarget && !target);
164
+ const activeFeedback = !cancelling && !humanHasControl && !structuredTargetLost ? feedback : undefined;
165
+ const effect = activeFeedback?.effect;
166
+ const highlightedRect = effect === "navigate" || activeFeedback?.centered ? undefined : rect;
365
167
  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 },
168
+ const operationFeedbackActive = Boolean(activeFeedback);
169
+ const controllerMounted = Boolean(controllerVisibility);
170
+ const controllerExiting = controllerVisibility === "exiting";
171
+ const controllerLabel = humanHasControl ? undefined : operation.progressLabel;
172
+ const compactStatusLabel = humanHasControl ? "等待你完成操作" : operation.progressLabel;
173
+ const statusLabel = humanHasControl ? "等待你完成操作" : {
174
+ running: "Agent 正在控制", completed: running ? "工具已完成,任务继续中" : "操作已完成",
175
+ failed: running ? "Agent 正在处理错误" : "操作失败", cancelled: "任务已终止",
176
+ }[operation.status];
177
+ const targetPosition = rect ? center(rect) : undefined;
178
+ 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 ?? "action", "data-run-active": running, "data-status": operation.status },
376
179
  agentControlsSurface && surfaceRect ? React.createElement("div", { "aria-hidden": true, className: "agent-operation__surface", style: {
377
- ...(highlightedRect ? {
378
- "--agent-operation-cutout-height": `${highlightedRect.height}px`,
180
+ ...(highlightedRect ? { "--agent-operation-cutout-height": `${highlightedRect.height}px`,
379
181
  "--agent-operation-cutout-left": `${highlightedRect.left - surfaceRect.left}px`,
380
- "--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`,
381
- "--agent-operation-cutout-width": `${highlightedRect.width}px`,
382
- } : {}),
182
+ "--agent-operation-cutout-top": `${highlightedRect.top - surfaceRect.top}px`, "--agent-operation-cutout-width": `${highlightedRect.width}px` } : {}),
383
183
  height: surfaceRect.height, left: surfaceRect.left, top: surfaceRect.top, width: surfaceRect.width,
384
184
  } }) : null,
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 } : {}) }),
185
+ activeFeedback && 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,
186
+ React.createElement(AgentPointer, { active: agentControlsSurface && !cancelling, feedback: feedback?.presentation ? feedback : activeFeedback, targetPosition: targetPosition, surfaceCenter: surfaceRect ? center(surfaceRect) : undefined }),
187
+ activeFeedback && effect !== "action" && surfaceRect ? React.createElement(FeedbackIndicator, { key: activeFeedback.id, feedback: activeFeedback, position: activeFeedback.centered ? center(surfaceRect) : targetPosition }) : null,
389
188
  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 },
390
189
  historyOpen ? React.createElement("aside", { "aria-label": "\u672C\u6B21\u8FD0\u884C\u7684\u64CD\u4F5C\u8BB0\u5F55", className: "agent-operation__history", id: historyId },
391
190
  React.createElement("header", null,
@@ -407,7 +206,7 @@ export function OperationOverlayView({ operation, progressHistory = [], running,
407
206
  React.createElement("strong", null, controllerLabel)),
408
207
  statusLabel ? React.createElement("small", { className: "agent-operation__description" },
409
208
  React.createElement("span", { className: "agent-operation__status" }, statusLabel)) : null),
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);
209
+ (running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("span", { className: "agent-operation__compact-status", title: compactStatusLabel }, compactStatusLabel) : null,
210
+ cancel && (running || humanHasControl) && operation.status !== "cancelled" ? React.createElement("button", { ref: cancelButtonRef, className: "agent-operation__stop", type: "button", disabled: cancelling, onClick: cancel }, cancelling ? "正在终止" : "终止任务") : null) : null), document.body);
412
211
  }
413
212
  //# sourceMappingURL=operation-view.js.map