@agent-native/core 0.106.0 → 0.106.1
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/index.ts +2 -0
- package/corpus/core/src/application-state/script-helpers.ts +12 -0
- package/corpus/core/src/application-state/store.ts +38 -0
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +16 -0
- package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
- package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
- package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
- package/corpus/templates/design/actions/view-screen.ts +98 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
- package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
- package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
- package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +32 -0
- package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
- package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +15 -0
- package/corpus/templates/design/shared/node-rewrite.ts +213 -0
- package/corpus/templates/design/shared/review-anchor.ts +26 -3
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/script-helpers.d.ts +1 -0
- package/dist/application-state/script-helpers.d.ts.map +1 -1
- package/dist/application-state/script-helpers.js +7 -1
- package/dist/application-state/script-helpers.js.map +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildReviewThreads,
|
|
3
|
+
callAction,
|
|
3
4
|
ReviewCommentComposer,
|
|
5
|
+
setClientAppState,
|
|
4
6
|
useCreateReviewComment,
|
|
5
7
|
useReplyReviewComment,
|
|
6
8
|
useResolveReviewThread,
|
|
@@ -9,11 +11,17 @@ import {
|
|
|
9
11
|
type ReviewThread,
|
|
10
12
|
} from "@agent-native/core/client";
|
|
11
13
|
import type { ReviewComment } from "@agent-native/core/review";
|
|
14
|
+
import {
|
|
15
|
+
designRepromptPendingStateKey,
|
|
16
|
+
type NodeRewriteTarget,
|
|
17
|
+
} from "@shared/node-rewrite";
|
|
12
18
|
import {
|
|
13
19
|
IconArrowUp,
|
|
20
|
+
IconChevronDown,
|
|
14
21
|
IconCircleCheck,
|
|
15
22
|
IconMessageCircle,
|
|
16
23
|
IconRobot,
|
|
24
|
+
IconSend,
|
|
17
25
|
IconX,
|
|
18
26
|
} from "@tabler/icons-react";
|
|
19
27
|
import {
|
|
@@ -28,8 +36,24 @@ import { toast } from "sonner";
|
|
|
28
36
|
|
|
29
37
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
30
38
|
import { Button } from "@/components/ui/button";
|
|
39
|
+
import {
|
|
40
|
+
DropdownMenu,
|
|
41
|
+
DropdownMenuContent,
|
|
42
|
+
DropdownMenuRadioGroup,
|
|
43
|
+
DropdownMenuRadioItem,
|
|
44
|
+
DropdownMenuTrigger,
|
|
45
|
+
} from "@/components/ui/dropdown-menu";
|
|
31
46
|
import { Input } from "@/components/ui/input";
|
|
32
47
|
import { Spinner } from "@/components/ui/spinner";
|
|
48
|
+
import { sendToDesignAgentChatAndConfirm } from "@/lib/agent-chat";
|
|
49
|
+
import {
|
|
50
|
+
formatNodeSelectionQuestion,
|
|
51
|
+
formatNodeRepromptSubmission,
|
|
52
|
+
inferNodeRepromptSendMode,
|
|
53
|
+
NODE_REPROMPT_PRESENTED_EVENT,
|
|
54
|
+
NODE_REPROMPT_RESOLVED_EVENT,
|
|
55
|
+
type NodeRepromptSendMode,
|
|
56
|
+
} from "@/lib/node-reprompt";
|
|
33
57
|
import { cn } from "@/lib/utils";
|
|
34
58
|
|
|
35
59
|
import {
|
|
@@ -38,11 +62,19 @@ import {
|
|
|
38
62
|
type ReviewAnchorPoint,
|
|
39
63
|
} from "../../../shared/review-anchor";
|
|
40
64
|
import {
|
|
65
|
+
getReviewPinPosition,
|
|
41
66
|
getReviewPopoverPlacement,
|
|
42
67
|
placeReviewDraftPin,
|
|
43
68
|
type ReviewDraftPin,
|
|
44
69
|
} from "./review-canvas-state";
|
|
45
70
|
|
|
71
|
+
export interface RepromptDraftRequest {
|
|
72
|
+
nonce: number;
|
|
73
|
+
fileId: string;
|
|
74
|
+
target: NodeRewriteTarget;
|
|
75
|
+
point?: ReviewAnchorPoint;
|
|
76
|
+
}
|
|
77
|
+
|
|
46
78
|
export interface ReviewFocusRequest {
|
|
47
79
|
nonce: number;
|
|
48
80
|
anchor: unknown;
|
|
@@ -63,11 +95,10 @@ interface ReviewCanvasPinsProps {
|
|
|
63
95
|
onDispatchCommentToAgent?: (comment: ReviewComment) => void;
|
|
64
96
|
onSendThreadToAgent?: (thread: ReviewThread) => void;
|
|
65
97
|
sendingThreadId?: string | null;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
source: "node" | "point";
|
|
98
|
+
sourceType?: "inline" | "localhost" | "fusion";
|
|
99
|
+
sourceVersionHash?: string;
|
|
100
|
+
repromptDraftRequest?: RepromptDraftRequest | null;
|
|
101
|
+
onRepromptDraftConsumed?: (nonce: number) => void;
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
interface ReviewFrameNodeGeometry {
|
|
@@ -114,12 +145,11 @@ function findNodeElement(canvas: HTMLElement, nodeId: string): Element | null {
|
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
|
|
117
|
-
function
|
|
148
|
+
function elementPoint(
|
|
118
149
|
canvas: HTMLElement,
|
|
119
|
-
|
|
150
|
+
element: Element | null,
|
|
120
151
|
frameGeometry?: ReviewFrameNodeGeometry,
|
|
121
152
|
): ReviewAnchorPoint | null {
|
|
122
|
-
const element = findNodeElement(canvas, nodeId);
|
|
123
153
|
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
124
154
|
"iframe[data-design-preview-iframe]",
|
|
125
155
|
);
|
|
@@ -153,11 +183,69 @@ function nodePoint(
|
|
|
153
183
|
};
|
|
154
184
|
}
|
|
155
185
|
|
|
186
|
+
function nodePoint(
|
|
187
|
+
canvas: HTMLElement,
|
|
188
|
+
nodeId: string,
|
|
189
|
+
frameGeometry?: ReviewFrameNodeGeometry,
|
|
190
|
+
): ReviewAnchorPoint | null {
|
|
191
|
+
return elementPoint(canvas, findNodeElement(canvas, nodeId), frameGeometry);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function selectorPoint(
|
|
195
|
+
canvas: HTMLElement,
|
|
196
|
+
selector: string,
|
|
197
|
+
): ReviewAnchorPoint | null {
|
|
198
|
+
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
199
|
+
"iframe[data-design-preview-iframe]",
|
|
200
|
+
);
|
|
201
|
+
try {
|
|
202
|
+
return elementPoint(
|
|
203
|
+
canvas,
|
|
204
|
+
iframe?.contentDocument?.querySelector(selector) ?? null,
|
|
205
|
+
);
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function structuralSelector(element: Element, document: Document): string {
|
|
212
|
+
if (element === document.body || element === document.documentElement) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
const parts: string[] = [];
|
|
216
|
+
let current: Element | null = element;
|
|
217
|
+
while (
|
|
218
|
+
current &&
|
|
219
|
+
current !== document.body &&
|
|
220
|
+
current !== document.documentElement
|
|
221
|
+
) {
|
|
222
|
+
const tag = current.tagName.toLowerCase();
|
|
223
|
+
const siblings = current.parentElement
|
|
224
|
+
? Array.from(current.parentElement.children).filter(
|
|
225
|
+
(candidate) => candidate.tagName === current?.tagName,
|
|
226
|
+
)
|
|
227
|
+
: [];
|
|
228
|
+
const index = siblings.indexOf(current);
|
|
229
|
+
parts.unshift(
|
|
230
|
+
siblings.length > 1 && index >= 0
|
|
231
|
+
? `${tag}:nth-of-type(${index + 1})`
|
|
232
|
+
: tag,
|
|
233
|
+
);
|
|
234
|
+
current = current.parentElement;
|
|
235
|
+
}
|
|
236
|
+
return parts.length > 0 ? `body > ${parts.join(" > ")}` : "";
|
|
237
|
+
}
|
|
238
|
+
|
|
156
239
|
function elementAnchorAtPoint(
|
|
157
240
|
canvas: HTMLElement,
|
|
158
241
|
clientX: number,
|
|
159
242
|
clientY: number,
|
|
160
|
-
): {
|
|
243
|
+
): {
|
|
244
|
+
nodeId?: string;
|
|
245
|
+
targetSelector?: string;
|
|
246
|
+
layerName?: string;
|
|
247
|
+
tagName?: string;
|
|
248
|
+
} {
|
|
161
249
|
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
162
250
|
"iframe[data-design-preview-iframe]",
|
|
163
251
|
);
|
|
@@ -171,10 +259,22 @@ function elementAnchorAtPoint(
|
|
|
171
259
|
(clientX - iframeRect.left) * scaleX,
|
|
172
260
|
(clientY - iframeRect.top) * scaleY,
|
|
173
261
|
);
|
|
174
|
-
const
|
|
262
|
+
const identifiedAncestor = element?.closest(
|
|
175
263
|
"[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[id]",
|
|
176
264
|
);
|
|
177
|
-
|
|
265
|
+
const anchor =
|
|
266
|
+
identifiedAncestor &&
|
|
267
|
+
identifiedAncestor !== document.body &&
|
|
268
|
+
identifiedAncestor !== document.documentElement
|
|
269
|
+
? identifiedAncestor
|
|
270
|
+
: element;
|
|
271
|
+
if (
|
|
272
|
+
!anchor ||
|
|
273
|
+
anchor === document.body ||
|
|
274
|
+
anchor === document.documentElement
|
|
275
|
+
) {
|
|
276
|
+
return {};
|
|
277
|
+
}
|
|
178
278
|
const nodeId =
|
|
179
279
|
anchor.getAttribute("data-agent-native-node-id") ??
|
|
180
280
|
anchor.getAttribute("data-code-layer-id") ??
|
|
@@ -184,8 +284,12 @@ function elementAnchorAtPoint(
|
|
|
184
284
|
undefined;
|
|
185
285
|
const layerName =
|
|
186
286
|
anchor.getAttribute("data-agent-native-layer-name") ?? undefined;
|
|
287
|
+
const targetSelector = nodeId
|
|
288
|
+
? undefined
|
|
289
|
+
: structuralSelector(anchor, document) || undefined;
|
|
187
290
|
return {
|
|
188
291
|
...(nodeId ? { nodeId } : {}),
|
|
292
|
+
...(targetSelector ? { targetSelector } : {}),
|
|
189
293
|
...(layerName ? { layerName } : {}),
|
|
190
294
|
tagName: anchor.tagName.toLowerCase(),
|
|
191
295
|
};
|
|
@@ -208,26 +312,19 @@ function anchorAtPoint(
|
|
|
208
312
|
return {
|
|
209
313
|
anchor: {
|
|
210
314
|
...(element.nodeId ? { nodeId: element.nodeId } : {}),
|
|
315
|
+
...(element.targetSelector ? { selector: element.targetSelector } : {}),
|
|
211
316
|
point: { xPct, yPct },
|
|
212
317
|
},
|
|
213
318
|
metadata: {
|
|
214
319
|
...(element.layerName ? { layerName: element.layerName } : {}),
|
|
215
320
|
...(element.tagName ? { tagName: element.tagName } : {}),
|
|
321
|
+
...(element.targetSelector
|
|
322
|
+
? { targetSelector: element.targetSelector }
|
|
323
|
+
: {}),
|
|
216
324
|
},
|
|
217
325
|
};
|
|
218
326
|
}
|
|
219
327
|
|
|
220
|
-
function resolvePinPosition(
|
|
221
|
-
canvas: HTMLElement,
|
|
222
|
-
anchor: unknown,
|
|
223
|
-
frameGeometry: Record<string, ReviewFrameNodeGeometry>,
|
|
224
|
-
): PinPosition | null {
|
|
225
|
-
const resolved = resolveReviewAnchor(anchor, (nodeId) =>
|
|
226
|
-
nodePoint(canvas, nodeId, frameGeometry[nodeId]),
|
|
227
|
-
);
|
|
228
|
-
return resolved ? { point: resolved.point, source: resolved.source } : null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
328
|
export function ReviewCanvasPins({
|
|
232
329
|
active,
|
|
233
330
|
hidden = false,
|
|
@@ -242,6 +339,10 @@ export function ReviewCanvasPins({
|
|
|
242
339
|
onDispatchCommentToAgent,
|
|
243
340
|
onSendThreadToAgent,
|
|
244
341
|
sendingThreadId,
|
|
342
|
+
sourceType = "inline",
|
|
343
|
+
sourceVersionHash,
|
|
344
|
+
repromptDraftRequest,
|
|
345
|
+
onRepromptDraftConsumed,
|
|
245
346
|
}: ReviewCanvasPinsProps) {
|
|
246
347
|
const t = useT();
|
|
247
348
|
const comments = useReviewComments(
|
|
@@ -265,11 +366,17 @@ export function ReviewCanvasPins({
|
|
|
265
366
|
const [draftComposerOpen, setDraftComposerOpen] = useState(false);
|
|
266
367
|
const [activeThreadId, setActiveThreadId] = useState<string | null>(null);
|
|
267
368
|
const [replyDraft, setReplyDraft] = useState("");
|
|
369
|
+
const [draftMode, setDraftMode] = useState<"comment" | "reprompt">("comment");
|
|
370
|
+
const [pendingRepromptId, setPendingRepromptId] = useState<string | null>(
|
|
371
|
+
null,
|
|
372
|
+
);
|
|
373
|
+
const [agentSubmitting, setAgentSubmitting] = useState(false);
|
|
268
374
|
const [frameNodeGeometry, setFrameNodeGeometry] = useState<
|
|
269
375
|
Record<string, ReviewFrameNodeGeometry>
|
|
270
376
|
>({});
|
|
271
377
|
const lastFocusNonceRef = useRef<number | null>(null);
|
|
272
378
|
const pendingFocusNonceRef = useRef<number | null>(null);
|
|
379
|
+
const lastRepromptDraftNonceRef = useRef<number | null>(null);
|
|
273
380
|
const frameCallbacksRef = useRef<
|
|
274
381
|
Map<string, (payload: Record<string, unknown>) => void>
|
|
275
382
|
>(new Map());
|
|
@@ -277,8 +384,32 @@ export function ReviewCanvasPins({
|
|
|
277
384
|
const cancelDraft = useCallback(() => {
|
|
278
385
|
setDraftPin(null);
|
|
279
386
|
setDraftComposerOpen(false);
|
|
387
|
+
setDraftMode("comment");
|
|
388
|
+
setPendingRepromptId(null);
|
|
280
389
|
}, []);
|
|
281
390
|
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
const onRepromptSettled = (event: Event) => {
|
|
393
|
+
const detail = (event as CustomEvent<{ repromptId?: string }>).detail;
|
|
394
|
+
if (!pendingRepromptId || detail?.repromptId !== pendingRepromptId)
|
|
395
|
+
return;
|
|
396
|
+
cancelDraft();
|
|
397
|
+
onClose();
|
|
398
|
+
};
|
|
399
|
+
window.addEventListener(NODE_REPROMPT_PRESENTED_EVENT, onRepromptSettled);
|
|
400
|
+
window.addEventListener(NODE_REPROMPT_RESOLVED_EVENT, onRepromptSettled);
|
|
401
|
+
return () => {
|
|
402
|
+
window.removeEventListener(
|
|
403
|
+
NODE_REPROMPT_PRESENTED_EVENT,
|
|
404
|
+
onRepromptSettled,
|
|
405
|
+
);
|
|
406
|
+
window.removeEventListener(
|
|
407
|
+
NODE_REPROMPT_RESOLVED_EVENT,
|
|
408
|
+
onRepromptSettled,
|
|
409
|
+
);
|
|
410
|
+
};
|
|
411
|
+
}, [cancelDraft, onClose, pendingRepromptId]);
|
|
412
|
+
|
|
282
413
|
const threads = useMemo(
|
|
283
414
|
() => buildReviewThreads(comments.data?.comments ?? []),
|
|
284
415
|
[comments.data?.comments],
|
|
@@ -414,12 +545,29 @@ export function ReviewCanvasPins({
|
|
|
414
545
|
const focusAnchor = useCallback(
|
|
415
546
|
(anchor: unknown, nonce: number): boolean => {
|
|
416
547
|
if (!canvas) return false;
|
|
417
|
-
const resolved = resolveReviewAnchor(
|
|
418
|
-
|
|
548
|
+
const resolved = resolveReviewAnchor(
|
|
549
|
+
anchor,
|
|
550
|
+
(nodeId) => nodePoint(canvas, nodeId, frameNodeGeometry[nodeId]),
|
|
551
|
+
(selector) => selectorPoint(canvas, selector),
|
|
419
552
|
);
|
|
420
553
|
if (!resolved) return true;
|
|
421
|
-
if (resolved.anchor.nodeId) {
|
|
422
|
-
const element =
|
|
554
|
+
if (resolved.anchor.nodeId || resolved.anchor.selector) {
|
|
555
|
+
const element = resolved.anchor.nodeId
|
|
556
|
+
? findNodeElement(canvas, resolved.anchor.nodeId)
|
|
557
|
+
: (() => {
|
|
558
|
+
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
559
|
+
"iframe[data-design-preview-iframe]",
|
|
560
|
+
);
|
|
561
|
+
try {
|
|
562
|
+
return (
|
|
563
|
+
iframe?.contentDocument?.querySelector(
|
|
564
|
+
resolved.anchor.selector!,
|
|
565
|
+
) ?? null
|
|
566
|
+
);
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
})();
|
|
423
571
|
if (element instanceof HTMLElement || element instanceof SVGElement) {
|
|
424
572
|
element.scrollIntoView({ block: "center", inline: "center" });
|
|
425
573
|
const previousBoxShadow = element.style.boxShadow;
|
|
@@ -430,6 +578,7 @@ export function ReviewCanvasPins({
|
|
|
430
578
|
}, 700);
|
|
431
579
|
return true;
|
|
432
580
|
}
|
|
581
|
+
if (!resolved.anchor.nodeId) return true;
|
|
433
582
|
if (pendingFocusNonceRef.current === nonce) return false;
|
|
434
583
|
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
435
584
|
"iframe[data-design-preview-iframe]",
|
|
@@ -475,6 +624,50 @@ export function ReviewCanvasPins({
|
|
|
475
624
|
if (!active) cancelDraft();
|
|
476
625
|
}, [active, cancelDraft]);
|
|
477
626
|
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
if (
|
|
629
|
+
!canvas ||
|
|
630
|
+
!repromptDraftRequest ||
|
|
631
|
+
repromptDraftRequest.fileId !== targetId ||
|
|
632
|
+
repromptDraftRequest.nonce === lastRepromptDraftNonceRef.current
|
|
633
|
+
) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
lastRepromptDraftNonceRef.current = repromptDraftRequest.nonce;
|
|
637
|
+
const nodeId = repromptDraftRequest.target.nodeId;
|
|
638
|
+
const point = repromptDraftRequest.point ??
|
|
639
|
+
(nodeId
|
|
640
|
+
? nodePoint(canvas, nodeId, frameNodeGeometry[nodeId])
|
|
641
|
+
: null) ?? { xPct: 50, yPct: 50 };
|
|
642
|
+
setActiveThreadId(null);
|
|
643
|
+
setReplyDraft("");
|
|
644
|
+
setPendingRepromptId(null);
|
|
645
|
+
setDraftMode("reprompt");
|
|
646
|
+
setDraftPin({
|
|
647
|
+
id: crypto.randomUUID(),
|
|
648
|
+
anchor: {
|
|
649
|
+
...(nodeId ? { nodeId } : {}),
|
|
650
|
+
point,
|
|
651
|
+
},
|
|
652
|
+
draft: "",
|
|
653
|
+
resolutionTarget: "agent",
|
|
654
|
+
metadata: {
|
|
655
|
+
mode: "reprompt",
|
|
656
|
+
...(repromptDraftRequest.target.selector
|
|
657
|
+
? { targetSelector: repromptDraftRequest.target.selector }
|
|
658
|
+
: {}),
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
setDraftComposerOpen(true);
|
|
662
|
+
onRepromptDraftConsumed?.(repromptDraftRequest.nonce);
|
|
663
|
+
}, [
|
|
664
|
+
canvas,
|
|
665
|
+
frameNodeGeometry,
|
|
666
|
+
onRepromptDraftConsumed,
|
|
667
|
+
repromptDraftRequest,
|
|
668
|
+
targetId,
|
|
669
|
+
]);
|
|
670
|
+
|
|
478
671
|
useEffect(() => {
|
|
479
672
|
if (!active && !activeThreadId && !draftComposerOpen) return;
|
|
480
673
|
const onKeyDown = (event: KeyboardEvent) => {
|
|
@@ -525,6 +718,8 @@ export function ReviewCanvasPins({
|
|
|
525
718
|
if (!next) return;
|
|
526
719
|
setActiveThreadId(null);
|
|
527
720
|
setReplyDraft("");
|
|
721
|
+
setDraftMode("comment");
|
|
722
|
+
setPendingRepromptId(null);
|
|
528
723
|
setDraftPin((current) =>
|
|
529
724
|
placeReviewDraftPin(current, {
|
|
530
725
|
id: crypto.randomUUID(),
|
|
@@ -534,7 +729,6 @@ export function ReviewCanvasPins({
|
|
|
534
729
|
);
|
|
535
730
|
setDraftComposerOpen(true);
|
|
536
731
|
|
|
537
|
-
if (next.anchor.nodeId) return;
|
|
538
732
|
const iframe = canvas.querySelector<HTMLIFrameElement>(
|
|
539
733
|
"iframe[data-design-preview-iframe]",
|
|
540
734
|
);
|
|
@@ -546,11 +740,15 @@ export function ReviewCanvasPins({
|
|
|
546
740
|
frameCallbacksRef.current.set(correlationId, (payload) => {
|
|
547
741
|
const nodeId =
|
|
548
742
|
typeof payload.nodeId === "string" ? payload.nodeId : undefined;
|
|
743
|
+
const targetSelector =
|
|
744
|
+
typeof payload.targetSelector === "string"
|
|
745
|
+
? payload.targetSelector
|
|
746
|
+
: undefined;
|
|
549
747
|
const layerName =
|
|
550
748
|
typeof payload.layerName === "string" ? payload.layerName : undefined;
|
|
551
749
|
const tagName =
|
|
552
750
|
typeof payload.tagName === "string" ? payload.tagName : undefined;
|
|
553
|
-
if (!nodeId && !layerName && !tagName) return;
|
|
751
|
+
if (!nodeId && !targetSelector && !layerName && !tagName) return;
|
|
554
752
|
setDraftPin((current) => {
|
|
555
753
|
if (
|
|
556
754
|
!current ||
|
|
@@ -563,12 +761,14 @@ export function ReviewCanvasPins({
|
|
|
563
761
|
...current,
|
|
564
762
|
anchor: {
|
|
565
763
|
...(nodeId ? { nodeId } : {}),
|
|
764
|
+
...(targetSelector ? { selector: targetSelector } : {}),
|
|
566
765
|
point: current.anchor.point,
|
|
567
766
|
},
|
|
568
767
|
metadata: {
|
|
569
768
|
...current.metadata,
|
|
570
769
|
...(layerName ? { layerName } : {}),
|
|
571
770
|
...(tagName ? { tagName } : {}),
|
|
771
|
+
...(targetSelector ? { targetSelector } : {}),
|
|
572
772
|
},
|
|
573
773
|
};
|
|
574
774
|
});
|
|
@@ -631,6 +831,172 @@ export function ReviewCanvasPins({
|
|
|
631
831
|
],
|
|
632
832
|
);
|
|
633
833
|
|
|
834
|
+
const submitReprompt = useCallback(
|
|
835
|
+
async (pin: ReviewDraftPin) => {
|
|
836
|
+
const instruction = pin.draft.trim();
|
|
837
|
+
const resolved = resolveReviewAnchor(pin.anchor, () => null);
|
|
838
|
+
const nodeId = resolved?.anchor.nodeId;
|
|
839
|
+
const targetSelector =
|
|
840
|
+
typeof pin.metadata.targetSelector === "string"
|
|
841
|
+
? pin.metadata.targetSelector
|
|
842
|
+
: undefined;
|
|
843
|
+
if (
|
|
844
|
+
!instruction ||
|
|
845
|
+
(!nodeId && !targetSelector) ||
|
|
846
|
+
!sourceVersionHash ||
|
|
847
|
+
sourceType !== "inline" ||
|
|
848
|
+
agentSubmitting
|
|
849
|
+
) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const target: NodeRewriteTarget = {
|
|
853
|
+
...(nodeId ? { nodeId } : {}),
|
|
854
|
+
...(targetSelector ? { selector: targetSelector } : {}),
|
|
855
|
+
};
|
|
856
|
+
const repromptId = crypto.randomUUID();
|
|
857
|
+
const pending = {
|
|
858
|
+
repromptId,
|
|
859
|
+
designId: resourceId,
|
|
860
|
+
fileId: targetId,
|
|
861
|
+
target,
|
|
862
|
+
baseVersionHash: sourceVersionHash,
|
|
863
|
+
instruction,
|
|
864
|
+
createdAt: new Date().toISOString(),
|
|
865
|
+
};
|
|
866
|
+
let element = nodeId ? findNodeElement(canvas!, nodeId) : null;
|
|
867
|
+
if (!element && targetSelector) {
|
|
868
|
+
const iframe = canvas?.querySelector<HTMLIFrameElement>(
|
|
869
|
+
"iframe[data-design-preview-iframe]",
|
|
870
|
+
);
|
|
871
|
+
try {
|
|
872
|
+
element =
|
|
873
|
+
iframe?.contentDocument?.querySelector(targetSelector) ?? null;
|
|
874
|
+
} catch {
|
|
875
|
+
element = null;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const stateKey = designRepromptPendingStateKey(resourceId, targetId);
|
|
879
|
+
setAgentSubmitting(true);
|
|
880
|
+
try {
|
|
881
|
+
await setClientAppState(stateKey, pending);
|
|
882
|
+
const submission = formatNodeRepromptSubmission({
|
|
883
|
+
...pending,
|
|
884
|
+
subtreeHtml:
|
|
885
|
+
element instanceof HTMLElement || element instanceof SVGElement
|
|
886
|
+
? element.outerHTML
|
|
887
|
+
: undefined,
|
|
888
|
+
});
|
|
889
|
+
const delivery = await sendToDesignAgentChatAndConfirm(
|
|
890
|
+
{
|
|
891
|
+
...submission,
|
|
892
|
+
submit: true,
|
|
893
|
+
openSidebar: true,
|
|
894
|
+
},
|
|
895
|
+
{ timeoutMs: 10_000 },
|
|
896
|
+
);
|
|
897
|
+
if (!delivery.delivered) {
|
|
898
|
+
throw new Error(delivery.reason ?? "Reprompt was not delivered.");
|
|
899
|
+
}
|
|
900
|
+
setDraftMode("reprompt");
|
|
901
|
+
setPendingRepromptId(repromptId);
|
|
902
|
+
setDraftComposerOpen(false);
|
|
903
|
+
toast.success(t("designEditor.nodeRewrite.sent"));
|
|
904
|
+
} catch (error) {
|
|
905
|
+
await callAction("cancel-node-rewrite-request", {
|
|
906
|
+
designId: resourceId,
|
|
907
|
+
fileId: targetId,
|
|
908
|
+
repromptId,
|
|
909
|
+
}).catch(() => {});
|
|
910
|
+
toast.error(
|
|
911
|
+
error instanceof Error && error.message
|
|
912
|
+
? error.message
|
|
913
|
+
: t("designEditor.nodeRewrite.sendFailed"),
|
|
914
|
+
);
|
|
915
|
+
} finally {
|
|
916
|
+
setAgentSubmitting(false);
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
[
|
|
920
|
+
canvas,
|
|
921
|
+
agentSubmitting,
|
|
922
|
+
resourceId,
|
|
923
|
+
sourceType,
|
|
924
|
+
sourceVersionHash,
|
|
925
|
+
t,
|
|
926
|
+
targetId,
|
|
927
|
+
],
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
const submitSelectionQuestion = useCallback(
|
|
931
|
+
async (pin: ReviewDraftPin) => {
|
|
932
|
+
const instruction = pin.draft.trim();
|
|
933
|
+
const resolved = resolveReviewAnchor(pin.anchor, () => null);
|
|
934
|
+
const nodeId = resolved?.anchor.nodeId;
|
|
935
|
+
const targetSelector =
|
|
936
|
+
typeof pin.metadata.targetSelector === "string"
|
|
937
|
+
? pin.metadata.targetSelector
|
|
938
|
+
: undefined;
|
|
939
|
+
if (
|
|
940
|
+
!instruction ||
|
|
941
|
+
(!nodeId && !targetSelector) ||
|
|
942
|
+
sourceType !== "inline" ||
|
|
943
|
+
agentSubmitting
|
|
944
|
+
) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const target: NodeRewriteTarget = {
|
|
948
|
+
...(nodeId ? { nodeId } : {}),
|
|
949
|
+
...(targetSelector ? { selector: targetSelector } : {}),
|
|
950
|
+
};
|
|
951
|
+
let element = nodeId ? findNodeElement(canvas!, nodeId) : null;
|
|
952
|
+
if (!element && targetSelector) {
|
|
953
|
+
const iframe = canvas?.querySelector<HTMLIFrameElement>(
|
|
954
|
+
"iframe[data-design-preview-iframe]",
|
|
955
|
+
);
|
|
956
|
+
try {
|
|
957
|
+
element =
|
|
958
|
+
iframe?.contentDocument?.querySelector(targetSelector) ?? null;
|
|
959
|
+
} catch {
|
|
960
|
+
element = null;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
setAgentSubmitting(true);
|
|
964
|
+
try {
|
|
965
|
+
const submission = formatNodeSelectionQuestion({
|
|
966
|
+
designId: resourceId,
|
|
967
|
+
fileId: targetId,
|
|
968
|
+
target,
|
|
969
|
+
instruction,
|
|
970
|
+
subtreeHtml:
|
|
971
|
+
element instanceof HTMLElement || element instanceof SVGElement
|
|
972
|
+
? element.outerHTML
|
|
973
|
+
: undefined,
|
|
974
|
+
});
|
|
975
|
+
const delivery = await sendToDesignAgentChatAndConfirm(
|
|
976
|
+
{
|
|
977
|
+
...submission,
|
|
978
|
+
submit: true,
|
|
979
|
+
openSidebar: true,
|
|
980
|
+
},
|
|
981
|
+
{ timeoutMs: 10_000 },
|
|
982
|
+
);
|
|
983
|
+
if (!delivery.delivered) {
|
|
984
|
+
throw new Error(delivery.reason ?? "Question was not delivered.");
|
|
985
|
+
}
|
|
986
|
+
cancelDraft();
|
|
987
|
+
} catch (error) {
|
|
988
|
+
toast.error(
|
|
989
|
+
error instanceof Error && error.message
|
|
990
|
+
? error.message
|
|
991
|
+
: t("designEditor.nodeRewrite.sendFailed"),
|
|
992
|
+
);
|
|
993
|
+
} finally {
|
|
994
|
+
setAgentSubmitting(false);
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
[agentSubmitting, cancelDraft, canvas, resourceId, sourceType, t, targetId],
|
|
998
|
+
);
|
|
999
|
+
|
|
634
1000
|
if (hidden || !canvas) return null;
|
|
635
1001
|
const rect = canvas.getBoundingClientRect();
|
|
636
1002
|
void layoutTick;
|
|
@@ -639,12 +1005,18 @@ export function ReviewCanvasPins({
|
|
|
639
1005
|
(thread) => thread.root.status === "open" && thread.root.anchor,
|
|
640
1006
|
);
|
|
641
1007
|
const draftPinPosition = draftPin
|
|
642
|
-
?
|
|
1008
|
+
? getReviewPinPosition(draftPin.anchor)
|
|
643
1009
|
: null;
|
|
1010
|
+
const pinPlacementEnabled = active && canPost && !pendingRepromptId;
|
|
1011
|
+
const placementHintVisible =
|
|
1012
|
+
pinPlacementEnabled &&
|
|
1013
|
+
!draftComposerOpen &&
|
|
1014
|
+
!activeThreadId &&
|
|
1015
|
+
!repromptDraftRequest;
|
|
644
1016
|
|
|
645
1017
|
return (
|
|
646
1018
|
<>
|
|
647
|
-
{
|
|
1019
|
+
{pinPlacementEnabled ? (
|
|
648
1020
|
<div
|
|
649
1021
|
data-review-click-plane
|
|
650
1022
|
className="fixed z-40 cursor-crosshair"
|
|
@@ -661,7 +1033,7 @@ export function ReviewCanvasPins({
|
|
|
661
1033
|
}}
|
|
662
1034
|
/>
|
|
663
1035
|
) : null}
|
|
664
|
-
{
|
|
1036
|
+
{placementHintVisible ? (
|
|
665
1037
|
<div className="pointer-events-none fixed left-1/2 top-16 z-[45] flex -translate-x-1/2 items-center gap-2 rounded-full border border-border bg-popover px-3 py-1.5 text-xs shadow-lg">
|
|
666
1038
|
<IconMessageCircle className="size-3.5 text-primary" />
|
|
667
1039
|
{t("review.clickToPin")}
|
|
@@ -671,11 +1043,7 @@ export function ReviewCanvasPins({
|
|
|
671
1043
|
</div>
|
|
672
1044
|
) : null}
|
|
673
1045
|
{openThreads.map((thread, index) => {
|
|
674
|
-
const position =
|
|
675
|
-
canvas,
|
|
676
|
-
thread.root.anchor,
|
|
677
|
-
frameNodeGeometry,
|
|
678
|
-
);
|
|
1046
|
+
const position = getReviewPinPosition(thread.root.anchor);
|
|
679
1047
|
if (!position) return null;
|
|
680
1048
|
return (
|
|
681
1049
|
<ReviewPin
|
|
@@ -754,7 +1122,9 @@ export function ReviewCanvasPins({
|
|
|
754
1122
|
canvasRect={rect}
|
|
755
1123
|
point={draftPinPosition.point}
|
|
756
1124
|
draft
|
|
1125
|
+
pending={Boolean(pendingRepromptId)}
|
|
757
1126
|
onClick={() => {
|
|
1127
|
+
if (pendingRepromptId) return;
|
|
758
1128
|
setActiveThreadId(null);
|
|
759
1129
|
setReplyDraft("");
|
|
760
1130
|
setDraftComposerOpen(true);
|
|
@@ -776,10 +1146,25 @@ export function ReviewCanvasPins({
|
|
|
776
1146
|
);
|
|
777
1147
|
postDraft({ ...draftPin, resolutionTarget });
|
|
778
1148
|
}}
|
|
1149
|
+
onSmartSubmit={(mode) => {
|
|
1150
|
+
if (mode === "preview") void submitReprompt(draftPin);
|
|
1151
|
+
else void submitSelectionQuestion(draftPin);
|
|
1152
|
+
}}
|
|
779
1153
|
resolutionTarget={draftPin.resolutionTarget}
|
|
780
|
-
showAgentAction={
|
|
1154
|
+
showAgentAction={
|
|
1155
|
+
Boolean(onDispatchCommentToAgent) || draftMode === "reprompt"
|
|
1156
|
+
}
|
|
1157
|
+
smartAgentAvailable={
|
|
1158
|
+
sourceType === "inline" &&
|
|
1159
|
+
Boolean(
|
|
1160
|
+
resolveReviewAnchor(draftPin.anchor, () => null)?.anchor
|
|
1161
|
+
.nodeId || draftPin.metadata.targetSelector,
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
initialAgentMode={draftMode === "reprompt" ? "preview" : "auto"}
|
|
781
1165
|
placement={getReviewPopoverPlacement(draftPinPosition.point)}
|
|
782
|
-
|
|
1166
|
+
commentSubmitting={createComment.isPending}
|
|
1167
|
+
agentSubmitting={agentSubmitting}
|
|
783
1168
|
/>
|
|
784
1169
|
) : null}
|
|
785
1170
|
</ReviewPin>
|
|
@@ -794,6 +1179,7 @@ function ReviewPin({
|
|
|
794
1179
|
canvasRect,
|
|
795
1180
|
active,
|
|
796
1181
|
draft = false,
|
|
1182
|
+
pending = false,
|
|
797
1183
|
onClick,
|
|
798
1184
|
children,
|
|
799
1185
|
}: {
|
|
@@ -802,6 +1188,7 @@ function ReviewPin({
|
|
|
802
1188
|
canvasRect: DOMRect;
|
|
803
1189
|
active: boolean;
|
|
804
1190
|
draft?: boolean;
|
|
1191
|
+
pending?: boolean;
|
|
805
1192
|
onClick: () => void;
|
|
806
1193
|
children?: ReactNode;
|
|
807
1194
|
}) {
|
|
@@ -831,7 +1218,7 @@ function ReviewPin({
|
|
|
831
1218
|
}}
|
|
832
1219
|
aria-label={t("review.commentNumber", { count: index + 1 })}
|
|
833
1220
|
>
|
|
834
|
-
{index + 1}
|
|
1221
|
+
{pending ? <Spinner className="size-3" /> : index + 1}
|
|
835
1222
|
</button>
|
|
836
1223
|
{children}
|
|
837
1224
|
</div>
|
|
@@ -843,21 +1230,94 @@ function DraftComposer({
|
|
|
843
1230
|
onChange,
|
|
844
1231
|
onCancel,
|
|
845
1232
|
onSubmit,
|
|
1233
|
+
onSmartSubmit,
|
|
846
1234
|
resolutionTarget,
|
|
847
1235
|
showAgentAction,
|
|
1236
|
+
smartAgentAvailable,
|
|
1237
|
+
initialAgentMode,
|
|
848
1238
|
placement,
|
|
849
|
-
|
|
1239
|
+
commentSubmitting,
|
|
1240
|
+
agentSubmitting,
|
|
850
1241
|
}: {
|
|
851
1242
|
value: string;
|
|
852
1243
|
onChange: (value: string) => void;
|
|
853
1244
|
onCancel: () => void;
|
|
854
1245
|
onSubmit: (target: "agent" | "human") => void;
|
|
1246
|
+
onSmartSubmit: (mode: NodeRepromptSendMode) => void;
|
|
855
1247
|
resolutionTarget: "agent" | "human";
|
|
856
1248
|
showAgentAction: boolean;
|
|
1249
|
+
smartAgentAvailable: boolean;
|
|
1250
|
+
initialAgentMode: "auto" | NodeRepromptSendMode;
|
|
857
1251
|
placement: ReviewPopoverPlacement;
|
|
858
|
-
|
|
1252
|
+
commentSubmitting: boolean;
|
|
1253
|
+
agentSubmitting: boolean;
|
|
859
1254
|
}) {
|
|
860
1255
|
const t = useT();
|
|
1256
|
+
const [modeOverride, setModeOverride] = useState<
|
|
1257
|
+
"auto" | NodeRepromptSendMode
|
|
1258
|
+
>(initialAgentMode);
|
|
1259
|
+
const inferredMode = inferNodeRepromptSendMode(value, {
|
|
1260
|
+
hasEditableTarget: smartAgentAvailable,
|
|
1261
|
+
});
|
|
1262
|
+
const sendMode = modeOverride === "auto" ? inferredMode : modeOverride;
|
|
1263
|
+
const submitting = commentSubmitting || agentSubmitting;
|
|
1264
|
+
const agentLabel =
|
|
1265
|
+
modeOverride === "auto"
|
|
1266
|
+
? t("review.sendToAgent")
|
|
1267
|
+
: sendMode === "preview"
|
|
1268
|
+
? t("designEditor.nodeRewrite.modeRegenerate")
|
|
1269
|
+
: t("designEditor.nodeRewrite.modeAsk");
|
|
1270
|
+
const agentAction = smartAgentAvailable ? (
|
|
1271
|
+
<div className="flex w-full min-w-0 @2xs/review:flex-1">
|
|
1272
|
+
<Button
|
|
1273
|
+
type="button"
|
|
1274
|
+
size="sm"
|
|
1275
|
+
variant="outline"
|
|
1276
|
+
className="h-8 min-w-0 flex-1 gap-1.5 rounded-e-none"
|
|
1277
|
+
disabled={submitting || !value.trim()}
|
|
1278
|
+
onClick={() => onSmartSubmit(sendMode)}
|
|
1279
|
+
>
|
|
1280
|
+
{agentSubmitting ? (
|
|
1281
|
+
<Spinner className="size-3.5" />
|
|
1282
|
+
) : (
|
|
1283
|
+
<IconSend className="size-3.5" />
|
|
1284
|
+
)}
|
|
1285
|
+
<span className="truncate">{agentLabel}</span>
|
|
1286
|
+
</Button>
|
|
1287
|
+
<DropdownMenu>
|
|
1288
|
+
<DropdownMenuTrigger asChild>
|
|
1289
|
+
<Button
|
|
1290
|
+
type="button"
|
|
1291
|
+
size="sm"
|
|
1292
|
+
variant="outline"
|
|
1293
|
+
className="h-8 shrink-0 rounded-s-none border-s-0 px-2"
|
|
1294
|
+
disabled={submitting}
|
|
1295
|
+
aria-label={t("designEditor.nodeRewrite.agentModeOptions")}
|
|
1296
|
+
>
|
|
1297
|
+
<IconChevronDown className="size-3" />
|
|
1298
|
+
</Button>
|
|
1299
|
+
</DropdownMenuTrigger>
|
|
1300
|
+
<DropdownMenuContent align="end" className="w-56">
|
|
1301
|
+
<DropdownMenuRadioGroup
|
|
1302
|
+
value={modeOverride}
|
|
1303
|
+
onValueChange={(nextMode) =>
|
|
1304
|
+
setModeOverride(nextMode as "auto" | NodeRepromptSendMode)
|
|
1305
|
+
}
|
|
1306
|
+
>
|
|
1307
|
+
<DropdownMenuRadioItem value="auto">
|
|
1308
|
+
{t("designEditor.nodeRewrite.modeAuto")}
|
|
1309
|
+
</DropdownMenuRadioItem>
|
|
1310
|
+
<DropdownMenuRadioItem value="ask">
|
|
1311
|
+
{t("designEditor.nodeRewrite.modeAsk")}
|
|
1312
|
+
</DropdownMenuRadioItem>
|
|
1313
|
+
<DropdownMenuRadioItem value="preview">
|
|
1314
|
+
{t("designEditor.nodeRewrite.modeRegenerate")}
|
|
1315
|
+
</DropdownMenuRadioItem>
|
|
1316
|
+
</DropdownMenuRadioGroup>
|
|
1317
|
+
</DropdownMenuContent>
|
|
1318
|
+
</DropdownMenu>
|
|
1319
|
+
</div>
|
|
1320
|
+
) : undefined;
|
|
861
1321
|
return (
|
|
862
1322
|
<div
|
|
863
1323
|
data-review-popover
|
|
@@ -869,17 +1329,23 @@ function DraftComposer({
|
|
|
869
1329
|
onClick={(event) => event.stopPropagation()}
|
|
870
1330
|
>
|
|
871
1331
|
<div className="flex items-center justify-between px-3 pb-2 pt-3">
|
|
872
|
-
<span className="text-sm font-medium">
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1332
|
+
<span className="text-sm font-medium">
|
|
1333
|
+
{initialAgentMode === "preview"
|
|
1334
|
+
? t("designEditor.nodeRewrite.composerTitle")
|
|
1335
|
+
: t("review.newComment")}
|
|
1336
|
+
</span>
|
|
1337
|
+
<div className="flex items-center gap-0.5">
|
|
1338
|
+
<Button
|
|
1339
|
+
variant="ghost"
|
|
1340
|
+
size="icon"
|
|
1341
|
+
className="size-7 text-muted-foreground"
|
|
1342
|
+
disabled={submitting}
|
|
1343
|
+
onClick={onCancel}
|
|
1344
|
+
aria-label={t("designEditor.close")}
|
|
1345
|
+
>
|
|
1346
|
+
<IconX className="size-3.5" />
|
|
1347
|
+
</Button>
|
|
1348
|
+
</div>
|
|
883
1349
|
</div>
|
|
884
1350
|
<ReviewCommentComposer
|
|
885
1351
|
className="px-3 pb-3"
|
|
@@ -887,13 +1353,28 @@ function DraftComposer({
|
|
|
887
1353
|
value={value}
|
|
888
1354
|
disabled={submitting}
|
|
889
1355
|
onChange={onChange}
|
|
890
|
-
onSubmit={
|
|
891
|
-
|
|
1356
|
+
onSubmit={(target) => {
|
|
1357
|
+
if (target === "agent" && smartAgentAvailable) {
|
|
1358
|
+
onSmartSubmit(sendMode);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
onSubmit(target);
|
|
1362
|
+
}}
|
|
1363
|
+
submittingTarget={commentSubmitting ? resolutionTarget : null}
|
|
892
1364
|
showAgentAction={showAgentAction}
|
|
1365
|
+
agentAction={agentAction}
|
|
893
1366
|
placeholder={t("review.placeholder")}
|
|
894
1367
|
commentLabel={t("review.commentMode")}
|
|
895
1368
|
agentLabel={t("review.sendToAgent")}
|
|
1369
|
+
contextLabel={
|
|
1370
|
+
smartAgentAvailable && (modeOverride !== "auto" || value.trim())
|
|
1371
|
+
? sendMode === "preview"
|
|
1372
|
+
? t("designEditor.nodeRewrite.willPreview")
|
|
1373
|
+
: t("designEditor.nodeRewrite.willAsk")
|
|
1374
|
+
: undefined
|
|
1375
|
+
}
|
|
896
1376
|
submitOnEnter
|
|
1377
|
+
enterSubmitTarget={initialAgentMode === "preview" ? "agent" : "human"}
|
|
897
1378
|
onEscape={onCancel}
|
|
898
1379
|
/>
|
|
899
1380
|
</div>
|