@agent-native/core 0.80.11 → 0.81.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AgentPanel.tsx +33 -1
- 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/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +18 -3
- package/dist/client/AgentPanel.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/routes.d.ts +1 -1
- 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/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- 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<{
|
|
@@ -88,14 +102,21 @@ export interface DesignExtensionSlotContext extends Record<string, unknown> {
|
|
|
88
102
|
tweakValues: Record<string, string | number | boolean>;
|
|
89
103
|
onShaderFillPreview?: (descriptor: ShaderDescriptor, css: string) => void;
|
|
90
104
|
onShaderFillPreviewClear?: () => void;
|
|
105
|
+
onShaderFillApplied?: (
|
|
106
|
+
fileId: string,
|
|
107
|
+
content: string,
|
|
108
|
+
updatedAt?: string,
|
|
109
|
+
) => void;
|
|
91
110
|
}
|
|
92
111
|
|
|
93
112
|
interface DesignExtensionsPanelProps {
|
|
94
113
|
context: DesignExtensionSlotContext;
|
|
95
114
|
className?: string;
|
|
115
|
+
hideAssetLibrary?: boolean;
|
|
116
|
+
title?: string;
|
|
96
117
|
}
|
|
97
118
|
|
|
98
|
-
type
|
|
119
|
+
type CreateExtensionSubmitHandler = (text: string) => void;
|
|
99
120
|
|
|
100
121
|
// ─── First-party extension ids ───────────────────────────────────────────────
|
|
101
122
|
|
|
@@ -105,9 +126,23 @@ type FirstPartyExtId =
|
|
|
105
126
|
| "design.token-auditor"
|
|
106
127
|
| "design.motion-presets";
|
|
107
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
|
+
|
|
108
142
|
// ─── Assets picker types (mirrors PromptDialog) ───────────────────────────
|
|
109
143
|
|
|
110
|
-
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";
|
|
111
146
|
|
|
112
147
|
interface PickedAssetPayload {
|
|
113
148
|
url?: unknown;
|
|
@@ -121,12 +156,30 @@ interface PickedAssetPayload {
|
|
|
121
156
|
}
|
|
122
157
|
|
|
123
158
|
function assetsPickerUrl(): string {
|
|
124
|
-
|
|
159
|
+
const configured =
|
|
125
160
|
(typeof import.meta !== "undefined" &&
|
|
126
161
|
(import.meta as { env?: Record<string, string> }).env
|
|
127
162
|
?.VITE_AGENT_NATIVE_ASSETS_PICKER_URL) ||
|
|
128
|
-
DEFAULT_ASSETS_PICKER_URL
|
|
129
|
-
|
|
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
|
+
}
|
|
130
183
|
}
|
|
131
184
|
|
|
132
185
|
function pickedAssetString(value: unknown): string | null {
|
|
@@ -154,7 +207,7 @@ function buildExtensionCreateContext(
|
|
|
154
207
|
? JSON.stringify(context.selectedElement, null, 2)
|
|
155
208
|
: "No element is currently selected.";
|
|
156
209
|
return [
|
|
157
|
-
`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}")` : ""}.`,
|
|
158
211
|
context.activeFileId
|
|
159
212
|
? `Active screen: "${context.activeFilename ?? context.activeFileId}" (file id: "${context.activeFileId}").`
|
|
160
213
|
: "There is no active screen yet.",
|
|
@@ -162,7 +215,7 @@ function buildExtensionCreateContext(
|
|
|
162
215
|
`User request: "${prompt}"`,
|
|
163
216
|
"",
|
|
164
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.",
|
|
165
|
-
'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.',
|
|
166
219
|
"",
|
|
167
220
|
"The extension will receive window.slotContext and onSlotContext updates with the current design selection:",
|
|
168
221
|
JSON.stringify(
|
|
@@ -247,7 +300,6 @@ interface FirstPartyRowProps {
|
|
|
247
300
|
|
|
248
301
|
function FirstPartyExtRow({
|
|
249
302
|
label,
|
|
250
|
-
description,
|
|
251
303
|
icon,
|
|
252
304
|
badge,
|
|
253
305
|
isOpen,
|
|
@@ -255,50 +307,171 @@ function FirstPartyExtRow({
|
|
|
255
307
|
children,
|
|
256
308
|
}: FirstPartyRowProps) {
|
|
257
309
|
return (
|
|
258
|
-
<div className="overflow-hidden rounded
|
|
310
|
+
<div className="overflow-hidden rounded-md">
|
|
259
311
|
<button
|
|
260
312
|
type="button"
|
|
261
313
|
onClick={onToggle}
|
|
262
|
-
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"
|
|
263
315
|
>
|
|
264
|
-
<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">
|
|
265
317
|
{icon}
|
|
266
318
|
</span>
|
|
267
319
|
<span className="min-w-0 flex-1">
|
|
268
|
-
<span className="block truncate text-
|
|
320
|
+
<span className="block truncate text-sm font-medium leading-tight text-foreground">
|
|
269
321
|
{label}
|
|
270
322
|
</span>
|
|
271
|
-
<span className="
|
|
272
|
-
|
|
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>
|
|
273
326
|
</span>
|
|
274
327
|
</span>
|
|
275
328
|
{badge}
|
|
276
329
|
</button>
|
|
277
|
-
{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
|
+
)}
|
|
278
335
|
</div>
|
|
279
336
|
);
|
|
280
337
|
}
|
|
281
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
|
+
|
|
282
379
|
// ─── Asset Library panel ──────────────────────────────────────────────────────
|
|
283
380
|
|
|
284
381
|
interface AssetLibraryPanelProps {
|
|
285
382
|
context: DesignExtensionSlotContext;
|
|
286
383
|
}
|
|
287
384
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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");
|
|
292
452
|
const insertAsset = useActionMutation("insert-asset");
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
);
|
|
297
466
|
|
|
298
467
|
const handleReady = useCallback(
|
|
299
468
|
(_payload: unknown, _event: MessageEvent, ref: EmbeddedAppRef) => {
|
|
300
|
-
|
|
301
|
-
ref.postMessage("configure", {
|
|
469
|
+
setAssetPickerReady(true);
|
|
470
|
+
ref.postMessage("configure", {
|
|
471
|
+
mediaType: "image",
|
|
472
|
+
layout: "vertical",
|
|
473
|
+
callerAppId: "design",
|
|
474
|
+
});
|
|
302
475
|
},
|
|
303
476
|
[],
|
|
304
477
|
);
|
|
@@ -306,7 +479,7 @@ function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
306
479
|
const handleMessage = useCallback(
|
|
307
480
|
(name: string, payload: unknown) => {
|
|
308
481
|
if (name === "close") {
|
|
309
|
-
|
|
482
|
+
setAssetPickerReady(false);
|
|
310
483
|
return;
|
|
311
484
|
}
|
|
312
485
|
if (name !== "chooseImage" && name !== "chooseAsset") return;
|
|
@@ -343,99 +516,293 @@ function AssetLibraryPanel({ context }: AssetLibraryPanelProps) {
|
|
|
343
516
|
},
|
|
344
517
|
},
|
|
345
518
|
);
|
|
346
|
-
|
|
347
|
-
setPickerOpen(false);
|
|
348
519
|
},
|
|
349
520
|
[context.designId, context.activeFileId, insertAsset],
|
|
350
521
|
);
|
|
351
522
|
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
);
|
|
369
577
|
};
|
|
370
578
|
|
|
371
579
|
return (
|
|
372
|
-
<div className="
|
|
373
|
-
<
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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"
|
|
384
602
|
>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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"
|
|
394
654
|
>
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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>
|
|
404
671
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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}
|
|
419
695
|
>
|
|
420
|
-
|
|
421
|
-
|
|
696
|
+
<IconSearch className="size-3" />
|
|
697
|
+
{figmaAssets.isFetching ? "Loading" : "Browse"}
|
|
698
|
+
</Button>
|
|
422
699
|
</div>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
className="
|
|
435
|
-
|
|
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
|
+
))}
|
|
436
713
|
</div>
|
|
437
|
-
|
|
438
|
-
|
|
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>
|
|
439
806
|
)}
|
|
440
807
|
</div>
|
|
441
808
|
);
|
|
@@ -447,6 +814,13 @@ interface ShaderFillsExtPanelProps {
|
|
|
447
814
|
context: DesignExtensionSlotContext;
|
|
448
815
|
}
|
|
449
816
|
|
|
817
|
+
interface PreviewedShaderFill {
|
|
818
|
+
descriptor: ShaderDescriptor;
|
|
819
|
+
fileId?: string;
|
|
820
|
+
nodeId?: string;
|
|
821
|
+
selector?: string;
|
|
822
|
+
}
|
|
823
|
+
|
|
450
824
|
function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
451
825
|
const [showShaders, setShowShaders] = useState(false);
|
|
452
826
|
const clearPreviewRef = useRef(context.onShaderFillPreviewClear);
|
|
@@ -466,7 +840,7 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
466
840
|
// The most recently previewed descriptor — Apply persists exactly this one,
|
|
467
841
|
// so the write is intentional (one atomic call) rather than firing on every
|
|
468
842
|
// slider tweak.
|
|
469
|
-
const [previewed, setPreviewed] = useState<
|
|
843
|
+
const [previewed, setPreviewed] = useState<PreviewedShaderFill | null>(null);
|
|
470
844
|
const applyShaderFill = useActionMutation("apply-shader-fill");
|
|
471
845
|
|
|
472
846
|
// The persisting apply path needs an HTML file plus a target element. Without
|
|
@@ -476,6 +850,16 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
476
850
|
const canPersist = Boolean(
|
|
477
851
|
context.activeFileId && (targetNodeId || targetSelector),
|
|
478
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]);
|
|
479
863
|
|
|
480
864
|
const persistFill = (descriptor: ShaderDescriptor) => {
|
|
481
865
|
if (!canPersist) return;
|
|
@@ -498,13 +882,41 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
498
882
|
kind: "design-file" as const,
|
|
499
883
|
designId: context.designId || undefined,
|
|
500
884
|
fileId: context.activeFileId || undefined,
|
|
885
|
+
revision: context.activeFileUpdatedAt || undefined,
|
|
886
|
+
currentContent: context.activeContent,
|
|
501
887
|
},
|
|
502
888
|
},
|
|
503
889
|
{
|
|
504
890
|
onSuccess: (res) => {
|
|
505
|
-
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;
|
|
506
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
|
+
}
|
|
507
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
|
+
);
|
|
508
920
|
} else {
|
|
509
921
|
toast.message("Shader fill previewed — nothing was written.");
|
|
510
922
|
}
|
|
@@ -556,7 +968,12 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
556
968
|
// for agent context on every tune and the iframe shows the gradient.
|
|
557
969
|
// We just record the latest descriptor here; the explicit Apply
|
|
558
970
|
// button below performs the single intentional persist write.
|
|
559
|
-
setPreviewed(
|
|
971
|
+
setPreviewed({
|
|
972
|
+
descriptor,
|
|
973
|
+
fileId: context.activeFileId || undefined,
|
|
974
|
+
nodeId: targetNodeId,
|
|
975
|
+
selector: targetSelector,
|
|
976
|
+
});
|
|
560
977
|
context.onShaderFillPreview?.(descriptor, css);
|
|
561
978
|
}}
|
|
562
979
|
onBack={closeShaders}
|
|
@@ -575,15 +992,17 @@ function ShaderFillsExtPanel({ context }: ShaderFillsExtPanelProps) {
|
|
|
575
992
|
type="button"
|
|
576
993
|
size="sm"
|
|
577
994
|
className="h-6 cursor-pointer gap-1 px-2 text-[11px]"
|
|
578
|
-
disabled={!
|
|
995
|
+
disabled={!previewMatchesTarget || applyShaderFill.isPending}
|
|
579
996
|
onClick={() => {
|
|
580
|
-
if (previewed)
|
|
997
|
+
if (previewMatchesTarget && previewed) {
|
|
998
|
+
persistFill(previewed.descriptor);
|
|
999
|
+
}
|
|
581
1000
|
}}
|
|
582
1001
|
>
|
|
583
1002
|
<IconSparkles className="size-3" />
|
|
584
1003
|
{applyShaderFill.isPending
|
|
585
1004
|
? "Applying…"
|
|
586
|
-
:
|
|
1005
|
+
: previewMatchesTarget
|
|
587
1006
|
? "Apply fill"
|
|
588
1007
|
: "Pick a preset"}
|
|
589
1008
|
</Button>
|
|
@@ -708,11 +1127,17 @@ function MotionPresetsPanel({ context }: MotionPresetsPanelProps) {
|
|
|
708
1127
|
export function DesignExtensionsPanel({
|
|
709
1128
|
context,
|
|
710
1129
|
className,
|
|
1130
|
+
hideAssetLibrary = false,
|
|
1131
|
+
title,
|
|
711
1132
|
}: DesignExtensionsPanelProps) {
|
|
712
1133
|
const t = useT();
|
|
713
1134
|
const queryClient = useQueryClient();
|
|
714
1135
|
const [createOpen, setCreateOpen] = useState(false);
|
|
715
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");
|
|
716
1141
|
const [openFirstParty, setOpenFirstParty] = useState<FirstPartyExtId | null>(
|
|
717
1142
|
null,
|
|
718
1143
|
);
|
|
@@ -731,12 +1156,7 @@ export function DesignExtensionsPanel({
|
|
|
731
1156
|
setOpenFirstParty((prev) => (prev === id ? null : id));
|
|
732
1157
|
};
|
|
733
1158
|
|
|
734
|
-
const submitCreatePrompt:
|
|
735
|
-
text: string,
|
|
736
|
-
_files,
|
|
737
|
-
_references,
|
|
738
|
-
options: PromptComposerSubmitOptions,
|
|
739
|
-
) => {
|
|
1159
|
+
const submitCreatePrompt: CreateExtensionSubmitHandler = (text: string) => {
|
|
740
1160
|
const trimmed = text.trim();
|
|
741
1161
|
if (!trimmed) return;
|
|
742
1162
|
sendToAgentChat({
|
|
@@ -745,9 +1165,6 @@ export function DesignExtensionsPanel({
|
|
|
745
1165
|
submit: true,
|
|
746
1166
|
openSidebar: true,
|
|
747
1167
|
newTab: true,
|
|
748
|
-
model: options.model,
|
|
749
|
-
engine: options.engine,
|
|
750
|
-
effort: options.effort,
|
|
751
1168
|
});
|
|
752
1169
|
setCreateOpen(false);
|
|
753
1170
|
};
|
|
@@ -780,18 +1197,12 @@ export function DesignExtensionsPanel({
|
|
|
780
1197
|
};
|
|
781
1198
|
|
|
782
1199
|
// First-party extension row config
|
|
783
|
-
const
|
|
784
|
-
id: FirstPartyExtId;
|
|
785
|
-
label: string;
|
|
786
|
-
description: string;
|
|
787
|
-
icon: React.ReactNode;
|
|
788
|
-
badge?: React.ReactNode;
|
|
789
|
-
panel: React.ReactNode;
|
|
790
|
-
}> = [
|
|
1200
|
+
const allFirstPartyRows: FirstPartyRow[] = [
|
|
791
1201
|
{
|
|
792
1202
|
id: "design.asset-library",
|
|
793
1203
|
label: "Asset Library",
|
|
794
1204
|
description: "Browse & insert assets into the active screen",
|
|
1205
|
+
category: "plugins",
|
|
795
1206
|
icon: <IconPhoto className="size-3.5" />,
|
|
796
1207
|
panel: <AssetLibraryPanel context={context} />,
|
|
797
1208
|
},
|
|
@@ -799,6 +1210,7 @@ export function DesignExtensionsPanel({
|
|
|
799
1210
|
id: "design.shader-fills",
|
|
800
1211
|
label: "Shader Fills",
|
|
801
1212
|
description: "GPU shader fill presets — preview & apply",
|
|
1213
|
+
category: "shader",
|
|
802
1214
|
icon: <IconSparkles className="size-3.5" />,
|
|
803
1215
|
panel: <ShaderFillsExtPanel context={context} />,
|
|
804
1216
|
},
|
|
@@ -806,39 +1218,62 @@ export function DesignExtensionsPanel({
|
|
|
806
1218
|
id: "design.token-auditor",
|
|
807
1219
|
label: "Token Auditor",
|
|
808
1220
|
description: "Audit CSS token usage, flag clashes",
|
|
809
|
-
|
|
1221
|
+
category: "tokens",
|
|
1222
|
+
icon: <IconAssembly className="size-3.5" />,
|
|
810
1223
|
panel: <TokenAuditorPanel context={context} />,
|
|
811
1224
|
},
|
|
812
1225
|
{
|
|
813
1226
|
id: "design.motion-presets",
|
|
814
1227
|
label: "Motion Presets",
|
|
815
1228
|
description: "One-click animation presets for elements",
|
|
1229
|
+
category: "motion",
|
|
816
1230
|
icon: <IconPlayerPlay className="size-3.5" />,
|
|
817
1231
|
panel: <MotionPresetsPanel context={context} />,
|
|
818
1232
|
},
|
|
819
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;
|
|
820
1270
|
|
|
821
1271
|
return (
|
|
822
1272
|
<div className={cn("flex min-h-0 flex-1 flex-col", className)}>
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
{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")}
|
|
827
1276
|
</h3>
|
|
828
|
-
<Tooltip>
|
|
829
|
-
<TooltipTrigger asChild>
|
|
830
|
-
<a
|
|
831
|
-
href="https://www.agent-native.com/docs/extensions"
|
|
832
|
-
target="_blank"
|
|
833
|
-
rel="noreferrer"
|
|
834
|
-
className="inline-flex size-6 cursor-pointer items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
835
|
-
aria-label={t("designEditor.extensionsDocs")}
|
|
836
|
-
>
|
|
837
|
-
<IconExternalLink className="size-3.5" />
|
|
838
|
-
</a>
|
|
839
|
-
</TooltipTrigger>
|
|
840
|
-
<TooltipContent>{t("designEditor.extensionsDocs")}</TooltipContent>
|
|
841
|
-
</Tooltip>
|
|
842
1277
|
<CreateExtensionPopover
|
|
843
1278
|
open={createOpen}
|
|
844
1279
|
onOpenChange={setCreateOpen}
|
|
@@ -846,117 +1281,146 @@ export function DesignExtensionsPanel({
|
|
|
846
1281
|
/>
|
|
847
1282
|
</div>
|
|
848
1283
|
|
|
849
|
-
<div className="design-inspector-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain px-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
<
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
id={row.id}
|
|
859
|
-
label={row.label}
|
|
860
|
-
description={row.description}
|
|
861
|
-
icon={row.icon}
|
|
862
|
-
badge={row.badge}
|
|
863
|
-
isOpen={openFirstParty === row.id}
|
|
864
|
-
onToggle={() => toggleFirstParty(row.id)}
|
|
865
|
-
>
|
|
866
|
-
{row.panel}
|
|
867
|
-
</FirstPartyExtRow>
|
|
868
|
-
))}
|
|
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
|
+
/>
|
|
869
1293
|
</div>
|
|
870
1294
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
+
/>
|
|
878
1318
|
</div>
|
|
879
1319
|
|
|
880
|
-
{/* ── User-installed extensions ──────────────────────────────────── */}
|
|
881
1320
|
{isLoading ? (
|
|
882
|
-
<div className="space-y-
|
|
883
|
-
<Skeleton className="h-
|
|
884
|
-
<Skeleton className="h-
|
|
885
|
-
|
|
886
|
-
) : installs.length > 0 ? (
|
|
887
|
-
<div className="space-y-1.5">
|
|
888
|
-
<p className="mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
|
889
|
-
Installed
|
|
890
|
-
</p>
|
|
891
|
-
{installs.map((install) => (
|
|
892
|
-
<EmbeddedExtension
|
|
893
|
-
key={install.installId}
|
|
894
|
-
extensionId={install.extensionId}
|
|
895
|
-
slotId={slotId}
|
|
896
|
-
context={context}
|
|
897
|
-
initialHeight={180}
|
|
898
|
-
className="overflow-hidden rounded border border-border bg-background"
|
|
899
|
-
/>
|
|
900
|
-
))}
|
|
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" />
|
|
901
1325
|
</div>
|
|
902
1326
|
) : (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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}
|
|
927
1408
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
<
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
<IconSparkles className="size-3 shrink-0 text-muted-foreground" />
|
|
944
|
-
<span className="min-w-0 flex-1 truncate text-[11px] font-medium text-foreground">
|
|
945
|
-
{extension.name}
|
|
946
|
-
</span>
|
|
947
|
-
{extension.description ? (
|
|
948
|
-
<span className="hidden truncate text-[11px] text-muted-foreground sm:block">
|
|
949
|
-
{extension.description}
|
|
950
|
-
</span>
|
|
951
|
-
) : null}
|
|
952
|
-
<span className="shrink-0 text-[11px] text-muted-foreground">
|
|
953
|
-
{t("designEditor.extensionsInstall")}
|
|
954
|
-
</span>
|
|
955
|
-
</button>
|
|
956
|
-
))}
|
|
957
|
-
</div>
|
|
958
|
-
</div>
|
|
959
|
-
) : 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
|
+
)}
|
|
960
1424
|
</div>
|
|
961
1425
|
</div>
|
|
962
1426
|
);
|
|
@@ -971,40 +1435,65 @@ function CreateExtensionPopover({
|
|
|
971
1435
|
}: {
|
|
972
1436
|
open: boolean;
|
|
973
1437
|
onOpenChange: (open: boolean) => void;
|
|
974
|
-
onSubmit:
|
|
1438
|
+
onSubmit: CreateExtensionSubmitHandler;
|
|
975
1439
|
}) {
|
|
976
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
|
+
|
|
977
1450
|
return (
|
|
978
1451
|
<Popover open={open} onOpenChange={onOpenChange}>
|
|
979
|
-
<
|
|
980
|
-
<
|
|
981
|
-
|
|
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">
|
|
982
1477
|
<Button
|
|
983
1478
|
type="button"
|
|
984
1479
|
variant="ghost"
|
|
985
|
-
size="
|
|
986
|
-
className="
|
|
987
|
-
|
|
1480
|
+
size="sm"
|
|
1481
|
+
className="h-8 px-3"
|
|
1482
|
+
onClick={() => onOpenChange(false)}
|
|
988
1483
|
>
|
|
989
|
-
|
|
1484
|
+
Cancel
|
|
990
1485
|
</Button>
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
plusMenuMode="hidden"
|
|
1003
|
-
layoutVariant="compact"
|
|
1004
|
-
draftScope="design:editor-extension-create"
|
|
1005
|
-
placeholder={t("designEditor.extensionsPlaceholder")}
|
|
1006
|
-
onSubmit={onSubmit}
|
|
1007
|
-
/>
|
|
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>
|
|
1008
1497
|
</PopoverContent>
|
|
1009
1498
|
</Popover>
|
|
1010
1499
|
);
|