@agent-native/core 0.80.10 → 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 +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- 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/sse-event-processor.ts +9 -0
- 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/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -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/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +233 -52
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- 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 +76 -25
- package/corpus/templates/design/actions/apply-design-state.ts +17 -4
- package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
- 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/delete-design.ts +20 -0
- 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 +21 -26
- 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 +18 -5
- 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-design-states.ts +19 -1
- 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/open-component-source.ts +7 -2
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
- package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
- 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 +74 -10
- 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 +244 -3197
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
- package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
- 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 +22 -0
- package/corpus/templates/design/app/i18n-data.ts +369 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
- 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-token-edits-now-stay-visible-in-previews-and-token-li.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/global-setup.ts +94 -2
- 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/component-model.ts +35 -0
- package/corpus/templates/design/shared/motion-compiler.ts +137 -15
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- 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/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- 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/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.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/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.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 +3 -3
- 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/agent-engine-api-key-route.d.ts +1 -1
- 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
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
// i18n-raw-literal-disable-file — new Design Studio panel; UI strings are localized when this feature is finalized in the follow-up PR.
|
|
2
2
|
import {
|
|
3
|
-
PromptComposer,
|
|
4
3
|
agentNativePath,
|
|
5
4
|
sendToAgentChat,
|
|
5
|
+
useActionQuery,
|
|
6
6
|
useActionMutation,
|
|
7
7
|
useChangeVersions,
|
|
8
8
|
useT,
|
|
9
|
-
type PromptComposerProps,
|
|
10
|
-
type PromptComposerSubmitOptions,
|
|
11
9
|
} from "@agent-native/core/client";
|
|
12
10
|
import { EmbeddedExtension } from "@agent-native/core/client/extensions";
|
|
13
11
|
import {
|
|
@@ -16,31 +14,45 @@ import {
|
|
|
16
14
|
} from "@agent-native/core/embedding/react";
|
|
17
15
|
import type { ShaderDescriptor } from "@shared/shader-presets";
|
|
18
16
|
import {
|
|
17
|
+
IconAdjustmentsHorizontal,
|
|
18
|
+
IconAssembly,
|
|
19
|
+
IconBrandFigma,
|
|
20
|
+
IconChevronDown,
|
|
19
21
|
IconExternalLink,
|
|
20
22
|
IconLock,
|
|
21
|
-
IconPalette,
|
|
22
23
|
IconPhoto,
|
|
23
24
|
IconPlayerPlay,
|
|
24
|
-
IconPlus,
|
|
25
25
|
IconPuzzle,
|
|
26
|
+
IconSearch,
|
|
26
27
|
IconSparkles,
|
|
27
28
|
} from "@tabler/icons-react";
|
|
28
29
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
useCallback,
|
|
32
|
+
useEffect,
|
|
33
|
+
useMemo,
|
|
34
|
+
useRef,
|
|
35
|
+
useState,
|
|
36
|
+
type FormEvent,
|
|
37
|
+
} from "react";
|
|
30
38
|
import { toast } from "sonner";
|
|
31
39
|
|
|
32
40
|
import { Button } from "@/components/ui/button";
|
|
41
|
+
import {
|
|
42
|
+
DropdownMenu,
|
|
43
|
+
DropdownMenuContent,
|
|
44
|
+
DropdownMenuItem,
|
|
45
|
+
DropdownMenuTrigger,
|
|
46
|
+
} from "@/components/ui/dropdown-menu";
|
|
47
|
+
import { Input } from "@/components/ui/input";
|
|
33
48
|
import {
|
|
34
49
|
Popover,
|
|
35
50
|
PopoverContent,
|
|
36
51
|
PopoverTrigger,
|
|
37
52
|
} from "@/components/ui/popover";
|
|
38
53
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
TooltipContent,
|
|
42
|
-
TooltipTrigger,
|
|
43
|
-
} from "@/components/ui/tooltip";
|
|
54
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
55
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
44
56
|
import { cn } from "@/lib/utils";
|
|
45
57
|
|
|
46
58
|
import { ShaderFillsPanel } from "./inspector/ShaderFillsPanel";
|
|
@@ -74,6 +86,8 @@ export interface DesignExtensionSlotContext extends Record<string, unknown> {
|
|
|
74
86
|
designTitle: string | null;
|
|
75
87
|
activeFileId: string | null;
|
|
76
88
|
activeFilename: string | null;
|
|
89
|
+
activeFileUpdatedAt: string | null;
|
|
90
|
+
activeContent: string;
|
|
77
91
|
viewMode: "single" | "overview";
|
|
78
92
|
zoom: number;
|
|
79
93
|
screens: Array<{
|
|
@@ -86,14 +100,23 @@ export interface DesignExtensionSlotContext extends Record<string, unknown> {
|
|
|
86
100
|
mode: string;
|
|
87
101
|
activeTool: string;
|
|
88
102
|
tweakValues: Record<string, string | number | boolean>;
|
|
103
|
+
onShaderFillPreview?: (descriptor: ShaderDescriptor, css: string) => void;
|
|
104
|
+
onShaderFillPreviewClear?: () => void;
|
|
105
|
+
onShaderFillApplied?: (
|
|
106
|
+
fileId: string,
|
|
107
|
+
content: string,
|
|
108
|
+
updatedAt?: string,
|
|
109
|
+
) => void;
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
interface DesignExtensionsPanelProps {
|
|
92
113
|
context: DesignExtensionSlotContext;
|
|
93
114
|
className?: string;
|
|
115
|
+
hideAssetLibrary?: boolean;
|
|
116
|
+
title?: string;
|
|
94
117
|
}
|
|
95
118
|
|
|
96
|
-
type
|
|
119
|
+
type CreateExtensionSubmitHandler = (text: string) => void;
|
|
97
120
|
|
|
98
121
|
// ─── First-party extension ids ───────────────────────────────────────────────
|
|
99
122
|
|
|
@@ -103,9 +126,23 @@ type FirstPartyExtId =
|
|
|
103
126
|
| "design.token-auditor"
|
|
104
127
|
| "design.motion-presets";
|
|
105
128
|
|
|
129
|
+
type ToolSourceFilter = "all" | "built-in" | "extensions";
|
|
130
|
+
type ToolCategoryFilter = "all" | "shader" | "tokens" | "motion" | "plugins";
|
|
131
|
+
|
|
132
|
+
type FirstPartyRow = {
|
|
133
|
+
id: FirstPartyExtId;
|
|
134
|
+
label: string;
|
|
135
|
+
description: string;
|
|
136
|
+
category: ToolCategoryFilter;
|
|
137
|
+
icon: React.ReactNode;
|
|
138
|
+
badge?: React.ReactNode;
|
|
139
|
+
panel: React.ReactNode;
|
|
140
|
+
};
|
|
141
|
+
|
|
106
142
|
// ─── Assets picker types (mirrors PromptDialog) ───────────────────────────
|
|
107
143
|
|
|
108
|
-
const DEFAULT_ASSETS_PICKER_URL =
|
|
144
|
+
const DEFAULT_ASSETS_PICKER_URL =
|
|
145
|
+
"https://assets.agent-native.com/library?__an_picker=1&mediaType=image&layout=vertical";
|
|
109
146
|
|
|
110
147
|
interface PickedAssetPayload {
|
|
111
148
|
url?: unknown;
|
|
@@ -119,12 +156,30 @@ interface PickedAssetPayload {
|
|
|
119
156
|
}
|
|
120
157
|
|
|
121
158
|
function assetsPickerUrl(): string {
|
|
122
|
-
|
|
159
|
+
const configured =
|
|
123
160
|
(typeof import.meta !== "undefined" &&
|
|
124
161
|
(import.meta as { env?: Record<string, string> }).env
|
|
125
162
|
?.VITE_AGENT_NATIVE_ASSETS_PICKER_URL) ||
|
|
126
|
-
DEFAULT_ASSETS_PICKER_URL
|
|
127
|
-
|
|
163
|
+
DEFAULT_ASSETS_PICKER_URL;
|
|
164
|
+
try {
|
|
165
|
+
const base =
|
|
166
|
+
typeof window !== "undefined"
|
|
167
|
+
? window.location.origin
|
|
168
|
+
: "https://assets.agent-native.com";
|
|
169
|
+
const url = new URL(configured, base);
|
|
170
|
+
if (url.pathname === "/picker") url.pathname = "/library";
|
|
171
|
+
url.searchParams.set("__an_picker", "1");
|
|
172
|
+
url.searchParams.set(
|
|
173
|
+
"mediaType",
|
|
174
|
+
url.searchParams.get("mediaType") || "image",
|
|
175
|
+
);
|
|
176
|
+
url.searchParams.set("layout", "vertical");
|
|
177
|
+
url.searchParams.set("embedded", "1");
|
|
178
|
+
url.searchParams.set("callerAppId", "design");
|
|
179
|
+
return url.toString();
|
|
180
|
+
} catch {
|
|
181
|
+
return configured;
|
|
182
|
+
}
|
|
128
183
|
}
|
|
129
184
|
|
|
130
185
|
function pickedAssetString(value: unknown): string | null {
|
|
@@ -152,7 +207,7 @@ function buildExtensionCreateContext(
|
|
|
152
207
|
? JSON.stringify(context.selectedElement, null, 2)
|
|
153
208
|
: "No element is currently selected.";
|
|
154
209
|
return [
|
|
155
|
-
`The user is in the Design editor
|
|
210
|
+
`The user is in the Design editor Tools panel for design id "${context.designId}"${context.designTitle ? ` (title: "${context.designTitle}")` : ""}.`,
|
|
156
211
|
context.activeFileId
|
|
157
212
|
? `Active screen: "${context.activeFilename ?? context.activeFileId}" (file id: "${context.activeFileId}").`
|
|
158
213
|
: "There is no active screen yet.",
|
|
@@ -160,7 +215,7 @@ function buildExtensionCreateContext(
|
|
|
160
215
|
`User request: "${prompt}"`,
|
|
161
216
|
"",
|
|
162
217
|
"After create-extension succeeds, call add-extension-slot-target with this slot id, then install-extension with this slot id so the extension appears in the editor panel immediately.",
|
|
163
|
-
'If create-extension opens the standalone extension editor, call navigate with view "editor", the current design id, and
|
|
218
|
+
'If create-extension opens the standalone extension editor, call navigate with view "editor", the current design id, and leftPanel "tools" after install so the user returns to this inline panel.',
|
|
164
219
|
"",
|
|
165
220
|
"The extension will receive window.slotContext and onSlotContext updates with the current design selection:",
|
|
166
221
|
JSON.stringify(
|
|
@@ -245,7 +300,6 @@ interface FirstPartyRowProps {
|
|
|
245
300
|
|
|
246
301
|
function FirstPartyExtRow({
|
|
247
302
|
label,
|
|
248
|
-
description,
|
|
249
303
|
icon,
|
|
250
304
|
badge,
|
|
251
305
|
isOpen,
|
|
@@ -253,50 +307,171 @@ function FirstPartyExtRow({
|
|
|
253
307
|
children,
|
|
254
308
|
}: FirstPartyRowProps) {
|
|
255
309
|
return (
|
|
256
|
-
<div className="overflow-hidden rounded
|
|
310
|
+
<div className="overflow-hidden rounded-md">
|
|
257
311
|
<button
|
|
258
312
|
type="button"
|
|
259
313
|
onClick={onToggle}
|
|
260
|
-
className="flex w-full cursor-pointer items-center gap-
|
|
314
|
+
className="group flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-2.5 text-left transition-colors hover:bg-accent/60 active:bg-accent"
|
|
261
315
|
>
|
|
262
|
-
<span className="flex size-
|
|
316
|
+
<span className="flex size-10 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/70 text-muted-foreground transition-colors group-hover:border-border group-hover:bg-muted">
|
|
263
317
|
{icon}
|
|
264
318
|
</span>
|
|
265
319
|
<span className="min-w-0 flex-1">
|
|
266
|
-
<span className="block truncate text-
|
|
320
|
+
<span className="block truncate text-sm font-medium leading-tight text-foreground">
|
|
267
321
|
{label}
|
|
268
322
|
</span>
|
|
269
|
-
<span className="
|
|
270
|
-
|
|
323
|
+
<span className="mt-0.5 flex items-center gap-1.5 truncate text-xs leading-none text-muted-foreground">
|
|
324
|
+
<IconPuzzle className="size-3 shrink-0" />
|
|
325
|
+
<span className="truncate">Built-in</span>
|
|
271
326
|
</span>
|
|
272
327
|
</span>
|
|
273
328
|
{badge}
|
|
274
329
|
</button>
|
|
275
|
-
{isOpen &&
|
|
330
|
+
{isOpen && (
|
|
331
|
+
<div className="mb-2 ml-[3.75rem] overflow-hidden rounded-md border border-border/70 bg-background/70">
|
|
332
|
+
{children}
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
276
335
|
</div>
|
|
277
336
|
);
|
|
278
337
|
}
|
|
279
338
|
|
|
339
|
+
function ToolFilterMenu<T extends string>({
|
|
340
|
+
label,
|
|
341
|
+
value,
|
|
342
|
+
options,
|
|
343
|
+
onChange,
|
|
344
|
+
}: {
|
|
345
|
+
label: string;
|
|
346
|
+
value: T;
|
|
347
|
+
options: Array<{ value: T; label: string }>;
|
|
348
|
+
onChange: (value: T) => void;
|
|
349
|
+
}) {
|
|
350
|
+
const selected = options.find((option) => option.value === value);
|
|
351
|
+
return (
|
|
352
|
+
<DropdownMenu>
|
|
353
|
+
<DropdownMenuTrigger asChild>
|
|
354
|
+
<Button
|
|
355
|
+
type="button"
|
|
356
|
+
variant="outline"
|
|
357
|
+
size="sm"
|
|
358
|
+
className="h-8 cursor-pointer gap-1.5 rounded-md bg-transparent px-2.5 text-sm font-medium"
|
|
359
|
+
>
|
|
360
|
+
<IconAdjustmentsHorizontal className="size-4 text-muted-foreground" />
|
|
361
|
+
<span>{selected?.label ?? label}</span>
|
|
362
|
+
<IconChevronDown className="size-3.5 text-muted-foreground" />
|
|
363
|
+
</Button>
|
|
364
|
+
</DropdownMenuTrigger>
|
|
365
|
+
<DropdownMenuContent align="start" className="min-w-36">
|
|
366
|
+
{options.map((option) => (
|
|
367
|
+
<DropdownMenuItem
|
|
368
|
+
key={option.value}
|
|
369
|
+
onClick={() => onChange(option.value)}
|
|
370
|
+
>
|
|
371
|
+
{option.label}
|
|
372
|
+
</DropdownMenuItem>
|
|
373
|
+
))}
|
|
374
|
+
</DropdownMenuContent>
|
|
375
|
+
</DropdownMenu>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
280
379
|
// ─── Asset Library panel ──────────────────────────────────────────────────────
|
|
281
380
|
|
|
282
381
|
interface AssetLibraryPanelProps {
|
|
283
382
|
context: DesignExtensionSlotContext;
|
|
284
383
|
}
|
|
285
384
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
385
|
+
type FigmaLibraryAsset = {
|
|
386
|
+
id: string;
|
|
387
|
+
kind: "component" | "component_set";
|
|
388
|
+
fileKey: string;
|
|
389
|
+
nodeId: string | null;
|
|
390
|
+
componentKey: string | null;
|
|
391
|
+
name: string;
|
|
392
|
+
description: string | null;
|
|
393
|
+
thumbnailUrl: string | null;
|
|
394
|
+
renderUrl: string | null;
|
|
395
|
+
insertUrl: string | null;
|
|
396
|
+
sourceUrl: string | null;
|
|
397
|
+
containingFrame: { name: string | null; nodeId: string | null } | null;
|
|
398
|
+
updatedAt: string | null;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
type FigmaLibraryResponse = {
|
|
402
|
+
fileKey: string;
|
|
403
|
+
total: number;
|
|
404
|
+
returned: number;
|
|
405
|
+
assets: FigmaLibraryAsset[];
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
type DesignNativeAssetKind =
|
|
409
|
+
| "section-frame"
|
|
410
|
+
| "text-block"
|
|
411
|
+
| "button"
|
|
412
|
+
| "card"
|
|
413
|
+
| "input"
|
|
414
|
+
| "nav-bar"
|
|
415
|
+
| "hero"
|
|
416
|
+
| "feature-grid";
|
|
417
|
+
|
|
418
|
+
type DesignNativeAsset = {
|
|
419
|
+
kind: DesignNativeAssetKind;
|
|
420
|
+
title: string;
|
|
421
|
+
description: string;
|
|
422
|
+
category: "primitive" | "component" | "layout";
|
|
423
|
+
componentName: string;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
type DesignNativeAssetsResponse = {
|
|
427
|
+
source: "design-native";
|
|
428
|
+
assets: DesignNativeAsset[];
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const NATIVE_ASSET_CATEGORY_LABELS: Record<
|
|
432
|
+
DesignNativeAsset["category"],
|
|
433
|
+
string
|
|
434
|
+
> = {
|
|
435
|
+
primitive: "Primitive",
|
|
436
|
+
component: "Component",
|
|
437
|
+
layout: "Layout",
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
441
|
+
const [assetPickerReady, setAssetPickerReady] = useState(false);
|
|
442
|
+
const [figmaFileInput, setFigmaFileInput] = useState("");
|
|
443
|
+
const [figmaQueryInput, setFigmaQueryInput] = useState("");
|
|
444
|
+
const [figmaRequest, setFigmaRequest] = useState<{
|
|
445
|
+
fileUrl: string;
|
|
446
|
+
query?: string;
|
|
447
|
+
} | null>(null);
|
|
448
|
+
const nativeAssets = useActionQuery<DesignNativeAssetsResponse>(
|
|
449
|
+
"list-design-native-assets",
|
|
450
|
+
);
|
|
451
|
+
const insertNativeAsset = useActionMutation("insert-design-native-asset");
|
|
290
452
|
const insertAsset = useActionMutation("insert-asset");
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
453
|
+
const insertFigmaAsset = useActionMutation("insert-figma-library-asset");
|
|
454
|
+
const figmaAssets = useActionQuery<FigmaLibraryResponse>(
|
|
455
|
+
"list-figma-library-assets",
|
|
456
|
+
figmaRequest
|
|
457
|
+
? {
|
|
458
|
+
fileUrl: figmaRequest.fileUrl,
|
|
459
|
+
query: figmaRequest.query,
|
|
460
|
+
limit: 48,
|
|
461
|
+
renderFormat: "svg",
|
|
462
|
+
}
|
|
463
|
+
: undefined,
|
|
464
|
+
{ enabled: Boolean(figmaRequest) },
|
|
465
|
+
);
|
|
295
466
|
|
|
296
467
|
const handleReady = useCallback(
|
|
297
468
|
(_payload: unknown, _event: MessageEvent, ref: EmbeddedAppRef) => {
|
|
298
|
-
|
|
299
|
-
ref.postMessage("configure", {
|
|
469
|
+
setAssetPickerReady(true);
|
|
470
|
+
ref.postMessage("configure", {
|
|
471
|
+
mediaType: "image",
|
|
472
|
+
layout: "vertical",
|
|
473
|
+
callerAppId: "design",
|
|
474
|
+
});
|
|
300
475
|
},
|
|
301
476
|
[],
|
|
302
477
|
);
|
|
@@ -304,7 +479,7 @@ function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
304
479
|
const handleMessage = useCallback(
|
|
305
480
|
(name: string, payload: unknown) => {
|
|
306
481
|
if (name === "close") {
|
|
307
|
-
|
|
482
|
+
setAssetPickerReady(false);
|
|
308
483
|
return;
|
|
309
484
|
}
|
|
310
485
|
if (name !== "chooseImage" && name !== "chooseAsset") return;
|
|
@@ -341,99 +516,293 @@ function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
341
516
|
},
|
|
342
517
|
},
|
|
343
518
|
);
|
|
344
|
-
|
|
345
|
-
setPickerOpen(false);
|
|
346
519
|
},
|
|
347
520
|
[context.designId, context.activeFileId, insertAsset],
|
|
348
521
|
);
|
|
349
522
|
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
523
|
+
const handleInsertNativeAsset = (asset: DesignNativeAsset) => {
|
|
524
|
+
insertNativeAsset.mutate(
|
|
525
|
+
{
|
|
526
|
+
kind: asset.kind,
|
|
527
|
+
designId: context.designId || undefined,
|
|
528
|
+
fileId: context.activeFileId || undefined,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
onSuccess: () => {
|
|
532
|
+
toast.success(`${asset.title} inserted into design.`);
|
|
533
|
+
},
|
|
534
|
+
onError: (error) => {
|
|
535
|
+
toast.error(error.message || "Failed to insert native asset.");
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const handleBrowseFigma = (event: FormEvent<HTMLFormElement>) => {
|
|
542
|
+
event.preventDefault();
|
|
543
|
+
const fileUrl = figmaFileInput.trim();
|
|
544
|
+
if (!fileUrl) return;
|
|
545
|
+
const query = figmaQueryInput.trim();
|
|
546
|
+
setFigmaRequest({ fileUrl, query: query || undefined });
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const handleInsertFigmaAsset = (asset: FigmaLibraryAsset) => {
|
|
550
|
+
const renderUrl = asset.insertUrl ?? asset.renderUrl ?? asset.thumbnailUrl;
|
|
551
|
+
if (!renderUrl) {
|
|
552
|
+
toast.error("Figma did not return an insertable render URL.");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
insertFigmaAsset.mutate(
|
|
556
|
+
{
|
|
557
|
+
renderUrl,
|
|
558
|
+
fileKey: asset.fileKey,
|
|
559
|
+
nodeId: asset.nodeId ?? undefined,
|
|
560
|
+
componentKey: asset.componentKey ?? undefined,
|
|
561
|
+
kind: asset.kind,
|
|
562
|
+
name: asset.name,
|
|
563
|
+
description: asset.description ?? undefined,
|
|
564
|
+
sourceUrl: asset.sourceUrl ?? undefined,
|
|
565
|
+
designId: context.designId || undefined,
|
|
566
|
+
fileId: context.activeFileId || undefined,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
onSuccess: () => {
|
|
570
|
+
toast.success("Figma asset inserted into design.");
|
|
571
|
+
},
|
|
572
|
+
onError: (error) => {
|
|
573
|
+
toast.error(error.message || "Failed to insert Figma asset.");
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
);
|
|
367
577
|
};
|
|
368
578
|
|
|
369
579
|
return (
|
|
370
|
-
<div className="
|
|
371
|
-
<
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
580
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
581
|
+
<Tabs defaultValue="native" className="flex min-h-0 flex-1 flex-col">
|
|
582
|
+
<div className="border-b border-border/60 px-2 py-1.5">
|
|
583
|
+
<TabsList className="grid h-7 w-full grid-cols-3 rounded-md p-0.5">
|
|
584
|
+
<TabsTrigger value="native" className="h-6 gap-1 px-2 text-[11px]">
|
|
585
|
+
<IconAssembly className="size-3" />
|
|
586
|
+
Native
|
|
587
|
+
</TabsTrigger>
|
|
588
|
+
<TabsTrigger value="media" className="h-6 gap-1 px-2 text-[11px]">
|
|
589
|
+
<IconPhoto className="size-3" />
|
|
590
|
+
Media
|
|
591
|
+
</TabsTrigger>
|
|
592
|
+
<TabsTrigger value="figma" className="h-6 gap-1 px-2 text-[11px]">
|
|
593
|
+
<IconBrandFigma className="size-3" />
|
|
594
|
+
Figma
|
|
595
|
+
</TabsTrigger>
|
|
596
|
+
</TabsList>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<TabsContent
|
|
600
|
+
value="native"
|
|
601
|
+
className="design-inspector-scroll m-0 min-h-0 flex-1 overflow-y-auto p-2.5"
|
|
382
602
|
>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
603
|
+
{nativeAssets.isLoading && (
|
|
604
|
+
<div className="space-y-1.5">
|
|
605
|
+
{Array.from({ length: 5 }).map((_, index) => (
|
|
606
|
+
<Skeleton key={index} className="h-16 rounded-md" />
|
|
607
|
+
))}
|
|
608
|
+
</div>
|
|
609
|
+
)}
|
|
610
|
+
|
|
611
|
+
{nativeAssets.isError && (
|
|
612
|
+
<p className="rounded border border-destructive/30 bg-destructive/5 px-2 py-1.5 text-[10px] leading-snug text-destructive">
|
|
613
|
+
{nativeAssets.error.message}
|
|
614
|
+
</p>
|
|
615
|
+
)}
|
|
616
|
+
|
|
617
|
+
{!nativeAssets.isLoading && nativeAssets.data && (
|
|
618
|
+
<div className="space-y-1.5">
|
|
619
|
+
{nativeAssets.data.assets.map((asset) => (
|
|
620
|
+
<button
|
|
621
|
+
key={asset.kind}
|
|
622
|
+
type="button"
|
|
623
|
+
disabled={
|
|
624
|
+
!context.activeFileId || insertNativeAsset.isPending
|
|
625
|
+
}
|
|
626
|
+
onClick={() => handleInsertNativeAsset(asset)}
|
|
627
|
+
className="group flex w-full cursor-pointer items-start gap-2 rounded-md border border-border bg-background px-2 py-2 text-left transition hover:border-primary/50 hover:bg-accent/30 disabled:cursor-not-allowed disabled:opacity-55"
|
|
628
|
+
>
|
|
629
|
+
<span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/50 text-muted-foreground group-hover:text-foreground">
|
|
630
|
+
<IconAssembly className="size-4" />
|
|
631
|
+
</span>
|
|
632
|
+
<span className="min-w-0 flex-1">
|
|
633
|
+
<span className="flex items-center gap-1.5">
|
|
634
|
+
<span className="truncate text-[11px] font-medium leading-tight text-foreground">
|
|
635
|
+
{asset.title}
|
|
636
|
+
</span>
|
|
637
|
+
<span className="shrink-0 rounded border border-border/70 px-1 py-0.5 text-[9px] leading-none text-muted-foreground">
|
|
638
|
+
{NATIVE_ASSET_CATEGORY_LABELS[asset.category]}
|
|
639
|
+
</span>
|
|
640
|
+
</span>
|
|
641
|
+
<span className="mt-1 line-clamp-2 text-[10px] leading-snug text-muted-foreground">
|
|
642
|
+
{asset.description}
|
|
643
|
+
</span>
|
|
644
|
+
</span>
|
|
645
|
+
</button>
|
|
646
|
+
))}
|
|
647
|
+
</div>
|
|
648
|
+
)}
|
|
649
|
+
</TabsContent>
|
|
650
|
+
|
|
651
|
+
<TabsContent
|
|
652
|
+
value="media"
|
|
653
|
+
className="m-0 flex min-h-0 flex-1 flex-col overflow-hidden"
|
|
392
654
|
>
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
655
|
+
<div className="relative min-h-[540px] flex-1 overflow-hidden bg-background">
|
|
656
|
+
{!assetPickerReady && (
|
|
657
|
+
<div className="absolute inset-0 z-10 p-2.5">
|
|
658
|
+
<Skeleton className="h-full w-full rounded-none" />
|
|
659
|
+
</div>
|
|
660
|
+
)}
|
|
661
|
+
<EmbeddedApp
|
|
662
|
+
url={assetsPickerUrl()}
|
|
663
|
+
title="Assets picker"
|
|
664
|
+
onLoad={() => setAssetPickerReady(true)}
|
|
665
|
+
onReady={handleReady}
|
|
666
|
+
onMessage={handleMessage}
|
|
667
|
+
className="h-full w-full"
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
</TabsContent>
|
|
402
671
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
672
|
+
<TabsContent
|
|
673
|
+
value="figma"
|
|
674
|
+
className="design-inspector-scroll m-0 min-h-0 flex-1 overflow-y-auto p-2.5"
|
|
675
|
+
>
|
|
676
|
+
<form className="space-y-1.5" onSubmit={handleBrowseFigma}>
|
|
677
|
+
<Input
|
|
678
|
+
value={figmaFileInput}
|
|
679
|
+
onChange={(event) => setFigmaFileInput(event.target.value)}
|
|
680
|
+
placeholder="Figma file URL or key"
|
|
681
|
+
className="h-7 text-[11px]"
|
|
682
|
+
/>
|
|
683
|
+
<div className="flex gap-1.5">
|
|
684
|
+
<Input
|
|
685
|
+
value={figmaQueryInput}
|
|
686
|
+
onChange={(event) => setFigmaQueryInput(event.target.value)}
|
|
687
|
+
placeholder="Search components"
|
|
688
|
+
className="h-7 min-w-0 flex-1 text-[11px]"
|
|
689
|
+
/>
|
|
690
|
+
<Button
|
|
691
|
+
type="submit"
|
|
692
|
+
size="sm"
|
|
693
|
+
className="h-7 cursor-pointer gap-1 px-2 text-[11px]"
|
|
694
|
+
disabled={!figmaFileInput.trim() || figmaAssets.isFetching}
|
|
417
695
|
>
|
|
418
|
-
|
|
419
|
-
|
|
696
|
+
<IconSearch className="size-3" />
|
|
697
|
+
{figmaAssets.isFetching ? "Loading" : "Browse"}
|
|
698
|
+
</Button>
|
|
420
699
|
</div>
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
className="
|
|
433
|
-
|
|
700
|
+
</form>
|
|
701
|
+
|
|
702
|
+
{figmaAssets.isError && (
|
|
703
|
+
<p className="mt-2 rounded border border-destructive/30 bg-destructive/5 px-2 py-1.5 text-[10px] leading-snug text-destructive">
|
|
704
|
+
{figmaAssets.error.message}
|
|
705
|
+
</p>
|
|
706
|
+
)}
|
|
707
|
+
|
|
708
|
+
{figmaAssets.isFetching && (
|
|
709
|
+
<div className="mt-2 grid grid-cols-2 gap-1.5">
|
|
710
|
+
{Array.from({ length: 4 }).map((_, index) => (
|
|
711
|
+
<Skeleton key={index} className="aspect-square rounded-md" />
|
|
712
|
+
))}
|
|
434
713
|
</div>
|
|
435
|
-
|
|
436
|
-
|
|
714
|
+
)}
|
|
715
|
+
|
|
716
|
+
{!figmaAssets.isFetching &&
|
|
717
|
+
figmaAssets.data &&
|
|
718
|
+
figmaAssets.data.assets.length === 0 && (
|
|
719
|
+
<p className="mt-2 rounded border border-border bg-muted/30 px-2 py-1.5 text-[10px] leading-snug text-muted-foreground">
|
|
720
|
+
No components matched this Figma file and search.
|
|
721
|
+
</p>
|
|
722
|
+
)}
|
|
723
|
+
|
|
724
|
+
{!figmaAssets.isFetching &&
|
|
725
|
+
figmaAssets.data &&
|
|
726
|
+
figmaAssets.data.assets.length > 0 && (
|
|
727
|
+
<div className="mt-2 space-y-1.5">
|
|
728
|
+
<div className="flex items-center justify-between gap-2 text-[10px] text-muted-foreground">
|
|
729
|
+
<span>
|
|
730
|
+
{figmaAssets.data.returned} of {figmaAssets.data.total}
|
|
731
|
+
</span>
|
|
732
|
+
<span className="truncate">{figmaAssets.data.fileKey}</span>
|
|
733
|
+
</div>
|
|
734
|
+
<div className="grid grid-cols-2 gap-1.5">
|
|
735
|
+
{figmaAssets.data.assets.map((asset) => {
|
|
736
|
+
const preview = asset.thumbnailUrl ?? asset.renderUrl;
|
|
737
|
+
const canInsert = Boolean(
|
|
738
|
+
context.activeFileId &&
|
|
739
|
+
(asset.insertUrl ??
|
|
740
|
+
asset.renderUrl ??
|
|
741
|
+
asset.thumbnailUrl),
|
|
742
|
+
);
|
|
743
|
+
return (
|
|
744
|
+
<div
|
|
745
|
+
key={asset.id}
|
|
746
|
+
className="overflow-hidden rounded-md border border-border bg-background"
|
|
747
|
+
>
|
|
748
|
+
<button
|
|
749
|
+
type="button"
|
|
750
|
+
className="block aspect-square w-full cursor-pointer bg-muted/30"
|
|
751
|
+
disabled={!canInsert || insertFigmaAsset.isPending}
|
|
752
|
+
onClick={() => handleInsertFigmaAsset(asset)}
|
|
753
|
+
>
|
|
754
|
+
{preview ? (
|
|
755
|
+
<img
|
|
756
|
+
src={preview}
|
|
757
|
+
alt={asset.name}
|
|
758
|
+
className="size-full object-contain p-2"
|
|
759
|
+
loading="lazy"
|
|
760
|
+
/>
|
|
761
|
+
) : (
|
|
762
|
+
<span className="flex size-full items-center justify-center text-muted-foreground">
|
|
763
|
+
<IconAssembly className="size-5" />
|
|
764
|
+
</span>
|
|
765
|
+
)}
|
|
766
|
+
</button>
|
|
767
|
+
<div className="border-t border-border/70 px-1.5 py-1">
|
|
768
|
+
<div className="flex items-center gap-1">
|
|
769
|
+
<span className="min-w-0 flex-1 truncate text-[10px] font-medium leading-tight">
|
|
770
|
+
{asset.name}
|
|
771
|
+
</span>
|
|
772
|
+
{asset.sourceUrl && (
|
|
773
|
+
<a
|
|
774
|
+
href={asset.sourceUrl}
|
|
775
|
+
target="_blank"
|
|
776
|
+
rel="noreferrer"
|
|
777
|
+
className="shrink-0 text-muted-foreground hover:text-foreground"
|
|
778
|
+
aria-label="Open in Figma"
|
|
779
|
+
>
|
|
780
|
+
<IconExternalLink className="size-3" />
|
|
781
|
+
</a>
|
|
782
|
+
)}
|
|
783
|
+
</div>
|
|
784
|
+
<p className="mt-0.5 truncate text-[9px] text-muted-foreground">
|
|
785
|
+
{asset.kind === "component_set"
|
|
786
|
+
? "Component set"
|
|
787
|
+
: "Component"}
|
|
788
|
+
{asset.containingFrame?.name
|
|
789
|
+
? ` · ${asset.containingFrame.name}`
|
|
790
|
+
: ""}
|
|
791
|
+
</p>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
);
|
|
795
|
+
})}
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
)}
|
|
799
|
+
</TabsContent>
|
|
800
|
+
</Tabs>
|
|
801
|
+
|
|
802
|
+
{!context.activeFileId && (
|
|
803
|
+
<p className="border-t border-border/60 px-2.5 py-1.5 text-[10px] text-amber-600 dark:text-amber-400">
|
|
804
|
+
Open a design screen first to insert assets.
|
|
805
|
+
</p>
|
|
437
806
|
)}
|
|
438
807
|
</div>
|
|
439
808
|
);
|
|
@@ -445,12 +814,33 @@ interface ShaderFillsExtPanelProps {
|
|
|
445
814
|
context: DesignExtensionSlotContext;
|
|
446
815
|
}
|
|
447
816
|
|
|
817
|
+
interface PreviewedShaderFill {
|
|
818
|
+
descriptor: ShaderDescriptor;
|
|
819
|
+
fileId?: string;
|
|
820
|
+
nodeId?: string;
|
|
821
|
+
selector?: string;
|
|
822
|
+
}
|
|
823
|
+
|
|
448
824
|
function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
449
825
|
const [showShaders, setShowShaders] = useState(false);
|
|
826
|
+
const clearPreviewRef = useRef(context.onShaderFillPreviewClear);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
clearPreviewRef.current = context.onShaderFillPreviewClear;
|
|
829
|
+
}, [context.onShaderFillPreviewClear]);
|
|
830
|
+
useEffect(
|
|
831
|
+
() => () => {
|
|
832
|
+
clearPreviewRef.current?.();
|
|
833
|
+
},
|
|
834
|
+
[],
|
|
835
|
+
);
|
|
836
|
+
const closeShaders = () => {
|
|
837
|
+
context.onShaderFillPreviewClear?.();
|
|
838
|
+
setShowShaders(false);
|
|
839
|
+
};
|
|
450
840
|
// The most recently previewed descriptor — Apply persists exactly this one,
|
|
451
841
|
// so the write is intentional (one atomic call) rather than firing on every
|
|
452
842
|
// slider tweak.
|
|
453
|
-
const [previewed, setPreviewed] = useState<
|
|
843
|
+
const [previewed, setPreviewed] = useState<PreviewedShaderFill | null>(null);
|
|
454
844
|
const applyShaderFill = useActionMutation("apply-shader-fill");
|
|
455
845
|
|
|
456
846
|
// The persisting apply path needs an HTML file plus a target element. Without
|
|
@@ -460,6 +850,16 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
460
850
|
const canPersist = Boolean(
|
|
461
851
|
context.activeFileId && (targetNodeId || targetSelector),
|
|
462
852
|
);
|
|
853
|
+
const previewMatchesTarget = Boolean(
|
|
854
|
+
previewed &&
|
|
855
|
+
previewed.fileId === context.activeFileId &&
|
|
856
|
+
previewed.nodeId === targetNodeId &&
|
|
857
|
+
previewed.selector === targetSelector,
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
setPreviewed(null);
|
|
862
|
+
}, [context.activeFileId, targetNodeId, targetSelector]);
|
|
463
863
|
|
|
464
864
|
const persistFill = (descriptor: ShaderDescriptor) => {
|
|
465
865
|
if (!canPersist) return;
|
|
@@ -482,13 +882,41 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
482
882
|
kind: "design-file" as const,
|
|
483
883
|
designId: context.designId || undefined,
|
|
484
884
|
fileId: context.activeFileId || undefined,
|
|
885
|
+
revision: context.activeFileUpdatedAt || undefined,
|
|
886
|
+
currentContent: context.activeContent,
|
|
485
887
|
},
|
|
486
888
|
},
|
|
487
889
|
{
|
|
488
890
|
onSuccess: (res) => {
|
|
489
|
-
const r = res as
|
|
891
|
+
const r = res as
|
|
892
|
+
| {
|
|
893
|
+
fileId?: unknown;
|
|
894
|
+
patchedContent?: unknown;
|
|
895
|
+
persisted?: boolean;
|
|
896
|
+
conflict?: boolean;
|
|
897
|
+
error?: unknown;
|
|
898
|
+
note?: unknown;
|
|
899
|
+
updatedAt?: unknown;
|
|
900
|
+
}
|
|
901
|
+
| undefined;
|
|
490
902
|
if (r?.persisted) {
|
|
903
|
+
if (
|
|
904
|
+
typeof r.fileId === "string" &&
|
|
905
|
+
typeof r.patchedContent === "string"
|
|
906
|
+
) {
|
|
907
|
+
context.onShaderFillApplied?.(
|
|
908
|
+
r.fileId,
|
|
909
|
+
r.patchedContent,
|
|
910
|
+
typeof r.updatedAt === "string" ? r.updatedAt : undefined,
|
|
911
|
+
);
|
|
912
|
+
}
|
|
491
913
|
toast.success("Shader fill applied to the selected element.");
|
|
914
|
+
} else if (r?.conflict) {
|
|
915
|
+
toast.error(
|
|
916
|
+
typeof r.error === "string" || typeof r.note === "string"
|
|
917
|
+
? String(r.error ?? r.note)
|
|
918
|
+
: "This file changed since the shader fill was previewed. Refresh and try again.",
|
|
919
|
+
);
|
|
492
920
|
} else {
|
|
493
921
|
toast.message("Shader fill previewed — nothing was written.");
|
|
494
922
|
}
|
|
@@ -535,14 +963,20 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
535
963
|
return (
|
|
536
964
|
<div className="flex flex-col">
|
|
537
965
|
<ShaderFillsPanel
|
|
538
|
-
onApply={(descriptor,
|
|
966
|
+
onApply={(descriptor, css) => {
|
|
539
967
|
// Preview only: ShaderFillsPanel fires apply-shader (planning/codegen)
|
|
540
968
|
// for agent context on every tune and the iframe shows the gradient.
|
|
541
969
|
// We just record the latest descriptor here; the explicit Apply
|
|
542
970
|
// button below performs the single intentional persist write.
|
|
543
|
-
setPreviewed(
|
|
971
|
+
setPreviewed({
|
|
972
|
+
descriptor,
|
|
973
|
+
fileId: context.activeFileId || undefined,
|
|
974
|
+
nodeId: targetNodeId,
|
|
975
|
+
selector: targetSelector,
|
|
976
|
+
});
|
|
977
|
+
context.onShaderFillPreview?.(descriptor, css);
|
|
544
978
|
}}
|
|
545
|
-
onBack={
|
|
979
|
+
onBack={closeShaders}
|
|
546
980
|
applyContext={{
|
|
547
981
|
designId: context.designId || undefined,
|
|
548
982
|
fileId: context.activeFileId || undefined,
|
|
@@ -558,15 +992,17 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
558
992
|
type="button"
|
|
559
993
|
size="sm"
|
|
560
994
|
className="h-6 cursor-pointer gap-1 px-2 text-[11px]"
|
|
561
|
-
disabled={!
|
|
995
|
+
disabled={!previewMatchesTarget || applyShaderFill.isPending}
|
|
562
996
|
onClick={() => {
|
|
563
|
-
if (previewed)
|
|
997
|
+
if (previewMatchesTarget && previewed) {
|
|
998
|
+
persistFill(previewed.descriptor);
|
|
999
|
+
}
|
|
564
1000
|
}}
|
|
565
1001
|
>
|
|
566
1002
|
<IconSparkles className="size-3" />
|
|
567
1003
|
{applyShaderFill.isPending
|
|
568
1004
|
? "Applying…"
|
|
569
|
-
:
|
|
1005
|
+
: previewMatchesTarget
|
|
570
1006
|
? "Apply fill"
|
|
571
1007
|
: "Pick a preset"}
|
|
572
1008
|
</Button>
|
|
@@ -691,11 +1127,17 @@ function MotionPresetsPanel({ context }: MotionPresetsPanelProps) {
|
|
|
691
1127
|
export function DesignExtensionsPanel({
|
|
692
1128
|
context,
|
|
693
1129
|
className,
|
|
1130
|
+
hideAssetLibrary = false,
|
|
1131
|
+
title,
|
|
694
1132
|
}: DesignExtensionsPanelProps) {
|
|
695
1133
|
const t = useT();
|
|
696
1134
|
const queryClient = useQueryClient();
|
|
697
1135
|
const [createOpen, setCreateOpen] = useState(false);
|
|
698
1136
|
const [installingId, setInstallingId] = useState<string | null>(null);
|
|
1137
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
1138
|
+
const [sourceFilter, setSourceFilter] = useState<ToolSourceFilter>("all");
|
|
1139
|
+
const [categoryFilter, setCategoryFilter] =
|
|
1140
|
+
useState<ToolCategoryFilter>("all");
|
|
699
1141
|
const [openFirstParty, setOpenFirstParty] = useState<FirstPartyExtId | null>(
|
|
700
1142
|
null,
|
|
701
1143
|
);
|
|
@@ -714,12 +1156,7 @@ export function DesignExtensionsPanel({
|
|
|
714
1156
|
setOpenFirstParty((prev) => (prev === id ? null : id));
|
|
715
1157
|
};
|
|
716
1158
|
|
|
717
|
-
const submitCreatePrompt:
|
|
718
|
-
text: string,
|
|
719
|
-
_files,
|
|
720
|
-
_references,
|
|
721
|
-
options: PromptComposerSubmitOptions,
|
|
722
|
-
) => {
|
|
1159
|
+
const submitCreatePrompt: CreateExtensionSubmitHandler = (text: string) => {
|
|
723
1160
|
const trimmed = text.trim();
|
|
724
1161
|
if (!trimmed) return;
|
|
725
1162
|
sendToAgentChat({
|
|
@@ -728,9 +1165,6 @@ export function DesignExtensionsPanel({
|
|
|
728
1165
|
submit: true,
|
|
729
1166
|
openSidebar: true,
|
|
730
1167
|
newTab: true,
|
|
731
|
-
model: options.model,
|
|
732
|
-
engine: options.engine,
|
|
733
|
-
effort: options.effort,
|
|
734
1168
|
});
|
|
735
1169
|
setCreateOpen(false);
|
|
736
1170
|
};
|
|
@@ -763,18 +1197,12 @@ export function DesignExtensionsPanel({
|
|
|
763
1197
|
};
|
|
764
1198
|
|
|
765
1199
|
// First-party extension row config
|
|
766
|
-
const
|
|
767
|
-
id: FirstPartyExtId;
|
|
768
|
-
label: string;
|
|
769
|
-
description: string;
|
|
770
|
-
icon: React.ReactNode;
|
|
771
|
-
badge?: React.ReactNode;
|
|
772
|
-
panel: React.ReactNode;
|
|
773
|
-
}> = [
|
|
1200
|
+
const allFirstPartyRows: FirstPartyRow[] = [
|
|
774
1201
|
{
|
|
775
1202
|
id: "design.asset-library",
|
|
776
1203
|
label: "Asset Library",
|
|
777
1204
|
description: "Browse & insert assets into the active screen",
|
|
1205
|
+
category: "plugins",
|
|
778
1206
|
icon: <IconPhoto className="size-3.5" />,
|
|
779
1207
|
panel: <AssetLibraryPanel context={context} />,
|
|
780
1208
|
},
|
|
@@ -782,6 +1210,7 @@ export function DesignExtensionsPanel({
|
|
|
782
1210
|
id: "design.shader-fills",
|
|
783
1211
|
label: "Shader Fills",
|
|
784
1212
|
description: "GPU shader fill presets — preview & apply",
|
|
1213
|
+
category: "shader",
|
|
785
1214
|
icon: <IconSparkles className="size-3.5" />,
|
|
786
1215
|
panel: <ShaderFillsExtPanel context={context} />,
|
|
787
1216
|
},
|
|
@@ -789,39 +1218,62 @@ export function DesignExtensionsPanel({
|
|
|
789
1218
|
id: "design.token-auditor",
|
|
790
1219
|
label: "Token Auditor",
|
|
791
1220
|
description: "Audit CSS token usage, flag clashes",
|
|
792
|
-
|
|
1221
|
+
category: "tokens",
|
|
1222
|
+
icon: <IconAssembly className="size-3.5" />,
|
|
793
1223
|
panel: <TokenAuditorPanel context={context} />,
|
|
794
1224
|
},
|
|
795
1225
|
{
|
|
796
1226
|
id: "design.motion-presets",
|
|
797
1227
|
label: "Motion Presets",
|
|
798
1228
|
description: "One-click animation presets for elements",
|
|
1229
|
+
category: "motion",
|
|
799
1230
|
icon: <IconPlayerPlay className="size-3.5" />,
|
|
800
1231
|
panel: <MotionPresetsPanel context={context} />,
|
|
801
1232
|
},
|
|
802
1233
|
];
|
|
1234
|
+
const firstPartyRows = allFirstPartyRows.filter(
|
|
1235
|
+
(row) => !hideAssetLibrary || row.id !== "design.asset-library",
|
|
1236
|
+
);
|
|
1237
|
+
const normalizedSearch = searchQuery.trim().toLowerCase();
|
|
1238
|
+
const sourceMatches = (source: ToolSourceFilter) =>
|
|
1239
|
+
sourceFilter === "all" || sourceFilter === source;
|
|
1240
|
+
const categoryMatches = (category: ToolCategoryFilter) =>
|
|
1241
|
+
categoryFilter === "all" || categoryFilter === category;
|
|
1242
|
+
const textMatches = (name: string, description?: string | null) => {
|
|
1243
|
+
if (!normalizedSearch) return true;
|
|
1244
|
+
return `${name} ${description ?? ""}`
|
|
1245
|
+
.toLowerCase()
|
|
1246
|
+
.includes(normalizedSearch);
|
|
1247
|
+
};
|
|
1248
|
+
const visibleFirstPartyRows = firstPartyRows.filter(
|
|
1249
|
+
(row) =>
|
|
1250
|
+
sourceMatches("built-in") &&
|
|
1251
|
+
categoryMatches(row.category) &&
|
|
1252
|
+
textMatches(row.label, row.description),
|
|
1253
|
+
);
|
|
1254
|
+
const visibleInstalls = installs.filter(
|
|
1255
|
+
(install) =>
|
|
1256
|
+
sourceMatches("extensions") &&
|
|
1257
|
+
categoryMatches("plugins") &&
|
|
1258
|
+
textMatches(install.name, install.description),
|
|
1259
|
+
);
|
|
1260
|
+
const visibleInstallable = installable.filter(
|
|
1261
|
+
(extension) =>
|
|
1262
|
+
sourceMatches("extensions") &&
|
|
1263
|
+
categoryMatches("plugins") &&
|
|
1264
|
+
textMatches(extension.name, extension.description),
|
|
1265
|
+
);
|
|
1266
|
+
const hasAnyVisibleTool =
|
|
1267
|
+
visibleFirstPartyRows.length > 0 ||
|
|
1268
|
+
visibleInstalls.length > 0 ||
|
|
1269
|
+
visibleInstallable.length > 0;
|
|
803
1270
|
|
|
804
1271
|
return (
|
|
805
1272
|
<div className={cn("flex min-h-0 flex-1 flex-col", className)}>
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
{t("designEditor.extensions")}
|
|
1273
|
+
<div className="flex h-16 shrink-0 items-center gap-3 border-b border-border/60 px-4">
|
|
1274
|
+
<h3 className="min-w-0 flex-1 truncate text-xl font-semibold tracking-tight text-foreground">
|
|
1275
|
+
{title ?? t("designEditor.extensions")}
|
|
810
1276
|
</h3>
|
|
811
|
-
<Tooltip>
|
|
812
|
-
<TooltipTrigger asChild>
|
|
813
|
-
<a
|
|
814
|
-
href="https://www.agent-native.com/docs/extensions"
|
|
815
|
-
target="_blank"
|
|
816
|
-
rel="noreferrer"
|
|
817
|
-
className="inline-flex size-6 cursor-pointer items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
818
|
-
aria-label={t("designEditor.extensionsDocs")}
|
|
819
|
-
>
|
|
820
|
-
<IconExternalLink className="size-3.5" />
|
|
821
|
-
</a>
|
|
822
|
-
</TooltipTrigger>
|
|
823
|
-
<TooltipContent>{t("designEditor.extensionsDocs")}</TooltipContent>
|
|
824
|
-
</Tooltip>
|
|
825
1277
|
<CreateExtensionPopover
|
|
826
1278
|
open={createOpen}
|
|
827
1279
|
onOpenChange={setCreateOpen}
|
|
@@ -829,117 +1281,146 @@ export function DesignExtensionsPanel({
|
|
|
829
1281
|
/>
|
|
830
1282
|
</div>
|
|
831
1283
|
|
|
832
|
-
<div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
<
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
id={row.id}
|
|
842
|
-
label={row.label}
|
|
843
|
-
description={row.description}
|
|
844
|
-
icon={row.icon}
|
|
845
|
-
badge={row.badge}
|
|
846
|
-
isOpen={openFirstParty === row.id}
|
|
847
|
-
onToggle={() => toggleFirstParty(row.id)}
|
|
848
|
-
>
|
|
849
|
-
{row.panel}
|
|
850
|
-
</FirstPartyExtRow>
|
|
851
|
-
))}
|
|
1284
|
+
<div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pb-4 pt-4">
|
|
1285
|
+
<div className="relative mb-4">
|
|
1286
|
+
<IconSearch className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
1287
|
+
<Input
|
|
1288
|
+
value={searchQuery}
|
|
1289
|
+
onChange={(event) => setSearchQuery(event.target.value)}
|
|
1290
|
+
placeholder="Search all tools"
|
|
1291
|
+
className="h-9 border-0 bg-muted/70 pl-8 text-sm shadow-none focus-visible:ring-1"
|
|
1292
|
+
/>
|
|
852
1293
|
</div>
|
|
853
1294
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1295
|
+
<div className="mb-5 flex flex-wrap gap-2">
|
|
1296
|
+
<ToolFilterMenu
|
|
1297
|
+
label="Source"
|
|
1298
|
+
value={sourceFilter}
|
|
1299
|
+
onChange={setSourceFilter}
|
|
1300
|
+
options={[
|
|
1301
|
+
{ value: "all", label: "Source" },
|
|
1302
|
+
{ value: "built-in", label: "Built-in" },
|
|
1303
|
+
{ value: "extensions", label: "Extensions" },
|
|
1304
|
+
]}
|
|
1305
|
+
/>
|
|
1306
|
+
<ToolFilterMenu
|
|
1307
|
+
label="Category"
|
|
1308
|
+
value={categoryFilter}
|
|
1309
|
+
onChange={setCategoryFilter}
|
|
1310
|
+
options={[
|
|
1311
|
+
{ value: "all", label: "Category" },
|
|
1312
|
+
{ value: "shader", label: "Shaders" },
|
|
1313
|
+
{ value: "tokens", label: "Tokens" },
|
|
1314
|
+
{ value: "motion", label: "Motion" },
|
|
1315
|
+
{ value: "plugins", label: "Plugins" },
|
|
1316
|
+
]}
|
|
1317
|
+
/>
|
|
861
1318
|
</div>
|
|
862
1319
|
|
|
863
|
-
{/* ── User-installed extensions ──────────────────────────────────── */}
|
|
864
1320
|
{isLoading ? (
|
|
865
|
-
<div className="space-y-
|
|
866
|
-
<Skeleton className="h-
|
|
867
|
-
<Skeleton className="h-
|
|
868
|
-
|
|
869
|
-
) : installs.length > 0 ? (
|
|
870
|
-
<div className="space-y-1.5">
|
|
871
|
-
<p className="mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
|
872
|
-
Installed
|
|
873
|
-
</p>
|
|
874
|
-
{installs.map((install) => (
|
|
875
|
-
<EmbeddedExtension
|
|
876
|
-
key={install.installId}
|
|
877
|
-
extensionId={install.extensionId}
|
|
878
|
-
slotId={slotId}
|
|
879
|
-
context={context}
|
|
880
|
-
initialHeight={180}
|
|
881
|
-
className="overflow-hidden rounded border border-border bg-background"
|
|
882
|
-
/>
|
|
883
|
-
))}
|
|
1321
|
+
<div className="space-y-3">
|
|
1322
|
+
<Skeleton className="h-12 rounded-md" />
|
|
1323
|
+
<Skeleton className="h-12 rounded-md" />
|
|
1324
|
+
<Skeleton className="h-12 rounded-md" />
|
|
884
1325
|
</div>
|
|
885
1326
|
) : (
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1327
|
+
<>
|
|
1328
|
+
{visibleInstalls.length > 0 ? (
|
|
1329
|
+
<div className="mb-6">
|
|
1330
|
+
<p className="mb-3 text-sm font-medium text-muted-foreground">
|
|
1331
|
+
Recents
|
|
1332
|
+
</p>
|
|
1333
|
+
<div className="space-y-2">
|
|
1334
|
+
{visibleInstalls.map((install) => (
|
|
1335
|
+
<EmbeddedExtension
|
|
1336
|
+
key={install.installId}
|
|
1337
|
+
extensionId={install.extensionId}
|
|
1338
|
+
slotId={slotId}
|
|
1339
|
+
context={context}
|
|
1340
|
+
initialHeight={180}
|
|
1341
|
+
className="overflow-hidden rounded-md border border-border bg-background"
|
|
1342
|
+
/>
|
|
1343
|
+
))}
|
|
1344
|
+
</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
) : null}
|
|
1347
|
+
|
|
1348
|
+
{visibleFirstPartyRows.length > 0 ? (
|
|
1349
|
+
<div className="mb-6">
|
|
1350
|
+
<p className="mb-3 text-sm font-medium text-muted-foreground">
|
|
1351
|
+
Suggested
|
|
1352
|
+
</p>
|
|
1353
|
+
<div className="space-y-1">
|
|
1354
|
+
{visibleFirstPartyRows.map((row) => (
|
|
1355
|
+
<FirstPartyExtRow
|
|
1356
|
+
key={row.id}
|
|
1357
|
+
id={row.id}
|
|
1358
|
+
label={row.label}
|
|
1359
|
+
description={row.description}
|
|
1360
|
+
icon={row.icon}
|
|
1361
|
+
badge={row.badge}
|
|
1362
|
+
isOpen={openFirstParty === row.id}
|
|
1363
|
+
onToggle={() => toggleFirstParty(row.id)}
|
|
1364
|
+
>
|
|
1365
|
+
{row.panel}
|
|
1366
|
+
</FirstPartyExtRow>
|
|
1367
|
+
))}
|
|
1368
|
+
</div>
|
|
1369
|
+
</div>
|
|
1370
|
+
) : null}
|
|
1371
|
+
|
|
1372
|
+
{visibleInstallable.length > 0 ? (
|
|
1373
|
+
<div className="border-t border-border/60 pt-4">
|
|
1374
|
+
<p className="mb-3 text-sm font-medium text-muted-foreground">
|
|
1375
|
+
Available
|
|
1376
|
+
</p>
|
|
1377
|
+
<div className="space-y-1">
|
|
1378
|
+
{visibleInstallable.map((extension) => (
|
|
1379
|
+
<button
|
|
1380
|
+
key={extension.extensionId}
|
|
1381
|
+
type="button"
|
|
1382
|
+
disabled={installingId === extension.extensionId}
|
|
1383
|
+
onClick={() => installExtension(extension.extensionId)}
|
|
1384
|
+
className="group flex w-full cursor-pointer items-center gap-3 rounded-md px-2 py-2.5 text-left transition-colors hover:bg-accent/60 active:bg-accent disabled:cursor-default disabled:opacity-50"
|
|
1385
|
+
>
|
|
1386
|
+
<span className="flex size-10 shrink-0 items-center justify-center rounded-md border border-border/70 bg-muted/70 text-muted-foreground transition-colors group-hover:border-border group-hover:bg-muted">
|
|
1387
|
+
<IconPuzzle className="size-4" />
|
|
1388
|
+
</span>
|
|
1389
|
+
<span className="min-w-0 flex-1">
|
|
1390
|
+
<span className="block truncate text-sm font-medium leading-tight text-foreground">
|
|
1391
|
+
{extension.name}
|
|
1392
|
+
</span>
|
|
1393
|
+
<span className="mt-0.5 flex items-center gap-1.5 truncate text-xs leading-none text-muted-foreground">
|
|
1394
|
+
<IconPuzzle className="size-3 shrink-0" />
|
|
1395
|
+
<span className="truncate">
|
|
1396
|
+
{extension.description || "Extension"}
|
|
1397
|
+
</span>
|
|
1398
|
+
</span>
|
|
1399
|
+
</span>
|
|
1400
|
+
<span className="shrink-0 text-xs font-medium text-muted-foreground">
|
|
1401
|
+
{t("designEditor.extensionsInstall")}
|
|
1402
|
+
</span>
|
|
1403
|
+
</button>
|
|
1404
|
+
))}
|
|
1405
|
+
</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
) : null}
|
|
910
1408
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
<
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
<IconSparkles className="size-3 shrink-0 text-muted-foreground" />
|
|
927
|
-
<span className="min-w-0 flex-1 truncate text-[11px] font-medium text-foreground">
|
|
928
|
-
{extension.name}
|
|
929
|
-
</span>
|
|
930
|
-
{extension.description ? (
|
|
931
|
-
<span className="hidden truncate text-[11px] text-muted-foreground sm:block">
|
|
932
|
-
{extension.description}
|
|
933
|
-
</span>
|
|
934
|
-
) : null}
|
|
935
|
-
<span className="shrink-0 text-[11px] text-muted-foreground">
|
|
936
|
-
{t("designEditor.extensionsInstall")}
|
|
937
|
-
</span>
|
|
938
|
-
</button>
|
|
939
|
-
))}
|
|
940
|
-
</div>
|
|
941
|
-
</div>
|
|
942
|
-
) : null}
|
|
1409
|
+
{!hasAnyVisibleTool ? (
|
|
1410
|
+
<div className="py-12 text-center">
|
|
1411
|
+
<div className="mx-auto mb-3 flex size-9 items-center justify-center rounded-md bg-muted/70">
|
|
1412
|
+
<IconSearch className="size-4 text-muted-foreground" />
|
|
1413
|
+
</div>
|
|
1414
|
+
<p className="text-sm font-medium text-foreground">
|
|
1415
|
+
No tools found
|
|
1416
|
+
</p>
|
|
1417
|
+
<p className="mx-auto mt-1 max-w-52 text-xs leading-5 text-muted-foreground">
|
|
1418
|
+
Try another search or clear the filters.
|
|
1419
|
+
</p>
|
|
1420
|
+
</div>
|
|
1421
|
+
) : null}
|
|
1422
|
+
</>
|
|
1423
|
+
)}
|
|
943
1424
|
</div>
|
|
944
1425
|
</div>
|
|
945
1426
|
);
|
|
@@ -954,40 +1435,65 @@ function CreateExtensionPopover({
|
|
|
954
1435
|
}: {
|
|
955
1436
|
open: boolean;
|
|
956
1437
|
onOpenChange: (open: boolean) => void;
|
|
957
|
-
onSubmit:
|
|
1438
|
+
onSubmit: CreateExtensionSubmitHandler;
|
|
958
1439
|
}) {
|
|
959
1440
|
const t = useT();
|
|
1441
|
+
const [draft, setDraft] = useState("");
|
|
1442
|
+
const canSubmit = draft.trim().length > 0;
|
|
1443
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
1444
|
+
event.preventDefault();
|
|
1445
|
+
if (!canSubmit) return;
|
|
1446
|
+
onSubmit(draft);
|
|
1447
|
+
setDraft("");
|
|
1448
|
+
};
|
|
1449
|
+
|
|
960
1450
|
return (
|
|
961
1451
|
<Popover open={open} onOpenChange={onOpenChange}>
|
|
962
|
-
<
|
|
963
|
-
<
|
|
964
|
-
|
|
1452
|
+
<PopoverTrigger asChild>
|
|
1453
|
+
<Button
|
|
1454
|
+
type="button"
|
|
1455
|
+
variant="outline"
|
|
1456
|
+
size="sm"
|
|
1457
|
+
className="h-8 cursor-pointer gap-1.5 rounded-md bg-transparent px-3 text-sm font-medium"
|
|
1458
|
+
aria-label={t("designEditor.addExtension")}
|
|
1459
|
+
>
|
|
1460
|
+
Create
|
|
1461
|
+
<IconChevronDown className="size-3.5 text-muted-foreground" />
|
|
1462
|
+
</Button>
|
|
1463
|
+
</PopoverTrigger>
|
|
1464
|
+
<PopoverContent align="end" sideOffset={8} className="w-80 p-3">
|
|
1465
|
+
<form onSubmit={handleSubmit} className="space-y-3">
|
|
1466
|
+
<p className="px-0.5 text-sm font-semibold text-foreground">
|
|
1467
|
+
{t("designEditor.extensionsPromptTitle")}
|
|
1468
|
+
</p>
|
|
1469
|
+
<Textarea
|
|
1470
|
+
autoFocus
|
|
1471
|
+
value={draft}
|
|
1472
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
1473
|
+
placeholder={t("designEditor.extensionsPlaceholder")}
|
|
1474
|
+
className="min-h-24 resize-none border-border/80 bg-background/80 text-sm shadow-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0"
|
|
1475
|
+
/>
|
|
1476
|
+
<div className="flex justify-end gap-2">
|
|
965
1477
|
<Button
|
|
966
1478
|
type="button"
|
|
967
1479
|
variant="ghost"
|
|
968
|
-
size="
|
|
969
|
-
className="
|
|
970
|
-
|
|
1480
|
+
size="sm"
|
|
1481
|
+
className="h-8 px-3"
|
|
1482
|
+
onClick={() => onOpenChange(false)}
|
|
971
1483
|
>
|
|
972
|
-
|
|
1484
|
+
Cancel
|
|
973
1485
|
</Button>
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
plusMenuMode="hidden"
|
|
986
|
-
layoutVariant="compact"
|
|
987
|
-
draftScope="design:editor-extension-create"
|
|
988
|
-
placeholder={t("designEditor.extensionsPlaceholder")}
|
|
989
|
-
onSubmit={onSubmit}
|
|
990
|
-
/>
|
|
1486
|
+
<Button
|
|
1487
|
+
type="submit"
|
|
1488
|
+
size="sm"
|
|
1489
|
+
className="h-8 gap-1.5 px-3"
|
|
1490
|
+
disabled={!canSubmit}
|
|
1491
|
+
>
|
|
1492
|
+
<IconSparkles className="size-3.5" />
|
|
1493
|
+
Create
|
|
1494
|
+
</Button>
|
|
1495
|
+
</div>
|
|
1496
|
+
</form>
|
|
991
1497
|
</PopoverContent>
|
|
992
1498
|
</Popover>
|
|
993
1499
|
);
|