@agent-native/core 0.80.11 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +47 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -139,6 +139,10 @@ export interface DesignColorPickerProps {
|
|
|
139
139
|
onChange: (value: string) => void;
|
|
140
140
|
onPaintValueChange?: (value: string) => void;
|
|
141
141
|
onImageFillChange?: (value: ImageFillValue) => void;
|
|
142
|
+
backgroundImage?: string;
|
|
143
|
+
backgroundSize?: string;
|
|
144
|
+
backgroundRepeat?: string;
|
|
145
|
+
backgroundPosition?: string;
|
|
142
146
|
label?: string;
|
|
143
147
|
opacity?: number;
|
|
144
148
|
onOpacityChange?: (opacity: number) => void;
|
|
@@ -684,6 +688,10 @@ export function DesignColorPicker({
|
|
|
684
688
|
onChange,
|
|
685
689
|
onPaintValueChange,
|
|
686
690
|
onImageFillChange,
|
|
691
|
+
backgroundImage,
|
|
692
|
+
backgroundSize,
|
|
693
|
+
backgroundRepeat,
|
|
694
|
+
backgroundPosition,
|
|
687
695
|
label: _label,
|
|
688
696
|
opacity,
|
|
689
697
|
onOpacityChange,
|
|
@@ -723,6 +731,27 @@ export function DesignColorPicker({
|
|
|
723
731
|
)
|
|
724
732
|
? blendMode
|
|
725
733
|
: "normal";
|
|
734
|
+
const parsedImageFill = useMemo(
|
|
735
|
+
() =>
|
|
736
|
+
backgroundImage !== undefined ||
|
|
737
|
+
backgroundSize !== undefined ||
|
|
738
|
+
backgroundRepeat !== undefined ||
|
|
739
|
+
backgroundPosition !== undefined
|
|
740
|
+
? parseImageFillCss({
|
|
741
|
+
backgroundImage: backgroundImage ?? value,
|
|
742
|
+
backgroundSize,
|
|
743
|
+
backgroundRepeat,
|
|
744
|
+
backgroundPosition,
|
|
745
|
+
})
|
|
746
|
+
: parseImageFillCss(value),
|
|
747
|
+
[
|
|
748
|
+
backgroundImage,
|
|
749
|
+
backgroundPosition,
|
|
750
|
+
backgroundRepeat,
|
|
751
|
+
backgroundSize,
|
|
752
|
+
value,
|
|
753
|
+
],
|
|
754
|
+
);
|
|
726
755
|
|
|
727
756
|
const [mode, setMode] = useState<DesignColorMode>("hex");
|
|
728
757
|
const [hexDraft, setHexDraft] = useState(() => toDisplayHex(color));
|
|
@@ -748,10 +777,9 @@ export function DesignColorPicker({
|
|
|
748
777
|
null,
|
|
749
778
|
);
|
|
750
779
|
const [selectedStopId, setSelectedStopId] = useState<string>("");
|
|
751
|
-
const [imageFill, setImageFill] = useState<ImageFillValue>(
|
|
752
|
-
url: "",
|
|
753
|
-
|
|
754
|
-
});
|
|
780
|
+
const [imageFill, setImageFill] = useState<ImageFillValue>(
|
|
781
|
+
() => parsedImageFill ?? { url: "", fit: "fill" },
|
|
782
|
+
);
|
|
755
783
|
const [shaderDescriptor, setShaderDescriptor] =
|
|
756
784
|
useState<ShaderDescriptor | null>(null);
|
|
757
785
|
|
|
@@ -796,16 +824,14 @@ export function DesignColorPicker({
|
|
|
796
824
|
|
|
797
825
|
// Keep image-fill state synced when the incoming value is an image fill.
|
|
798
826
|
useEffect(() => {
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
)
|
|
805
|
-
setImageFill(parsed);
|
|
806
|
-
}
|
|
827
|
+
if (!parsedImageFill) return;
|
|
828
|
+
setImageFill((current) =>
|
|
829
|
+
current.url === parsedImageFill.url && current.fit === parsedImageFill.fit
|
|
830
|
+
? current
|
|
831
|
+
: parsedImageFill,
|
|
832
|
+
);
|
|
807
833
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
808
|
-
}, [
|
|
834
|
+
}, [parsedImageFill?.url, parsedImageFill?.fit]);
|
|
809
835
|
|
|
810
836
|
// Ensure a selected stop id exists whenever a gradient is active.
|
|
811
837
|
useEffect(() => {
|
|
@@ -987,11 +1013,14 @@ export function DesignColorPicker({
|
|
|
987
1013
|
return;
|
|
988
1014
|
}
|
|
989
1015
|
if (nextType === "image") {
|
|
990
|
-
|
|
991
|
-
|
|
1016
|
+
const nextImageFill = parsedImageFill ?? imageFill;
|
|
1017
|
+
if (onImageFillChange && nextImageFill.url) {
|
|
1018
|
+
onImageFillChange(nextImageFill);
|
|
992
1019
|
return;
|
|
993
1020
|
}
|
|
994
|
-
emitPaintValue(
|
|
1021
|
+
emitPaintValue(
|
|
1022
|
+
nextImageFill.url ? imageFillToCss(nextImageFill) : "transparent",
|
|
1023
|
+
);
|
|
995
1024
|
return;
|
|
996
1025
|
}
|
|
997
1026
|
if (nextType === "video") {
|
|
@@ -25,6 +25,13 @@ export interface ImageFillValue {
|
|
|
25
25
|
fit: ImageFitMode;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface ImageFillBackgroundStyles {
|
|
29
|
+
backgroundImage: string;
|
|
30
|
+
backgroundSize?: string;
|
|
31
|
+
backgroundRepeat?: string;
|
|
32
|
+
backgroundPosition?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
|
|
29
36
|
{ mode: "fill", label: "Fill" }, // i18n-ignore image fit mode
|
|
30
37
|
{ mode: "fit", label: "Fit" }, // i18n-ignore image fit mode
|
|
@@ -98,8 +105,8 @@ export function imageFillToBackgroundStyles(
|
|
|
98
105
|
backgroundPosition: "center",
|
|
99
106
|
};
|
|
100
107
|
}
|
|
101
|
-
const safeUrl = url
|
|
102
|
-
const backgroundImage = `url("${safeUrl}")`;
|
|
108
|
+
const safeUrl = escapeForQuotedUrl(url);
|
|
109
|
+
const backgroundImage = `url("${safeUrl}") ${imageFitMarker(value.fit)}`;
|
|
103
110
|
switch (value.fit) {
|
|
104
111
|
case "fit":
|
|
105
112
|
return {
|
|
@@ -139,23 +146,95 @@ export function imageFillToBackgroundStyles(
|
|
|
139
146
|
// group 3 — unquoted: url(...no-parens-or-quotes...)
|
|
140
147
|
const URL_RE = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)'"]*?))\s*\)/i;
|
|
141
148
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
function normalizeCssLayer(value: string | undefined): string {
|
|
150
|
+
return value?.trim().toLowerCase() ?? "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function firstCssLayer(value: string | undefined): string {
|
|
154
|
+
const trimmed = value?.trim() ?? "";
|
|
155
|
+
if (!trimmed) return "";
|
|
156
|
+
let depth = 0;
|
|
157
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
158
|
+
const char = trimmed[index];
|
|
159
|
+
if (char === "(") depth += 1;
|
|
160
|
+
if (char === ")") depth = Math.max(0, depth - 1);
|
|
161
|
+
if (char === "," && depth === 0) return trimmed.slice(0, index).trim();
|
|
162
|
+
}
|
|
163
|
+
return trimmed;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isStartBackgroundPosition(position: string): boolean {
|
|
167
|
+
if (!position) return false;
|
|
168
|
+
const normalized = position
|
|
169
|
+
.replace(/\s+/g, " ")
|
|
170
|
+
.replace(/\b0(?:\.0+)?(?:px|em|rem|%)\b/g, "0")
|
|
171
|
+
.trim();
|
|
172
|
+
const tokens = normalized.split(" ").filter(Boolean);
|
|
173
|
+
if (tokens.length === 1) {
|
|
174
|
+
return tokens[0] === "0" || tokens[0] === "top" || tokens[0] === "left";
|
|
175
|
+
}
|
|
176
|
+
const horizontal = tokens[0];
|
|
177
|
+
const vertical = tokens[1];
|
|
178
|
+
return (
|
|
179
|
+
(horizontal === "left" || horizontal === "0") &&
|
|
180
|
+
(vertical === "top" || vertical === "0")
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function inferFitFromBackgroundStyles(
|
|
185
|
+
styles: ImageFillBackgroundStyles,
|
|
186
|
+
): ImageFitMode | null {
|
|
187
|
+
const size = normalizeCssLayer(firstCssLayer(styles.backgroundSize));
|
|
188
|
+
const repeat = normalizeCssLayer(firstCssLayer(styles.backgroundRepeat));
|
|
189
|
+
const position = normalizeCssLayer(firstCssLayer(styles.backgroundPosition));
|
|
190
|
+
|
|
191
|
+
if (size === "contain") return "fit";
|
|
192
|
+
if (repeat === "repeat" || repeat === "repeat-x" || repeat === "repeat-y") {
|
|
193
|
+
return "tile";
|
|
194
|
+
}
|
|
195
|
+
if (size === "auto" && repeat === "repeat") return "tile";
|
|
196
|
+
if (
|
|
197
|
+
isStartBackgroundPosition(position) &&
|
|
198
|
+
(repeat === "repeat" || size === "auto")
|
|
199
|
+
) {
|
|
200
|
+
return "tile";
|
|
201
|
+
}
|
|
202
|
+
if (size === "cover") return "fill";
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Extract the URL + fit mode from CSS background input, if present. */
|
|
207
|
+
export function parseImageFillCss(value: string): ImageFillValue | null;
|
|
208
|
+
export function parseImageFillCss(
|
|
209
|
+
value: ImageFillBackgroundStyles,
|
|
210
|
+
): ImageFillValue | null;
|
|
211
|
+
export function parseImageFillCss(
|
|
212
|
+
value: string | ImageFillBackgroundStyles,
|
|
213
|
+
): ImageFillValue | null {
|
|
214
|
+
const styles = typeof value === "string" ? { backgroundImage: value } : value;
|
|
215
|
+
const match = styles.backgroundImage.match(URL_RE);
|
|
145
216
|
if (!match) return null;
|
|
146
217
|
const url = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
147
|
-
const marker =
|
|
218
|
+
const marker = styles.backgroundImage.match(FIT_MARKER_RE)?.[1] as
|
|
219
|
+
| ImageFitMode
|
|
220
|
+
| undefined;
|
|
148
221
|
if (marker) return { url, fit: marker };
|
|
222
|
+
const inferredFit = inferFitFromBackgroundStyles(styles);
|
|
223
|
+
if (inferredFit) return { url, fit: inferredFit };
|
|
149
224
|
// Heuristic fallback when no marker comment is present (e.g. CSS pasted from
|
|
150
225
|
// DevTools or Figma inspect). Note: "crop" and "fill" produce identical CSS
|
|
151
226
|
// (center / cover no-repeat), so external CSS without the marker comment will
|
|
152
227
|
// always parse as "fill". Crop mode is only recoverable via the proprietary
|
|
153
228
|
// agent-native-image-fit marker written by imageFillToCss.
|
|
154
229
|
let fit: ImageFitMode = "fill";
|
|
155
|
-
|
|
156
|
-
|
|
230
|
+
const backgroundImage = styles.backgroundImage;
|
|
231
|
+
if (/contain/i.test(backgroundImage)) fit = "fit";
|
|
232
|
+
else if (
|
|
233
|
+
/repeat(?!\s+no)/i.test(backgroundImage) &&
|
|
234
|
+
!/no-repeat/i.test(backgroundImage)
|
|
235
|
+
)
|
|
157
236
|
fit = "tile";
|
|
158
|
-
else if (/cover/i.test(
|
|
237
|
+
else if (/cover/i.test(backgroundImage)) fit = "fill";
|
|
159
238
|
return { url, fit };
|
|
160
239
|
}
|
|
161
240
|
|
|
@@ -47,7 +47,9 @@ export interface UploadedFile {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const DEFAULT_ASSETS_PICKER_URL = "https://assets.agent-native.com/picker";
|
|
50
|
-
const
|
|
50
|
+
const RAW_CHAT_IMAGE_ATTACHMENT_BYTES = 512 * 1024;
|
|
51
|
+
const MAX_TOTAL_CHAT_IMAGE_DATA_URL_BYTES = 3_000_000;
|
|
52
|
+
const DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES = 1_250_000;
|
|
51
53
|
const CHAT_IMAGE_ATTACHMENT_TYPES = new Set([
|
|
52
54
|
"image/gif",
|
|
53
55
|
"image/jpeg",
|
|
@@ -55,6 +57,11 @@ const CHAT_IMAGE_ATTACHMENT_TYPES = new Set([
|
|
|
55
57
|
"image/png",
|
|
56
58
|
"image/webp",
|
|
57
59
|
]);
|
|
60
|
+
const IMAGE_COMPRESSION_PASSES = [
|
|
61
|
+
{ maxDimension: 1400, jpegQuality: 0.76 },
|
|
62
|
+
{ maxDimension: 1024, jpegQuality: 0.7 },
|
|
63
|
+
{ maxDimension: 768, jpegQuality: 0.65 },
|
|
64
|
+
];
|
|
58
65
|
|
|
59
66
|
interface PickedAssetImagePayload {
|
|
60
67
|
url?: unknown;
|
|
@@ -113,14 +120,11 @@ function pickedAssetContext(payload: unknown, url: string) {
|
|
|
113
120
|
return lines.join("\n");
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
!CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase())
|
|
120
|
-
) {
|
|
121
|
-
return Promise.resolve(null);
|
|
122
|
-
}
|
|
123
|
+
function dataUrlBytes(dataUrl: string): number {
|
|
124
|
+
return new TextEncoder().encode(dataUrl).byteLength;
|
|
125
|
+
}
|
|
123
126
|
|
|
127
|
+
function readFileDataUrl(file: File): Promise<string | null> {
|
|
124
128
|
return new Promise((resolve) => {
|
|
125
129
|
const reader = new FileReader();
|
|
126
130
|
reader.onload = () =>
|
|
@@ -130,6 +134,77 @@ function readChatImageAttachment(file: File): Promise<string | null> {
|
|
|
130
134
|
});
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const img = new Image();
|
|
140
|
+
img.onload = () => resolve(img);
|
|
141
|
+
img.onerror = () => reject(new Error("Failed to decode image"));
|
|
142
|
+
img.src = url;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function compressImageAttachment(
|
|
147
|
+
file: File,
|
|
148
|
+
maxDimension: number,
|
|
149
|
+
jpegQuality: number,
|
|
150
|
+
): Promise<string | null> {
|
|
151
|
+
if (typeof document === "undefined" || typeof Image === "undefined") {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const objectUrl = URL.createObjectURL(file);
|
|
156
|
+
try {
|
|
157
|
+
const image = await loadImage(objectUrl);
|
|
158
|
+
const ratio = Math.min(
|
|
159
|
+
maxDimension / image.naturalWidth,
|
|
160
|
+
maxDimension / image.naturalHeight,
|
|
161
|
+
1,
|
|
162
|
+
);
|
|
163
|
+
const width = Math.max(1, Math.round(image.naturalWidth * ratio));
|
|
164
|
+
const height = Math.max(1, Math.round(image.naturalHeight * ratio));
|
|
165
|
+
const canvas = document.createElement("canvas");
|
|
166
|
+
canvas.width = width;
|
|
167
|
+
canvas.height = height;
|
|
168
|
+
const ctx = canvas.getContext("2d");
|
|
169
|
+
if (!ctx) return null;
|
|
170
|
+
ctx.drawImage(image, 0, 0, width, height);
|
|
171
|
+
return canvas.toDataURL("image/jpeg", jpegQuality);
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
} finally {
|
|
175
|
+
URL.revokeObjectURL(objectUrl);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function readChatImageAttachment(
|
|
180
|
+
file: File,
|
|
181
|
+
maxDataUrlBytes = DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES,
|
|
182
|
+
): Promise<string | null> {
|
|
183
|
+
if (!CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase())) return null;
|
|
184
|
+
|
|
185
|
+
if (file.size <= RAW_CHAT_IMAGE_ATTACHMENT_BYTES) {
|
|
186
|
+
const raw = await readFileDataUrl(file);
|
|
187
|
+
if (raw && dataUrlBytes(raw) <= maxDataUrlBytes) return raw;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let fallback: string | null = null;
|
|
191
|
+
for (const pass of IMAGE_COMPRESSION_PASSES) {
|
|
192
|
+
const compressed = await compressImageAttachment(
|
|
193
|
+
file,
|
|
194
|
+
pass.maxDimension,
|
|
195
|
+
pass.jpegQuality,
|
|
196
|
+
);
|
|
197
|
+
if (!compressed) continue;
|
|
198
|
+
fallback = compressed;
|
|
199
|
+
if (dataUrlBytes(compressed) <= maxDataUrlBytes) {
|
|
200
|
+
return compressed;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return fallback && dataUrlBytes(fallback) <= maxDataUrlBytes
|
|
204
|
+
? fallback
|
|
205
|
+
: null;
|
|
206
|
+
}
|
|
207
|
+
|
|
133
208
|
interface AssetsPickerDialogProps {
|
|
134
209
|
open: boolean;
|
|
135
210
|
onOpenChange: (open: boolean) => void;
|
|
@@ -355,8 +430,18 @@ export default function PromptPopover({
|
|
|
355
430
|
);
|
|
356
431
|
}
|
|
357
432
|
const uploaded = (await res.json()) as UploadedFile[];
|
|
433
|
+
const imageFileCount =
|
|
434
|
+
files.filter((file) =>
|
|
435
|
+
CHAT_IMAGE_ATTACHMENT_TYPES.has(file.type.toLowerCase()),
|
|
436
|
+
).length || 1;
|
|
437
|
+
const maxImageDataUrlBytes = Math.min(
|
|
438
|
+
DEFAULT_MAX_CHAT_IMAGE_DATA_URL_BYTES,
|
|
439
|
+
Math.floor(MAX_TOTAL_CHAT_IMAGE_DATA_URL_BYTES / imageFileCount),
|
|
440
|
+
);
|
|
358
441
|
const visualAttachments = await Promise.all(
|
|
359
|
-
files.map((file) =>
|
|
442
|
+
files.map((file) =>
|
|
443
|
+
readChatImageAttachment(file, maxImageDataUrlBytes),
|
|
444
|
+
),
|
|
360
445
|
);
|
|
361
446
|
return uploaded.map((file, index) =>
|
|
362
447
|
visualAttachments[index]
|
|
@@ -39,9 +39,9 @@ const BARE_PREFIXES = ["/present/"];
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Routes where the page renders its own toolbar instead of the global Header.
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* The Header is hidden so the page can supply richer custom chrome (e.g.
|
|
43
|
+
* DesignEditor mode/zoom/device, shared ExtensionViewer / ExtensionsListPage
|
|
44
|
+
* chrome). The editor owns its agent surface inside its Figma-style left rail.
|
|
45
45
|
*/
|
|
46
46
|
const EDITOR_PREFIXES = ["/design/", "/extensions"];
|
|
47
47
|
|
|
@@ -117,6 +117,22 @@ export function Layout({ children }: LayoutProps) {
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
if (isDesignEditor) {
|
|
121
|
+
return (
|
|
122
|
+
<HeaderActionsProvider>
|
|
123
|
+
<MobileSidebarContext.Provider value={null}>
|
|
124
|
+
<div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background text-foreground">
|
|
125
|
+
<div className="agent-layout-main-surface design-editor-main-surface flex h-full flex-1 flex-col overflow-hidden">
|
|
126
|
+
<main className="agent-native-app-main flex-1 overflow-hidden">
|
|
127
|
+
{children}
|
|
128
|
+
</main>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</MobileSidebarContext.Provider>
|
|
132
|
+
</HeaderActionsProvider>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
return (
|
|
121
137
|
<HeaderActionsProvider>
|
|
122
138
|
<MobileSidebarContext.Provider
|
|
@@ -220,6 +220,13 @@
|
|
|
220
220
|
box-shadow: none;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
.agent-layout-shell .design-editor-main-surface {
|
|
224
|
+
border-start-start-radius: 0;
|
|
225
|
+
border-end-start-radius: 0;
|
|
226
|
+
border-top-left-radius: 0;
|
|
227
|
+
border-bottom-left-radius: 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
223
230
|
.dark .agent-layout-shell,
|
|
224
231
|
.dark .agent-sidebar-shell,
|
|
225
232
|
.dark.agent-layout-shell,
|
|
@@ -12,6 +12,8 @@ export interface NavigationState {
|
|
|
12
12
|
editorView?: "single" | "overview";
|
|
13
13
|
inspectorTab?: "design" | "tweaks" | "extensions";
|
|
14
14
|
inspector?: "design" | "tweaks" | "extensions";
|
|
15
|
+
leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
|
|
16
|
+
panel?: "file" | "agent" | "assets" | "tools" | "tokens";
|
|
15
17
|
fileId?: string;
|
|
16
18
|
screenId?: string;
|
|
17
19
|
filename?: string;
|
|
@@ -44,6 +46,8 @@ export interface DesignEditorCommand {
|
|
|
44
46
|
viewMode?: "single" | "overview";
|
|
45
47
|
inspectorTab?: "design" | "tweaks" | "extensions";
|
|
46
48
|
inspector?: "design" | "tweaks" | "extensions";
|
|
49
|
+
leftPanel?: "file" | "agent" | "assets" | "tools" | "tokens";
|
|
50
|
+
panel?: "file" | "agent" | "assets" | "tools" | "tokens";
|
|
47
51
|
fileId?: string;
|
|
48
52
|
screenId?: string;
|
|
49
53
|
filename?: string;
|
|
@@ -80,6 +84,19 @@ function normalizeInspectorTab(
|
|
|
80
84
|
: undefined;
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
function normalizeLeftPanel(
|
|
88
|
+
value: unknown,
|
|
89
|
+
): "file" | "agent" | "assets" | "tools" | "tokens" | undefined {
|
|
90
|
+
if (value === "extensions") return "tools";
|
|
91
|
+
return value === "file" ||
|
|
92
|
+
value === "agent" ||
|
|
93
|
+
value === "assets" ||
|
|
94
|
+
value === "tools" ||
|
|
95
|
+
value === "tokens"
|
|
96
|
+
? value
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
function normalizeDesignTool(value: unknown): string | undefined {
|
|
84
101
|
return typeof value === "string" &&
|
|
85
102
|
DESIGN_EDITOR_TOOLS.includes(value as (typeof DESIGN_EDITOR_TOOLS)[number])
|
|
@@ -96,6 +113,8 @@ export function editorPathFromCommand(cmd: NavigationState): string | null {
|
|
|
96
113
|
if (editorView) params.set("view", editorView);
|
|
97
114
|
const inspectorTab = normalizeInspectorTab(cmd.inspectorTab ?? cmd.inspector);
|
|
98
115
|
if (inspectorTab) params.set("inspector", inspectorTab);
|
|
116
|
+
const leftPanel = normalizeLeftPanel(cmd.leftPanel ?? cmd.panel);
|
|
117
|
+
if (leftPanel) params.set("panel", leftPanel);
|
|
99
118
|
const screen = cmd.fileId ?? cmd.screenId ?? cmd.filename ?? cmd.screen;
|
|
100
119
|
if (screen) params.set("screen", screen);
|
|
101
120
|
if (typeof cmd.zoom === "number" && Number.isFinite(cmd.zoom)) {
|
|
@@ -117,6 +136,9 @@ export function editorCommandFromNavigate(
|
|
|
117
136
|
if (cmd.view !== "editor" || !cmd.designId) return null;
|
|
118
137
|
const editorView = normalizeEditorView(cmd.editorView);
|
|
119
138
|
const inspectorTab = normalizeInspectorTab(cmd.inspectorTab ?? cmd.inspector);
|
|
139
|
+
const leftPanel =
|
|
140
|
+
normalizeLeftPanel(cmd.leftPanel ?? cmd.panel) ??
|
|
141
|
+
normalizeLeftPanel(cmd.inspectorTab ?? cmd.inspector);
|
|
120
142
|
const command: DesignEditorCommand = {
|
|
121
143
|
designId: cmd.designId,
|
|
122
144
|
issuedAt: Date.now(),
|
|
@@ -124,6 +146,7 @@ export function editorCommandFromNavigate(
|
|
|
124
146
|
};
|
|
125
147
|
if (editorView) command.editorView = editorView;
|
|
126
148
|
if (inspectorTab) command.inspectorTab = inspectorTab;
|
|
149
|
+
if (leftPanel) command.leftPanel = leftPanel;
|
|
127
150
|
if (cmd.fileId) command.fileId = cmd.fileId;
|
|
128
151
|
if (cmd.screenId) command.screenId = cmd.screenId;
|
|
129
152
|
if (cmd.filename) command.filename = cmd.filename;
|
|
@@ -240,6 +240,17 @@ const messages = {
|
|
|
240
240
|
},
|
|
241
241
|
designEditor: {
|
|
242
242
|
askAgent: "詢問代理",
|
|
243
|
+
applyToSource: "套用至來源",
|
|
244
|
+
applyToSourcePath: "套用至來源({{path}})",
|
|
245
|
+
writingToSource: "寫入中…",
|
|
246
|
+
leftRail: {
|
|
247
|
+
file: "檔案",
|
|
248
|
+
agent: "代理",
|
|
249
|
+
assets: "資源",
|
|
250
|
+
tools: "工具",
|
|
251
|
+
tokens: "權杖",
|
|
252
|
+
label: "設計工作區",
|
|
253
|
+
},
|
|
243
254
|
tokens: {
|
|
244
255
|
title: "權杖",
|
|
245
256
|
newToken: "新增權杖",
|
|
@@ -247,6 +258,15 @@ const messages = {
|
|
|
247
258
|
value: "值",
|
|
248
259
|
add: "新增權杖",
|
|
249
260
|
refresh: "重新整理權杖",
|
|
261
|
+
import: "匯入權杖",
|
|
262
|
+
importTitle: "匯入權杖",
|
|
263
|
+
importHint: "先使用 design.md、CSS、主題檔案或目前設計,再手動新增權杖。",
|
|
264
|
+
files: "檔案",
|
|
265
|
+
folder: "資料夾",
|
|
266
|
+
current: "目前",
|
|
267
|
+
pastePlaceholder: "--color-accent: #2563eb;\n主要顏色: #2563eb",
|
|
268
|
+
importPasted: "匯入貼上的權杖",
|
|
269
|
+
importedCount: "已匯入 {{count}} 個權杖",
|
|
250
270
|
empty: "尚無權杖",
|
|
251
271
|
emptyHint: "新增設計權杖以重複使用顏色、間距等。",
|
|
252
272
|
applying: "套用中…",
|
|
@@ -466,6 +486,7 @@ const messages = {
|
|
|
466
486
|
hide: "隱藏圖層",
|
|
467
487
|
show: "顯示圖層",
|
|
468
488
|
rename: "重新命名圖層",
|
|
489
|
+
deleteBoardObject: "刪除畫布物件",
|
|
469
490
|
selected: "已選取 {{count}} 個",
|
|
470
491
|
},
|
|
471
492
|
multiScreenCanvas: {
|