Merge upstream/main into feat/ctrl-v
@ -1,13 +1,32 @@
|
||||
export const defaultModelsList = [
|
||||
{ label: "General Photo (Real-ESRGAN)", value: "realesrgan-x4plus" },
|
||||
{
|
||||
label: "General Photo (Fast Real-ESRGAN)",
|
||||
value: "realesrgan-x4fast",
|
||||
export const MODELS = {
|
||||
"realesrgan-x4plus": {
|
||||
id: "realesrgan-x4plus",
|
||||
name: "Upscayl Standard",
|
||||
},
|
||||
{ label: "General Photo (Remacri)", value: "remacri" },
|
||||
{ label: "General Photo (Ultramix Balanced)", value: "ultramix_balanced" },
|
||||
{ label: "General Photo (Ultrasharp)", value: "ultrasharp" },
|
||||
{ label: "Digital Art", value: "realesrgan-x4plus-anime" },
|
||||
];
|
||||
"realesrgan-x4fast": {
|
||||
id: "realesrgan-x4fast",
|
||||
name: "Upscayl Lite",
|
||||
},
|
||||
remacri: {
|
||||
id: "remacri",
|
||||
name: "Remacri (Non-Commercial)",
|
||||
},
|
||||
ultramix_balanced: {
|
||||
id: "ultramix_balanced",
|
||||
name: "Ultramix (Non-Commercial)",
|
||||
},
|
||||
ultrasharp: {
|
||||
id: "ultrasharp",
|
||||
name: "Ultrasharp (Non-Commercial)",
|
||||
},
|
||||
"realesrgan-x4plus-anime": {
|
||||
id: "realesrgan-x4plus-anime",
|
||||
name: "Digital Art",
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_MODELS = defaultModelsList.map((model) => model.value);
|
||||
export type ModelId = keyof typeof MODELS;
|
||||
|
||||
export const DEFAULT_MODELS_ID_LIST = Object.keys(MODELS).map(
|
||||
(modelId) => modelId,
|
||||
);
|
||||
|
@ -14,7 +14,7 @@ import { modelsPath } from "../utils/get-resource-paths";
|
||||
import { ELECTRON_COMMANDS } from "../../common/electron-commands";
|
||||
import { BatchUpscaylPayload } from "../../common/types/types";
|
||||
import showNotification from "../utils/show-notification";
|
||||
import { DEFAULT_MODELS } from "../../common/models-list";
|
||||
import { DEFAULT_MODELS_ID_LIST } from "../../common/models-list";
|
||||
|
||||
const batchUpscayl = async (event, payload: BatchUpscaylPayload) => {
|
||||
const mainWindow = getMainWindow();
|
||||
@ -41,7 +41,7 @@ const batchUpscayl = async (event, payload: BatchUpscaylPayload) => {
|
||||
fs.mkdirSync(outputFolderPath, { recursive: true });
|
||||
}
|
||||
|
||||
const isDefaultModel = DEFAULT_MODELS.includes(model);
|
||||
const isDefaultModel = DEFAULT_MODELS_ID_LIST.includes(model);
|
||||
|
||||
// UPSCALE
|
||||
const upscayl = spawnUpscayl(
|
||||
|
@ -18,7 +18,7 @@ import { ELECTRON_COMMANDS } from "../../common/electron-commands";
|
||||
import { DoubleUpscaylPayload } from "../../common/types/types";
|
||||
import { ImageFormat } from "../types/types";
|
||||
import showNotification from "../utils/show-notification";
|
||||
import { DEFAULT_MODELS } from "../../common/models-list";
|
||||
import { DEFAULT_MODELS_ID_LIST } from "../../common/models-list";
|
||||
import getFilenameFromPath from "../../common/get-file-name";
|
||||
import decodePath from "../../common/decode-path";
|
||||
import getDirectoryFromPath from "../../common/get-directory-from-path";
|
||||
@ -41,7 +41,7 @@ const doubleUpscayl = async (event, payload: DoubleUpscaylPayload) => {
|
||||
const fullfileName = getFilenameFromPath(imagePath);
|
||||
const fileName = parse(fullfileName).name;
|
||||
|
||||
const isDefaultModel = DEFAULT_MODELS.includes(model);
|
||||
const isDefaultModel = DEFAULT_MODELS_ID_LIST.includes(model);
|
||||
|
||||
// COPY IMAGE TO TMP FOLDER
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { getMainWindow } from "../main-window";
|
||||
import { ImageUpscaylPayload } from "../../common/types/types";
|
||||
import { ImageFormat } from "../types/types";
|
||||
import showNotification from "../utils/show-notification";
|
||||
import { DEFAULT_MODELS } from "../../common/models-list";
|
||||
import { DEFAULT_MODELS_ID_LIST } from "../../common/models-list";
|
||||
import getFilenameFromPath from "../../common/get-file-name";
|
||||
import decodePath from "../../common/decode-path";
|
||||
import getDirectoryFromPath from "../../common/get-directory-from-path";
|
||||
@ -55,7 +55,7 @@ const imageUpscayl = async (event, payload: ImageUpscaylPayload) => {
|
||||
"." +
|
||||
saveImageAs;
|
||||
|
||||
const isDefaultModel = DEFAULT_MODELS.includes(model);
|
||||
const isDefaultModel = DEFAULT_MODELS_ID_LIST.includes(model);
|
||||
|
||||
// Check if windows can write the new filename to the file system
|
||||
if (outFile.length >= 255) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import log from "electron-log";
|
||||
import { ELECTRON_COMMANDS } from "../../common/electron-commands";
|
||||
import { getMainWindow } from "../main-window";
|
||||
import { ELECTRON_COMMANDS } from "../../common/electron-commands";
|
||||
|
||||
const logit = (...args: any) => {
|
||||
const mainWindow = getMainWindow();
|
||||
|
@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
X-Desktop-File-Install-Version=0.26
|
||||
Exec=upscayl %U --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations
|
||||
Comment=Upscale Images with AI
|
||||
Name=Upscayl
|
||||
StartupNotify=false
|
||||
Categories=ImageProcessing;RasterGraphics;Graphics;
|
||||
Type=Application
|
173
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@ -1739,6 +1740,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@radix-ui/number": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||
"integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
@ -1866,6 +1872,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||
"integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
@ -2092,6 +2112,159 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.0.tgz",
|
||||
"integrity": "sha512-q2jMBdsJ9zB7QG6ngQNzNwlvxLQqONyL58QbEGwuyRZZb/ARQwk3uQVbCF7GvQVOtV6EU/pDxAw3zRzJZI3rpQ==",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
|
||||
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
|
||||
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
|
||||
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
||||
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
|
@ -225,6 +225,7 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
@ -1,9 +1,3 @@
|
||||
import { defaultModelsList } from "@common/models-list";
|
||||
import { atom } from "jotai";
|
||||
|
||||
export type TModelsList = {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
|
||||
export const modelsListAtom = atom<TModelsList>(defaultModelsList);
|
||||
export const customModelIdsAtom = atom([] as string[]);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ImageFormat } from "@/lib/valid-formats";
|
||||
import { ModelId } from "@common/models-list";
|
||||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
|
||||
@ -6,6 +8,17 @@ export const customModelsPathAtom = atomWithStorage<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
export const selectedModelIdAtom = atomWithStorage<ModelId | string>(
|
||||
"selectedModelId",
|
||||
"realesrgan-x4plus",
|
||||
);
|
||||
export const doubleUpscaylAtom = atomWithStorage("selectedModelId", false);
|
||||
export const gpuIdAtom = atomWithStorage("gpuId", "");
|
||||
export const saveImageAsAtom = atomWithStorage<ImageFormat>(
|
||||
"saveImageAs",
|
||||
"png",
|
||||
);
|
||||
|
||||
export const scaleAtom = atomWithStorage<string>("scale", "4");
|
||||
|
||||
export const batchModeAtom = atom<boolean>(false);
|
||||
|
@ -15,7 +15,7 @@ import { useToast } from "@/components/ui/use-toast";
|
||||
import { sanitizePath } from "@common/sanitize-path";
|
||||
import getDirectoryFromPath from "@common/get-directory-from-path";
|
||||
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||
import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
|
||||
import { ImageFormat, VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
|
||||
import ProgressBar from "./progress-bar";
|
||||
import InstructionsCard from "./instructions-card";
|
||||
import ImageViewSettings from "./image-view-settings";
|
||||
@ -172,7 +172,7 @@ const MainContent = ({
|
||||
const fileName = `${currentTime}-${fileObject.name}`;
|
||||
const file = {
|
||||
name: fileName,
|
||||
extension: fileName.split(".").pop(),
|
||||
extension: fileName.split(".").pop() as ImageFormat,
|
||||
size: fileObject.size,
|
||||
type: fileObject.type.split("/")[0],
|
||||
encodedBuffer: "",
|
||||
|
@ -14,6 +14,10 @@ import {
|
||||
useCustomWidthAtom,
|
||||
tileSizeAtom,
|
||||
showSidebarAtom,
|
||||
selectedModelIdAtom,
|
||||
doubleUpscaylAtom,
|
||||
gpuIdAtom,
|
||||
saveImageAsAtom,
|
||||
} from "../../atoms/user-settings-atom";
|
||||
import useLogger from "../hooks/use-logger";
|
||||
import {
|
||||
@ -63,16 +67,16 @@ const Sidebar = ({
|
||||
|
||||
// LOCAL STATES
|
||||
// TODO: Add electron handler for os
|
||||
const [model, setModel] = useState("realesrgan-x4plus");
|
||||
const [doubleUpscayl, setDoubleUpscayl] = useState(false);
|
||||
const overwrite = useAtomValue(overwriteAtom);
|
||||
const [gpuId, setGpuId] = useState("");
|
||||
const [saveImageAs, setSaveImageAs] = useState("png");
|
||||
const [selectedModelId, setSelectedModelId] = useAtom(selectedModelIdAtom);
|
||||
const [doubleUpscayl, setDoubleUpscayl] = useAtom(doubleUpscaylAtom);
|
||||
const [gpuId, setGpuId] = useAtom(gpuIdAtom);
|
||||
const [saveImageAs, setSaveImageAs] = useAtom(saveImageAsAtom);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [showCloudModal, setShowCloudModal] = useState(false);
|
||||
|
||||
// ATOMIC STATES
|
||||
const overwrite = useAtomValue(overwriteAtom);
|
||||
const outputPath = useAtomValue(savedOutputPathAtom);
|
||||
const [compression, setCompression] = useAtom(compressionAtom);
|
||||
const setProgress = useSetAtom(progressAtom);
|
||||
@ -87,7 +91,7 @@ const Sidebar = ({
|
||||
const [showSidebar, setShowSidebar] = useAtom(showSidebarAtom);
|
||||
|
||||
const handleModelChange = (e: any) => {
|
||||
setModel(e.value);
|
||||
setSelectedModelId(e.value);
|
||||
logit("🔀 Model changed: ", e.value);
|
||||
localStorage.setItem(
|
||||
"model",
|
||||
@ -108,7 +112,7 @@ const Sidebar = ({
|
||||
{
|
||||
imagePath,
|
||||
outputPath,
|
||||
model,
|
||||
model: selectedModelId,
|
||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
||||
saveImageAs,
|
||||
scale,
|
||||
@ -128,7 +132,7 @@ const Sidebar = ({
|
||||
{
|
||||
batchFolderPath,
|
||||
outputPath,
|
||||
model,
|
||||
model: selectedModelId,
|
||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
||||
saveImageAs,
|
||||
scale,
|
||||
@ -145,7 +149,7 @@ const Sidebar = ({
|
||||
window.electron.send<ImageUpscaylPayload>(ELECTRON_COMMANDS.UPSCAYL, {
|
||||
imagePath,
|
||||
outputPath,
|
||||
model,
|
||||
model: selectedModelId,
|
||||
gpuId: gpuId.length === 0 ? null : gpuId,
|
||||
saveImageAs,
|
||||
scale,
|
||||
@ -181,7 +185,7 @@ const Sidebar = ({
|
||||
className={`relative flex h-screen min-w-[350px] max-w-[350px] flex-col bg-base-100 ${showSidebar ? "" : "hidden"}`}
|
||||
>
|
||||
<button
|
||||
className="absolute -right-0 top-1/2 z-[999] -translate-y-1/2 translate-x-1/2 rounded-full bg-base-100 p-4"
|
||||
className="absolute -right-0 top-1/2 z-50 -translate-y-1/2 translate-x-1/2 rounded-full bg-base-100 p-4"
|
||||
onClick={() => setShowSidebar((prev) => !prev)}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
@ -201,7 +205,6 @@ const Sidebar = ({
|
||||
<UpscaylSteps
|
||||
selectImageHandler={selectImageHandler}
|
||||
selectFolderHandler={selectFolderHandler}
|
||||
handleModelChange={handleModelChange}
|
||||
upscaylHandler={upscaylHandler}
|
||||
batchMode={batchMode}
|
||||
setBatchMode={setBatchMode}
|
||||
@ -210,8 +213,6 @@ const Sidebar = ({
|
||||
setDoubleUpscayl={setDoubleUpscayl}
|
||||
dimensions={dimensions}
|
||||
setGpuId={setGpuId}
|
||||
model={model}
|
||||
setModel={setModel}
|
||||
setSaveImageAs={setSaveImageAs}
|
||||
/>
|
||||
)}
|
||||
@ -219,7 +220,6 @@ const Sidebar = ({
|
||||
{selectedTab === 1 && (
|
||||
<SettingsTab
|
||||
batchMode={batchMode}
|
||||
setModel={setModel}
|
||||
compression={compression}
|
||||
setCompression={setCompression}
|
||||
gpuId={gpuId}
|
||||
|
@ -10,7 +10,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { themeChange } from "theme-change";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { customModelsPathAtom, scaleAtom } from "@/atoms/user-settings-atom";
|
||||
import { modelsListAtom } from "@/atoms/models-list-atom";
|
||||
import { customModelIdsAtom } from "@/atoms/models-list-atom";
|
||||
import useLogger from "@/components/hooks/use-logger";
|
||||
import { InputCompression } from "./input-compression";
|
||||
import OverwriteToggle from "./overwrite-toggle";
|
||||
@ -23,12 +23,12 @@ import { InputCustomResolution } from "./input-custom-resolution";
|
||||
import { InputTileSize } from "./input-tile-size";
|
||||
import LanguageSwitcher from "./language-switcher";
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { ImageFormat } from "@/lib/valid-formats";
|
||||
|
||||
interface IProps {
|
||||
batchMode: boolean;
|
||||
setModel: React.Dispatch<React.SetStateAction<string>>;
|
||||
saveImageAs: string;
|
||||
setSaveImageAs: React.Dispatch<React.SetStateAction<string>>;
|
||||
saveImageAs: ImageFormat;
|
||||
setSaveImageAs: React.Dispatch<React.SetStateAction<ImageFormat>>;
|
||||
compression: number;
|
||||
setCompression: React.Dispatch<React.SetStateAction<number>>;
|
||||
gpuId: string;
|
||||
@ -41,7 +41,6 @@ interface IProps {
|
||||
|
||||
function SettingsTab({
|
||||
batchMode,
|
||||
setModel,
|
||||
compression,
|
||||
setCompression,
|
||||
gpuId,
|
||||
@ -49,7 +48,6 @@ function SettingsTab({
|
||||
saveImageAs,
|
||||
setSaveImageAs,
|
||||
logData,
|
||||
|
||||
show,
|
||||
setShow,
|
||||
setDontShowCloudModal,
|
||||
@ -57,65 +55,17 @@ function SettingsTab({
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const [customModelsPath, setCustomModelsPath] = useAtom(customModelsPathAtom);
|
||||
const modelOptions = useAtomValue(modelsListAtom);
|
||||
const [scale, setScale] = useAtom(scaleAtom);
|
||||
const [enableScrollbar, setEnableScrollbar] = useState(true);
|
||||
const [timeoutId, setTimeoutId] = useState(null);
|
||||
const t = useAtomValue(translationAtom);
|
||||
|
||||
const logit = useLogger();
|
||||
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
|
||||
if (!localStorage.getItem("saveImageAs")) {
|
||||
logit("⚙️ Setting saveImageAs to png");
|
||||
localStorage.setItem("saveImageAs", "png");
|
||||
} else {
|
||||
const currentlySavedImageFormat = localStorage.getItem("saveImageAs");
|
||||
logit(
|
||||
"⚙️ Getting saveImageAs from localStorage: ",
|
||||
currentlySavedImageFormat,
|
||||
);
|
||||
setSaveImageAs(currentlySavedImageFormat);
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("model")) {
|
||||
setModel(modelOptions[0].value);
|
||||
localStorage.setItem("model", JSON.stringify(modelOptions[0]));
|
||||
logit("🔀 Setting model to", modelOptions[0].value);
|
||||
} else {
|
||||
let currentlySavedModel = JSON.parse(
|
||||
localStorage.getItem("model"),
|
||||
) as (typeof modelOptions)[0];
|
||||
if (
|
||||
modelOptions.find(
|
||||
(model) => model.value === currentlySavedModel.value,
|
||||
) === undefined
|
||||
) {
|
||||
localStorage.setItem("model", JSON.stringify(modelOptions[0]));
|
||||
logit("🔀 Setting model to", modelOptions[0].value);
|
||||
currentlySavedModel = modelOptions[0];
|
||||
}
|
||||
setModel(currentlySavedModel.value);
|
||||
logit(
|
||||
"⚙️ Getting model from localStorage: ",
|
||||
JSON.stringify(currentlySavedModel),
|
||||
);
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("gpuId")) {
|
||||
localStorage.setItem("gpuId", "");
|
||||
logit("⚙️ Setting gpuId to empty string");
|
||||
} else {
|
||||
const currentlySavedGpuId = localStorage.getItem("gpuId");
|
||||
setGpuId(currentlySavedGpuId);
|
||||
logit("⚙️ Getting gpuId from localStorage: ", currentlySavedGpuId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// HANDLERS
|
||||
const setExportType = (format: string) => {
|
||||
const setExportType = (format: ImageFormat) => {
|
||||
setSaveImageAs(format);
|
||||
localStorage.setItem("saveImageAs", format);
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ const SidebarToggleButton = ({
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
"fixed left-0 top-1/2 z-[999] -translate-y-1/2 rounded-r-full bg-base-100 p-4 ",
|
||||
"fixed left-0 top-1/2 z-50 -translate-y-1/2 rounded-r-full bg-base-100 p-4 ",
|
||||
showSidebar ? "hidden" : "",
|
||||
)}
|
||||
onClick={() => setShowSidebar((prev) => !prev)}
|
||||
|
157
renderer/components/sidebar/upscayl-tab/select-model.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Maximize2, SwatchBookIcon, X } from "lucide-react";
|
||||
import { ModelId, MODELS } from "@common/models-list";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { selectedModelIdAtom } from "@/atoms/user-settings-atom";
|
||||
import { customModelIdsAtom } from "@/atoms/models-list-atom";
|
||||
|
||||
export default function SelectModel() {
|
||||
const [selectedModelId, setSelectedModelId] = useAtom(selectedModelIdAtom);
|
||||
const customModelIds = useAtomValue(customModelIdsAtom);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [zoomedModel, setZoomedModel] = useState<ModelId | null>(null);
|
||||
|
||||
const handleModelSelect = (model: ModelId | string) => {
|
||||
setSelectedModelId(model);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleZoom = (event: React.MouseEvent, model: ModelId) => {
|
||||
event.stopPropagation();
|
||||
setZoomedModel(model);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<button className="btn btn-primary justify-start border-border">
|
||||
<SwatchBookIcon className="mr-2 h-5 w-5" />
|
||||
{MODELS[selectedModelId]?.name || selectedModelId}
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="z-50 sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Select AI Model</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[600px] pr-4">
|
||||
<div className="grid gap-4">
|
||||
{Object.entries(MODELS).map((modelData) => {
|
||||
const modelId = modelData[0] as ModelId;
|
||||
const model = modelData[1];
|
||||
return (
|
||||
<button
|
||||
key={modelId}
|
||||
className="btn h-auto w-full flex-col items-start p-4"
|
||||
onClick={() => handleModelSelect(modelId)}
|
||||
>
|
||||
<div className="mb-2 font-semibold">{model.name}</div>
|
||||
<div className="relative h-52 w-full overflow-hidden rounded-md">
|
||||
<div className="flex h-full w-full">
|
||||
<img
|
||||
src={`/model-comparison/${model.id}/before.webp`}
|
||||
alt={`${model.name} Before`}
|
||||
className="h-full w-1/2 object-cover"
|
||||
/>
|
||||
<img
|
||||
src={`/model-comparison/${model.id}/after.webp`}
|
||||
alt={`${model.name} After`}
|
||||
className="h-full w-1/2 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="h-full w-px bg-white opacity-50"></div>
|
||||
</div>
|
||||
<div className="absolute bottom-2 left-2 rounded bg-black bg-opacity-50 px-1 text-xs text-white">
|
||||
Before
|
||||
</div>
|
||||
<div className="absolute bottom-2 right-2 rounded bg-black bg-opacity-50 px-1 text-xs text-white">
|
||||
After
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="absolute right-2 top-2"
|
||||
onClick={(e) => handleZoom(e, modelId)}
|
||||
>
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
<span className="sr-only">Zoom</span>
|
||||
</Button>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<p className="font-semibold text-base-content">
|
||||
Imported Custom Models
|
||||
</p>
|
||||
{customModelIds.map((customModel) => {
|
||||
return (
|
||||
<button
|
||||
key={customModel}
|
||||
className="btn h-auto w-full flex-col items-start p-4"
|
||||
onClick={() => handleModelSelect(customModel)}
|
||||
>
|
||||
{customModel}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={!!zoomedModel}
|
||||
onOpenChange={(open) => !open && setZoomedModel(null)}
|
||||
>
|
||||
<DialogContent
|
||||
className="h-screen w-screen max-w-full p-0"
|
||||
hideCloseButton
|
||||
>
|
||||
<div className="relative flex h-full w-full items-center justify-center bg-black">
|
||||
<div className="flex h-full w-full">
|
||||
<div className="relative h-full w-1/2">
|
||||
<img
|
||||
src={`/model-comparison/${MODELS[zoomedModel]?.id}/before.webp`}
|
||||
alt={`${MODELS[zoomedModel]?.name} Before`}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
<div className="absolute bottom-4 left-4 rounded bg-black bg-opacity-50 px-2 py-1 text-sm text-white">
|
||||
Before
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative h-full w-1/2">
|
||||
<img
|
||||
src={`/model-comparison/${MODELS[zoomedModel]?.id}/after.webp`}
|
||||
alt={`${MODELS[zoomedModel]?.name} After`}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
<div className="absolute bottom-4 right-4 rounded bg-black bg-opacity-50 px-2 py-1 text-sm text-white">
|
||||
After
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-circle btn-secondary absolute right-4 top-4"
|
||||
onClick={() => setZoomedModel(null)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { themeChange } from "theme-change";
|
||||
import { TModelsList, modelsListAtom } from "../../../atoms/models-list-atom";
|
||||
import useLogger from "../../hooks/use-logger";
|
||||
import {
|
||||
savedOutputPathAtom,
|
||||
@ -14,16 +13,15 @@ import {
|
||||
} from "../../../atoms/user-settings-atom";
|
||||
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||
import Select from "react-select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
import { SelectImageScale } from "../settings-tab/select-image-scale";
|
||||
import SelectModel from "./select-model";
|
||||
import { ImageFormat } from "@/lib/valid-formats";
|
||||
|
||||
interface IProps {
|
||||
selectImageHandler: () => Promise<void>;
|
||||
selectFolderHandler: () => Promise<void>;
|
||||
handleModelChange: (e: any) => void;
|
||||
upscaylHandler: () => Promise<void>;
|
||||
batchMode: boolean;
|
||||
setBatchMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@ -34,16 +32,13 @@ interface IProps {
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
};
|
||||
setSaveImageAs: React.Dispatch<React.SetStateAction<string>>;
|
||||
model: string;
|
||||
setModel: React.Dispatch<React.SetStateAction<string>>;
|
||||
setSaveImageAs: React.Dispatch<React.SetStateAction<ImageFormat>>;
|
||||
setGpuId: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
function UpscaylSteps({
|
||||
selectImageHandler,
|
||||
selectFolderHandler,
|
||||
handleModelChange,
|
||||
upscaylHandler,
|
||||
batchMode,
|
||||
setBatchMode,
|
||||
@ -51,22 +46,11 @@ function UpscaylSteps({
|
||||
doubleUpscayl,
|
||||
setDoubleUpscayl,
|
||||
dimensions,
|
||||
setSaveImageAs,
|
||||
model,
|
||||
setModel,
|
||||
setGpuId,
|
||||
}: IProps) {
|
||||
const [currentModel, setCurrentModel] = useState<TModelsList[0]>({
|
||||
label: null,
|
||||
value: null,
|
||||
});
|
||||
|
||||
const modelOptions = useAtomValue(modelsListAtom);
|
||||
const [scale, setScale] = useAtom(scaleAtom);
|
||||
const [outputPath, setOutputPath] = useAtom(savedOutputPathAtom);
|
||||
const [progress, setProgress] = useAtom(progressAtom);
|
||||
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const customWidth = useAtomValue(customWidthAtom);
|
||||
const useCustomWidth = useAtomValue(useCustomWidthAtom);
|
||||
|
||||
@ -86,50 +70,8 @@ function UpscaylSteps({
|
||||
|
||||
useEffect(() => {
|
||||
themeChange(false);
|
||||
|
||||
if (!localStorage.getItem("saveImageAs")) {
|
||||
logit("⚙️ Setting saveImageAs to png");
|
||||
localStorage.setItem("saveImageAs", "png");
|
||||
} else {
|
||||
const currentlySavedImageFormat = localStorage.getItem("saveImageAs");
|
||||
logit(
|
||||
"⚙️ Getting saveImageAs from localStorage: ",
|
||||
currentlySavedImageFormat,
|
||||
);
|
||||
setSaveImageAs(currentlySavedImageFormat);
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("model")) {
|
||||
setCurrentModel(modelOptions[0]);
|
||||
setModel(modelOptions[0].value);
|
||||
localStorage.setItem("model", JSON.stringify(modelOptions[0]));
|
||||
logit("🔀 Setting model to", modelOptions[0].value);
|
||||
} else {
|
||||
const currentlySavedModel = JSON.parse(
|
||||
localStorage.getItem("model"),
|
||||
) as (typeof modelOptions)[0];
|
||||
setCurrentModel(currentlySavedModel);
|
||||
setModel(currentlySavedModel.value);
|
||||
logit(
|
||||
"⚙️ Getting model from localStorage: ",
|
||||
JSON.stringify(currentlySavedModel),
|
||||
);
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("gpuId")) {
|
||||
localStorage.setItem("gpuId", "");
|
||||
logit("⚙️ Setting gpuId to empty string");
|
||||
} else {
|
||||
const currentlySavedGpuId = localStorage.getItem("gpuId");
|
||||
setGpuId(currentlySavedGpuId);
|
||||
logit("⚙️ Getting gpuId from localStorage: ", currentlySavedGpuId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
logit("🔀 Setting model to", currentModel.value);
|
||||
}, [currentModel]);
|
||||
|
||||
const upscaylResolution = useMemo(() => {
|
||||
const newDimensions = {
|
||||
width: dimensions.width,
|
||||
@ -214,25 +156,7 @@ function UpscaylSteps({
|
||||
<p className="step-heading">{t("APP.MODEL_SELECTION.TITLE")}</p>
|
||||
<p className="mb-2 text-sm">{t("APP.MODEL_SELECTION.DESCRIPTION")}</p>
|
||||
|
||||
<Select
|
||||
onMenuOpen={() => setOpen(true)}
|
||||
onMenuClose={() => setOpen(false)}
|
||||
options={modelOptions}
|
||||
components={{
|
||||
IndicatorSeparator: () => null,
|
||||
DropdownIndicator: () => null,
|
||||
}}
|
||||
onChange={(e) => {
|
||||
handleModelChange(e);
|
||||
setCurrentModel({ label: e.label, value: e.value });
|
||||
}}
|
||||
className={cn(
|
||||
"react-select-container transition-all group-hover:w-full group-active:w-full focus:w-full",
|
||||
open && "!w-full",
|
||||
)}
|
||||
classNamePrefix="react-select"
|
||||
value={currentModel}
|
||||
/>
|
||||
<SelectModel />
|
||||
</div>
|
||||
|
||||
{!batchMode && (
|
||||
|
@ -4,8 +4,7 @@ import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { translationAtom } from "@/atoms/translations-atom";
|
||||
|
||||
const t = useAtomValue(translationAtom);
|
||||
import useTranslation from "../hooks/use-translation";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
@ -32,26 +31,34 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">{t("APP.DIALOG_BOX.CLOSE")}</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||
hideCloseButton?: boolean;
|
||||
}
|
||||
>(({ className, children, hideCloseButton, ...props }, ref) => {
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{!hideCloseButton && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">{t("APP.DIALOG_BOX.CLOSE")}</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
|
46
renderer/components/ui/scroll-area.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
@ -1 +1,9 @@
|
||||
export const VALID_IMAGE_FORMATS = ["png", "jpg", "jpeg", "jfif", "webp"];
|
||||
export const VALID_IMAGE_FORMATS = [
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"jfif",
|
||||
"webp",
|
||||
] as const;
|
||||
|
||||
export type ImageFormat = (typeof VALID_IMAGE_FORMATS)[number];
|
||||
|
@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ELECTRON_COMMANDS } from "@common/electron-commands";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { modelsListAtom } from "../atoms/models-list-atom";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { customModelIdsAtom } from "../atoms/models-list-atom";
|
||||
import {
|
||||
batchModeAtom,
|
||||
savedOutputPathAtom,
|
||||
@ -10,7 +10,6 @@ import {
|
||||
rememberOutputFolderAtom,
|
||||
} from "../atoms/user-settings-atom";
|
||||
import useLogger from "../components/hooks/use-logger";
|
||||
import { newsAtom, showNewsModalAtom } from "@/atoms/news-atom";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { ToastAction } from "@/components/ui/toast";
|
||||
import UpscaylSVGLogo from "@/components/icons/upscayl-logo-svg";
|
||||
@ -19,7 +18,7 @@ import Sidebar from "@/components/sidebar";
|
||||
import MainContent from "@/components/main-content";
|
||||
import getDirectoryFromPath from "@common/get-directory-from-path";
|
||||
import { FEATURE_FLAGS } from "@common/feature-flags";
|
||||
import { VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
|
||||
import { ImageFormat, VALID_IMAGE_FORMATS } from "@/lib/valid-formats";
|
||||
import { initCustomModels } from "@/components/hooks/use-custom-models";
|
||||
|
||||
const Home = () => {
|
||||
@ -30,7 +29,6 @@ const Home = () => {
|
||||
initCustomModels();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [imagePath, setImagePath] = useState("");
|
||||
const [upscaledImagePath, setUpscaledImagePath] = useState("");
|
||||
const [dimensions, setDimensions] = useState({
|
||||
@ -39,18 +37,12 @@ const Home = () => {
|
||||
});
|
||||
const setOutputPath = useSetAtom(savedOutputPathAtom);
|
||||
const rememberOutputFolder = useAtomValue(rememberOutputFolderAtom);
|
||||
|
||||
const batchMode = useAtomValue(batchModeAtom);
|
||||
const [batchFolderPath, setBatchFolderPath] = useState("");
|
||||
const [upscaledBatchFolderPath, setUpscaledBatchFolderPath] = useState("");
|
||||
|
||||
const setProgress = useSetAtom(progressAtom);
|
||||
const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0);
|
||||
|
||||
const [modelOptions, setModelOptions] = useAtom(modelsListAtom);
|
||||
|
||||
const [news, setNews] = useAtom(newsAtom);
|
||||
const [showNewsModal, setShowNewsModal] = useAtom(showNewsModalAtom);
|
||||
const setModelIds = useSetAtom(customModelIdsAtom);
|
||||
|
||||
const selectImageHandler = async () => {
|
||||
resetImagePaths();
|
||||
@ -89,7 +81,7 @@ const Home = () => {
|
||||
const validateImagePath = (path: string) => {
|
||||
if (path.length > 0) {
|
||||
logit("🖼 imagePath: ", path);
|
||||
const extension = path.split(".").pop().toLowerCase();
|
||||
const extension = path.split(".").pop().toLowerCase() as ImageFormat;
|
||||
logit("🔤 Extension: ", extension);
|
||||
if (!VALID_IMAGE_FORMATS.includes(extension)) {
|
||||
toast({
|
||||
@ -260,19 +252,8 @@ const Home = () => {
|
||||
ELECTRON_COMMANDS.CUSTOM_MODEL_FILES_LIST,
|
||||
(_, data: string[]) => {
|
||||
logit(`📜 CUSTOM_MODEL_FILES_LIST: `, data);
|
||||
const newModelOptions = data.map((model) => {
|
||||
return {
|
||||
value: model,
|
||||
label: model,
|
||||
};
|
||||
});
|
||||
// Add newModelsList to modelOptions and remove duplicates
|
||||
const modelMap = new Map();
|
||||
[...modelOptions, ...newModelOptions].forEach((model) => {
|
||||
modelMap.set(model.value, model);
|
||||
});
|
||||
const uniqueModelOptions = Array.from(modelMap.values());
|
||||
setModelOptions(uniqueModelOptions);
|
||||
console.log("🚀 => data:", data);
|
||||
setModelIds(data);
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
BIN
renderer/public/model-comparison/realesrgan-x4fast/after.webp
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
renderer/public/model-comparison/realesrgan-x4fast/before.webp
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 14 KiB |
BIN
renderer/public/model-comparison/realesrgan-x4plus/after.webp
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
renderer/public/model-comparison/realesrgan-x4plus/before.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
renderer/public/model-comparison/remacri/after.webp
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
renderer/public/model-comparison/remacri/before.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
renderer/public/model-comparison/ultramix_balanced/after.webp
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
renderer/public/model-comparison/ultramix_balanced/before.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
renderer/public/model-comparison/ultrasharp/after.webp
Normal file
After Width: | Height: | Size: 718 KiB |
BIN
renderer/public/model-comparison/ultrasharp/before.webp
Normal file
After Width: | Height: | Size: 14 KiB |