@deenruv/replicate-simple-bg-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +54 -0
- package/dist/plugin-server/constants.d.ts +13 -0
- package/dist/plugin-server/constants.js +122 -0
- package/dist/plugin-server/entities/replicate-simple-bg.d.ts +11 -0
- package/dist/plugin-server/entities/replicate-simple-bg.js +49 -0
- package/dist/plugin-server/extensions/replicate-simple-bg.extension.d.ts +1 -0
- package/dist/plugin-server/extensions/replicate-simple-bg.extension.js +105 -0
- package/dist/plugin-server/graphql/generated-admin-types.d.ts +5807 -0
- package/dist/plugin-server/graphql/generated-admin-types.js +1030 -0
- package/dist/plugin-server/index.d.ts +5 -0
- package/dist/plugin-server/index.js +40 -0
- package/dist/plugin-server/resolvers/replicate-simple-bg-admin.resolver.d.ts +51 -0
- package/dist/plugin-server/resolvers/replicate-simple-bg-admin.resolver.js +105 -0
- package/dist/plugin-server/services/replicate-simple-bg.service.d.ts +45 -0
- package/dist/plugin-server/services/replicate-simple-bg.service.js +254 -0
- package/dist/plugin-server/types.d.ts +29 -0
- package/dist/plugin-server/types.js +2 -0
- package/dist/plugin-server/zeus/const.d.ts +6 -0
- package/dist/plugin-server/zeus/const.js +3714 -0
- package/dist/plugin-server/zeus/index.d.ts +18885 -0
- package/dist/plugin-server/zeus/index.js +1101 -0
- package/dist/plugin-server/zeus/typedDocumentNode.d.ts +3 -0
- package/dist/plugin-server/zeus/typedDocumentNode.js +13 -0
- package/dist/plugin-ui/components/FileUpload.d.ts +11 -0
- package/dist/plugin-ui/components/FileUpload.js +50 -0
- package/dist/plugin-ui/components/RoomStyleSelect.d.ts +7 -0
- package/dist/plugin-ui/components/RoomStyleSelect.js +19 -0
- package/dist/plugin-ui/components/RoomTypeSelect.d.ts +7 -0
- package/dist/plugin-ui/components/RoomTypeSelect.js +21 -0
- package/dist/plugin-ui/components/index.d.ts +1 -0
- package/dist/plugin-ui/components/index.js +1 -0
- package/dist/plugin-ui/constants.d.ts +1 -0
- package/dist/plugin-ui/constants.js +15 -0
- package/dist/plugin-ui/graphql/mutations.d.ts +22 -0
- package/dist/plugin-ui/graphql/mutations.js +21 -0
- package/dist/plugin-ui/graphql/queries.d.ts +87 -0
- package/dist/plugin-ui/graphql/queries.js +32 -0
- package/dist/plugin-ui/graphql/selectors.d.ts +43 -0
- package/dist/plugin-ui/graphql/selectors.js +38 -0
- package/dist/plugin-ui/index.d.ts +1 -0
- package/dist/plugin-ui/index.js +33 -0
- package/dist/plugin-ui/locales/en/index.d.ts +67 -0
- package/dist/plugin-ui/locales/en/index.js +2 -0
- package/dist/plugin-ui/locales/en/simpleBg.json +66 -0
- package/dist/plugin-ui/locales/pl/index.d.ts +67 -0
- package/dist/plugin-ui/locales/pl/index.js +2 -0
- package/dist/plugin-ui/locales/pl/simpleBg.json +66 -0
- package/dist/plugin-ui/pages/ReplicatePage.d.ts +2 -0
- package/dist/plugin-ui/pages/ReplicatePage.js +282 -0
- package/dist/plugin-ui/pages/ReplicateProductSidebar.d.ts +2 -0
- package/dist/plugin-ui/pages/ReplicateProductSidebar.js +186 -0
- package/dist/plugin-ui/translation-ns.d.ts +1 -0
- package/dist/plugin-ui/translation-ns.js +1 -0
- package/dist/plugin-ui/tsconfig.json +18 -0
- package/dist/plugin-ui/types.d.ts +35 -0
- package/dist/plugin-ui/types.js +15 -0
- package/dist/plugin-ui/zeus/const.d.ts +6 -0
- package/dist/plugin-ui/zeus/const.js +3711 -0
- package/dist/plugin-ui/zeus/index.d.ts +18885 -0
- package/dist/plugin-ui/zeus/index.js +1093 -0
- package/dist/plugin-ui/zeus/scalars.d.ts +18 -0
- package/dist/plugin-ui/zeus/scalars.js +25 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.d.ts +3 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.js +9 -0
- package/package.json +53 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"input": "Wejście",
|
|
3
|
+
"output": "Wyjście",
|
|
4
|
+
"run_model": "Uruchom Model",
|
|
5
|
+
"upload_furniture_image": "Prześlij zdjęcie mebla",
|
|
6
|
+
"original_room": "Oryginalny Pokój",
|
|
7
|
+
"choose_room_type_and_theme": "Wybierz pomieszczenie i styl pokoju",
|
|
8
|
+
"select_room_type": "Wybierz pomieszczenie",
|
|
9
|
+
"select_room_theme": "Wybierz styl",
|
|
10
|
+
"generated_room": "Wygenerowany Pokój",
|
|
11
|
+
"loading": "Oczekiwanie na wynik... (ok. 2min)",
|
|
12
|
+
"download_image": "Pobierz Obraz",
|
|
13
|
+
"bedroom": "Sypialnia",
|
|
14
|
+
"living_room": "Salon",
|
|
15
|
+
"dining_room": "Jadalnia",
|
|
16
|
+
"kitchen": "Kuchnia",
|
|
17
|
+
"bathroom": "Łazienka",
|
|
18
|
+
"office": "Biuro",
|
|
19
|
+
"modern": "Nowoczesny",
|
|
20
|
+
"summer": "Letni",
|
|
21
|
+
"professional": "Profesjonalny",
|
|
22
|
+
"tropical": "Tropikalny",
|
|
23
|
+
"coastal": "Nadmorski",
|
|
24
|
+
"vintage": "Vintage",
|
|
25
|
+
"industrial": "Industrialny",
|
|
26
|
+
"neoclassic": "Neoklasyczny",
|
|
27
|
+
"choose_file": "Wybierz plik",
|
|
28
|
+
"no_file_chosen": "Nie wybrano pliku",
|
|
29
|
+
"previous_predictions": "Poprzednie Predykcje",
|
|
30
|
+
"starting": "W trakcie",
|
|
31
|
+
"preprocessing": "Przetwarzanie",
|
|
32
|
+
"succeeded": "Zakończono",
|
|
33
|
+
"failed": "Niepowodzenie",
|
|
34
|
+
"generate_new_background": "Wygeneruj nowe tło",
|
|
35
|
+
"no_room_style": "Brak stylu pomieszczenia",
|
|
36
|
+
"no_room_type": "Brak rodzaju pomieszczenia",
|
|
37
|
+
"no_file": "Brak pliku",
|
|
38
|
+
"refresh": "Odśwież liste",
|
|
39
|
+
"status": "Status",
|
|
40
|
+
"room_type": "Rodzaj pokoju",
|
|
41
|
+
"room_style": "Styl pokoju",
|
|
42
|
+
"prediction": "Predykcja",
|
|
43
|
+
"prediction_details": "Szczególy predykcji",
|
|
44
|
+
"generating_asset": "Generenowanie zdjęcia..",
|
|
45
|
+
"asset_assigned": "Zdjęcie przypisane do produktu",
|
|
46
|
+
"prompt_placeholder": "Czy chcesz coś zmienić? Opisz to tutaj!",
|
|
47
|
+
"prompt": "Zapytanie",
|
|
48
|
+
"error": {
|
|
49
|
+
"loading": "Trwa generowanie zdjęcia.."
|
|
50
|
+
},
|
|
51
|
+
"modal": {
|
|
52
|
+
"title": "Predykcja",
|
|
53
|
+
"choose_room_type": "Wybierz pomieszczenie",
|
|
54
|
+
"choose_room_theme": "Wybierz styl",
|
|
55
|
+
"generate_new": "Wygeneruj nowy",
|
|
56
|
+
"close": "Wyjdź",
|
|
57
|
+
"add_to_product": "Dodaj do produktu",
|
|
58
|
+
"assign_prediction_to_product": "Przypisz predykcję do produktu",
|
|
59
|
+
"product_name": "Nazwa",
|
|
60
|
+
"product_sku": "SKU",
|
|
61
|
+
"product_action": "Akcja",
|
|
62
|
+
"product_assign": "Przypisz",
|
|
63
|
+
"run_model_again": "Uruchom model ponownie",
|
|
64
|
+
"input_prompt": "Czy chcesz coś zmienić? Opisz to tutaj!"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { apiClient, apiUploadClient, Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, DialogProductPicker, Label, PageBlock, ScrollArea, useLazyQuery, useMutation, useQuery, useTranslation, } from "@deenruv/react-ui-devkit";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { translationNS } from "../translation-ns.js";
|
|
5
|
+
import { getPredictionSimpleBGIDQuery, getSimpleBgItemQuery, getSimpleBgPredictionsQuery, getSimpleBgRoomOptions, } from "../graphql/queries.js";
|
|
6
|
+
import { SortOrder } from "../zeus/index.js";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { FileUpload } from "../components/FileUpload.js";
|
|
9
|
+
import { getPredictionAssetMutation, startGenerateSimpleBgMutation, } from "../graphql/mutations.js";
|
|
10
|
+
import { $ } from "@deenruv/admin-types";
|
|
11
|
+
import { Loader2 } from "lucide-react";
|
|
12
|
+
import { useSearchParams } from "react-router-dom";
|
|
13
|
+
import { toast } from "sonner";
|
|
14
|
+
import { RoomStyleSelect } from "../components/RoomStyleSelect.js";
|
|
15
|
+
import { RoomTypeSelect } from "../components/RoomTypeSelect.js";
|
|
16
|
+
export const ReplicatePage = () => {
|
|
17
|
+
const { t } = useTranslation(translationNS);
|
|
18
|
+
const [uploadState, setUploadState] = useState({
|
|
19
|
+
file: null,
|
|
20
|
+
room_style_enum: undefined,
|
|
21
|
+
room_type_enum: undefined,
|
|
22
|
+
});
|
|
23
|
+
const { data } = useQuery(getSimpleBgRoomOptions);
|
|
24
|
+
const [getPredictionID] = useLazyQuery(getPredictionSimpleBGIDQuery);
|
|
25
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
26
|
+
const predictionEntityID = searchParams.get("predictionId");
|
|
27
|
+
const [getPredictionItem, { data: predictionItem }] = useLazyQuery(getSimpleBgItemQuery);
|
|
28
|
+
const [loading, setLoading] = useState(false);
|
|
29
|
+
const [uploadLoading, setUploadLoading] = useState(false);
|
|
30
|
+
const [startGenerateSimpleBg] = useMutation(startGenerateSimpleBgMutation);
|
|
31
|
+
const [predictions, setPredictions] = useState();
|
|
32
|
+
const [getReplicatePredictions, { loading: loadingPredictions }] = useLazyQuery(getSimpleBgPredictionsQuery);
|
|
33
|
+
const [getPredictionAsset] = useMutation(getPredictionAssetMutation);
|
|
34
|
+
const [page, setPage] = useState(1);
|
|
35
|
+
const [filters] = useState({
|
|
36
|
+
sort: { finishedAt: SortOrder.DESC },
|
|
37
|
+
filter: { status: { eq: "succeeded" } },
|
|
38
|
+
});
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
getReplicatePredictions({
|
|
41
|
+
options: {
|
|
42
|
+
...filters,
|
|
43
|
+
take: page * 10,
|
|
44
|
+
skip: (page - 1) * 10,
|
|
45
|
+
},
|
|
46
|
+
}).then((res) => {
|
|
47
|
+
setPredictions((prev) => [...(prev || []), ...res.getSimpleBgPredictions.items].filter((p, i, a) => a.findIndex((t) => t.id === p.id) === i));
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
const intervalRef = useRef(null);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (predictionEntityID &&
|
|
53
|
+
predictionItem?.getSimpleBgItem.status !== "succeeded") {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
intervalRef.current = setInterval(() => {
|
|
56
|
+
getPredictionID({
|
|
57
|
+
prediction_simple_bg_entity_id: predictionEntityID,
|
|
58
|
+
}).then((res) => {
|
|
59
|
+
const predictionID = res.getSimpleBgID;
|
|
60
|
+
if (predictionID) {
|
|
61
|
+
getPredictionItem({ id: predictionID });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}, 5000);
|
|
65
|
+
}
|
|
66
|
+
return () => {
|
|
67
|
+
if (intervalRef.current) {
|
|
68
|
+
clearInterval(intervalRef.current);
|
|
69
|
+
intervalRef.current = null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, [predictionEntityID]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (predictionItem?.getSimpleBgItem.status === "succeeded" &&
|
|
75
|
+
intervalRef.current) {
|
|
76
|
+
clearInterval(intervalRef.current);
|
|
77
|
+
intervalRef.current = null;
|
|
78
|
+
setLoading(false);
|
|
79
|
+
setPage(1);
|
|
80
|
+
getReplicatePredictions({
|
|
81
|
+
options: { ...filters, take: 10, skip: 0 },
|
|
82
|
+
}).then((res) => {
|
|
83
|
+
setPredictions(res.getSimpleBgPredictions.items);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}, [predictionItem?.getSimpleBgItem.status]);
|
|
87
|
+
const getReplicateItem = async (id) => {
|
|
88
|
+
const scrollToElement = document.getElementById("scrollto");
|
|
89
|
+
if (scrollToElement) {
|
|
90
|
+
scrollToElement.scrollIntoView({
|
|
91
|
+
behavior: "smooth",
|
|
92
|
+
block: "start",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
await getPredictionItem({ id });
|
|
96
|
+
};
|
|
97
|
+
const generateSimpleBg = async () => {
|
|
98
|
+
try {
|
|
99
|
+
const { file, room_style_enum, room_type_enum } = uploadState;
|
|
100
|
+
switch (true) {
|
|
101
|
+
case !room_style_enum: {
|
|
102
|
+
toast.error(t("no_room_style"));
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case !room_type_enum: {
|
|
106
|
+
toast.error(t("no_room_type"));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case !file: {
|
|
110
|
+
toast.error(t("no_file"));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
default: {
|
|
114
|
+
setUploadLoading(true);
|
|
115
|
+
const { createAssets } = await apiUploadClient("mutation")({
|
|
116
|
+
createAssets: [
|
|
117
|
+
{ input: $("input", "[CreateAssetInput!]!") },
|
|
118
|
+
{
|
|
119
|
+
__typename: true,
|
|
120
|
+
"...on Asset": { id: true, source: true },
|
|
121
|
+
"...on MimeTypeError": {
|
|
122
|
+
fileName: true,
|
|
123
|
+
mimeType: true,
|
|
124
|
+
errorCode: true,
|
|
125
|
+
message: true,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
}, { variables: { input: [{ file }] } });
|
|
130
|
+
const asset = createAssets[0];
|
|
131
|
+
if (!asset || asset.__typename !== "Asset")
|
|
132
|
+
throw new Error("Cannot upload image");
|
|
133
|
+
const response = await startGenerateSimpleBg({
|
|
134
|
+
input: {
|
|
135
|
+
assetId: asset.id,
|
|
136
|
+
roomStyle: room_style_enum,
|
|
137
|
+
roomType: room_type_enum,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
setSearchParams({ predictionId: response.startGenerateSimpleBg });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
setUploadLoading(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const uploadButtonDisabled = useMemo(() => !uploadState.file ||
|
|
151
|
+
!uploadState.room_style_enum ||
|
|
152
|
+
!uploadState.room_type_enum, [uploadState.file, uploadState.room_style_enum, uploadState.room_type_enum]);
|
|
153
|
+
return (React.createElement(PageBlock, null,
|
|
154
|
+
React.createElement("div", { className: "flex justify-between gap-4" },
|
|
155
|
+
React.createElement(Card, { className: "w-full h-full" },
|
|
156
|
+
React.createElement(CardHeader, null,
|
|
157
|
+
React.createElement(CardTitle, null, t("input"))),
|
|
158
|
+
React.createElement(CardContent, null,
|
|
159
|
+
React.createElement("div", { className: "flex flex-col gap-4" },
|
|
160
|
+
React.createElement(RoomTypeSelect, { roomTypes: data?.getSimpleBgOptions.roomTypes, selectedValue: uploadState.room_type_enum, onValueChange: (newValue) => setUploadState((prev) => ({
|
|
161
|
+
...prev,
|
|
162
|
+
room_type_enum: newValue,
|
|
163
|
+
})) }),
|
|
164
|
+
React.createElement(RoomStyleSelect, { selectedValue: uploadState.room_style_enum, roomThemes: data?.getSimpleBgOptions.roomThemes, onSelect: (value) => setUploadState((prev) => ({
|
|
165
|
+
...prev,
|
|
166
|
+
room_style_enum: value,
|
|
167
|
+
})) }),
|
|
168
|
+
React.createElement("div", null,
|
|
169
|
+
React.createElement(Label, { className: "block text-sm font-medium mb-2" }, t("upload_image")),
|
|
170
|
+
React.createElement(FileUpload, { value: uploadState.file, onChange: (file) => {
|
|
171
|
+
setUploadState((prev) => ({
|
|
172
|
+
...prev,
|
|
173
|
+
file,
|
|
174
|
+
}));
|
|
175
|
+
} })))),
|
|
176
|
+
React.createElement(CardFooter, { className: "flex justify-end items-center w-full" },
|
|
177
|
+
React.createElement(Button, { className: "relative", type: "button", onClick: generateSimpleBg, disabled: uploadButtonDisabled || uploadLoading || loading },
|
|
178
|
+
React.createElement("span", { className: cn("transition-opacity", uploadLoading && "opacity-0") }, t("run_model")),
|
|
179
|
+
uploadLoading && (React.createElement("div", { className: "absolute inset-0 flex items-center justify-center" },
|
|
180
|
+
React.createElement(Loader2, { className: "animate-spin", size: 16, strokeWidth: 2 })))))),
|
|
181
|
+
React.createElement(Card, { className: "w-full h-[500px]" },
|
|
182
|
+
React.createElement(CardHeader, null,
|
|
183
|
+
React.createElement("div", { className: "flex w-full flex-row items-center justify-between" },
|
|
184
|
+
React.createElement(CardTitle, null, t("previous_predictions")))),
|
|
185
|
+
React.createElement(CardContent, null,
|
|
186
|
+
React.createElement(ScrollArea, { className: "h-[350px] pr-4", id: "scrollable" }, predictions?.length === 0 ? (React.createElement("div", { className: "flex items-center justify-center h-full" },
|
|
187
|
+
React.createElement("p", { className: "text-muted-foreground" }, t("no_predictions")))) : (React.createElement("div", { className: "flex flex-col gap-2" }, predictions?.map((prediction) => (React.createElement(Card, { key: prediction.id, className: cn("flex items-center justify-between p-2 cursor-pointer", predictionItem?.getSimpleBgItem.id === prediction.id
|
|
188
|
+
? "bg-primary text-primary-foreground"
|
|
189
|
+
: "hover:bg-secondary hover:text-secondary-foreground", loading ? "pointer-events-none" : ""), onClick: async () => {
|
|
190
|
+
if (loading)
|
|
191
|
+
return;
|
|
192
|
+
await getReplicateItem(prediction.id);
|
|
193
|
+
} },
|
|
194
|
+
prediction.id,
|
|
195
|
+
" - ",
|
|
196
|
+
prediction.status,
|
|
197
|
+
" -",
|
|
198
|
+
" ",
|
|
199
|
+
prediction.finishedAt))))))),
|
|
200
|
+
React.createElement(CardFooter, { className: "flex justify-end items-center w-full" },
|
|
201
|
+
React.createElement(Button, { type: "button", disabled: loadingPredictions || loading, onClick: () => {
|
|
202
|
+
setPage((prev) => prev + 1);
|
|
203
|
+
getReplicatePredictions({
|
|
204
|
+
options: {
|
|
205
|
+
...filters,
|
|
206
|
+
take: page * 10,
|
|
207
|
+
skip: (page - 1) * 10,
|
|
208
|
+
},
|
|
209
|
+
}).then((res) => {
|
|
210
|
+
const scrollableElement = document.getElementById("scrollable");
|
|
211
|
+
if (scrollableElement) {
|
|
212
|
+
scrollableElement.scrollTop = 0;
|
|
213
|
+
}
|
|
214
|
+
setPredictions((prev) => [
|
|
215
|
+
...(prev || []),
|
|
216
|
+
...res.getSimpleBgPredictions.items,
|
|
217
|
+
].filter((p, i, a) => a.findIndex((t) => t.id === p.id) === i));
|
|
218
|
+
});
|
|
219
|
+
} }, t("refresh"))))),
|
|
220
|
+
React.createElement(Card, { className: "w-full mt-4" },
|
|
221
|
+
React.createElement(CardHeader, null,
|
|
222
|
+
React.createElement("div", { className: "flex w-full flex-row items-center justify-between" },
|
|
223
|
+
React.createElement(CardTitle, null, t("output")))),
|
|
224
|
+
React.createElement(CardContent, null, loading ? (React.createElement("div", { className: "flex flex-col gap-2 items-center justify-center h-[350px] w-full" },
|
|
225
|
+
React.createElement(Loader2, { className: "animate-spin text-primary", size: 24, strokeWidth: 2 }),
|
|
226
|
+
React.createElement("p", { className: "text-muted-foreground" }, t("loading")))) : predictionItem?.getSimpleBgItem?.image ? (React.createElement("div", { className: "h-[450px] w-full flex justify-between items-start gap-8" },
|
|
227
|
+
React.createElement("div", { className: "relative w-full h-full overflow-hidden rounded-lg" },
|
|
228
|
+
React.createElement("img", { className: "absolute inset-0 w-full h-full object-cover rounded-md", src: predictionItem.getSimpleBgItem.image || "/placeholder.svg", alt: "Generated Room" })),
|
|
229
|
+
React.createElement(Card, { className: "w-full h-full flex-col flex justify-between" },
|
|
230
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
231
|
+
React.createElement(CardHeader, null,
|
|
232
|
+
React.createElement(CardTitle, null, t("prediction_details"))),
|
|
233
|
+
React.createElement(CardContent, null,
|
|
234
|
+
predictionItem.getSimpleBgItem.roomType && (React.createElement("div", null,
|
|
235
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
236
|
+
t("room_type"),
|
|
237
|
+
":"),
|
|
238
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, predictionItem.getSimpleBgItem.roomType))),
|
|
239
|
+
predictionItem.getSimpleBgItem.roomStyle && (React.createElement("div", null,
|
|
240
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
241
|
+
t("room_style"),
|
|
242
|
+
":"),
|
|
243
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, predictionItem.getSimpleBgItem.roomStyle))),
|
|
244
|
+
predictionItem.getSimpleBgItem.status && (React.createElement("div", null,
|
|
245
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
246
|
+
t("status"),
|
|
247
|
+
":"),
|
|
248
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, predictionItem.getSimpleBgItem.status))))),
|
|
249
|
+
React.createElement(CardFooter, null,
|
|
250
|
+
React.createElement(DialogProductPicker, { initialValue: "", mode: "product", onSubmit: async ({ productId }) => {
|
|
251
|
+
const response = await apiClient("query")({
|
|
252
|
+
product: [{ id: productId }, { assets: { id: true } }],
|
|
253
|
+
});
|
|
254
|
+
const asset = await getPredictionAsset({
|
|
255
|
+
input: {
|
|
256
|
+
predictionId: predictionEntityID ?? "",
|
|
257
|
+
productId,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
const assetIds = response.product?.assets
|
|
261
|
+
.map((asset) => asset.id)
|
|
262
|
+
.concat(asset.getPredictionAsset.id);
|
|
263
|
+
const { updateProduct } = await apiClient("mutation")({
|
|
264
|
+
updateProduct: [
|
|
265
|
+
{ input: { id: productId, assetIds } },
|
|
266
|
+
{ id: true },
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
// toast(
|
|
270
|
+
// <div className="flex gap-2 items-center">
|
|
271
|
+
// <p className="text-sm font-semibold">
|
|
272
|
+
// {t("prediction_assigned")}
|
|
273
|
+
// </p>
|
|
274
|
+
// <Link to={Routes.products.to(updateProduct.id)}>
|
|
275
|
+
// {t("view_product")}
|
|
276
|
+
// </Link>
|
|
277
|
+
// </div>,
|
|
278
|
+
// );
|
|
279
|
+
} }))))) : (React.createElement("div", { className: "flex items-center justify-center h-[350px]" },
|
|
280
|
+
React.createElement("p", { className: "text-muted-foreground" }, t("no_output")))))),
|
|
281
|
+
React.createElement("div", { id: "scrollto", className: "h-[50px]" })));
|
|
282
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { Card, CardHeader, Button, useDetailView, useMutation, Dialog, useTranslation, CardContent, cn, useQuery, DialogContent, DialogHeader, DialogTitle, CardTitle, useLazyQuery, Textarea, } from "@deenruv/react-ui-devkit";
|
|
3
|
+
import { translationNS } from "../translation-ns";
|
|
4
|
+
import { getPredictionAssetMutation, startGenerateSimpleBgMutation, } from "../graphql/mutations.js";
|
|
5
|
+
import { ChevronDown, Image, Loader2 } from "lucide-react";
|
|
6
|
+
import { getPredictionSimpleBGIDQuery, getSimpleBgItemQuery, getSimpleBgRoomOptions, } from "../graphql/queries.js";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
import { RoomStyleSelect } from "../components/RoomStyleSelect.js";
|
|
9
|
+
import { RoomTypeSelect } from "../components/RoomTypeSelect.js";
|
|
10
|
+
export const ReplicateProductSidebar = () => {
|
|
11
|
+
const { t } = useTranslation(translationNS);
|
|
12
|
+
const { id: productId, entity, markAsDirty, form: { base: { state, setField }, }, } = useDetailView("products-detail-view");
|
|
13
|
+
const [form, setForm] = useState({ roomType: undefined, roomStyle: undefined, prompt: null });
|
|
14
|
+
const { data } = useQuery(getSimpleBgRoomOptions);
|
|
15
|
+
const [getPredictionItem, { data: predictionItem, setData: setPredictionItem },] = useLazyQuery(getSimpleBgItemQuery);
|
|
16
|
+
const [getPredictionID] = useLazyQuery(getPredictionSimpleBGIDQuery);
|
|
17
|
+
const [getPredictionAsset] = useMutation(getPredictionAssetMutation);
|
|
18
|
+
const [startGenerateSimpleBg, { loading: gettingInitialID }] = useMutation(startGenerateSimpleBgMutation);
|
|
19
|
+
const [predictionEntityID, setPredictionEntityID] = useState(null);
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [isDialogVisible, setIsDialogVisible] = useState(false);
|
|
22
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
23
|
+
const [height, setHeight] = useState(undefined);
|
|
24
|
+
const contentRef = useRef(null);
|
|
25
|
+
const intervalRef = useRef(null);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (predictionEntityID &&
|
|
28
|
+
predictionItem?.getSimpleBgItem.status !== "succeeded") {
|
|
29
|
+
setLoading(true);
|
|
30
|
+
intervalRef.current = setInterval(() => {
|
|
31
|
+
getPredictionID({
|
|
32
|
+
prediction_simple_bg_entity_id: predictionEntityID,
|
|
33
|
+
}).then((res) => {
|
|
34
|
+
const predictionID = res.getSimpleBgID;
|
|
35
|
+
if (predictionID) {
|
|
36
|
+
getPredictionItem({ id: predictionID });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, 5000);
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
if (intervalRef.current) {
|
|
43
|
+
clearInterval(intervalRef.current);
|
|
44
|
+
intervalRef.current = null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}, [predictionEntityID]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (predictionItem?.getSimpleBgItem.status === "succeeded" &&
|
|
50
|
+
intervalRef.current) {
|
|
51
|
+
clearInterval(intervalRef.current);
|
|
52
|
+
intervalRef.current = null;
|
|
53
|
+
setIsDialogVisible(true);
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}, [predictionItem?.getSimpleBgItem.status]);
|
|
57
|
+
const handleClose = () => {
|
|
58
|
+
if (loading) {
|
|
59
|
+
toast.error(t("error.loading"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setIsDialogVisible(false);
|
|
63
|
+
setPredictionEntityID(null);
|
|
64
|
+
setForm({ roomType: undefined, roomStyle: undefined, prompt: null });
|
|
65
|
+
};
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (contentRef.current) {
|
|
68
|
+
setHeight(isOpen ? contentRef.current.scrollHeight : 0);
|
|
69
|
+
}
|
|
70
|
+
}, [isOpen, contentRef.current, data?.getSimpleBgOptions]);
|
|
71
|
+
const runModel = async () => {
|
|
72
|
+
const assetId = entity?.featuredAsset?.id;
|
|
73
|
+
if (assetId) {
|
|
74
|
+
const { roomStyle, roomType, prompt } = form;
|
|
75
|
+
const response = await startGenerateSimpleBg({
|
|
76
|
+
input: { assetId, roomType, roomStyle, prompt },
|
|
77
|
+
});
|
|
78
|
+
const id = response.startGenerateSimpleBg;
|
|
79
|
+
if (!id) {
|
|
80
|
+
toast.error(t("error.start_model_run"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setPredictionEntityID(id);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const assignPredictionToProduct = async () => {
|
|
87
|
+
if (!productId || !predictionEntityID)
|
|
88
|
+
return;
|
|
89
|
+
const asset = await getPredictionAsset({
|
|
90
|
+
input: { predictionId: predictionEntityID, productId: productId },
|
|
91
|
+
});
|
|
92
|
+
if (!asset || !entity)
|
|
93
|
+
return;
|
|
94
|
+
const newAsset = asset.getPredictionAsset;
|
|
95
|
+
if (state.assetIds?.value) {
|
|
96
|
+
setField("assetIds", [...state.assetIds.value, newAsset.id]);
|
|
97
|
+
}
|
|
98
|
+
markAsDirty();
|
|
99
|
+
setIsDialogVisible(false);
|
|
100
|
+
setPredictionEntityID(null);
|
|
101
|
+
setForm({ roomType: undefined, roomStyle: undefined, prompt: null });
|
|
102
|
+
setLoading(false);
|
|
103
|
+
toast.success(t("success.asset_assigned"));
|
|
104
|
+
};
|
|
105
|
+
return (React.createElement(React.Fragment, null,
|
|
106
|
+
React.createElement("div", { className: "w-full flex flex-col gap-4" },
|
|
107
|
+
React.createElement(Card, { className: "overflow-hidden rounded-lg border bg-card border-l-orange-200 duration-200 hover:shadow h-full border-l-4" },
|
|
108
|
+
React.createElement(CardHeader, { className: "p-4 flex flex-row items-center justify-between cursor-pointer select-none", onClick: () => setIsOpen(!isOpen) },
|
|
109
|
+
React.createElement("div", { className: "flex items-center gap-2" },
|
|
110
|
+
React.createElement(Image, { className: "w-5 h-5 text-white/70" }),
|
|
111
|
+
React.createElement("span", { className: "font-semibold tracking-tight text-lg" }, t("generate_new_background"))),
|
|
112
|
+
React.createElement(ChevronDown, { className: cn("h-5 w-5 text-white/70 transition-transform duration-300 ease-in-out", isOpen && "transform -rotate-180") })),
|
|
113
|
+
React.createElement("div", { ref: contentRef, style: { height: height !== undefined ? `${height}px` : undefined }, className: cn("transition-all duration-300 ease-in-out p-2", !isOpen && "opacity-0") },
|
|
114
|
+
React.createElement(CardContent, { className: "pt-0 pb-4 px-4" },
|
|
115
|
+
React.createElement("div", { className: "flex flex-col gap-4" },
|
|
116
|
+
React.createElement(RoomStyleSelect, { selectedValue: form.roomStyle, roomThemes: data?.getSimpleBgOptions.roomThemes, onSelect: (value) => setForm((prev) => ({
|
|
117
|
+
...prev,
|
|
118
|
+
roomStyle: value,
|
|
119
|
+
})) }),
|
|
120
|
+
React.createElement(RoomTypeSelect, { roomTypes: data?.getSimpleBgOptions.roomTypes, selectedValue: form.roomType, onValueChange: (newValue) => {
|
|
121
|
+
setForm((prev) => ({
|
|
122
|
+
...prev,
|
|
123
|
+
roomType: newValue,
|
|
124
|
+
}));
|
|
125
|
+
} }),
|
|
126
|
+
React.createElement(Button, { className: "mt-2 relative", disabled: (!form.roomType || !form.roomStyle || loading) &&
|
|
127
|
+
!isDialogVisible, onClick: runModel },
|
|
128
|
+
React.createElement("span", { className: cn("transition-opacity", loading && "opacity-0") }, t("run_model")),
|
|
129
|
+
loading && (React.createElement("div", { className: "absolute inset-0 flex items-center gap-2 justify-center" },
|
|
130
|
+
React.createElement(Loader2, { className: "animate-spin", size: 16, strokeWidth: 2 }),
|
|
131
|
+
React.createElement("span", null, t("generating_asset")))))))))),
|
|
132
|
+
React.createElement(Dialog, { open: isDialogVisible, onOpenChange: handleClose },
|
|
133
|
+
React.createElement(DialogContent, { className: "w-full lg:max-w-[900px] xl:max-w-[1100px] h-[600px] grid-rows-[auto_1fr_auto]", onInteractOutside: (e) => e.preventDefault() },
|
|
134
|
+
React.createElement(DialogHeader, null,
|
|
135
|
+
React.createElement(DialogTitle, null, t("modal.title"))),
|
|
136
|
+
React.createElement("div", { className: "w-full h-full relative grid md:grid-cols-[1fr_auto] gap-2" },
|
|
137
|
+
React.createElement("div", { className: "flex flex-col gap-2 h-full" },
|
|
138
|
+
React.createElement("div", { className: "relative w-full min-h-[300px] grow overflow-hidden rounded-lg" }, predictionItem?.getSimpleBgItem.image ? (React.createElement("img", { className: "absolute inset-0 w-full h-full object-contain rounded-md", src: predictionItem?.getSimpleBgItem.image, alt: "Generated Room" })) : (React.createElement("div", { className: "absolute inset-0 flex items-center justify-center" },
|
|
139
|
+
React.createElement(Image, { className: "w-10 h-10 text-muted-foreground" }))))),
|
|
140
|
+
React.createElement("div", { className: "flex flex-col gap-2 w-[300px]" },
|
|
141
|
+
React.createElement(Card, { className: "w-full h-full flex-col flex justify-between" },
|
|
142
|
+
React.createElement("div", { className: "flex flex-col gap-2 h-full" },
|
|
143
|
+
React.createElement(CardHeader, null,
|
|
144
|
+
React.createElement(CardTitle, null, t("prediction_details"))),
|
|
145
|
+
React.createElement(CardContent, { className: "flex flex-col justify-between h-full" },
|
|
146
|
+
React.createElement("div", { className: "flex flex-col gap-2 h-full" },
|
|
147
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
148
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
149
|
+
t("room_type"),
|
|
150
|
+
":"),
|
|
151
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, t(form.roomType?.toLowerCase() ||
|
|
152
|
+
predictionItem?.getSimpleBgItem.roomType ||
|
|
153
|
+
""))),
|
|
154
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
155
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
156
|
+
t("room_style"),
|
|
157
|
+
":"),
|
|
158
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, t(form.roomStyle?.toLowerCase() ||
|
|
159
|
+
predictionItem?.getSimpleBgItem.roomStyle ||
|
|
160
|
+
""))),
|
|
161
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
162
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
163
|
+
t("status"),
|
|
164
|
+
":"),
|
|
165
|
+
React.createElement("p", { className: "text-sm text-muted-foreground" }, t(form.roomType?.toLowerCase() ||
|
|
166
|
+
predictionItem?.getSimpleBgItem.status ||
|
|
167
|
+
""))),
|
|
168
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
169
|
+
React.createElement("p", { className: "text-sm font-semibold" },
|
|
170
|
+
t("prompt"),
|
|
171
|
+
":"),
|
|
172
|
+
React.createElement(Textarea, { className: "w-full h-24 resize-none", value: form.prompt || "", onChange: (e) => setForm((prev) => ({
|
|
173
|
+
...prev,
|
|
174
|
+
prompt: e.target.value,
|
|
175
|
+
})), placeholder: t("modal.prompt_placeholder"), disabled: loading })),
|
|
176
|
+
React.createElement("div", { className: "w-full h-full flex flex-col justify-end" },
|
|
177
|
+
React.createElement(Button, { variant: "secondary", onClick: async () => {
|
|
178
|
+
setPredictionEntityID(null);
|
|
179
|
+
setPredictionItem(null);
|
|
180
|
+
await runModel();
|
|
181
|
+
}, disabled: loading || gettingInitialID }, loading ? (React.createElement("div", { className: "flex items-center gap-2" },
|
|
182
|
+
React.createElement(Loader2, { className: "animate-spin", size: 16, strokeWidth: 2 }),
|
|
183
|
+
t("generating_asset"))) : (t("modal.run_model_again")))))))),
|
|
184
|
+
React.createElement("div", { className: "flex justify-end" },
|
|
185
|
+
React.createElement(Button, { onClick: assignPredictionToProduct, disabled: !predictionItem?.getSimpleBgItem.image }, t("modal.assign_prediction_to_product")))))))));
|
|
186
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const translationNS: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const translationNS = Symbol("replicate-simple-bg-plugin").toString();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "node",
|
|
5
|
+
"target": "ES2020",
|
|
6
|
+
"jsx": "react",
|
|
7
|
+
"outDir": "../../dist/plugin-ui",
|
|
8
|
+
"importHelpers": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"allowSyntheticDefaultImports": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["./**/*.tsx", "./**/*.json", "./**/*.ts"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const formSchema: z.ZodObject<{
|
|
4
|
+
file: z.ZodUnion<[z.ZodType<File, z.ZodTypeDef, File>, z.ZodNull]>;
|
|
5
|
+
room_type_enum: z.ZodString;
|
|
6
|
+
room_style_enum: z.ZodString;
|
|
7
|
+
prompt: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
file: File | null;
|
|
10
|
+
room_style_enum: string;
|
|
11
|
+
room_type_enum: string;
|
|
12
|
+
prompt?: string | null | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
file: File | null;
|
|
15
|
+
room_style_enum: string;
|
|
16
|
+
room_type_enum: string;
|
|
17
|
+
prompt?: string | null | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
export type FormValues = z.infer<typeof formSchema>;
|
|
20
|
+
export declare const useReplicateForm: () => import("react-hook-form").UseFormReturn<{
|
|
21
|
+
file: File | null;
|
|
22
|
+
room_style_enum: string;
|
|
23
|
+
room_type_enum: string;
|
|
24
|
+
prompt?: string | null | undefined;
|
|
25
|
+
}, any, {
|
|
26
|
+
file: File | null;
|
|
27
|
+
room_style_enum: string;
|
|
28
|
+
room_type_enum: string;
|
|
29
|
+
prompt?: string | null | undefined;
|
|
30
|
+
}>;
|
|
31
|
+
export interface CustomFileInputProps {
|
|
32
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
33
|
+
accept?: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
3
|
+
import { useForm } from "react-hook-form";
|
|
4
|
+
export const formSchema = z.object({
|
|
5
|
+
file: z.union([z.instanceof(File), z.null()]),
|
|
6
|
+
room_type_enum: z.string().min(1),
|
|
7
|
+
room_style_enum: z.string().min(1),
|
|
8
|
+
prompt: z.string().optional().nullable(),
|
|
9
|
+
});
|
|
10
|
+
export const useReplicateForm = () => {
|
|
11
|
+
// zodResolver type constraint mismatches due to zod v3/v4 compat layer;
|
|
12
|
+
// cast through unknown to bridge the incompatible ZodType definitions.
|
|
13
|
+
const resolver = zodResolver(formSchema);
|
|
14
|
+
return useForm({ resolver });
|
|
15
|
+
};
|