@agent-native/core 0.118.1 → 0.119.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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-plan.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +371 -0
- package/corpus/core/src/a2a/client.ts +3 -1
- package/corpus/core/src/a2a/index.ts +21 -0
- package/corpus/core/src/a2a/types.ts +8 -0
- package/corpus/core/src/agent/model-config.ts +19 -23
- package/corpus/core/src/agent/production-agent.ts +167 -100
- package/corpus/core/src/agent/types.ts +16 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
- package/corpus/core/src/client/chat-model-groups.ts +67 -27
- package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
- package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
- package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
- package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
- package/corpus/core/src/client/sse-event-processor.ts +85 -1
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/corpus/core/src/scripts/call-agent.ts +49 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +9 -1
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/dist/a2a/activity.d.ts +50 -0
- package/dist/a2a/activity.d.ts.map +1 -0
- package/dist/a2a/activity.js +236 -0
- package/dist/a2a/activity.js.map +1 -0
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +1 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/index.d.ts +2 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/types.d.ts +1 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +16 -16
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +19 -23
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +26 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +137 -80
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +63 -14
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +44 -21
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -4
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +1 -2
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sharing/useShareButtonController.d.ts +0 -1
- package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
- package/dist/client/sharing/useShareButtonController.js +0 -6
- package/dist/client/sharing/useShareButtonController.js.map +1 -1
- package/dist/client/sharing/useShareDialogController.d.ts +0 -1
- package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
- package/dist/client/sharing/useShareDialogController.js +0 -1
- package/dist/client/sharing/useShareDialogController.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +12 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +63 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +6 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +49 -6
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/docs/content/a2a-protocol.mdx +15 -0
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-plan.mdx +3 -3
- package/package.json +1 -1
- package/src/a2a/activity.ts +371 -0
- package/src/a2a/client.ts +3 -1
- package/src/a2a/index.ts +21 -0
- package/src/a2a/types.ts +8 -0
- package/src/agent/model-config.ts +19 -23
- package/src/agent/production-agent.ts +167 -100
- package/src/agent/types.ts +16 -1
- package/src/client/chat/tool-call-display.tsx +190 -35
- package/src/client/chat-model-groups.ts +67 -27
- package/src/client/sharing/ShareButton.tsx +1 -27
- package/src/client/sharing/ShareDialog.tsx +0 -16
- package/src/client/sharing/useShareButtonController.ts +0 -7
- package/src/client/sharing/useShareDialogController.ts +0 -2
- package/src/client/sse-event-processor.ts +85 -1
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
- package/src/localization/default-messages.ts +6 -0
- package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/src/scripts/call-agent.ts +49 -6
- package/src/server/agent-chat-plugin.ts +50 -10
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
- package/src/observability/hosted-model-experiment.ts +0 -118
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FigmaHydrationDialog — shown after a no-token local-kiwi clipboard import
|
|
3
|
+
* when IMAGE fills couldn't be resolved. Collects a Figma access token, saves
|
|
4
|
+
* it, then calls `hydrate-figma-paste-images` for each imported file to
|
|
5
|
+
* replace the `url("about:blank")` placeholders with real durable images.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { callAction } from "@agent-native/core/client/hooks";
|
|
9
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
10
|
+
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import {
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogFooter,
|
|
19
|
+
DialogHeader,
|
|
20
|
+
DialogTitle,
|
|
21
|
+
} from "@/components/ui/dialog";
|
|
22
|
+
import { Input } from "@/components/ui/input";
|
|
23
|
+
import { Label } from "@/components/ui/label";
|
|
24
|
+
import {
|
|
25
|
+
hydrateImagesFromFig,
|
|
26
|
+
validateFigUploadFile,
|
|
27
|
+
} from "@/lib/design-file-upload";
|
|
28
|
+
import {
|
|
29
|
+
getFigmaConnectionStatus,
|
|
30
|
+
saveFigmaAccessToken,
|
|
31
|
+
} from "@/lib/figma-connection";
|
|
32
|
+
|
|
33
|
+
interface FigmaHydrationDialogProps {
|
|
34
|
+
open: boolean;
|
|
35
|
+
onOpenChange: (open: boolean) => void;
|
|
36
|
+
designId: string;
|
|
37
|
+
fileIds: string[];
|
|
38
|
+
imageCount: number;
|
|
39
|
+
onHydrated: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function FigmaHydrationDialog({
|
|
43
|
+
open,
|
|
44
|
+
onOpenChange,
|
|
45
|
+
designId,
|
|
46
|
+
fileIds,
|
|
47
|
+
imageCount,
|
|
48
|
+
onHydrated,
|
|
49
|
+
}: FigmaHydrationDialogProps) {
|
|
50
|
+
const t = useT();
|
|
51
|
+
const [token, setToken] = useState("");
|
|
52
|
+
const [busy, setBusy] = useState(false);
|
|
53
|
+
const [error, setError] = useState<string | null>(null);
|
|
54
|
+
const [docsUrl, setDocsUrl] = useState<string | null>(null);
|
|
55
|
+
const figInputRef = useRef<HTMLInputElement>(null);
|
|
56
|
+
|
|
57
|
+
const screensPlural = fileIds.length === 1 ? "" : "s";
|
|
58
|
+
const imagePlural = imageCount === 1 ? "" : "s";
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!open) return;
|
|
62
|
+
getFigmaConnectionStatus()
|
|
63
|
+
.then((status) => {
|
|
64
|
+
if (status.docsUrl) setDocsUrl(status.docsUrl);
|
|
65
|
+
})
|
|
66
|
+
.catch(() => {});
|
|
67
|
+
}, [open]);
|
|
68
|
+
|
|
69
|
+
async function handleFigSelected(e: React.ChangeEvent<HTMLInputElement>) {
|
|
70
|
+
const file = e.target.files?.[0];
|
|
71
|
+
e.target.value = "";
|
|
72
|
+
if (!file) return;
|
|
73
|
+
if (validateFigUploadFile(file)) {
|
|
74
|
+
setError(t("designEditor.import.figmaHydrationInvalidFig"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
setBusy(true);
|
|
78
|
+
setError(null);
|
|
79
|
+
try {
|
|
80
|
+
const result = await hydrateImagesFromFig({
|
|
81
|
+
designId,
|
|
82
|
+
file,
|
|
83
|
+
fileIds,
|
|
84
|
+
fallbackErrorMessage: t("designEditor.import.figmaHydrationFigError"),
|
|
85
|
+
});
|
|
86
|
+
if (result.error) {
|
|
87
|
+
setError(result.error);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const totalResolved = result.totalResolved ?? 0;
|
|
91
|
+
onOpenChange(false);
|
|
92
|
+
setToken("");
|
|
93
|
+
onHydrated();
|
|
94
|
+
toast.success(t("designEditor.import.figmaHydrationSuccess"), {
|
|
95
|
+
description: t(
|
|
96
|
+
"designEditor.import.figmaHydrationFigSuccessDescription",
|
|
97
|
+
{ count: totalResolved, plural: totalResolved === 1 ? "" : "s" },
|
|
98
|
+
),
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
setError(
|
|
102
|
+
err instanceof Error
|
|
103
|
+
? err.message
|
|
104
|
+
: t("designEditor.import.figmaHydrationFigError"),
|
|
105
|
+
);
|
|
106
|
+
} finally {
|
|
107
|
+
setBusy(false);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
if (!token.trim()) return;
|
|
114
|
+
setBusy(true);
|
|
115
|
+
setError(null);
|
|
116
|
+
try {
|
|
117
|
+
const status = await saveFigmaAccessToken(token.trim());
|
|
118
|
+
if (status.docsUrl) setDocsUrl(status.docsUrl);
|
|
119
|
+
|
|
120
|
+
let totalResolved = 0;
|
|
121
|
+
for (const fileId of fileIds) {
|
|
122
|
+
const result = await callAction<{
|
|
123
|
+
resolved?: number;
|
|
124
|
+
missing?: number;
|
|
125
|
+
}>("hydrate-figma-paste-images", { fileId });
|
|
126
|
+
totalResolved += result?.resolved ?? 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
onOpenChange(false);
|
|
130
|
+
setToken("");
|
|
131
|
+
onHydrated();
|
|
132
|
+
toast.success(t("designEditor.import.figmaHydrationSuccess"), {
|
|
133
|
+
description: t("designEditor.import.figmaHydrationSuccessDescription", {
|
|
134
|
+
count: totalResolved,
|
|
135
|
+
plural: totalResolved === 1 ? "" : "s",
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const message =
|
|
140
|
+
err instanceof Error ? err.message : t("common.genericError");
|
|
141
|
+
const is403 =
|
|
142
|
+
message.includes("403") || message.toLowerCase().includes("forbidden");
|
|
143
|
+
const isServerError = /internal server error/i.test(message);
|
|
144
|
+
setError(
|
|
145
|
+
is403
|
|
146
|
+
? 'Token rejected (403). In Figma\'s token settings, enable the "File content" and "Current user" scopes, then generate a new token.'
|
|
147
|
+
: isServerError
|
|
148
|
+
? "Server error — Figma's API may be rate-limited. Wait ~1 minute then try again; repeated retries extend the cooldown."
|
|
149
|
+
: message,
|
|
150
|
+
);
|
|
151
|
+
} finally {
|
|
152
|
+
setBusy(false);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
158
|
+
<DialogContent className="sm:max-w-md">
|
|
159
|
+
<form
|
|
160
|
+
onSubmit={(e) => {
|
|
161
|
+
void handleSubmit(e);
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
<DialogHeader>
|
|
165
|
+
<DialogTitle>
|
|
166
|
+
{t("designEditor.import.figmaHydrationDialogTitle")}
|
|
167
|
+
</DialogTitle>
|
|
168
|
+
<DialogDescription>
|
|
169
|
+
{t("designEditor.import.figmaHydrationDialogDescription", {
|
|
170
|
+
count: imageCount,
|
|
171
|
+
plural: imagePlural,
|
|
172
|
+
screensPlural,
|
|
173
|
+
})}
|
|
174
|
+
</DialogDescription>
|
|
175
|
+
</DialogHeader>
|
|
176
|
+
|
|
177
|
+
<div className="mt-4 rounded-md border border-border bg-muted/30 p-3">
|
|
178
|
+
<div className="flex items-center gap-2">
|
|
179
|
+
<p className="text-xs font-medium text-foreground">
|
|
180
|
+
{t("designEditor.import.figmaHydrationFigTitle")}
|
|
181
|
+
</p>
|
|
182
|
+
<span className="rounded-full bg-primary/10 px-1.5 py-px text-[9px] font-semibold uppercase tracking-wide text-primary">
|
|
183
|
+
{t("designEditor.import.figmaHydrationRecommended")}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
<p className="mt-1 text-[10px] leading-snug text-muted-foreground">
|
|
187
|
+
{t("designEditor.import.figmaHydrationFigOption")}
|
|
188
|
+
</p>
|
|
189
|
+
<input
|
|
190
|
+
ref={figInputRef}
|
|
191
|
+
type="file"
|
|
192
|
+
accept=".fig"
|
|
193
|
+
className="hidden"
|
|
194
|
+
onChange={(e) => void handleFigSelected(e)}
|
|
195
|
+
/>
|
|
196
|
+
<Button
|
|
197
|
+
type="button"
|
|
198
|
+
size="sm"
|
|
199
|
+
variant="secondary"
|
|
200
|
+
className="mt-2 w-full"
|
|
201
|
+
disabled={busy}
|
|
202
|
+
onClick={() => figInputRef.current?.click()}
|
|
203
|
+
>
|
|
204
|
+
{t("designEditor.import.figmaHydrationChooseFig")}
|
|
205
|
+
</Button>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="mt-3 text-center text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
209
|
+
{t("designEditor.import.figmaHydrationOrToken")}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div className="mt-2 space-y-2">
|
|
213
|
+
<div className="flex items-center justify-between gap-2">
|
|
214
|
+
<Label htmlFor="figma-hydration-token" className="text-xs">
|
|
215
|
+
{t("designEditor.import.figmaTokenLabel")}
|
|
216
|
+
</Label>
|
|
217
|
+
{docsUrl ? (
|
|
218
|
+
<a
|
|
219
|
+
href={docsUrl}
|
|
220
|
+
target="_blank"
|
|
221
|
+
rel="noreferrer"
|
|
222
|
+
className="shrink-0 text-[10px] font-medium text-foreground underline-offset-2 hover:underline"
|
|
223
|
+
>
|
|
224
|
+
{t("designEditor.import.figmaTokenDocs")}
|
|
225
|
+
</a>
|
|
226
|
+
) : null}
|
|
227
|
+
</div>
|
|
228
|
+
<Input
|
|
229
|
+
id="figma-hydration-token"
|
|
230
|
+
type="password"
|
|
231
|
+
value={token}
|
|
232
|
+
onChange={(e) => setToken(e.target.value)}
|
|
233
|
+
placeholder={t("designEditor.import.figmaTokenPlaceholder")}
|
|
234
|
+
autoComplete="new-password"
|
|
235
|
+
aria-invalid={error ? true : undefined}
|
|
236
|
+
className="h-8 text-xs"
|
|
237
|
+
disabled={busy}
|
|
238
|
+
/>
|
|
239
|
+
{error ? (
|
|
240
|
+
<p className="rounded-md border border-destructive/30 bg-destructive/5 px-2 py-1.5 text-[10px] leading-snug text-destructive">
|
|
241
|
+
{error}
|
|
242
|
+
</p>
|
|
243
|
+
) : (
|
|
244
|
+
<div className="space-y-1">
|
|
245
|
+
<p className="text-[10px] leading-snug text-muted-foreground">
|
|
246
|
+
{t("designEditor.import.figmaHydrationTokenDescription")}
|
|
247
|
+
</p>
|
|
248
|
+
<p className="text-[10px] leading-snug text-muted-foreground/70">
|
|
249
|
+
{t("designEditor.import.figmaHydrationRateLimit")}
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<DialogFooter className="mt-4">
|
|
256
|
+
<Button
|
|
257
|
+
type="button"
|
|
258
|
+
variant="ghost"
|
|
259
|
+
size="sm"
|
|
260
|
+
onClick={() => onOpenChange(false)}
|
|
261
|
+
disabled={busy}
|
|
262
|
+
>
|
|
263
|
+
{t("home.cancel")}
|
|
264
|
+
</Button>
|
|
265
|
+
<Button type="submit" size="sm" disabled={busy || !token.trim()}>
|
|
266
|
+
{t("designEditor.import.figmaHydrationConnectAndLoad")}
|
|
267
|
+
</Button>
|
|
268
|
+
</DialogFooter>
|
|
269
|
+
</form>
|
|
270
|
+
</DialogContent>
|
|
271
|
+
</Dialog>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
@@ -455,6 +455,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
455
455
|
onBoardElementDblClickText,
|
|
456
456
|
onBoardIframeHotkey,
|
|
457
457
|
onBoardFigmaClipboardPaste,
|
|
458
|
+
onBoardImagePaste,
|
|
458
459
|
onBoardIframeContextMenu,
|
|
459
460
|
onBoardTextEditingStateChange,
|
|
460
461
|
boardClearSelectionRequest,
|
|
@@ -7670,6 +7671,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7670
7671
|
onClearSelection={onBoardElementClear}
|
|
7671
7672
|
onIframeHotkey={onBoardIframeHotkey}
|
|
7672
7673
|
onFigmaClipboardPaste={onBoardFigmaClipboardPaste}
|
|
7674
|
+
onImagePaste={onBoardImagePaste}
|
|
7673
7675
|
onIframeContextMenu={onBoardIframeContextMenu}
|
|
7674
7676
|
onVisualStructureChange={onBoardVisualStructureChange}
|
|
7675
7677
|
onVisualStyleChange={onBoardVisualStyleChange}
|
|
@@ -9170,14 +9172,12 @@ const Screen = memo(function Screen({
|
|
|
9170
9172
|
// Memoize the srcdoc with the hit-test responder injected so we don't
|
|
9171
9173
|
// rebuild the string every render (that would reload the iframe).
|
|
9172
9174
|
// Keyed only on screen.content; the hit-test script itself is constant.
|
|
9173
|
-
const srcdocWithHitTest = useMemo(
|
|
9174
|
-
(
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
),
|
|
9175
|
+
const srcdocWithHitTest = useMemo(() => {
|
|
9176
|
+
return injectSessionReplayIframeBootstrap(
|
|
9177
|
+
appendHitTestResponder(screen.content),
|
|
9178
|
+
);
|
|
9178
9179
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
9179
|
-
|
|
9180
|
-
);
|
|
9180
|
+
}, [screen.content]);
|
|
9181
9181
|
|
|
9182
9182
|
const updateDirectHover = useCallback((next: boolean) => {
|
|
9183
9183
|
setDirectlyHovered((current) => (current === next ? current : next));
|
|
@@ -10447,15 +10447,63 @@ declare var __LIVE_REFLOW_ENABLED__: boolean;
|
|
|
10447
10447
|
}
|
|
10448
10448
|
var content = getFigmaClipboardContent(e.clipboardData);
|
|
10449
10449
|
clearPendingPlainPasteHotkey();
|
|
10450
|
-
if (
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10450
|
+
if (content) {
|
|
10451
|
+
stopNativeInteraction(e);
|
|
10452
|
+
(window.parent as Window).postMessage(
|
|
10453
|
+
{ type: "figma-clipboard-paste", content: content },
|
|
10454
|
+
"*",
|
|
10455
|
+
);
|
|
10456
|
+
return;
|
|
10457
|
+
}
|
|
10458
|
+
// Relay image files pasted while the canvas has focus (e.g. "Copy as PNG"
|
|
10459
|
+
// from Figma, or a screenshot). The parent's handleEditorPaste cannot see
|
|
10460
|
+
// these because paste events inside an iframe don't bubble to the parent
|
|
10461
|
+
// document — the bridge reads each file as a data URL and relays it so
|
|
10462
|
+
// the parent's handlePastedImageFiles can insert an <img> layer.
|
|
10463
|
+
var imageFiles = Array.from(e.clipboardData?.items ?? [])
|
|
10464
|
+
.filter(function (item) {
|
|
10465
|
+
return item.kind === "file" && item.type.startsWith("image/");
|
|
10466
|
+
})
|
|
10467
|
+
.map(function (item) {
|
|
10468
|
+
return item.getAsFile();
|
|
10469
|
+
})
|
|
10470
|
+
.filter(function (f): f is File {
|
|
10471
|
+
return Boolean(f);
|
|
10472
|
+
});
|
|
10473
|
+
if (imageFiles.length > 0) {
|
|
10474
|
+
stopNativeInteraction(e);
|
|
10475
|
+
var readPromises = imageFiles.map(function (file) {
|
|
10476
|
+
return new Promise<{
|
|
10477
|
+
dataUrl: string;
|
|
10478
|
+
type: string;
|
|
10479
|
+
name: string;
|
|
10480
|
+
} | null>(function (resolve) {
|
|
10481
|
+
var reader = new FileReader();
|
|
10482
|
+
reader.onload = function () {
|
|
10483
|
+
resolve({
|
|
10484
|
+
dataUrl: typeof reader.result === "string" ? reader.result : "",
|
|
10485
|
+
type: file.type,
|
|
10486
|
+
name: file.name,
|
|
10487
|
+
});
|
|
10488
|
+
};
|
|
10489
|
+
reader.onerror = function () {
|
|
10490
|
+
resolve(null);
|
|
10491
|
+
};
|
|
10492
|
+
reader.readAsDataURL(file);
|
|
10493
|
+
});
|
|
10494
|
+
});
|
|
10495
|
+
void Promise.all(readPromises).then(function (results) {
|
|
10496
|
+
var valid = results.filter(function (r) {
|
|
10497
|
+
return r && r.dataUrl;
|
|
10498
|
+
});
|
|
10499
|
+
if (valid.length > 0) {
|
|
10500
|
+
(window.parent as Window).postMessage(
|
|
10501
|
+
{ type: "canvas-image-paste", files: valid },
|
|
10502
|
+
"*",
|
|
10503
|
+
);
|
|
10504
|
+
}
|
|
10505
|
+
});
|
|
10506
|
+
}
|
|
10459
10507
|
},
|
|
10460
10508
|
true,
|
|
10461
10509
|
);
|
|
@@ -14,6 +14,16 @@ export interface IframeFigmaClipboardPastePayload {
|
|
|
14
14
|
content: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface IframeImagePasteFile {
|
|
18
|
+
dataUrl: string;
|
|
19
|
+
type: string;
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IframeImagePastePayload {
|
|
24
|
+
files: IframeImagePasteFile[];
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
export interface IframeContextMenuPayload {
|
|
18
28
|
screenId?: string;
|
|
19
29
|
clientX: number;
|
|
@@ -279,7 +279,14 @@ function stripExecutableStaticPreviewContent(html: string) {
|
|
|
279
279
|
.replace(/<(?:iframe|object|embed|audio|video|source)\b[^>]*\/?\s*>/gi, "")
|
|
280
280
|
.replace(/<(?:link|meta|base)\b[^>]*>/gi, "")
|
|
281
281
|
.replace(/@import\s+(?:url\([^)]*\)|["'][^"']*["'])\s*[^;]*;/gi, "")
|
|
282
|
-
.replace(/url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi,
|
|
282
|
+
.replace(/url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, (match) => {
|
|
283
|
+
// Strip data:, blob:, and unknown-scheme url() references to prevent
|
|
284
|
+
// large embedded payloads or local-resource leaks. Allow https:// URLs
|
|
285
|
+
// (CDN images/fonts) — they can only fetch inert assets, not execute code.
|
|
286
|
+
const raw = match.slice(4, -1).trim();
|
|
287
|
+
const inner = raw.replace(/^['"]|['"]$/g, "").trim();
|
|
288
|
+
return /^https:\/\//i.test(inner) ? match : "none";
|
|
289
|
+
})
|
|
283
290
|
.replace(/<(?:img|image|use)\b[^>]*>/gi, (tag) =>
|
|
284
291
|
tag.replace(
|
|
285
292
|
/\s+(?:src|srcset|href|xlink:href)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
IframeContextMenuPayload,
|
|
11
11
|
IframeFigmaClipboardPastePayload,
|
|
12
12
|
IframeHotkeyPayload,
|
|
13
|
+
IframeImagePastePayload,
|
|
13
14
|
} from "../design-canvas/iframe-events";
|
|
14
15
|
import type {
|
|
15
16
|
DeviceFrameType,
|
|
@@ -363,6 +364,7 @@ export interface MultiScreenCanvasProps {
|
|
|
363
364
|
onBoardFigmaClipboardPaste?: (
|
|
364
365
|
event: IframeFigmaClipboardPastePayload,
|
|
365
366
|
) => void;
|
|
367
|
+
onBoardImagePaste?: (event: IframeImagePastePayload) => void;
|
|
366
368
|
onBoardIframeContextMenu?: (event: IframeContextMenuPayload) => void;
|
|
367
369
|
onBoardTextEditingStateChange?: (state: {
|
|
368
370
|
active: boolean;
|
|
@@ -940,6 +942,7 @@ export type {
|
|
|
940
942
|
IframeContextMenuPayload,
|
|
941
943
|
IframeFigmaClipboardPastePayload,
|
|
942
944
|
IframeHotkeyPayload,
|
|
945
|
+
IframeImagePastePayload,
|
|
943
946
|
};
|
|
944
947
|
|
|
945
948
|
export interface ResolvedScreenMetadata {
|