@agent-native/core 0.78.0 → 0.78.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 +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.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/collab/routes.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,42 +1,63 @@
|
|
|
1
1
|
import {
|
|
2
|
-
agentNativePath,
|
|
3
2
|
appPath,
|
|
4
3
|
getBrowserTabId,
|
|
5
4
|
getEmbedAuthToken,
|
|
6
5
|
isEmbedMcpChatBridgeActive,
|
|
7
6
|
isEmbedAuthActive,
|
|
7
|
+
insertAgentComposerReference,
|
|
8
|
+
readClientAppState,
|
|
8
9
|
sendMcpAppHostMessage,
|
|
9
10
|
updateMcpAppModelContext,
|
|
10
11
|
useActionMutation,
|
|
11
12
|
useActionQuery,
|
|
13
|
+
useAgentChatGenerating,
|
|
12
14
|
useT,
|
|
15
|
+
writeClientAppState,
|
|
13
16
|
} from "@agent-native/core/client";
|
|
14
17
|
import {
|
|
15
18
|
createEmbeddedAppBridge,
|
|
16
19
|
type EmbeddedAppBridge,
|
|
17
|
-
} from "@agent-native/core/embedding
|
|
20
|
+
} from "@agent-native/core/embedding";
|
|
18
21
|
import {
|
|
19
22
|
IconArrowUpRight,
|
|
20
23
|
IconCheck,
|
|
24
|
+
IconChevronDown,
|
|
21
25
|
IconChevronLeft,
|
|
22
26
|
IconChevronRight,
|
|
23
27
|
IconClipboard,
|
|
28
|
+
IconLibraryPhoto,
|
|
24
29
|
IconPhotoPlus,
|
|
30
|
+
IconSearch,
|
|
25
31
|
IconX,
|
|
26
32
|
} from "@tabler/icons-react";
|
|
33
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
27
34
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
28
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
Link,
|
|
37
|
+
useSearchParams,
|
|
38
|
+
useLocation,
|
|
39
|
+
useNavigate,
|
|
40
|
+
useParams,
|
|
41
|
+
} from "react-router";
|
|
29
42
|
import { toast } from "sonner";
|
|
30
43
|
|
|
44
|
+
import { CreateLibraryDialog } from "@/components/library/CreateLibraryDialog";
|
|
45
|
+
import { LibraryPresetGrid } from "@/components/library/LibraryPresetGrid";
|
|
46
|
+
import { Badge } from "@/components/ui/badge";
|
|
31
47
|
import { Button } from "@/components/ui/button";
|
|
32
48
|
import {
|
|
33
49
|
Dialog,
|
|
34
|
-
DialogClose,
|
|
35
50
|
DialogContent,
|
|
36
51
|
DialogDescription,
|
|
37
52
|
DialogTitle,
|
|
53
|
+
DialogClose,
|
|
38
54
|
} from "@/components/ui/dialog";
|
|
39
55
|
import { Input } from "@/components/ui/input";
|
|
56
|
+
import {
|
|
57
|
+
Popover,
|
|
58
|
+
PopoverContent,
|
|
59
|
+
PopoverTrigger,
|
|
60
|
+
} from "@/components/ui/popover";
|
|
40
61
|
import {
|
|
41
62
|
Select,
|
|
42
63
|
SelectContent,
|
|
@@ -54,10 +75,27 @@ import {
|
|
|
54
75
|
TooltipProvider,
|
|
55
76
|
TooltipTrigger,
|
|
56
77
|
} from "@/components/ui/tooltip";
|
|
78
|
+
import {
|
|
79
|
+
sortLibrariesByUsage,
|
|
80
|
+
type ImageLibrarySummary,
|
|
81
|
+
} from "@/lib/libraries";
|
|
82
|
+
import { buildPickerChatHandoffPrompt } from "@/lib/picker-chat-handoff";
|
|
57
83
|
import { cn } from "@/lib/utils";
|
|
58
84
|
|
|
59
|
-
import type {
|
|
60
|
-
|
|
85
|
+
import type {
|
|
86
|
+
AssetVariantState,
|
|
87
|
+
ImageQualityTier,
|
|
88
|
+
StyleStrength,
|
|
89
|
+
} from "../../shared/api";
|
|
90
|
+
import {
|
|
91
|
+
DEFAULT_LIBRARY_PRESETS,
|
|
92
|
+
LibraryPreset,
|
|
93
|
+
} from "../../shared/library-presets";
|
|
94
|
+
import {
|
|
95
|
+
BrandKitDetailRoute,
|
|
96
|
+
LiveCandidatesStage,
|
|
97
|
+
type VariantSlot,
|
|
98
|
+
} from "./brand-kits.$id";
|
|
61
99
|
|
|
62
100
|
type AssetTab = "all" | "generated" | "references";
|
|
63
101
|
|
|
@@ -84,6 +122,7 @@ type Asset = {
|
|
|
84
122
|
id: string;
|
|
85
123
|
libraryId: string;
|
|
86
124
|
role?: string | null;
|
|
125
|
+
status?: string | null;
|
|
87
126
|
title?: string | null;
|
|
88
127
|
description?: string | null;
|
|
89
128
|
altText?: string | null;
|
|
@@ -98,6 +137,7 @@ type Asset = {
|
|
|
98
137
|
downloadUrl?: string;
|
|
99
138
|
embedUrl?: string;
|
|
100
139
|
embedPath?: string;
|
|
140
|
+
libraryTitle?: string | null;
|
|
101
141
|
lineage?: {
|
|
102
142
|
label?: string | null;
|
|
103
143
|
kind?: string | null;
|
|
@@ -125,6 +165,7 @@ type GenerationPreset = {
|
|
|
125
165
|
mediaType?: string | null;
|
|
126
166
|
aspectRatio?: string | null;
|
|
127
167
|
imageSize?: string | null;
|
|
168
|
+
model?: string | null;
|
|
128
169
|
};
|
|
129
170
|
|
|
130
171
|
type HostConfig = {
|
|
@@ -516,7 +557,7 @@ function blobToBase64(blob: Blob): Promise<string> {
|
|
|
516
557
|
});
|
|
517
558
|
}
|
|
518
559
|
|
|
519
|
-
const MCP_IMAGE_CONTENT_MAX_BYTES =
|
|
560
|
+
const MCP_IMAGE_CONTENT_MAX_BYTES = 2.5 * 1024 * 1024;
|
|
520
561
|
|
|
521
562
|
function base64ByteLength(data: string) {
|
|
522
563
|
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
@@ -716,7 +757,1178 @@ function AssetOverlayImage({ asset }: { asset: Asset }) {
|
|
|
716
757
|
);
|
|
717
758
|
}
|
|
718
759
|
|
|
719
|
-
|
|
760
|
+
function EmptyLibraryStarter({ onCreateBlank }: { onCreateBlank: () => void }) {
|
|
761
|
+
const t = useT();
|
|
762
|
+
const navigate = useNavigate();
|
|
763
|
+
const { data: presetData } = useActionQuery("list-library-presets", {});
|
|
764
|
+
const createFromPreset = useActionMutation("create-library-from-preset");
|
|
765
|
+
const [creatingPresetId, setCreatingPresetId] = useState<string | null>(null);
|
|
766
|
+
const presets = ((presetData as any)?.presets ?? []) as LibraryPreset[];
|
|
767
|
+
|
|
768
|
+
function createPresetLibrary(presetId: string) {
|
|
769
|
+
setCreatingPresetId(presetId);
|
|
770
|
+
createFromPreset.mutate(
|
|
771
|
+
{ presetId } as any,
|
|
772
|
+
{
|
|
773
|
+
onSuccess: (library: any) => {
|
|
774
|
+
setCreatingPresetId(null);
|
|
775
|
+
navigate(`/library/${library.id}`);
|
|
776
|
+
},
|
|
777
|
+
onError: (error: Error) => {
|
|
778
|
+
setCreatingPresetId(null);
|
|
779
|
+
toast.error(error.message || t("brandKits.presetCreateFailed"));
|
|
780
|
+
},
|
|
781
|
+
} as any,
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return (
|
|
786
|
+
<div className="mx-auto flex min-h-full max-w-5xl flex-col justify-center px-6 py-10">
|
|
787
|
+
<div className="mx-auto max-w-2xl text-center">
|
|
788
|
+
<IconLibraryPhoto className="mx-auto h-10 w-10 text-muted-foreground" />
|
|
789
|
+
<h2 className="mt-4 text-xl font-semibold tracking-tight">
|
|
790
|
+
{t("library.buildYourFirstKit")}
|
|
791
|
+
</h2>
|
|
792
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
793
|
+
{t("library.buildYourFirstKitDescription")}
|
|
794
|
+
</p>
|
|
795
|
+
</div>
|
|
796
|
+
<div className="mt-6">
|
|
797
|
+
<LibraryPresetGrid
|
|
798
|
+
presets={presets}
|
|
799
|
+
creatingId={creatingPresetId}
|
|
800
|
+
onCreate={createPresetLibrary}
|
|
801
|
+
/>
|
|
802
|
+
</div>
|
|
803
|
+
<div className="mt-5 flex justify-center">
|
|
804
|
+
<Button variant="outline" onClick={onCreateBlank}>
|
|
805
|
+
{t("library.createBlankKit")}
|
|
806
|
+
</Button>
|
|
807
|
+
</div>
|
|
808
|
+
</div>
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function LibraryShellHeader({
|
|
813
|
+
selectedLibraryId = null,
|
|
814
|
+
libraries,
|
|
815
|
+
isLoading,
|
|
816
|
+
onCreateKit,
|
|
817
|
+
}: {
|
|
818
|
+
selectedLibraryId?: string | null;
|
|
819
|
+
libraries: ImageLibrarySummary[];
|
|
820
|
+
isLoading?: boolean;
|
|
821
|
+
onCreateKit: () => void;
|
|
822
|
+
}) {
|
|
823
|
+
const t = useT();
|
|
824
|
+
const selectedLibrary = selectedLibraryId
|
|
825
|
+
? libraries.find((library) => library.id === selectedLibraryId)
|
|
826
|
+
: null;
|
|
827
|
+
const title =
|
|
828
|
+
selectedLibrary?.title ??
|
|
829
|
+
(selectedLibraryId ? t("library.library") : t("library.allAssets"));
|
|
830
|
+
const visibility = (selectedLibrary as any)?.visibility;
|
|
831
|
+
|
|
832
|
+
return (
|
|
833
|
+
<header className="border-b border-border bg-background px-4 py-3 md:px-6">
|
|
834
|
+
<div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
835
|
+
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
836
|
+
<LibraryKitSelector
|
|
837
|
+
selectedLibraryId={selectedLibraryId}
|
|
838
|
+
libraries={libraries}
|
|
839
|
+
isLoading={isLoading}
|
|
840
|
+
triggerLabel={title}
|
|
841
|
+
triggerStyle="title"
|
|
842
|
+
onCreateKit={onCreateKit}
|
|
843
|
+
/>
|
|
844
|
+
{visibility ? (
|
|
845
|
+
<Badge variant="outline" className="h-6 rounded-full px-2 text-xs">
|
|
846
|
+
{visibility}
|
|
847
|
+
</Badge>
|
|
848
|
+
) : null}
|
|
849
|
+
</div>
|
|
850
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2 self-start sm:self-auto sm:flex-nowrap">
|
|
851
|
+
{selectedLibraryId ? (
|
|
852
|
+
<div
|
|
853
|
+
id="assets-library-detail-primary-actions"
|
|
854
|
+
className="contents"
|
|
855
|
+
aria-label={t("library.primaryKitActions")}
|
|
856
|
+
/>
|
|
857
|
+
) : null}
|
|
858
|
+
<Button
|
|
859
|
+
size="sm"
|
|
860
|
+
className="h-8 shrink-0 gap-1.5"
|
|
861
|
+
onClick={onCreateKit}
|
|
862
|
+
>
|
|
863
|
+
<IconPhotoPlus className="h-4 w-4" />
|
|
864
|
+
{t("library.newKit")}
|
|
865
|
+
</Button>
|
|
866
|
+
{selectedLibraryId ? (
|
|
867
|
+
<div
|
|
868
|
+
id="assets-library-detail-more-actions"
|
|
869
|
+
className="contents"
|
|
870
|
+
aria-label={t("library.kitActions")}
|
|
871
|
+
/>
|
|
872
|
+
) : null}
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
</header>
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function LibraryKitSelector({
|
|
880
|
+
selectedLibraryId = null,
|
|
881
|
+
libraries,
|
|
882
|
+
isLoading,
|
|
883
|
+
triggerLabel,
|
|
884
|
+
triggerStyle = "button",
|
|
885
|
+
onCreateKit,
|
|
886
|
+
}: {
|
|
887
|
+
selectedLibraryId?: string | null;
|
|
888
|
+
libraries: ImageLibrarySummary[];
|
|
889
|
+
isLoading?: boolean;
|
|
890
|
+
triggerLabel?: string;
|
|
891
|
+
triggerStyle?: "button" | "title";
|
|
892
|
+
onCreateKit: () => void;
|
|
893
|
+
}) {
|
|
894
|
+
const t = useT();
|
|
895
|
+
const navigate = useNavigate();
|
|
896
|
+
const [open, setOpen] = useState(false);
|
|
897
|
+
const [query, setQuery] = useState("");
|
|
898
|
+
const selectedLibrary = selectedLibraryId
|
|
899
|
+
? libraries.find((library) => library.id === selectedLibraryId)
|
|
900
|
+
: null;
|
|
901
|
+
const filteredLibraries = useMemo(() => {
|
|
902
|
+
const items = sortLibrariesByUsage(libraries.filter(Boolean));
|
|
903
|
+
const q = query.trim().toLowerCase();
|
|
904
|
+
if (!q) return items;
|
|
905
|
+
return items.filter((library) =>
|
|
906
|
+
[library.title, library.description]
|
|
907
|
+
.filter(Boolean)
|
|
908
|
+
.join(" ")
|
|
909
|
+
.toLowerCase()
|
|
910
|
+
.includes(q),
|
|
911
|
+
);
|
|
912
|
+
}, [libraries, query]);
|
|
913
|
+
|
|
914
|
+
function selectLibrary(libraryId: string | null) {
|
|
915
|
+
setOpen(false);
|
|
916
|
+
navigate(libraryId ? `/library/${libraryId}` : "/library");
|
|
917
|
+
}
|
|
918
|
+
const titleTrigger = triggerStyle === "title";
|
|
919
|
+
|
|
920
|
+
return (
|
|
921
|
+
<Popover
|
|
922
|
+
open={open}
|
|
923
|
+
onOpenChange={(next) => {
|
|
924
|
+
setOpen(next);
|
|
925
|
+
if (!next) setQuery("");
|
|
926
|
+
}}
|
|
927
|
+
>
|
|
928
|
+
<PopoverTrigger asChild>
|
|
929
|
+
{titleTrigger ? (
|
|
930
|
+
<button
|
|
931
|
+
type="button"
|
|
932
|
+
className="-ml-1.5 inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-left text-xl font-semibold leading-tight tracking-tight transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
933
|
+
>
|
|
934
|
+
<span className="block min-w-0 max-w-[min(48rem,calc(100vw-7rem))] truncate sm:max-w-none">
|
|
935
|
+
{triggerLabel ?? selectedLibrary?.title ?? t("library.allAssets")}
|
|
936
|
+
</span>
|
|
937
|
+
<IconChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
938
|
+
</button>
|
|
939
|
+
) : (
|
|
940
|
+
<Button
|
|
941
|
+
variant="outline"
|
|
942
|
+
size="sm"
|
|
943
|
+
className="h-8 max-w-[18rem] gap-1.5 px-2.5"
|
|
944
|
+
>
|
|
945
|
+
<IconLibraryPhoto className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
946
|
+
<span className="min-w-0 truncate">
|
|
947
|
+
{triggerLabel ?? selectedLibrary?.title ?? t("library.allAssets")}
|
|
948
|
+
</span>
|
|
949
|
+
<IconChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
950
|
+
</Button>
|
|
951
|
+
)}
|
|
952
|
+
</PopoverTrigger>
|
|
953
|
+
<PopoverContent
|
|
954
|
+
align="start"
|
|
955
|
+
className="w-[min(25rem,calc(100vw-2rem))] p-2"
|
|
956
|
+
>
|
|
957
|
+
<div className="flex h-9 items-center gap-2 rounded-md border border-border bg-background px-2.5">
|
|
958
|
+
<IconSearch className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
959
|
+
<input
|
|
960
|
+
value={query}
|
|
961
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
962
|
+
placeholder={t("library.searchKits")}
|
|
963
|
+
className="h-full min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
964
|
+
/>
|
|
965
|
+
</div>
|
|
966
|
+
<div className="mt-2 max-h-72 overflow-y-auto">
|
|
967
|
+
<button
|
|
968
|
+
type="button"
|
|
969
|
+
onClick={() => selectLibrary(null)}
|
|
970
|
+
className={cn(
|
|
971
|
+
"flex w-full min-w-0 items-center gap-3 rounded-md px-2 py-2 text-left transition hover:bg-accent",
|
|
972
|
+
!selectedLibraryId && "bg-accent",
|
|
973
|
+
)}
|
|
974
|
+
>
|
|
975
|
+
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground">
|
|
976
|
+
<IconLibraryPhoto className="h-4 w-4" />
|
|
977
|
+
</span>
|
|
978
|
+
<span className="min-w-0 flex-1">
|
|
979
|
+
<span className="block truncate text-sm font-medium">
|
|
980
|
+
{t("library.allAssets")}
|
|
981
|
+
</span>
|
|
982
|
+
<span className="block truncate text-xs text-muted-foreground">
|
|
983
|
+
{t("library.allAssetsDescription")}
|
|
984
|
+
</span>
|
|
985
|
+
</span>
|
|
986
|
+
{!selectedLibraryId ? <IconCheck className="h-4 w-4" /> : null}
|
|
987
|
+
</button>
|
|
988
|
+
|
|
989
|
+
{isLoading ? (
|
|
990
|
+
<div className="mt-2 grid gap-1.5">
|
|
991
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
992
|
+
<Skeleton key={index} className="h-12 rounded-md" />
|
|
993
|
+
))}
|
|
994
|
+
</div>
|
|
995
|
+
) : filteredLibraries.length ? (
|
|
996
|
+
<div className="mt-1 grid gap-1">
|
|
997
|
+
{filteredLibraries.map((library) => {
|
|
998
|
+
const selected = selectedLibraryId === library.id;
|
|
999
|
+
return (
|
|
1000
|
+
<button
|
|
1001
|
+
key={library.id}
|
|
1002
|
+
type="button"
|
|
1003
|
+
onClick={() => selectLibrary(library.id)}
|
|
1004
|
+
className={cn(
|
|
1005
|
+
"flex w-full min-w-0 items-center gap-3 rounded-md px-2 py-2 text-left transition hover:bg-accent",
|
|
1006
|
+
selected && "bg-accent",
|
|
1007
|
+
)}
|
|
1008
|
+
>
|
|
1009
|
+
<span className="min-w-0 flex-1">
|
|
1010
|
+
<span className="block truncate text-sm font-medium">
|
|
1011
|
+
{library.title}
|
|
1012
|
+
</span>
|
|
1013
|
+
<span className="mt-1 flex min-w-0 flex-wrap items-center gap-1.5">
|
|
1014
|
+
<Badge variant="secondary" className="px-1.5 py-0">
|
|
1015
|
+
{t("library.referenceCount", {
|
|
1016
|
+
count: library.referenceCount ?? 0,
|
|
1017
|
+
})}
|
|
1018
|
+
</Badge>
|
|
1019
|
+
<Badge variant="outline" className="px-1.5 py-0">
|
|
1020
|
+
{t("library.assetCount", {
|
|
1021
|
+
count: library.generatedCount ?? 0,
|
|
1022
|
+
})}
|
|
1023
|
+
</Badge>
|
|
1024
|
+
</span>
|
|
1025
|
+
</span>
|
|
1026
|
+
{selected ? <IconCheck className="h-4 w-4" /> : null}
|
|
1027
|
+
</button>
|
|
1028
|
+
);
|
|
1029
|
+
})}
|
|
1030
|
+
</div>
|
|
1031
|
+
) : (
|
|
1032
|
+
<div className="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
1033
|
+
{t("library.noKitsMatch")}
|
|
1034
|
+
</div>
|
|
1035
|
+
)}
|
|
1036
|
+
</div>
|
|
1037
|
+
<div className="mt-2 border-t border-border pt-2">
|
|
1038
|
+
<Button
|
|
1039
|
+
variant="ghost"
|
|
1040
|
+
size="sm"
|
|
1041
|
+
className="h-8 w-full justify-start gap-2"
|
|
1042
|
+
onClick={() => {
|
|
1043
|
+
setOpen(false);
|
|
1044
|
+
onCreateKit();
|
|
1045
|
+
}}
|
|
1046
|
+
>
|
|
1047
|
+
<IconPhotoPlus className="h-4 w-4" />
|
|
1048
|
+
{t("library.newKit")}
|
|
1049
|
+
</Button>
|
|
1050
|
+
</div>
|
|
1051
|
+
</PopoverContent>
|
|
1052
|
+
</Popover>
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function AllAssetsBrowser() {
|
|
1057
|
+
const t = useT();
|
|
1058
|
+
const navigate = useNavigate();
|
|
1059
|
+
const [query, setQuery] = useState("");
|
|
1060
|
+
const [assetTab, setAssetTab] = useState<AssetTab>("all");
|
|
1061
|
+
const [previewAsset, setPreviewAsset] = useState<Asset | null>(null);
|
|
1062
|
+
const [standaloneSelection, setStandaloneSelection] = useState<ReturnType<
|
|
1063
|
+
typeof assetPayload
|
|
1064
|
+
> | null>(null);
|
|
1065
|
+
const [standaloneCopyOk, setStandaloneCopyOk] = useState(false);
|
|
1066
|
+
|
|
1067
|
+
const { data: assetData, isLoading } = useActionQuery("list-assets", {
|
|
1068
|
+
query: query.trim() || undefined,
|
|
1069
|
+
} as any) as {
|
|
1070
|
+
data?: { assets?: Asset[] };
|
|
1071
|
+
isLoading: boolean;
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
const allAssets = assetData?.assets ?? [];
|
|
1075
|
+
const assets = useMemo(
|
|
1076
|
+
() => allAssets.filter((asset) => assetMatchesTab(asset, assetTab)),
|
|
1077
|
+
[allAssets, assetTab],
|
|
1078
|
+
);
|
|
1079
|
+
const visibleAssetCount = assets.length;
|
|
1080
|
+
const assetCountLabel = isLoading
|
|
1081
|
+
? t("library.loading")
|
|
1082
|
+
: query.trim() || assetTab !== "all"
|
|
1083
|
+
? t("library.shownCount", { count: visibleAssetCount })
|
|
1084
|
+
: t("library.assetCount", { count: allAssets.length });
|
|
1085
|
+
const standaloneSelectionText = useMemo(
|
|
1086
|
+
() =>
|
|
1087
|
+
standaloneSelection
|
|
1088
|
+
? selectedAssetClipboardText(standaloneSelection)
|
|
1089
|
+
: "",
|
|
1090
|
+
[standaloneSelection],
|
|
1091
|
+
);
|
|
1092
|
+
const standaloneSelectionUrl =
|
|
1093
|
+
standaloneSelection?.url ??
|
|
1094
|
+
standaloneSelection?.downloadUrl ??
|
|
1095
|
+
standaloneSelection?.previewUrl;
|
|
1096
|
+
|
|
1097
|
+
const copyStandaloneSelection = useCallback(
|
|
1098
|
+
async (payload: ReturnType<typeof assetPayload>) => {
|
|
1099
|
+
const text = selectedAssetClipboardText(payload);
|
|
1100
|
+
try {
|
|
1101
|
+
await navigator.clipboard.writeText(text);
|
|
1102
|
+
setStandaloneCopyOk(true);
|
|
1103
|
+
toast.success(t("library.selectionCopied"));
|
|
1104
|
+
} catch {
|
|
1105
|
+
setStandaloneCopyOk(false);
|
|
1106
|
+
toast.info(t("library.selectionReady"));
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
[],
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1112
|
+
function chooseAsset(asset: Asset) {
|
|
1113
|
+
const payload = assetPayload(asset, "image");
|
|
1114
|
+
setStandaloneSelection(payload);
|
|
1115
|
+
setStandaloneCopyOk(false);
|
|
1116
|
+
void copyStandaloneSelection(payload);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return (
|
|
1120
|
+
<div className="flex min-w-0 flex-col">
|
|
1121
|
+
<div className="border-b border-border px-4 py-3 md:px-6">
|
|
1122
|
+
<div className="flex w-full min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
1123
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
1124
|
+
<Tabs
|
|
1125
|
+
value={assetTab}
|
|
1126
|
+
onValueChange={(value) => setAssetTab(value as AssetTab)}
|
|
1127
|
+
>
|
|
1128
|
+
<TabsList className="h-9">
|
|
1129
|
+
<TabsTrigger value="all">{t("library.tabsAll")}</TabsTrigger>
|
|
1130
|
+
<TabsTrigger value="generated">
|
|
1131
|
+
{t("library.generated")}
|
|
1132
|
+
</TabsTrigger>
|
|
1133
|
+
<TabsTrigger value="references">
|
|
1134
|
+
{t("library.references")}
|
|
1135
|
+
</TabsTrigger>
|
|
1136
|
+
</TabsList>
|
|
1137
|
+
</Tabs>
|
|
1138
|
+
<Badge
|
|
1139
|
+
variant="secondary"
|
|
1140
|
+
className="h-6 max-w-full rounded-full px-2 text-xs"
|
|
1141
|
+
>
|
|
1142
|
+
{assetCountLabel}
|
|
1143
|
+
</Badge>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div className="flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border/70 bg-background px-3 focus-within:ring-1 focus-within:ring-ring sm:max-w-sm">
|
|
1146
|
+
<IconSearch className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
1147
|
+
<input
|
|
1148
|
+
type="search"
|
|
1149
|
+
value={query}
|
|
1150
|
+
onInput={(event) => setQuery(event.currentTarget.value)}
|
|
1151
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
1152
|
+
placeholder={t("library.searchAssets")}
|
|
1153
|
+
className="h-full min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
1154
|
+
/>
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
|
|
1159
|
+
{standaloneSelection && (
|
|
1160
|
+
<section className="shrink-0 overflow-hidden border-b border-border bg-muted/30 px-4 py-3 md:px-6">
|
|
1161
|
+
<div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
1162
|
+
<div className="min-w-0 flex-1">
|
|
1163
|
+
<div className="truncate text-sm font-medium">
|
|
1164
|
+
{selectedAssetLabel(standaloneSelection)}
|
|
1165
|
+
</div>
|
|
1166
|
+
{standaloneSelectionUrl && (
|
|
1167
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1168
|
+
{standaloneSelectionUrl}
|
|
1169
|
+
</div>
|
|
1170
|
+
)}
|
|
1171
|
+
</div>
|
|
1172
|
+
<div className="flex w-full min-w-0 flex-wrap gap-2 sm:w-auto sm:flex-nowrap sm:justify-end">
|
|
1173
|
+
<Button
|
|
1174
|
+
variant="outline"
|
|
1175
|
+
size="sm"
|
|
1176
|
+
className="h-8 shrink-0 gap-1.5"
|
|
1177
|
+
onClick={() => copyStandaloneSelection(standaloneSelection)}
|
|
1178
|
+
>
|
|
1179
|
+
{standaloneCopyOk ? (
|
|
1180
|
+
<IconCheck className="h-3.5 w-3.5" />
|
|
1181
|
+
) : (
|
|
1182
|
+
<IconClipboard className="h-3.5 w-3.5" />
|
|
1183
|
+
)}
|
|
1184
|
+
{standaloneCopyOk ? t("library.copied") : t("library.copy")}
|
|
1185
|
+
</Button>
|
|
1186
|
+
<Button
|
|
1187
|
+
asChild
|
|
1188
|
+
variant="ghost"
|
|
1189
|
+
size="sm"
|
|
1190
|
+
className="h-8 shrink-0 gap-1.5"
|
|
1191
|
+
>
|
|
1192
|
+
<Link
|
|
1193
|
+
to={`/asset/${encodeURIComponent(
|
|
1194
|
+
standaloneSelection.assetId,
|
|
1195
|
+
)}`}
|
|
1196
|
+
>
|
|
1197
|
+
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
1198
|
+
{t("library.open")}
|
|
1199
|
+
</Link>
|
|
1200
|
+
</Button>
|
|
1201
|
+
<Button
|
|
1202
|
+
variant="ghost"
|
|
1203
|
+
size="icon"
|
|
1204
|
+
title={t("library.close")}
|
|
1205
|
+
aria-label={t("library.close")}
|
|
1206
|
+
className="h-8 w-8 shrink-0"
|
|
1207
|
+
onClick={() => {
|
|
1208
|
+
setStandaloneSelection(null);
|
|
1209
|
+
setStandaloneCopyOk(false);
|
|
1210
|
+
}}
|
|
1211
|
+
>
|
|
1212
|
+
<IconX className="h-3.5 w-3.5" />
|
|
1213
|
+
</Button>
|
|
1214
|
+
</div>
|
|
1215
|
+
</div>
|
|
1216
|
+
<details className="mt-2">
|
|
1217
|
+
<summary className="cursor-pointer select-none text-xs text-muted-foreground hover:text-foreground">
|
|
1218
|
+
{t("library.showPasteText")}
|
|
1219
|
+
</summary>
|
|
1220
|
+
<Textarea
|
|
1221
|
+
readOnly
|
|
1222
|
+
value={standaloneSelectionText}
|
|
1223
|
+
className="mt-2 h-24 max-w-full resize-none border-border/70 bg-background font-mono text-[11px] leading-relaxed"
|
|
1224
|
+
onFocus={(event) => event.currentTarget.select()}
|
|
1225
|
+
/>
|
|
1226
|
+
</details>
|
|
1227
|
+
</section>
|
|
1228
|
+
)}
|
|
1229
|
+
|
|
1230
|
+
<main className="p-4 md:p-6">
|
|
1231
|
+
{isLoading ? (
|
|
1232
|
+
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
|
1233
|
+
{Array.from({ length: 12 }).map((_, index) => (
|
|
1234
|
+
<Skeleton key={index} className="aspect-[4/3] rounded-lg" />
|
|
1235
|
+
))}
|
|
1236
|
+
</div>
|
|
1237
|
+
) : assets.length === 0 ? (
|
|
1238
|
+
<div className="flex min-h-64 items-center justify-center text-center">
|
|
1239
|
+
<div className="max-w-sm text-sm text-muted-foreground">
|
|
1240
|
+
{query
|
|
1241
|
+
? t("library.noMatchingAssets")
|
|
1242
|
+
: t("library.noReusableAssets")}
|
|
1243
|
+
</div>
|
|
1244
|
+
</div>
|
|
1245
|
+
) : (
|
|
1246
|
+
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
|
1247
|
+
{assets.map((asset) => (
|
|
1248
|
+
<div
|
|
1249
|
+
key={asset.id}
|
|
1250
|
+
className="group relative overflow-hidden rounded-lg border border-border/80 bg-background transition hover:border-foreground/25 hover:bg-muted/10 focus-within:ring-2 focus-within:ring-ring"
|
|
1251
|
+
>
|
|
1252
|
+
<button
|
|
1253
|
+
type="button"
|
|
1254
|
+
aria-label={t("library.selectAsset", {
|
|
1255
|
+
title: assetDisplayTitle(asset),
|
|
1256
|
+
})}
|
|
1257
|
+
onClick={() => setPreviewAsset(asset)}
|
|
1258
|
+
title={assetDisplayTitle(asset)}
|
|
1259
|
+
className="block w-full text-left focus-visible:outline-none"
|
|
1260
|
+
>
|
|
1261
|
+
<div className="aspect-[4/3] bg-muted/40">
|
|
1262
|
+
{asset.mediaType === "video" ||
|
|
1263
|
+
asset.mimeType?.startsWith("video/") ? (
|
|
1264
|
+
<video
|
|
1265
|
+
src={asset.previewUrl ?? asset.downloadUrl ?? asset.url}
|
|
1266
|
+
poster={asset.thumbnailUrl}
|
|
1267
|
+
muted
|
|
1268
|
+
playsInline
|
|
1269
|
+
className="h-full w-full object-cover transition group-hover:scale-[1.02]"
|
|
1270
|
+
/>
|
|
1271
|
+
) : (
|
|
1272
|
+
<AssetThumbnail asset={asset} />
|
|
1273
|
+
)}
|
|
1274
|
+
</div>
|
|
1275
|
+
</button>
|
|
1276
|
+
{(asset as any).libraryTitle ? (
|
|
1277
|
+
<button
|
|
1278
|
+
type="button"
|
|
1279
|
+
onClick={() => navigate(`/library/${asset.libraryId}`)}
|
|
1280
|
+
className="absolute bottom-2 left-2 z-10 max-w-[calc(100%-1rem)] truncate rounded-full bg-background/90 px-2.5 py-1 text-[11px] font-medium shadow-sm backdrop-blur transition hover:bg-background"
|
|
1281
|
+
>
|
|
1282
|
+
{(asset as any).libraryTitle}
|
|
1283
|
+
</button>
|
|
1284
|
+
) : null}
|
|
1285
|
+
<TooltipProvider>
|
|
1286
|
+
<Tooltip>
|
|
1287
|
+
<TooltipTrigger asChild>
|
|
1288
|
+
<button
|
|
1289
|
+
type="button"
|
|
1290
|
+
aria-label={t("library.copyToClipboard")}
|
|
1291
|
+
onClick={(event) => {
|
|
1292
|
+
event.stopPropagation();
|
|
1293
|
+
chooseAsset(asset);
|
|
1294
|
+
}}
|
|
1295
|
+
className="absolute right-2 top-2 z-10 inline-flex h-8 w-8 items-center justify-center rounded-full bg-background/80 text-foreground opacity-0 shadow-sm backdrop-blur transition hover:bg-primary hover:text-primary-foreground focus:outline-none focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100"
|
|
1296
|
+
>
|
|
1297
|
+
<IconClipboard className="h-4 w-4" />
|
|
1298
|
+
</button>
|
|
1299
|
+
</TooltipTrigger>
|
|
1300
|
+
<TooltipContent>
|
|
1301
|
+
{t("library.copyToClipboard")}
|
|
1302
|
+
</TooltipContent>
|
|
1303
|
+
</Tooltip>
|
|
1304
|
+
</TooltipProvider>
|
|
1305
|
+
</div>
|
|
1306
|
+
))}
|
|
1307
|
+
</div>
|
|
1308
|
+
)}
|
|
1309
|
+
</main>
|
|
1310
|
+
|
|
1311
|
+
<AssetPreviewDialog
|
|
1312
|
+
asset={previewAsset}
|
|
1313
|
+
assets={assets}
|
|
1314
|
+
onAssetChange={setPreviewAsset}
|
|
1315
|
+
/>
|
|
1316
|
+
</div>
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function AssetPreviewDialog({
|
|
1321
|
+
asset,
|
|
1322
|
+
assets,
|
|
1323
|
+
onAssetChange,
|
|
1324
|
+
}: {
|
|
1325
|
+
asset: Asset | null;
|
|
1326
|
+
assets: Asset[];
|
|
1327
|
+
onAssetChange: (asset: Asset | null) => void;
|
|
1328
|
+
}) {
|
|
1329
|
+
const t = useT();
|
|
1330
|
+
return (
|
|
1331
|
+
<Dialog
|
|
1332
|
+
open={Boolean(asset)}
|
|
1333
|
+
onOpenChange={(open) => {
|
|
1334
|
+
if (!open) onAssetChange(null);
|
|
1335
|
+
}}
|
|
1336
|
+
>
|
|
1337
|
+
{asset &&
|
|
1338
|
+
(() => {
|
|
1339
|
+
const previewIndex = assets.findIndex(
|
|
1340
|
+
(candidate) => candidate.id === asset.id,
|
|
1341
|
+
);
|
|
1342
|
+
const hasPrev = previewIndex > 0;
|
|
1343
|
+
const hasNext = previewIndex >= 0 && previewIndex < assets.length - 1;
|
|
1344
|
+
const showPreviousAsset = () => {
|
|
1345
|
+
if (hasPrev) onAssetChange(assets[previewIndex - 1]);
|
|
1346
|
+
};
|
|
1347
|
+
const showNextAsset = () => {
|
|
1348
|
+
if (hasNext) onAssetChange(assets[previewIndex + 1]);
|
|
1349
|
+
};
|
|
1350
|
+
return (
|
|
1351
|
+
<DialogContent
|
|
1352
|
+
hideClose
|
|
1353
|
+
onKeyDown={(event) => {
|
|
1354
|
+
if (event.key === "ArrowLeft") showPreviousAsset();
|
|
1355
|
+
if (event.key === "ArrowRight") showNextAsset();
|
|
1356
|
+
}}
|
|
1357
|
+
className="max-w-4xl border-0 bg-transparent p-0 shadow-none"
|
|
1358
|
+
>
|
|
1359
|
+
<DialogTitle className="sr-only">
|
|
1360
|
+
{assetDisplayTitle(asset)}
|
|
1361
|
+
</DialogTitle>
|
|
1362
|
+
<DialogDescription className="sr-only">
|
|
1363
|
+
{t("library.fullSizePreview", {
|
|
1364
|
+
title: assetDisplayTitle(asset),
|
|
1365
|
+
})}
|
|
1366
|
+
</DialogDescription>
|
|
1367
|
+
<div className="relative">
|
|
1368
|
+
<div className="absolute right-2 top-2 z-10 flex items-center gap-2">
|
|
1369
|
+
<Button asChild variant="outline" size="sm">
|
|
1370
|
+
<Link to={`/asset/${encodeURIComponent(asset.id)}`}>
|
|
1371
|
+
{t("library.viewDetails")}
|
|
1372
|
+
</Link>
|
|
1373
|
+
</Button>
|
|
1374
|
+
<DialogClose
|
|
1375
|
+
aria-label={t("library.closePreview")}
|
|
1376
|
+
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
1377
|
+
>
|
|
1378
|
+
<IconX className="h-5 w-5" />
|
|
1379
|
+
</DialogClose>
|
|
1380
|
+
</div>
|
|
1381
|
+
{asset.mediaType === "video" ||
|
|
1382
|
+
asset.mimeType?.startsWith("video/") ? (
|
|
1383
|
+
<video
|
|
1384
|
+
src={asset.previewUrl ?? asset.downloadUrl ?? asset.url}
|
|
1385
|
+
poster={asset.thumbnailUrl}
|
|
1386
|
+
controls
|
|
1387
|
+
autoPlay
|
|
1388
|
+
playsInline
|
|
1389
|
+
className="max-h-[85vh] w-full rounded-lg bg-black object-contain"
|
|
1390
|
+
/>
|
|
1391
|
+
) : (
|
|
1392
|
+
<AssetOverlayImage asset={asset} />
|
|
1393
|
+
)}
|
|
1394
|
+
</div>
|
|
1395
|
+
{(hasPrev || hasNext) && (
|
|
1396
|
+
<div className="mt-5 flex justify-center gap-2">
|
|
1397
|
+
<button
|
|
1398
|
+
type="button"
|
|
1399
|
+
aria-label={t("library.previousImage")}
|
|
1400
|
+
onClick={showPreviousAsset}
|
|
1401
|
+
disabled={!hasPrev}
|
|
1402
|
+
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white disabled:cursor-not-allowed disabled:opacity-40"
|
|
1403
|
+
>
|
|
1404
|
+
<IconChevronLeft className="h-5 w-5" />
|
|
1405
|
+
</button>
|
|
1406
|
+
<button
|
|
1407
|
+
type="button"
|
|
1408
|
+
aria-label={t("library.nextImage")}
|
|
1409
|
+
onClick={showNextAsset}
|
|
1410
|
+
disabled={!hasNext}
|
|
1411
|
+
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white disabled:cursor-not-allowed disabled:opacity-40"
|
|
1412
|
+
>
|
|
1413
|
+
<IconChevronRight className="h-5 w-5" />
|
|
1414
|
+
</button>
|
|
1415
|
+
</div>
|
|
1416
|
+
)}
|
|
1417
|
+
</DialogContent>
|
|
1418
|
+
);
|
|
1419
|
+
})()}
|
|
1420
|
+
</Dialog>
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function variantSlotTime(slot: AssetVariantState["slots"][number]): number {
|
|
1425
|
+
const raw = slot.updatedAt ?? slot.createdAt ?? "";
|
|
1426
|
+
const time = Date.parse(raw);
|
|
1427
|
+
return Number.isNaN(time) ? 0 : time;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function assetVariantStateKey(scopeId?: string | null) {
|
|
1431
|
+
const scopedId = typeof scopeId === "string" ? scopeId.trim() : "";
|
|
1432
|
+
return scopedId ? `asset-variants:${scopedId}` : "asset-variants";
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function referenceRoleForLibraryCandidate(asset?: Asset | null) {
|
|
1436
|
+
if (asset?.mediaType === "video" || asset?.mimeType?.startsWith("video/")) {
|
|
1437
|
+
return "video_reference";
|
|
1438
|
+
}
|
|
1439
|
+
return "style_reference";
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function setLibraryAssetSavedInCache(
|
|
1443
|
+
queryClient: ReturnType<typeof useQueryClient>,
|
|
1444
|
+
libraryId: string,
|
|
1445
|
+
assetId: string,
|
|
1446
|
+
savedAsset: unknown,
|
|
1447
|
+
) {
|
|
1448
|
+
queryClient.setQueryData(
|
|
1449
|
+
["action", "get-library", { id: libraryId }],
|
|
1450
|
+
(current: any) => {
|
|
1451
|
+
if (!current || !Array.isArray(current.assets)) return current;
|
|
1452
|
+
let changed = false;
|
|
1453
|
+
const assets = current.assets.map((asset: any) => {
|
|
1454
|
+
if (asset.id !== assetId) return asset;
|
|
1455
|
+
changed = true;
|
|
1456
|
+
return {
|
|
1457
|
+
...asset,
|
|
1458
|
+
...(savedAsset && typeof savedAsset === "object" ? savedAsset : {}),
|
|
1459
|
+
status: "saved",
|
|
1460
|
+
};
|
|
1461
|
+
});
|
|
1462
|
+
return changed ? { ...current, assets } : current;
|
|
1463
|
+
},
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function setLibraryAssetReferenceInCache(
|
|
1468
|
+
queryClient: ReturnType<typeof useQueryClient>,
|
|
1469
|
+
libraryId: string,
|
|
1470
|
+
assetId: string,
|
|
1471
|
+
) {
|
|
1472
|
+
queryClient.setQueryData(
|
|
1473
|
+
["action", "get-library", { id: libraryId }],
|
|
1474
|
+
(current: any) => {
|
|
1475
|
+
if (!current || !Array.isArray(current.assets)) return current;
|
|
1476
|
+
let changed = false;
|
|
1477
|
+
const assets = current.assets.map((asset: any) => {
|
|
1478
|
+
if (asset.id !== assetId) return asset;
|
|
1479
|
+
changed = true;
|
|
1480
|
+
return {
|
|
1481
|
+
...asset,
|
|
1482
|
+
status: "reference",
|
|
1483
|
+
role: referenceRoleForLibraryCandidate(asset),
|
|
1484
|
+
updatedAt: new Date().toISOString(),
|
|
1485
|
+
};
|
|
1486
|
+
});
|
|
1487
|
+
return changed ? { ...current, assets } : current;
|
|
1488
|
+
},
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
function updateLibraryVariantSlotsInCache(
|
|
1493
|
+
queryClient: ReturnType<typeof useQueryClient>,
|
|
1494
|
+
shouldRemove: (slot: any) => boolean,
|
|
1495
|
+
variantScopeId?: string | null,
|
|
1496
|
+
) {
|
|
1497
|
+
const stateKeys = new Set([
|
|
1498
|
+
"asset-variants",
|
|
1499
|
+
assetVariantStateKey(variantScopeId),
|
|
1500
|
+
]);
|
|
1501
|
+
for (const stateKey of stateKeys) {
|
|
1502
|
+
queryClient.setQueryData(["app-state", stateKey], (current: any) => {
|
|
1503
|
+
if (!current || !Array.isArray(current.slots)) return current;
|
|
1504
|
+
const slots = current.slots.filter((slot: any) => !shouldRemove(slot));
|
|
1505
|
+
if (slots.length === current.slots.length) return current;
|
|
1506
|
+
if (slots.length === 0) return null;
|
|
1507
|
+
return { ...current, slots, updatedAt: new Date().toISOString() };
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function removeLibraryVariantSlotFromCache(
|
|
1513
|
+
queryClient: ReturnType<typeof useQueryClient>,
|
|
1514
|
+
slot: VariantSlot,
|
|
1515
|
+
variantScopeId?: string | null,
|
|
1516
|
+
) {
|
|
1517
|
+
updateLibraryVariantSlotsInCache(
|
|
1518
|
+
queryClient,
|
|
1519
|
+
(candidate) =>
|
|
1520
|
+
candidate.slotId === slot.slotId ||
|
|
1521
|
+
(!!slot.assetId && candidate.assetId === slot.assetId),
|
|
1522
|
+
variantScopeId,
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function LibraryCandidateStage({
|
|
1527
|
+
activeLibraryId = null,
|
|
1528
|
+
foldersByLibraryId = {},
|
|
1529
|
+
variantScopeId = null,
|
|
1530
|
+
onUseAsset,
|
|
1531
|
+
inline = false,
|
|
1532
|
+
}: {
|
|
1533
|
+
activeLibraryId?: string | null;
|
|
1534
|
+
foldersByLibraryId?: Record<string, any[]>;
|
|
1535
|
+
variantScopeId?: string | null;
|
|
1536
|
+
onUseAsset?: (asset: Asset) => void;
|
|
1537
|
+
inline?: boolean;
|
|
1538
|
+
}) {
|
|
1539
|
+
const t = useT();
|
|
1540
|
+
const queryClient = useQueryClient();
|
|
1541
|
+
const [savingCandidateSlotId, setSavingCandidateSlotId] = useState<
|
|
1542
|
+
string | null
|
|
1543
|
+
>(null);
|
|
1544
|
+
const [promotingReferenceKeys, setPromotingReferenceKeys] = useState<
|
|
1545
|
+
Set<string>
|
|
1546
|
+
>(() => new Set());
|
|
1547
|
+
const { data: variants } = useQuery({
|
|
1548
|
+
queryKey: ["app-state", assetVariantStateKey(variantScopeId)],
|
|
1549
|
+
queryFn: ({ signal }) => {
|
|
1550
|
+
return readClientAppState<AssetVariantState>(
|
|
1551
|
+
assetVariantStateKey(variantScopeId),
|
|
1552
|
+
{ signal },
|
|
1553
|
+
);
|
|
1554
|
+
},
|
|
1555
|
+
refetchInterval: 1000,
|
|
1556
|
+
});
|
|
1557
|
+
const isAllAssetsStage = !activeLibraryId;
|
|
1558
|
+
const liveLibraryId = activeLibraryId ?? variants?.libraryId ?? null;
|
|
1559
|
+
const liveVariantsForLibrary =
|
|
1560
|
+
liveLibraryId && variants?.libraryId === liveLibraryId ? variants : null;
|
|
1561
|
+
const { data: libraryData } = useActionQuery(
|
|
1562
|
+
"get-library",
|
|
1563
|
+
{ id: activeLibraryId ?? "" } as any,
|
|
1564
|
+
{ enabled: Boolean(activeLibraryId) } as any,
|
|
1565
|
+
) as { data?: { library?: Library; assets?: Asset[]; folders?: any[] } };
|
|
1566
|
+
const { data: allCandidateData } = useActionQuery(
|
|
1567
|
+
"list-assets",
|
|
1568
|
+
{ includeCandidates: true, status: "candidate" } as any,
|
|
1569
|
+
{ enabled: isAllAssetsStage } as any,
|
|
1570
|
+
) as { data?: { assets?: Asset[] } };
|
|
1571
|
+
const saveGenerated = useActionMutation("save-generated-image");
|
|
1572
|
+
const updateAsset = useActionMutation("update-asset");
|
|
1573
|
+
const libraryAssets = isAllAssetsStage
|
|
1574
|
+
? (allCandidateData?.assets ?? [])
|
|
1575
|
+
: (libraryData?.assets ?? []);
|
|
1576
|
+
const assetById = useMemo(
|
|
1577
|
+
() => new Map(libraryAssets.map((asset) => [asset.id, asset])),
|
|
1578
|
+
[libraryAssets],
|
|
1579
|
+
);
|
|
1580
|
+
const slots = useMemo(
|
|
1581
|
+
() =>
|
|
1582
|
+
(liveVariantsForLibrary?.slots ?? [])
|
|
1583
|
+
.filter((slot) => ["pending", "ready", "failed"].includes(slot.status))
|
|
1584
|
+
.slice()
|
|
1585
|
+
.sort(
|
|
1586
|
+
(left, right) =>
|
|
1587
|
+
variantSlotTime(right) - variantSlotTime(left) ||
|
|
1588
|
+
right.slotId.localeCompare(left.slotId),
|
|
1589
|
+
),
|
|
1590
|
+
[liveVariantsForLibrary?.slots],
|
|
1591
|
+
);
|
|
1592
|
+
const liveAssetIds = useMemo(
|
|
1593
|
+
() =>
|
|
1594
|
+
new Set(
|
|
1595
|
+
slots
|
|
1596
|
+
.map((slot) => slot.assetId)
|
|
1597
|
+
.filter((assetId): assetId is string => Boolean(assetId)),
|
|
1598
|
+
),
|
|
1599
|
+
[slots],
|
|
1600
|
+
);
|
|
1601
|
+
const draftAssets = useMemo(
|
|
1602
|
+
() =>
|
|
1603
|
+
libraryAssets
|
|
1604
|
+
.filter(
|
|
1605
|
+
(asset) =>
|
|
1606
|
+
asset.status === "candidate" &&
|
|
1607
|
+
isGeneratedAsset(asset) &&
|
|
1608
|
+
!liveAssetIds.has(asset.id),
|
|
1609
|
+
)
|
|
1610
|
+
.slice()
|
|
1611
|
+
.sort((left, right) => String(right.id).localeCompare(String(left.id))),
|
|
1612
|
+
[libraryAssets, liveAssetIds],
|
|
1613
|
+
);
|
|
1614
|
+
const totalCount = slots.length + draftAssets.length;
|
|
1615
|
+
|
|
1616
|
+
if (totalCount === 0) return null;
|
|
1617
|
+
const stageLibraryId = liveLibraryId ?? draftAssets[0]?.libraryId ?? null;
|
|
1618
|
+
if (!stageLibraryId) return null;
|
|
1619
|
+
|
|
1620
|
+
function invalidateStage(
|
|
1621
|
+
libraryIdToInvalidate: string | null = stageLibraryId,
|
|
1622
|
+
) {
|
|
1623
|
+
void queryClient.invalidateQueries({
|
|
1624
|
+
queryKey: ["app-state"],
|
|
1625
|
+
refetchType: "active",
|
|
1626
|
+
});
|
|
1627
|
+
if (libraryIdToInvalidate) {
|
|
1628
|
+
void queryClient.invalidateQueries({
|
|
1629
|
+
queryKey: ["action", "get-library", { id: libraryIdToInvalidate }],
|
|
1630
|
+
refetchType: "active",
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
void queryClient.invalidateQueries({
|
|
1634
|
+
queryKey: ["action", "get-library"],
|
|
1635
|
+
refetchType: "active",
|
|
1636
|
+
});
|
|
1637
|
+
void queryClient.invalidateQueries({
|
|
1638
|
+
queryKey: ["action", "list-assets"],
|
|
1639
|
+
refetchType: "active",
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function setReferencePromoting(key: string, promoting: boolean) {
|
|
1644
|
+
setPromotingReferenceKeys((current) => {
|
|
1645
|
+
const next = new Set(current);
|
|
1646
|
+
if (promoting) next.add(key);
|
|
1647
|
+
else next.delete(key);
|
|
1648
|
+
return next.size === current.size ? current : next;
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
async function handleSaveLiveCandidate(
|
|
1653
|
+
slot: VariantSlot,
|
|
1654
|
+
folderId?: string | null,
|
|
1655
|
+
) {
|
|
1656
|
+
if (savingCandidateSlotId || (!slot.assetId && !slot.slotId)) return;
|
|
1657
|
+
if (!liveLibraryId) return;
|
|
1658
|
+
setSavingCandidateSlotId(slot.slotId);
|
|
1659
|
+
try {
|
|
1660
|
+
const savedAsset = await saveGenerated.mutateAsync({
|
|
1661
|
+
assetId: slot.assetId,
|
|
1662
|
+
slotId: slot.slotId,
|
|
1663
|
+
folderId,
|
|
1664
|
+
} as any);
|
|
1665
|
+
if (slot.assetId) {
|
|
1666
|
+
setLibraryAssetSavedInCache(
|
|
1667
|
+
queryClient,
|
|
1668
|
+
liveLibraryId,
|
|
1669
|
+
slot.assetId,
|
|
1670
|
+
savedAsset,
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
removeLibraryVariantSlotFromCache(queryClient, slot, variantScopeId);
|
|
1674
|
+
invalidateStage(liveLibraryId);
|
|
1675
|
+
toast.success(t("library.savedToLibrary"));
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
toast.error(
|
|
1678
|
+
error instanceof Error
|
|
1679
|
+
? error.message
|
|
1680
|
+
: t("library.couldNotSaveCandidate"),
|
|
1681
|
+
);
|
|
1682
|
+
} finally {
|
|
1683
|
+
setSavingCandidateSlotId(null);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
async function handleSaveDraftCandidate(
|
|
1688
|
+
asset: Asset,
|
|
1689
|
+
folderId?: string | null,
|
|
1690
|
+
) {
|
|
1691
|
+
if (!asset?.id || savingCandidateSlotId) return;
|
|
1692
|
+
const key = `draft:${asset.id}`;
|
|
1693
|
+
setSavingCandidateSlotId(key);
|
|
1694
|
+
const targetLibraryId = asset.libraryId ?? stageLibraryId;
|
|
1695
|
+
try {
|
|
1696
|
+
const savedAsset = await saveGenerated.mutateAsync({
|
|
1697
|
+
assetId: asset.id,
|
|
1698
|
+
folderId,
|
|
1699
|
+
} as any);
|
|
1700
|
+
setLibraryAssetSavedInCache(
|
|
1701
|
+
queryClient,
|
|
1702
|
+
targetLibraryId,
|
|
1703
|
+
asset.id,
|
|
1704
|
+
savedAsset,
|
|
1705
|
+
);
|
|
1706
|
+
invalidateStage(targetLibraryId);
|
|
1707
|
+
toast.success(t("library.savedToLibrary"));
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
toast.error(
|
|
1710
|
+
error instanceof Error ? error.message : t("library.couldNotSaveDraft"),
|
|
1711
|
+
);
|
|
1712
|
+
} finally {
|
|
1713
|
+
setSavingCandidateSlotId(null);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
async function handleMoveToReferences(asset: Asset, slot?: VariantSlot) {
|
|
1718
|
+
const key = slot?.slotId
|
|
1719
|
+
? `slot:${slot.slotId}`
|
|
1720
|
+
: asset?.id
|
|
1721
|
+
? `asset:${asset.id}`
|
|
1722
|
+
: "";
|
|
1723
|
+
if (!asset?.id || !key || promotingReferenceKeys.has(key)) return;
|
|
1724
|
+
setReferencePromoting(key, true);
|
|
1725
|
+
const targetLibraryId = asset.libraryId ?? stageLibraryId;
|
|
1726
|
+
try {
|
|
1727
|
+
await updateAsset.mutateAsync({
|
|
1728
|
+
id: asset.id,
|
|
1729
|
+
status: "reference",
|
|
1730
|
+
role: referenceRoleForLibraryCandidate(asset),
|
|
1731
|
+
} as any);
|
|
1732
|
+
setLibraryAssetReferenceInCache(queryClient, targetLibraryId, asset.id);
|
|
1733
|
+
updateLibraryVariantSlotsInCache(
|
|
1734
|
+
queryClient,
|
|
1735
|
+
(candidate) =>
|
|
1736
|
+
candidate.assetId === asset.id ||
|
|
1737
|
+
(!!slot?.slotId && candidate.slotId === slot.slotId),
|
|
1738
|
+
variantScopeId,
|
|
1739
|
+
);
|
|
1740
|
+
invalidateStage(targetLibraryId);
|
|
1741
|
+
toast.success(t("library.addedToReferences"));
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
toast.error(
|
|
1744
|
+
error instanceof Error
|
|
1745
|
+
? error.message
|
|
1746
|
+
: t("library.couldNotAddToReferences"),
|
|
1747
|
+
);
|
|
1748
|
+
} finally {
|
|
1749
|
+
setReferencePromoting(key, false);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function handleMoveLiveCandidateToReferences(slot: VariantSlot) {
|
|
1754
|
+
if (!slot.assetId) return;
|
|
1755
|
+
void handleMoveToReferences(
|
|
1756
|
+
assetById.get(slot.assetId) ?? {
|
|
1757
|
+
id: slot.assetId,
|
|
1758
|
+
libraryId: liveLibraryId ?? stageLibraryId,
|
|
1759
|
+
mediaType: "image",
|
|
1760
|
+
status: "candidate",
|
|
1761
|
+
},
|
|
1762
|
+
slot,
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function handleUseLiveCandidate(slot: VariantSlot) {
|
|
1767
|
+
if (!slot.assetId || !onUseAsset) return;
|
|
1768
|
+
onUseAsset(
|
|
1769
|
+
assetById.get(slot.assetId) ?? {
|
|
1770
|
+
id: slot.assetId,
|
|
1771
|
+
libraryId: liveLibraryId ?? stageLibraryId,
|
|
1772
|
+
mediaType: "image",
|
|
1773
|
+
status: "candidate",
|
|
1774
|
+
title: t("library.readyCandidate"),
|
|
1775
|
+
previewUrl: slot.previewUrl,
|
|
1776
|
+
thumbnailUrl: slot.thumbnailUrl,
|
|
1777
|
+
downloadUrl: slot.previewUrl,
|
|
1778
|
+
url: slot.previewUrl,
|
|
1779
|
+
},
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
return (
|
|
1784
|
+
<section
|
|
1785
|
+
className={cn(
|
|
1786
|
+
"min-w-0 overflow-hidden",
|
|
1787
|
+
inline
|
|
1788
|
+
? "mb-3"
|
|
1789
|
+
: "shrink-0 border-b border-border bg-background px-3 py-3 sm:px-4 md:px-6",
|
|
1790
|
+
)}
|
|
1791
|
+
>
|
|
1792
|
+
<LiveCandidatesStage
|
|
1793
|
+
slots={slots}
|
|
1794
|
+
draftAssets={draftAssets}
|
|
1795
|
+
libraryId={stageLibraryId}
|
|
1796
|
+
folders={
|
|
1797
|
+
libraryData?.folders ?? foldersByLibraryId[stageLibraryId] ?? []
|
|
1798
|
+
}
|
|
1799
|
+
foldersByLibraryId={foldersByLibraryId}
|
|
1800
|
+
savingSlotId={savingCandidateSlotId}
|
|
1801
|
+
promotingReferenceKeys={promotingReferenceKeys}
|
|
1802
|
+
onSave={(slot, folderId) => {
|
|
1803
|
+
void handleSaveLiveCandidate(slot, folderId);
|
|
1804
|
+
}}
|
|
1805
|
+
onSaveDraft={(asset, folderId) => {
|
|
1806
|
+
void handleSaveDraftCandidate(asset, folderId);
|
|
1807
|
+
}}
|
|
1808
|
+
onMoveToReferences={handleMoveLiveCandidateToReferences}
|
|
1809
|
+
onMoveDraftToReferences={(asset) => {
|
|
1810
|
+
void handleMoveToReferences(asset);
|
|
1811
|
+
}}
|
|
1812
|
+
onUse={onUseAsset ? handleUseLiveCandidate : undefined}
|
|
1813
|
+
onUseDraft={onUseAsset}
|
|
1814
|
+
/>
|
|
1815
|
+
</section>
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function useLibraryRouteSelectedId(explicitId: string | null) {
|
|
1820
|
+
const params = useParams();
|
|
1821
|
+
const location = useLocation();
|
|
1822
|
+
|
|
1823
|
+
return useMemo(() => {
|
|
1824
|
+
if (explicitId) return explicitId;
|
|
1825
|
+
if (typeof params.id === "string" && params.id.trim()) return params.id;
|
|
1826
|
+
|
|
1827
|
+
const segments = location.pathname
|
|
1828
|
+
.split("/")
|
|
1829
|
+
.filter(Boolean)
|
|
1830
|
+
.map((segment) => {
|
|
1831
|
+
try {
|
|
1832
|
+
return decodeURIComponent(segment);
|
|
1833
|
+
} catch {
|
|
1834
|
+
return segment;
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
const libraryIndex = segments.lastIndexOf("library");
|
|
1838
|
+
const nextSegment =
|
|
1839
|
+
libraryIndex >= 0 ? segments[libraryIndex + 1] : undefined;
|
|
1840
|
+
return nextSegment?.trim() || null;
|
|
1841
|
+
}, [explicitId, location.pathname, params.id]);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
export function LibraryWorkspace({
|
|
1845
|
+
selectedLibraryId = null,
|
|
1846
|
+
}: {
|
|
1847
|
+
selectedLibraryId?: string | null;
|
|
1848
|
+
}) {
|
|
1849
|
+
const navigate = useNavigate();
|
|
1850
|
+
const routeSelectedLibraryId = useLibraryRouteSelectedId(selectedLibraryId);
|
|
1851
|
+
const [createOpen, setCreateOpen] = useState(false);
|
|
1852
|
+
const { data, isLoading } = useActionQuery("list-libraries", {
|
|
1853
|
+
includeFolders: true,
|
|
1854
|
+
} as any);
|
|
1855
|
+
const libraries = ((data as any)?.libraries ?? []) as ImageLibrarySummary[];
|
|
1856
|
+
const foldersByLibraryId = useMemo(() => {
|
|
1857
|
+
const result: Record<string, any[]> = {};
|
|
1858
|
+
for (const library of libraries) {
|
|
1859
|
+
result[library.id] = Array.isArray(library.folders)
|
|
1860
|
+
? library.folders
|
|
1861
|
+
: [];
|
|
1862
|
+
}
|
|
1863
|
+
return result;
|
|
1864
|
+
}, [libraries]);
|
|
1865
|
+
const hasLibraries = isLoading || libraries.length > 0;
|
|
1866
|
+
const currentLibrary = useMemo(
|
|
1867
|
+
() =>
|
|
1868
|
+
routeSelectedLibraryId
|
|
1869
|
+
? libraries.find((library) => library.id === routeSelectedLibraryId)
|
|
1870
|
+
: null,
|
|
1871
|
+
[libraries, routeSelectedLibraryId],
|
|
1872
|
+
);
|
|
1873
|
+
|
|
1874
|
+
useEffect(() => {
|
|
1875
|
+
if (!routeSelectedLibraryId || !currentLibrary?.title) return;
|
|
1876
|
+
insertAgentComposerReference({
|
|
1877
|
+
label: currentLibrary.title,
|
|
1878
|
+
icon: "folder",
|
|
1879
|
+
source: "assets",
|
|
1880
|
+
refType: "brand-kit",
|
|
1881
|
+
refId: routeSelectedLibraryId,
|
|
1882
|
+
refPath: `/library/${encodeURIComponent(routeSelectedLibraryId)}`,
|
|
1883
|
+
slotKey: "brand-kit",
|
|
1884
|
+
slotLabel: "Brand kit",
|
|
1885
|
+
clearsSlots: ["preset"],
|
|
1886
|
+
metadata: {
|
|
1887
|
+
libraryId: routeSelectedLibraryId,
|
|
1888
|
+
},
|
|
1889
|
+
});
|
|
1890
|
+
}, [currentLibrary?.title, routeSelectedLibraryId]);
|
|
1891
|
+
|
|
1892
|
+
return (
|
|
1893
|
+
<div className="flex h-full min-h-0 flex-col bg-background text-foreground">
|
|
1894
|
+
<section className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
1895
|
+
{routeSelectedLibraryId || hasLibraries ? (
|
|
1896
|
+
<div className="h-full min-h-0 min-w-0 overflow-y-auto">
|
|
1897
|
+
<LibraryShellHeader
|
|
1898
|
+
selectedLibraryId={routeSelectedLibraryId}
|
|
1899
|
+
libraries={libraries}
|
|
1900
|
+
isLoading={isLoading}
|
|
1901
|
+
onCreateKit={() => setCreateOpen(true)}
|
|
1902
|
+
/>
|
|
1903
|
+
<LibraryCandidateStage
|
|
1904
|
+
activeLibraryId={routeSelectedLibraryId}
|
|
1905
|
+
foldersByLibraryId={foldersByLibraryId}
|
|
1906
|
+
/>
|
|
1907
|
+
<div className="min-w-0">
|
|
1908
|
+
{routeSelectedLibraryId ? (
|
|
1909
|
+
<BrandKitDetailRoute
|
|
1910
|
+
libraryId={routeSelectedLibraryId}
|
|
1911
|
+
headerMode="actions"
|
|
1912
|
+
/>
|
|
1913
|
+
) : (
|
|
1914
|
+
<AllAssetsBrowser />
|
|
1915
|
+
)}
|
|
1916
|
+
</div>
|
|
1917
|
+
</div>
|
|
1918
|
+
) : (
|
|
1919
|
+
<EmptyLibraryStarter onCreateBlank={() => setCreateOpen(true)} />
|
|
1920
|
+
)}
|
|
1921
|
+
</section>
|
|
1922
|
+
<CreateLibraryDialog
|
|
1923
|
+
open={createOpen}
|
|
1924
|
+
onOpenChange={setCreateOpen}
|
|
1925
|
+
onCreated={(library) => navigate(`/library/${library.id}`)}
|
|
1926
|
+
/>
|
|
1927
|
+
</div>
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
export function AssetPickerSurface() {
|
|
720
1932
|
const t = useT();
|
|
721
1933
|
const [searchParams] = useSearchParams();
|
|
722
1934
|
const searchParamsKey = searchParams.toString();
|
|
@@ -748,6 +1960,11 @@ export default function AssetPicker() {
|
|
|
748
1960
|
}, [searchParamsKey]);
|
|
749
1961
|
const bridgeRef = useRef<EmbeddedAppBridge | null>(null);
|
|
750
1962
|
const embedded = useMemo(() => isEmbeddedWindow() || isEmbedAuthActive(), []);
|
|
1963
|
+
const pickerVariantScopeId = useMemo(
|
|
1964
|
+
() =>
|
|
1965
|
+
typeof window === "undefined" ? null : `picker:${getBrowserTabId()}`,
|
|
1966
|
+
[],
|
|
1967
|
+
);
|
|
751
1968
|
const [hostConfig, setHostConfig] = useState<HostConfig>(() => urlHostConfig);
|
|
752
1969
|
const [mediaType, setMediaType] = useState<PickerMediaType>(
|
|
753
1970
|
() => hostConfig.mediaType ?? "image",
|
|
@@ -794,7 +2011,7 @@ export default function AssetPicker() {
|
|
|
794
2011
|
const starterLibrary: Library = useMemo(
|
|
795
2012
|
() => ({
|
|
796
2013
|
id: STARTER_LIBRARY_ID,
|
|
797
|
-
title: t("
|
|
2014
|
+
title: t("library.starterAssets"),
|
|
798
2015
|
description: STARTER_PRESET.description,
|
|
799
2016
|
}),
|
|
800
2017
|
[t],
|
|
@@ -840,7 +2057,6 @@ export default function AssetPicker() {
|
|
|
840
2057
|
const {
|
|
841
2058
|
data: presetData,
|
|
842
2059
|
isLoading: presetsLoading,
|
|
843
|
-
isFetching: presetsFetching,
|
|
844
2060
|
isPending: presetsPending,
|
|
845
2061
|
} = useActionQuery(
|
|
846
2062
|
"list-generation-presets",
|
|
@@ -849,7 +2065,6 @@ export default function AssetPicker() {
|
|
|
849
2065
|
) as {
|
|
850
2066
|
data?: { presets?: GenerationPreset[] };
|
|
851
2067
|
isLoading?: boolean;
|
|
852
|
-
isFetching?: boolean;
|
|
853
2068
|
isPending?: boolean;
|
|
854
2069
|
};
|
|
855
2070
|
const generationPresets =
|
|
@@ -859,50 +2074,16 @@ export default function AssetPicker() {
|
|
|
859
2074
|
? null
|
|
860
2075
|
: (generationPresets.find((preset) => preset.id === presetId) ?? null);
|
|
861
2076
|
const effectiveAspectRatio = selectedPreset?.aspectRatio || aspectRatio;
|
|
2077
|
+
const waitingForPresetData =
|
|
2078
|
+
!presetData && (presetsLoading || presetsPending);
|
|
862
2079
|
const waitingForRequestedPreset =
|
|
863
2080
|
mediaType === "image" &&
|
|
864
2081
|
presetId !== "none" &&
|
|
865
2082
|
!usingStarterLibrary &&
|
|
866
2083
|
Boolean(selectedLibraryId) &&
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
? t("brandKitDetail.video").toLowerCase()
|
|
871
|
-
: t("brandKitDetail.image").toLowerCase();
|
|
872
|
-
const generateBatch = useActionMutation(
|
|
873
|
-
"generate-image-batch" as any,
|
|
874
|
-
{
|
|
875
|
-
onSuccess: (result: any) => {
|
|
876
|
-
const images = Array.isArray(result?.images) ? result.images : [];
|
|
877
|
-
const generatedCount = images.filter((image: any) => image?.ok).length;
|
|
878
|
-
const failedCount = images.length - generatedCount;
|
|
879
|
-
setVisibleCandidateRunIds(
|
|
880
|
-
images
|
|
881
|
-
.map((image: any) =>
|
|
882
|
-
image?.ok && typeof image.runId === "string" ? image.runId : null,
|
|
883
|
-
)
|
|
884
|
-
.filter((runId: string | null): runId is string => Boolean(runId)),
|
|
885
|
-
);
|
|
886
|
-
if (generatedCount > 0) {
|
|
887
|
-
toast.success(
|
|
888
|
-
t("assetPicker.generatedCandidates", { count: generatedCount }),
|
|
889
|
-
{
|
|
890
|
-
description:
|
|
891
|
-
failedCount > 0
|
|
892
|
-
? t("assetPicker.failedCandidates", { count: failedCount })
|
|
893
|
-
: t("assetPicker.pickCandidate"),
|
|
894
|
-
},
|
|
895
|
-
);
|
|
896
|
-
setQuery("");
|
|
897
|
-
} else {
|
|
898
|
-
toast.error(images[0]?.error || t("assetPicker.noUsableCandidates"));
|
|
899
|
-
}
|
|
900
|
-
},
|
|
901
|
-
onError: (error: Error) => {
|
|
902
|
-
toast.error(error.message || t("assetPicker.generationFailed"));
|
|
903
|
-
},
|
|
904
|
-
} as any,
|
|
905
|
-
);
|
|
2084
|
+
!selectedPreset &&
|
|
2085
|
+
Boolean(waitingForPresetData);
|
|
2086
|
+
const mediaLabel = mediaType === "video" ? "video" : "image";
|
|
906
2087
|
const assetsParams = useMemo(
|
|
907
2088
|
() => ({
|
|
908
2089
|
libraryId: selectedLibraryId,
|
|
@@ -951,6 +2132,12 @@ export default function AssetPicker() {
|
|
|
951
2132
|
? visibleStarterAssets
|
|
952
2133
|
: []
|
|
953
2134
|
: (assetData?.assets ?? []);
|
|
2135
|
+
const currentLibrary = useMemo(
|
|
2136
|
+
() =>
|
|
2137
|
+
displayLibraries.find((library) => library.id === selectedLibraryId) ??
|
|
2138
|
+
null,
|
|
2139
|
+
[displayLibraries, selectedLibraryId],
|
|
2140
|
+
);
|
|
954
2141
|
const assets = useMemo(
|
|
955
2142
|
() => allAssets.filter((asset) => assetMatchesTab(asset, assetTab)),
|
|
956
2143
|
[allAssets, assetTab],
|
|
@@ -982,11 +2169,11 @@ export default function AssetPicker() {
|
|
|
982
2169
|
try {
|
|
983
2170
|
await navigator.clipboard.writeText(text);
|
|
984
2171
|
setStandaloneCopyOk(true);
|
|
985
|
-
toast.success(t("
|
|
2172
|
+
toast.success(t("library.selectionCopied"));
|
|
986
2173
|
return true;
|
|
987
2174
|
} catch {
|
|
988
2175
|
setStandaloneCopyOk(false);
|
|
989
|
-
toast.info(t("
|
|
2176
|
+
toast.info(t("library.selectionReady"));
|
|
990
2177
|
return false;
|
|
991
2178
|
}
|
|
992
2179
|
},
|
|
@@ -1024,7 +2211,7 @@ export default function AssetPicker() {
|
|
|
1024
2211
|
}),
|
|
1025
2212
|
);
|
|
1026
2213
|
} else {
|
|
1027
|
-
toast.error(t("
|
|
2214
|
+
toast.error(t("library.selectedAssetSendFailed"));
|
|
1028
2215
|
}
|
|
1029
2216
|
});
|
|
1030
2217
|
return;
|
|
@@ -1041,7 +2228,7 @@ export default function AssetPicker() {
|
|
|
1041
2228
|
if (!result?.id) return;
|
|
1042
2229
|
const library = {
|
|
1043
2230
|
id: result.id,
|
|
1044
|
-
title: result.title || t("
|
|
2231
|
+
title: result.title || t("library.mcpImagePicks"),
|
|
1045
2232
|
description: result.description ?? null,
|
|
1046
2233
|
};
|
|
1047
2234
|
setCreatedPickerLibrary(library);
|
|
@@ -1052,18 +2239,86 @@ export default function AssetPicker() {
|
|
|
1052
2239
|
// Allow the auto-create effect to retry after a transient failure;
|
|
1053
2240
|
// otherwise the picker stays stuck on "Preparing..." until reload.
|
|
1054
2241
|
autoCreateLibraryRef.current = false;
|
|
1055
|
-
toast.error(error.message || t("
|
|
2242
|
+
toast.error(error.message || t("library.couldNotPrepareImageLibrary"));
|
|
2243
|
+
},
|
|
2244
|
+
} as any,
|
|
2245
|
+
);
|
|
2246
|
+
const generateBatch = useActionMutation(
|
|
2247
|
+
"generate-image-batch" as any,
|
|
2248
|
+
{
|
|
2249
|
+
onSuccess: (result: any) => {
|
|
2250
|
+
const images = Array.isArray(result?.images) ? result.images : [];
|
|
2251
|
+
const generatedCount = images.filter((image: any) => image?.ok).length;
|
|
2252
|
+
const failedCount = images.length - generatedCount;
|
|
2253
|
+
setVisibleCandidateRunIds(
|
|
2254
|
+
images
|
|
2255
|
+
.map((image: any) =>
|
|
2256
|
+
image?.ok && typeof image.runId === "string" ? image.runId : null,
|
|
2257
|
+
)
|
|
2258
|
+
.filter((runId: string | null): runId is string => Boolean(runId)),
|
|
2259
|
+
);
|
|
2260
|
+
if (generatedCount > 0) {
|
|
2261
|
+
toast.success(
|
|
2262
|
+
`Generated ${generatedCount} image candidate${
|
|
2263
|
+
generatedCount === 1 ? "" : "s"
|
|
2264
|
+
}`,
|
|
2265
|
+
{
|
|
2266
|
+
description:
|
|
2267
|
+
failedCount > 0
|
|
2268
|
+
? `${failedCount} candidate${
|
|
2269
|
+
failedCount === 1 ? "" : "s"
|
|
2270
|
+
} failed.`
|
|
2271
|
+
: "Pick the one you want to send back.",
|
|
2272
|
+
},
|
|
2273
|
+
);
|
|
2274
|
+
setQuery("");
|
|
2275
|
+
} else {
|
|
2276
|
+
toast.error(
|
|
2277
|
+
images[0]?.error ||
|
|
2278
|
+
"Image generation finished without usable candidates.",
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
},
|
|
2282
|
+
onError: (error: Error) => {
|
|
2283
|
+
toast.error(error.message || "Image generation failed");
|
|
1056
2284
|
},
|
|
1057
2285
|
} as any,
|
|
1058
2286
|
);
|
|
2287
|
+
const [chatGenerating, sendToAgent] = useAgentChatGenerating();
|
|
1059
2288
|
|
|
1060
2289
|
const runGenerate = useCallback(() => {
|
|
1061
2290
|
if (!selectedLibraryId || !prompt.trim()) return;
|
|
1062
2291
|
if (waitingForRequestedPreset) return;
|
|
2292
|
+
if (!embedded) {
|
|
2293
|
+
sendToAgent({
|
|
2294
|
+
message: buildPickerChatHandoffPrompt({
|
|
2295
|
+
mediaType,
|
|
2296
|
+
prompt,
|
|
2297
|
+
count,
|
|
2298
|
+
aspectRatio: effectiveAspectRatio,
|
|
2299
|
+
libraryId: usingStarterLibrary ? null : selectedLibraryId,
|
|
2300
|
+
libraryTitle: usingStarterLibrary
|
|
2301
|
+
? null
|
|
2302
|
+
: (currentLibrary?.title ?? null),
|
|
2303
|
+
presetId: selectedPreset?.id ?? null,
|
|
2304
|
+
presetTitle: selectedPreset?.title ?? null,
|
|
2305
|
+
tier: hostConfig.tier,
|
|
2306
|
+
styleStrength: hostConfig.styleStrength ?? "balanced",
|
|
2307
|
+
includeLogo: hostConfig.includeLogo ?? false,
|
|
2308
|
+
}),
|
|
2309
|
+
submit: true,
|
|
2310
|
+
openSidebar: true,
|
|
2311
|
+
newTab: true,
|
|
2312
|
+
});
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
1063
2315
|
setVisibleCandidateRunIds([]);
|
|
2316
|
+
const variantScopeId =
|
|
2317
|
+
pickerVariantScopeId ?? `picker:${getBrowserTabId()}`;
|
|
1064
2318
|
generateBatch.mutate({
|
|
1065
2319
|
libraryId: selectedLibraryId,
|
|
1066
2320
|
presetId: selectedPreset?.id,
|
|
2321
|
+
variantScopeId,
|
|
1067
2322
|
slots: Array.from({ length: count }, (_, index) => ({
|
|
1068
2323
|
slotId: `picker-candidate-${index + 1}`,
|
|
1069
2324
|
prompt: prompt.trim(),
|
|
@@ -1079,48 +2334,44 @@ export default function AssetPicker() {
|
|
|
1079
2334
|
} as any);
|
|
1080
2335
|
}, [
|
|
1081
2336
|
count,
|
|
2337
|
+
currentLibrary?.title,
|
|
2338
|
+
embedded,
|
|
1082
2339
|
effectiveAspectRatio,
|
|
1083
2340
|
generateBatch,
|
|
1084
2341
|
hostConfig.callerAppId,
|
|
1085
2342
|
hostConfig.includeLogo,
|
|
1086
2343
|
hostConfig.styleStrength,
|
|
1087
2344
|
hostConfig.tier,
|
|
2345
|
+
mediaType,
|
|
1088
2346
|
prompt,
|
|
2347
|
+
pickerVariantScopeId,
|
|
2348
|
+
sendToAgent,
|
|
1089
2349
|
setVisibleCandidateRunIds,
|
|
1090
2350
|
selectedLibraryId,
|
|
1091
2351
|
selectedPreset,
|
|
2352
|
+
usingStarterLibrary,
|
|
1092
2353
|
waitingForRequestedPreset,
|
|
1093
2354
|
]);
|
|
1094
2355
|
|
|
1095
2356
|
useEffect(() => {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
2357
|
+
if (!visibleCandidateRunIds.length) return;
|
|
2358
|
+
if (normalizeMediaType(hostConfig.mediaType) !== mediaType) {
|
|
2359
|
+
setVisibleCandidateRunIds([]);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
if (
|
|
2363
|
+
hostConfig.libraryId &&
|
|
2364
|
+
selectedLibraryId &&
|
|
2365
|
+
hostConfig.libraryId !== selectedLibraryId
|
|
2366
|
+
) {
|
|
2367
|
+
setVisibleCandidateRunIds([]);
|
|
2368
|
+
}
|
|
1106
2369
|
}, [
|
|
1107
|
-
aspectRatio,
|
|
1108
|
-
count,
|
|
1109
|
-
hostConfig.aspectRatio,
|
|
1110
|
-
hostConfig.candidateRunIds,
|
|
1111
|
-
hostConfig.count,
|
|
1112
|
-
hostConfig.callerAppId,
|
|
1113
|
-
hostConfig.includeLogo,
|
|
1114
2370
|
hostConfig.libraryId,
|
|
1115
2371
|
hostConfig.mediaType,
|
|
1116
|
-
hostConfig.presetId,
|
|
1117
|
-
hostConfig.prompt,
|
|
1118
|
-
hostConfig.styleStrength,
|
|
1119
|
-
hostConfig.tier,
|
|
1120
2372
|
mediaType,
|
|
1121
|
-
presetId,
|
|
1122
|
-
prompt,
|
|
1123
2373
|
selectedLibraryId,
|
|
2374
|
+
visibleCandidateRunIds.length,
|
|
1124
2375
|
]);
|
|
1125
2376
|
|
|
1126
2377
|
useEffect(() => {
|
|
@@ -1150,25 +2401,17 @@ export default function AssetPicker() {
|
|
|
1150
2401
|
}, []);
|
|
1151
2402
|
|
|
1152
2403
|
useEffect(() => {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
`/_agent-native/application-state/navigation:${getBrowserTabId()}`,
|
|
1156
|
-
),
|
|
2404
|
+
void writeClientAppState(
|
|
2405
|
+
`navigation:${getBrowserTabId()}`,
|
|
1157
2406
|
{
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
view: "picker",
|
|
1165
|
-
mediaType,
|
|
1166
|
-
libraryId: selectedLibraryId || null,
|
|
1167
|
-
query,
|
|
1168
|
-
prompt,
|
|
1169
|
-
aspectRatio,
|
|
1170
|
-
}),
|
|
2407
|
+
view: "picker",
|
|
2408
|
+
mediaType,
|
|
2409
|
+
libraryId: selectedLibraryId || null,
|
|
2410
|
+
query,
|
|
2411
|
+
prompt,
|
|
2412
|
+
aspectRatio,
|
|
1171
2413
|
},
|
|
2414
|
+
{ requestSource: "assets-picker-ui" },
|
|
1172
2415
|
).catch(() => {});
|
|
1173
2416
|
}, [aspectRatio, mediaType, prompt, query, selectedLibraryId]);
|
|
1174
2417
|
|
|
@@ -1178,14 +2421,14 @@ export default function AssetPicker() {
|
|
|
1178
2421
|
!usingStarterLibrary &&
|
|
1179
2422
|
Boolean(prompt.trim()) &&
|
|
1180
2423
|
!waitingForRequestedPreset &&
|
|
1181
|
-
!generateBatch.isPending;
|
|
2424
|
+
!(embedded ? generateBatch.isPending : chatGenerating);
|
|
1182
2425
|
const setupNeeded = mediaType === "image" && config?.configured === false;
|
|
1183
2426
|
const setupMessage =
|
|
1184
2427
|
typeof config?.lastIssue?.message === "string"
|
|
1185
2428
|
? config.lastIssue.message
|
|
1186
2429
|
: config?.builderEnabled === false
|
|
1187
|
-
? t("
|
|
1188
|
-
: t("
|
|
2430
|
+
? t("library.addGenerationKey")
|
|
2431
|
+
: t("library.connectGenerationModels");
|
|
1189
2432
|
const needsGenerationLibrary =
|
|
1190
2433
|
mediaType === "image" &&
|
|
1191
2434
|
libraryListReady &&
|
|
@@ -1201,24 +2444,30 @@ export default function AssetPicker() {
|
|
|
1201
2444
|
(waitingForLibraries ||
|
|
1202
2445
|
needsGenerationLibrary ||
|
|
1203
2446
|
preparingGenerationLibrary);
|
|
1204
|
-
const generationButtonLabel =
|
|
1205
|
-
?
|
|
2447
|
+
const generationButtonLabel = (
|
|
2448
|
+
embedded ? generateBatch.isPending : chatGenerating
|
|
2449
|
+
)
|
|
2450
|
+
? t("library.generating")
|
|
1206
2451
|
: waitingForRequestedPreset
|
|
1207
|
-
? t("
|
|
2452
|
+
? t("library.loadingPreset")
|
|
1208
2453
|
: setupNeeded
|
|
1209
|
-
? t("
|
|
2454
|
+
? t("library.setupNeeded")
|
|
1210
2455
|
: preparingAutoLibrary
|
|
1211
|
-
? t("
|
|
1212
|
-
:
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
2456
|
+
? t("library.preparing")
|
|
2457
|
+
: embedded
|
|
2458
|
+
? t("library.generate")
|
|
2459
|
+
: t("library.openChat");
|
|
2460
|
+
const generationStatus = preparingAutoLibrary
|
|
2461
|
+
? waitingForLibraries
|
|
2462
|
+
? t("library.checkingImageLibraries")
|
|
2463
|
+
: t("library.preparingImageLibrary")
|
|
2464
|
+
: embedded && generateBatch.isPending
|
|
2465
|
+
? t("library.generatingCandidates")
|
|
2466
|
+
: !embedded && chatGenerating
|
|
2467
|
+
? t("library.generating")
|
|
2468
|
+
: waitingForRequestedPreset
|
|
2469
|
+
? t("library.loadingRequestedPreset")
|
|
2470
|
+
: null;
|
|
1222
2471
|
|
|
1223
2472
|
useEffect(() => {
|
|
1224
2473
|
if (!selectedPreset?.aspectRatio) return;
|
|
@@ -1228,10 +2477,10 @@ export default function AssetPicker() {
|
|
|
1228
2477
|
const prepareGenerationLibrary = useCallback(() => {
|
|
1229
2478
|
createPickerLibrary.mutate({
|
|
1230
2479
|
presetId: STARTER_PRESET.id,
|
|
1231
|
-
title: t("
|
|
1232
|
-
description: t("
|
|
2480
|
+
title: t("library.mcpImagePicks"),
|
|
2481
|
+
description: t("library.mcpImagePicksDescription"),
|
|
1233
2482
|
} as any);
|
|
1234
|
-
}, [createPickerLibrary]);
|
|
2483
|
+
}, [createPickerLibrary, t]);
|
|
1235
2484
|
|
|
1236
2485
|
useEffect(() => {
|
|
1237
2486
|
if (!prompt.trim()) return;
|
|
@@ -1251,6 +2500,7 @@ export default function AssetPicker() {
|
|
|
1251
2500
|
useEffect(() => {
|
|
1252
2501
|
if (!hostConfig.autoGenerate) return;
|
|
1253
2502
|
if (!prompt.trim() || mediaType !== "image") return;
|
|
2503
|
+
if (!embedded) return;
|
|
1254
2504
|
if (!canGenerate || setupNeeded || generateBatch.isPending) return;
|
|
1255
2505
|
if (waitingForRequestedPreset) return;
|
|
1256
2506
|
const key = [
|
|
@@ -1266,6 +2516,7 @@ export default function AssetPicker() {
|
|
|
1266
2516
|
}, [
|
|
1267
2517
|
canGenerate,
|
|
1268
2518
|
count,
|
|
2519
|
+
embedded,
|
|
1269
2520
|
effectiveAspectRatio,
|
|
1270
2521
|
generateBatch.isPending,
|
|
1271
2522
|
hostConfig.autoGenerate,
|
|
@@ -1287,7 +2538,7 @@ export default function AssetPicker() {
|
|
|
1287
2538
|
>
|
|
1288
2539
|
<header className="flex h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-3">
|
|
1289
2540
|
<div className="min-w-0 truncate text-sm font-semibold">
|
|
1290
|
-
{embedded ? t("navigation.brand") : t("
|
|
2541
|
+
{embedded ? t("navigation.brand") : t("library.library")}
|
|
1291
2542
|
</div>
|
|
1292
2543
|
<div className="flex shrink-0 items-center gap-2">
|
|
1293
2544
|
{mediaType === "image" && (
|
|
@@ -1302,9 +2553,7 @@ export default function AssetPicker() {
|
|
|
1302
2553
|
) : (
|
|
1303
2554
|
<IconPhotoPlus className="h-3.5 w-3.5" />
|
|
1304
2555
|
)}
|
|
1305
|
-
{showCreatePane
|
|
1306
|
-
? t("brandKitDetail.close")
|
|
1307
|
-
: t("navigation.create")}
|
|
2556
|
+
{showCreatePane ? t("library.close") : t("library.createPane")}
|
|
1308
2557
|
</Button>
|
|
1309
2558
|
)}
|
|
1310
2559
|
{embedded && (
|
|
@@ -1313,7 +2562,7 @@ export default function AssetPicker() {
|
|
|
1313
2562
|
asChild
|
|
1314
2563
|
variant="ghost"
|
|
1315
2564
|
size="icon"
|
|
1316
|
-
title={t("
|
|
2565
|
+
title={t("library.openAssets")}
|
|
1317
2566
|
>
|
|
1318
2567
|
<a href={absoluteAppUrl("/")} target="_blank" rel="noreferrer">
|
|
1319
2568
|
<IconArrowUpRight className="h-4 w-4" />
|
|
@@ -1322,7 +2571,7 @@ export default function AssetPicker() {
|
|
|
1322
2571
|
<Button
|
|
1323
2572
|
variant="ghost"
|
|
1324
2573
|
size="icon"
|
|
1325
|
-
title={t("
|
|
2574
|
+
title={t("library.close")}
|
|
1326
2575
|
onClick={() => bridgeRef.current?.close()}
|
|
1327
2576
|
>
|
|
1328
2577
|
<IconX className="h-4 w-4" />
|
|
@@ -1336,9 +2585,8 @@ export default function AssetPicker() {
|
|
|
1336
2585
|
<section className="shrink-0 border-b border-border px-3 py-3">
|
|
1337
2586
|
{mediaType === "image" ? (
|
|
1338
2587
|
<div className="mt-2 rounded-lg border border-border/80 bg-background focus-within:ring-1 focus-within:ring-ring">
|
|
1339
|
-
<
|
|
1340
|
-
|
|
1341
|
-
rows={1}
|
|
2588
|
+
<Input
|
|
2589
|
+
type="text"
|
|
1342
2590
|
value={prompt}
|
|
1343
2591
|
onChange={(event) => setPrompt(event.target.value)}
|
|
1344
2592
|
onKeyDown={(event) => {
|
|
@@ -1352,8 +2600,8 @@ export default function AssetPicker() {
|
|
|
1352
2600
|
event.preventDefault();
|
|
1353
2601
|
if (canGenerate) runGenerate();
|
|
1354
2602
|
}}
|
|
1355
|
-
placeholder={t("
|
|
1356
|
-
className="
|
|
2603
|
+
placeholder={t("library.generateImagePlaceholder")}
|
|
2604
|
+
className="h-11 border-0 bg-transparent px-3 py-2.5 leading-6 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
1357
2605
|
/>
|
|
1358
2606
|
<div className="flex items-center gap-1 px-2 pb-2">
|
|
1359
2607
|
<div className="flex min-w-0 flex-1 items-center justify-end gap-0.5 sm:gap-1">
|
|
@@ -1421,18 +2669,21 @@ export default function AssetPicker() {
|
|
|
1421
2669
|
) : null}
|
|
1422
2670
|
</div>
|
|
1423
2671
|
<Button
|
|
1424
|
-
className="h-7 shrink-0 px-3 text-xs"
|
|
2672
|
+
className="h-7 min-w-[5.75rem] shrink-0 px-3 text-xs"
|
|
1425
2673
|
disabled={!canGenerate}
|
|
1426
2674
|
onClick={runGenerate}
|
|
1427
2675
|
>
|
|
1428
2676
|
{generationButtonLabel}
|
|
1429
2677
|
</Button>
|
|
1430
2678
|
</div>
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2679
|
+
<div
|
|
2680
|
+
className={cn(
|
|
2681
|
+
"-mt-1 min-h-6 px-3 pb-2 text-[11px] leading-4 text-muted-foreground",
|
|
2682
|
+
!generationStatus && "invisible",
|
|
2683
|
+
)}
|
|
2684
|
+
>
|
|
2685
|
+
{generationStatus || t("library.ready")}
|
|
2686
|
+
</div>
|
|
1436
2687
|
</div>
|
|
1437
2688
|
) : null}
|
|
1438
2689
|
|
|
@@ -1450,7 +2701,7 @@ export default function AssetPicker() {
|
|
|
1450
2701
|
target="_blank"
|
|
1451
2702
|
rel="noreferrer"
|
|
1452
2703
|
>
|
|
1453
|
-
{t("
|
|
2704
|
+
{t("library.settings")}
|
|
1454
2705
|
</a>
|
|
1455
2706
|
</Button>
|
|
1456
2707
|
</div>
|
|
@@ -1460,8 +2711,8 @@ export default function AssetPicker() {
|
|
|
1460
2711
|
<div className="mt-2 flex items-center justify-between gap-3 rounded-md border border-border bg-muted/60 px-3 py-2 text-xs text-muted-foreground">
|
|
1461
2712
|
<span className="min-w-0 truncate">
|
|
1462
2713
|
{preparingGenerationLibrary
|
|
1463
|
-
? t("
|
|
1464
|
-
: t("
|
|
2714
|
+
? t("library.preparingImageLibraryCandidates")
|
|
2715
|
+
: t("library.createImageLibraryToGenerate")}
|
|
1465
2716
|
</span>
|
|
1466
2717
|
<Button
|
|
1467
2718
|
variant="outline"
|
|
@@ -1471,8 +2722,8 @@ export default function AssetPicker() {
|
|
|
1471
2722
|
disabled={preparingGenerationLibrary}
|
|
1472
2723
|
>
|
|
1473
2724
|
{preparingGenerationLibrary
|
|
1474
|
-
? t("
|
|
1475
|
-
: t("
|
|
2725
|
+
? t("library.preparing")
|
|
2726
|
+
: t("library.createLibrary")}
|
|
1476
2727
|
</Button>
|
|
1477
2728
|
</div>
|
|
1478
2729
|
)}
|
|
@@ -1480,9 +2731,9 @@ export default function AssetPicker() {
|
|
|
1480
2731
|
)}
|
|
1481
2732
|
|
|
1482
2733
|
{!embedded && standaloneSelection && (
|
|
1483
|
-
<section className="shrink-0 border-b border-border bg-muted/30 px-3 py-3">
|
|
1484
|
-
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
1485
|
-
<div className="min-w-0">
|
|
2734
|
+
<section className="shrink-0 overflow-hidden border-b border-border bg-muted/30 px-3 py-3">
|
|
2735
|
+
<div className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
2736
|
+
<div className="min-w-0 flex-1">
|
|
1486
2737
|
<div className="truncate text-sm font-medium">
|
|
1487
2738
|
{selectedAssetLabel(standaloneSelection)}
|
|
1488
2739
|
</div>
|
|
@@ -1492,11 +2743,11 @@ export default function AssetPicker() {
|
|
|
1492
2743
|
</div>
|
|
1493
2744
|
)}
|
|
1494
2745
|
</div>
|
|
1495
|
-
<div className="flex
|
|
2746
|
+
<div className="flex w-full min-w-0 flex-wrap gap-2 sm:w-auto sm:flex-nowrap sm:justify-end">
|
|
1496
2747
|
<Button
|
|
1497
2748
|
variant="outline"
|
|
1498
2749
|
size="sm"
|
|
1499
|
-
className="h-8 gap-1.5"
|
|
2750
|
+
className="h-8 shrink-0 gap-1.5"
|
|
1500
2751
|
onClick={() => copyStandaloneSelection(standaloneSelection)}
|
|
1501
2752
|
>
|
|
1502
2753
|
{standaloneCopyOk ? (
|
|
@@ -1504,16 +2755,14 @@ export default function AssetPicker() {
|
|
|
1504
2755
|
) : (
|
|
1505
2756
|
<IconClipboard className="h-3.5 w-3.5" />
|
|
1506
2757
|
)}
|
|
1507
|
-
{standaloneCopyOk
|
|
1508
|
-
? t("assetPicker.copied")
|
|
1509
|
-
: t("assetPicker.copy")}
|
|
2758
|
+
{standaloneCopyOk ? t("library.copied") : t("library.copy")}
|
|
1510
2759
|
</Button>
|
|
1511
2760
|
{canOpenStandaloneAsset && (
|
|
1512
2761
|
<Button
|
|
1513
2762
|
asChild
|
|
1514
2763
|
variant="ghost"
|
|
1515
2764
|
size="sm"
|
|
1516
|
-
className="h-8 gap-1.5"
|
|
2765
|
+
className="h-8 shrink-0 gap-1.5"
|
|
1517
2766
|
>
|
|
1518
2767
|
<Link
|
|
1519
2768
|
to={`/asset/${encodeURIComponent(
|
|
@@ -1521,16 +2770,16 @@ export default function AssetPicker() {
|
|
|
1521
2770
|
)}`}
|
|
1522
2771
|
>
|
|
1523
2772
|
<IconArrowUpRight className="h-3.5 w-3.5" />
|
|
1524
|
-
{t("
|
|
2773
|
+
{t("library.open")}
|
|
1525
2774
|
</Link>
|
|
1526
2775
|
</Button>
|
|
1527
2776
|
)}
|
|
1528
2777
|
<Button
|
|
1529
2778
|
variant="ghost"
|
|
1530
2779
|
size="icon"
|
|
1531
|
-
title={t("
|
|
1532
|
-
aria-label={t("
|
|
1533
|
-
className="h-8 w-8"
|
|
2780
|
+
title={t("library.close")}
|
|
2781
|
+
aria-label={t("library.close")}
|
|
2782
|
+
className="h-8 w-8 shrink-0"
|
|
1534
2783
|
onClick={() => {
|
|
1535
2784
|
setStandaloneSelection(null);
|
|
1536
2785
|
setStandaloneCopyOk(false);
|
|
@@ -1542,17 +2791,18 @@ export default function AssetPicker() {
|
|
|
1542
2791
|
</div>
|
|
1543
2792
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
1544
2793
|
{standaloneCopyOk
|
|
1545
|
-
? t("
|
|
1546
|
-
: t("
|
|
2794
|
+
? t("library.copiedPasteInstruction")
|
|
2795
|
+
: t("library.copyPasteInstruction")}
|
|
2796
|
+
. {t("library.tellAgentUseThis")}
|
|
1547
2797
|
</p>
|
|
1548
2798
|
<details className="mt-2">
|
|
1549
2799
|
<summary className="cursor-pointer select-none text-xs text-muted-foreground hover:text-foreground">
|
|
1550
|
-
{t("
|
|
2800
|
+
{t("library.showPasteText")}
|
|
1551
2801
|
</summary>
|
|
1552
2802
|
<Textarea
|
|
1553
2803
|
readOnly
|
|
1554
2804
|
value={standaloneSelectionText}
|
|
1555
|
-
className="mt-2 h-24 resize-none border-border/70 bg-background font-mono text-[11px] leading-relaxed"
|
|
2805
|
+
className="mt-2 h-24 max-w-full resize-none border-border/70 bg-background font-mono text-[11px] leading-relaxed"
|
|
1556
2806
|
onFocus={(event) => event.currentTarget.select()}
|
|
1557
2807
|
/>
|
|
1558
2808
|
</details>
|
|
@@ -1568,7 +2818,7 @@ export default function AssetPicker() {
|
|
|
1568
2818
|
onValueChange={setSelectedLibraryId}
|
|
1569
2819
|
>
|
|
1570
2820
|
<SelectTrigger className="h-9 w-full border-border/70 bg-background sm:w-48">
|
|
1571
|
-
<SelectValue placeholder={t("
|
|
2821
|
+
<SelectValue placeholder={t("library.library")} />
|
|
1572
2822
|
</SelectTrigger>
|
|
1573
2823
|
<SelectContent>
|
|
1574
2824
|
<SelectGroup>
|
|
@@ -1587,13 +2837,13 @@ export default function AssetPicker() {
|
|
|
1587
2837
|
>
|
|
1588
2838
|
<TabsList>
|
|
1589
2839
|
<TabsTrigger value="all">
|
|
1590
|
-
{t("
|
|
2840
|
+
{t("library.tabsAll")}
|
|
1591
2841
|
</TabsTrigger>
|
|
1592
2842
|
<TabsTrigger value="generated">
|
|
1593
|
-
{t("
|
|
2843
|
+
{t("library.generated")}
|
|
1594
2844
|
</TabsTrigger>
|
|
1595
2845
|
<TabsTrigger value="references">
|
|
1596
|
-
{t("
|
|
2846
|
+
{t("library.references")}
|
|
1597
2847
|
</TabsTrigger>
|
|
1598
2848
|
</TabsList>
|
|
1599
2849
|
</Tabs>
|
|
@@ -1605,19 +2855,26 @@ export default function AssetPicker() {
|
|
|
1605
2855
|
value={query}
|
|
1606
2856
|
onInput={(event) => setQuery(event.currentTarget.value)}
|
|
1607
2857
|
onChange={(event) => setQuery(event.target.value)}
|
|
1608
|
-
placeholder={t("
|
|
1609
|
-
media: mediaLabelText,
|
|
1610
|
-
})}
|
|
2858
|
+
placeholder={t("library.searchMedia", { mediaLabel })}
|
|
1611
2859
|
className="h-9 border-border/70 bg-background sm:max-w-xs"
|
|
1612
2860
|
/>
|
|
1613
2861
|
)}
|
|
1614
2862
|
</div>
|
|
1615
2863
|
)}
|
|
1616
2864
|
|
|
2865
|
+
{mediaType === "image" && selectedLibraryId && pickerVariantScopeId && (
|
|
2866
|
+
<LibraryCandidateStage
|
|
2867
|
+
activeLibraryId={selectedLibraryId}
|
|
2868
|
+
variantScopeId={pickerVariantScopeId}
|
|
2869
|
+
onUseAsset={chooseAsset}
|
|
2870
|
+
inline
|
|
2871
|
+
/>
|
|
2872
|
+
)}
|
|
2873
|
+
|
|
1617
2874
|
{!selectedLibraryId && (
|
|
1618
2875
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
1619
2876
|
<Button asChild variant="outline">
|
|
1620
|
-
<Link to="/
|
|
2877
|
+
<Link to="/library">{t("library.createBrandKit")}</Link>
|
|
1621
2878
|
</Button>
|
|
1622
2879
|
</div>
|
|
1623
2880
|
)}
|
|
@@ -1634,10 +2891,8 @@ export default function AssetPicker() {
|
|
|
1634
2891
|
<div className="flex h-full items-center justify-center text-center">
|
|
1635
2892
|
<div className="max-w-sm text-sm text-muted-foreground">
|
|
1636
2893
|
{query
|
|
1637
|
-
? t("
|
|
1638
|
-
|
|
1639
|
-
})
|
|
1640
|
-
: t("assetPicker.noAssetsYet", { media: mediaLabelText })}
|
|
2894
|
+
? t("library.noMatchingLibraryAssets", { mediaLabel })
|
|
2895
|
+
: t("library.noAssetsInLibrary", { mediaLabel })}
|
|
1641
2896
|
</div>
|
|
1642
2897
|
</div>
|
|
1643
2898
|
)}
|
|
@@ -1651,7 +2906,7 @@ export default function AssetPicker() {
|
|
|
1651
2906
|
>
|
|
1652
2907
|
<button
|
|
1653
2908
|
type="button"
|
|
1654
|
-
aria-label={t("
|
|
2909
|
+
aria-label={t("library.selectAsset", {
|
|
1655
2910
|
title: assetDisplayTitle(asset),
|
|
1656
2911
|
})}
|
|
1657
2912
|
onClick={() => {
|
|
@@ -1684,7 +2939,7 @@ export default function AssetPicker() {
|
|
|
1684
2939
|
<TooltipTrigger asChild>
|
|
1685
2940
|
<button
|
|
1686
2941
|
type="button"
|
|
1687
|
-
aria-label={t("
|
|
2942
|
+
aria-label={t("library.copyAsset", {
|
|
1688
2943
|
title: assetDisplayTitle(asset),
|
|
1689
2944
|
})}
|
|
1690
2945
|
onClick={(event) => {
|
|
@@ -1697,7 +2952,7 @@ export default function AssetPicker() {
|
|
|
1697
2952
|
</button>
|
|
1698
2953
|
</TooltipTrigger>
|
|
1699
2954
|
<TooltipContent>
|
|
1700
|
-
{t("
|
|
2955
|
+
{t("library.copyToClipboard")}
|
|
1701
2956
|
</TooltipContent>
|
|
1702
2957
|
</Tooltip>
|
|
1703
2958
|
</TooltipProvider>
|
|
@@ -1740,7 +2995,7 @@ export default function AssetPicker() {
|
|
|
1740
2995
|
{assetDisplayTitle(previewAsset)}
|
|
1741
2996
|
</DialogTitle>
|
|
1742
2997
|
<DialogDescription className="sr-only">
|
|
1743
|
-
{t("
|
|
2998
|
+
{t("library.fullSizePreview", {
|
|
1744
2999
|
title: assetDisplayTitle(previewAsset),
|
|
1745
3000
|
})}
|
|
1746
3001
|
</DialogDescription>
|
|
@@ -1750,11 +3005,11 @@ export default function AssetPicker() {
|
|
|
1750
3005
|
<Link
|
|
1751
3006
|
to={`/asset/${encodeURIComponent(previewAsset.id)}`}
|
|
1752
3007
|
>
|
|
1753
|
-
{t("
|
|
3008
|
+
{t("library.viewDetails")}
|
|
1754
3009
|
</Link>
|
|
1755
3010
|
</Button>
|
|
1756
3011
|
<DialogClose
|
|
1757
|
-
aria-label={t("
|
|
3012
|
+
aria-label={t("library.closePreview")}
|
|
1758
3013
|
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
1759
3014
|
>
|
|
1760
3015
|
<IconX className="h-5 w-5" />
|
|
@@ -1782,7 +3037,7 @@ export default function AssetPicker() {
|
|
|
1782
3037
|
<div className="mt-5 flex justify-center gap-2">
|
|
1783
3038
|
<button
|
|
1784
3039
|
type="button"
|
|
1785
|
-
aria-label={t("
|
|
3040
|
+
aria-label={t("library.previousImage")}
|
|
1786
3041
|
onClick={showPreviousAsset}
|
|
1787
3042
|
disabled={!hasPrev}
|
|
1788
3043
|
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white disabled:cursor-not-allowed disabled:opacity-40"
|
|
@@ -1791,7 +3046,7 @@ export default function AssetPicker() {
|
|
|
1791
3046
|
</button>
|
|
1792
3047
|
<button
|
|
1793
3048
|
type="button"
|
|
1794
|
-
aria-label={t("
|
|
3049
|
+
aria-label={t("library.nextImage")}
|
|
1795
3050
|
onClick={showNextAsset}
|
|
1796
3051
|
disabled={!hasNext}
|
|
1797
3052
|
className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white transition hover:bg-black/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white disabled:cursor-not-allowed disabled:opacity-40"
|
|
@@ -1807,3 +3062,27 @@ export default function AssetPicker() {
|
|
|
1807
3062
|
</div>
|
|
1808
3063
|
);
|
|
1809
3064
|
}
|
|
3065
|
+
|
|
3066
|
+
export default function LibraryRoute() {
|
|
3067
|
+
const [searchParams] = useSearchParams();
|
|
3068
|
+
const searchParamsKey = searchParams.toString();
|
|
3069
|
+
const embedded = useMemo(() => isEmbeddedWindow() || isEmbedAuthActive(), []);
|
|
3070
|
+
const { pickerRequested, queryLibraryId } = useMemo(() => {
|
|
3071
|
+
const params = new URLSearchParams(searchParamsKey);
|
|
3072
|
+
const requested =
|
|
3073
|
+
params.get("__an_picker") === "1" ||
|
|
3074
|
+
params.get("__an_mcp_chat_bridge") === "1";
|
|
3075
|
+
return {
|
|
3076
|
+
pickerRequested: requested,
|
|
3077
|
+
queryLibraryId: requested ? null : params.get("libraryId"),
|
|
3078
|
+
};
|
|
3079
|
+
}, [searchParamsKey]);
|
|
3080
|
+
if (queryLibraryId) {
|
|
3081
|
+
return <LibraryWorkspace selectedLibraryId={queryLibraryId} />;
|
|
3082
|
+
}
|
|
3083
|
+
return embedded || pickerRequested ? (
|
|
3084
|
+
<AssetPickerSurface />
|
|
3085
|
+
) : (
|
|
3086
|
+
<LibraryWorkspace />
|
|
3087
|
+
);
|
|
3088
|
+
}
|