1
0
mirror of https://github.com/upscayl/upscayl.git synced 2025-02-12 09:03:00 +01:00

Add stats and add posthog systeminfo

This commit is contained in:
Nayam Amarshe 2024-12-16 16:18:57 +05:30
parent c9e00de2b0
commit 5a4b29a840
22 changed files with 496 additions and 172 deletions

View File

@ -93,6 +93,21 @@ ipcMain.on(ELECTRON_COMMANDS.DOUBLE_UPSCAYL, doubleUpscayl);
ipcMain.on(ELECTRON_COMMANDS.PASTE_IMAGE, pasteImage);
ipcMain.handle("get-gpu-info", async () => {
try {
return await app.getGPUInfo("complete");
} catch (error) {
console.error("Failed to get GPU info:", error);
return null;
}
});
ipcMain.handle("get-app-version", () => {
return `${app.getVersion()} ${
FEATURE_FLAGS.APP_STORE_BUILD ? "MAC-APP-STORE" : "FOSS"
}`;
});
if (!FEATURE_FLAGS.APP_STORE_BUILD) {
autoUpdater.on("update-downloaded", autoUpdate);
}

View File

@ -1,5 +1,10 @@
import { ipcRenderer, contextBridge } from "electron";
import { getPlatform } from "./utils/get-device-specs";
import { ipcRenderer, contextBridge, app } from "electron";
import {
getAppVersion,
getDeviceSpecs,
getPlatform,
} from "./utils/get-device-specs";
import { FEATURE_FLAGS } from "@common/feature-flags";
// 'ipcRenderer' will be available in index.js with the method 'window.electron'
contextBridge.exposeInMainWorld("electron", {
@ -11,4 +16,6 @@ contextBridge.exposeInMainWorld("electron", {
invoke: (command: string, payload: any) =>
ipcRenderer.invoke(command, payload),
platform: getPlatform(),
getSystemInfo: async () => await getDeviceSpecs(),
getAppVersion: async () => await getAppVersion(),
});

View File

@ -1,9 +1,10 @@
"use strict";
import { platform, arch } from "os";
import { ipcRenderer } from "electron";
import os from "os";
export const getPlatform = () => {
switch (platform()) {
switch (os.platform()) {
case "aix":
case "freebsd":
case "linux":
@ -19,7 +20,7 @@ export const getPlatform = () => {
};
export const getArch = () => {
switch (arch()) {
switch (os.arch()) {
case "x64":
return "x64";
case "x32":
@ -30,3 +31,33 @@ export const getArch = () => {
return "arm64";
}
};
export const getAppVersion = async () => {
let appVersion = process.env.npm_package_version;
try {
appVersion = await ipcRenderer.invoke("get-app-version");
} catch (error) {
console.error("Failed to get app version:", error);
}
return appVersion;
};
export const getDeviceSpecs = async () => {
let gpuInfo;
try {
gpuInfo = await ipcRenderer.invoke("get-gpu-info");
} catch (error) {
console.error("Failed to get GPU info:", error);
gpuInfo = null;
}
return {
platform: getPlatform(),
release: os.release(),
arch: getArch(),
model: os.cpus()[0].model.trim(),
cpuCount: os.cpus().length,
...(gpuInfo && { gpu: gpuInfo.gpuDevice[0] }),
};
};

View File

@ -83,3 +83,13 @@ export const enableContributionAtom = atomWithStorage(
"enableContribution",
true,
);
export const userStatsAtom = atomWithStorage("userStats", {
totalUpscayls: 0,
doubleUpscayls: 0,
batchUpscayls: 0,
imageUpscayls: 0,
averageUpscaylTime: 0,
lastUpscaylDuration: 0,
lastUsedAt: 0,
});

View File

@ -1,118 +0,0 @@
import { translationAtom } from "@/atoms/translations-atom";
import { lensSizeAtom, viewTypeAtom } from "@/atoms/user-settings-atom";
import { cn } from "@/lib/utils";
import { useAtom, useAtomValue } from "jotai";
import { WrenchIcon } from "lucide-react";
import { useEffect, useState } from "react";
const ImageViewSettings = ({
zoomAmount,
setZoomAmount,
resetImagePaths,
}: {
zoomAmount: string;
setZoomAmount: (arg: any) => void;
resetImagePaths: () => void;
}) => {
const [openSidebar, setOpenSidebar] = useState(false);
const [viewType, setViewType] = useAtom(viewTypeAtom);
const [lensSize, setLensSize] = useAtom(lensSizeAtom);
const t = useAtomValue(translationAtom);
useEffect(() => {
if (!localStorage.getItem("zoomAmount")) {
localStorage.setItem("zoomAmount", zoomAmount);
} else {
setZoomAmount(localStorage.getItem("zoomAmount"));
}
}, []);
return (
<div
onDoubleClick={(e) => {
e.stopPropagation();
}}
className={`fixed right-0 top-0 z-50 h-screen w-[28rem] bg-base-100 text-base-content shadow-xl shadow-base-300 transition-all duration-500 ${
openSidebar ? "right-0" : "-right-full translate-x-full"
}`}
>
<div
className={`group absolute right-[100%] top-1/2 z-50 flex cursor-pointer items-center gap-2 rounded-btn rounded-r-none bg-base-100 p-4 transition-all duration-500`}
onClick={() => {
setOpenSidebar(!openSidebar);
}}
>
<WrenchIcon
className={cn(
"animate text-xl text-base-content",
openSidebar ? "rotate-180" : "rotate-0",
)}
/>
</div>
<div className="flex flex-col justify-center gap-5 overflow-auto p-5">
<button className="btn btn-primary" onClick={resetImagePaths}>
{t("APP.IMAGE_OPTIONS.RESET_BUTTON_TITLE")}
</button>
<div className="flex flex-row items-center gap-2">
<p className="text-sm font-medium">
{t("APP.IMAGE_OPTIONS.LENS_VIEW_TITLE")}
</p>
<input
type="checkbox"
className="toggle"
checked={viewType === "slider"}
onChange={(e) => {
setViewType(e.target.checked ? "slider" : "lens");
}}
/>
<p className="text-sm font-medium">
{t("APP.IMAGE_OPTIONS.SLIDER_VIEW_TITLE")}
</p>
</div>
{viewType !== "lens" && (
<>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">
{t("APP.IMAGE_OPTIONS.ZOOM_AMOUNT_TITLE")} ({zoomAmount}%)
</p>
<input
type="range"
min="100"
max="1000"
step={10}
className="range range-md"
value={parseInt(zoomAmount)}
onChange={(e) => {
setZoomAmount(e.target.value);
localStorage.setItem("zoomAmount", e.target.value);
}}
/>
</div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">
{t("APP.IMAGE_OPTIONS.LENS_SIZE_TITLE")} ({lensSize / 10})
</p>
<input
type="range"
min="20"
max="400"
step={10}
className="range range-md"
value={lensSize}
onChange={(e) => {
setLensSize(parseInt(e.target.value));
}}
/>
</div>
</>
)}
</div>
</div>
);
};
export default ImageViewSettings;

View File

@ -18,7 +18,7 @@ import { FEATURE_FLAGS } from "@common/feature-flags";
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";
import MoreOptionsDrawer from "./more-options-drawer";
import useUpscaylVersion from "../hooks/use-upscayl-version";
import MacTitlebarDragRegion from "./mac-titlebar-drag-region";
import LensViewer from "./lens-view";
@ -292,7 +292,7 @@ const MainContent = ({
<InstructionsCard version={version} batchMode={batchMode} />
)}
<ImageViewSettings
<MoreOptionsDrawer
zoomAmount={zoomAmount}
setZoomAmount={setZoomAmount}
resetImagePaths={resetImagePaths}

View File

@ -106,6 +106,7 @@ const LensViewer = ({
className="relative h-full w-full cursor-crosshair"
ref={originalImageContainerRef}
>
ORIGINAL IMAGE
<img
src={originalImage}
alt="Original"
@ -113,12 +114,15 @@ const LensViewer = ({
onMouseMove={handleMouseMove}
ref={originalImageRef}
/>
{/* Lens */}
<div
className="pointer-events-none absolute hidden h-10 w-10 cursor-cell border border-primary bg-black/10 group-hover:block"
className="pointer-events-none absolute hidden cursor-cell border border-primary bg-black/10 group-hover:block"
style={{
left: `${hoverPosition.relativeMouseX}px`,
top: `${hoverPosition.mouseY}px`,
transform: "translate(-50%, -50%)",
height: `48px`,
width: `48px`,
}}
/>
</div>
@ -132,6 +136,7 @@ const LensViewer = ({
transform: "translate(-50%, 0)",
}}
>
{/* ORIGINAL IMAGE */}
<div
className="relative h-48 w-48 border border-gray-300 bg-cover bg-no-repeat"
style={{
@ -144,6 +149,7 @@ const LensViewer = ({
Original
</span>
</div>
{/* UPSCALED IMAGE */}
<div
className="relative h-48 w-48 border border-gray-300 bg-cover bg-no-repeat"
style={{

View File

@ -0,0 +1,210 @@
import { translationAtom } from "@/atoms/translations-atom";
import {
lensSizeAtom,
userStatsAtom,
viewTypeAtom,
} from "@/atoms/user-settings-atom";
import { cn } from "@/lib/utils";
import { useAtom, useAtomValue } from "jotai";
import { EllipsisIcon, WrenchIcon } from "lucide-react";
import { useEffect, useState } from "react";
const formatDuration = (seconds: number): string => {
if (seconds < 60) return `${seconds.toFixed(1)}s`;
if (seconds < 3600) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours}h ${minutes}m`;
};
const MoreOptionsDrawer = ({
zoomAmount,
setZoomAmount,
resetImagePaths,
}: {
zoomAmount: string;
setZoomAmount: (arg: any) => void;
resetImagePaths: () => void;
}) => {
const [openSidebar, setOpenSidebar] = useState(false);
const [viewType, setViewType] = useAtom(viewTypeAtom);
const [lensSize, setLensSize] = useAtom(lensSizeAtom);
const t = useAtomValue(translationAtom);
const userStats = useAtomValue(userStatsAtom);
useEffect(() => {
if (!localStorage.getItem("zoomAmount")) {
localStorage.setItem("zoomAmount", zoomAmount);
} else {
setZoomAmount(localStorage.getItem("zoomAmount"));
}
}, []);
return (
<div
onDoubleClick={(e) => {
e.stopPropagation();
}}
className={`fixed right-0 top-0 z-50 h-screen w-[28rem] bg-base-100 text-base-content shadow-xl shadow-base-300 transition-all duration-500 ${
openSidebar ? "right-0" : "-right-full translate-x-full"
}`}
>
<div
className={`group absolute right-[100%] top-1/2 z-50 flex cursor-pointer items-center gap-2 rounded-btn rounded-r-none bg-base-100 p-4 transition-all duration-500`}
onClick={() => {
setOpenSidebar(!openSidebar);
}}
>
<EllipsisIcon
className={cn(
"animate text-xl text-base-content",
openSidebar ? "rotate-90" : "rotate-0",
)}
/>
</div>
<div className="flex h-full flex-col overflow-hidden p-5">
<div className="flex flex-col gap-5">
<button className="btn btn-primary" onClick={resetImagePaths}>
{t("APP.MORE_OPTIONS_DRAWER.RESET_BUTTON_TITLE")}
</button>
<div className="flex flex-row items-center gap-2">
<p className="text-sm font-medium">
{t("APP.MORE_OPTIONS_DRAWER.LENS_VIEW_TITLE")}
</p>
<input
type="checkbox"
className="toggle"
checked={viewType === "slider"}
onChange={(e) => {
setViewType(e.target.checked ? "slider" : "lens");
}}
/>
<p className="text-sm font-medium">
{t("APP.MORE_OPTIONS_DRAWER.SLIDER_VIEW_TITLE")}
</p>
</div>
{viewType !== "lens" && (
<>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">
{t("APP.MORE_OPTIONS_DRAWER.ZOOM_AMOUNT_TITLE")} ({zoomAmount}
%)
</p>
<input
type="range"
min="100"
max="1000"
step={10}
className="range range-md"
value={parseInt(zoomAmount)}
onChange={(e) => {
setZoomAmount(e.target.value);
localStorage.setItem("zoomAmount", e.target.value);
}}
/>
</div>
<div className="flex flex-col gap-2">
<p className="text-sm font-medium">
{t("APP.MORE_OPTIONS_DRAWER.LENS_SIZE_TITLE")} (
{lensSize / 10})
</p>
<input
type="range"
min="20"
max="400"
step={10}
className="range range-md"
value={lensSize}
onChange={(e) => {
setLensSize(parseInt(e.target.value));
}}
/>
</div>
</>
)}
</div>
<div className="mt-5 flex min-h-0 flex-1 flex-col gap-2">
<p className="text-sm font-semibold uppercase text-base-content">
User Stats
</p>
<div className="stats stats-vertical overflow-y-auto">
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.TOTAL_UPSCAYLS")}
</div>
<div className="stat-value text-2xl text-primary-content">
{userStats.totalUpscayls}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.TOTAL_BATCH_UPSCAYLS")}
</div>
<div className="stat-value text-2xl text-primary-content">
{userStats.batchUpscayls}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.TOTAL_IMAGE_UPSCAYLS")}
</div>
<div className="stat-value text-2xl text-primary-content">
{userStats.imageUpscayls}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.TOTAL_DOUBLE_UPSCAYLS")}
</div>
<div className="stat-value text-2xl text-primary-content">
{userStats.doubleUpscayls}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.AVERAGE_UPSCAYL_TIME")}
</div>
<div className="stat-value text-2xl text-primary-content">
{formatDuration(userStats.averageUpscaylTime / 1000)}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.LAST_UPSCAYL_DURATION")}
</div>
<div className="stat-value text-2xl text-primary-content">
{formatDuration(userStats.lastUpscaylDuration / 1000)}
</div>
</div>
<div className="stat">
<div className="stat-title">
{t("APP.MORE_OPTIONS_DRAWER.LAST_USED_AT")}
</div>
<div className="stat-value text-2xl text-primary-content">
{new Date(userStats.lastUsedAt).toLocaleString()}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default MoreOptionsDrawer;

View File

@ -77,7 +77,8 @@ export function OnboardingDialog() {
{
key: "video",
type: "video",
videoSrc: "https://www.youtube-nocookie.com/embed/3M77flVZlVY",
videoSrc:
"https://www.youtube-nocookie.com/embed/3M77flVZlVY?autoplay=1",
},
],
},
@ -140,14 +141,21 @@ export function OnboardingDialog() {
{currentStepData.configurationOptions && (
<div
className={cn(
"flex h-full w-full flex-col rounded-sm bg-primary p-8 ",
"flex h-full w-full flex-col rounded-sm bg-primary p-8",
currentStepData.type === "settings" && "h-auto w-auto gap-8",
currentStepData.configurationOptions[0].type === "video" && "p-0",
)}
>
{currentStepData.configurationOptions.map((option) => (
<div
key={option.key}
className="flex h-full w-full items-center justify-between gap-4"
data-tooltip-id="tooltip"
data-tooltip-content={
option.key === "improveUpscayl"
? t("SETTINGS.ENABLE_CONTRIBUTION.DESCRIPTION")
: null
}
>
{option.label && (
<label
@ -190,7 +198,7 @@ export function OnboardingDialog() {
)}
{option.type === "video" && (
<iframe
className="h-full w-full"
className="h-full w-full rounded-sm"
src={option.videoSrc}
title="YouTube video player"
frameBorder="0"
@ -202,6 +210,11 @@ export function OnboardingDialog() {
{option.type === "component" && option.component}
</div>
))}
{currentStepData.type === "settings" && (
<p className="text-sm text-base-content/70">
{t("ONBOARDING_DIALOG.SETTINGS_NOTE")}
</p>
)}
</div>
)}

View File

@ -0,0 +1,49 @@
import { enableContributionAtom } from "@/atoms/user-settings-atom";
import { useAtomValue } from "jotai";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
const PostHogProviderWrapper = ({
children,
}: {
children: React.ReactNode;
}) => {
const enableContribution = useAtomValue(enableContributionAtom);
useEffect(() => {
posthog.init("phc_QMcmlmComdofjfaRPzoN4KV9ziV2KgOwAOVyu4J3dIc", {
api_host: "https://us.i.posthog.com",
person_profiles: "identified_only",
autocapture: false,
capture_pageview: false,
capture_pageleave: false,
disable_session_recording: true,
loaded: async (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug();
const systemInfo = await window.electron.getSystemInfo();
const appVersion = await window.electron.getAppVersion();
// Set super properties that will be included with all events
posthog.register({
...systemInfo,
appVersion,
$ip: "0.0.0.0",
$geoip_disable: true,
});
// Capture initial session start
posthog.capture("app_launched", {
...systemInfo,
appVersion,
$ip: "0.0.0.0",
$geoip_disable: true,
});
},
});
}, []);
if (!enableContribution) return <>{children}</>;
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
};
export default PostHogProviderWrapper;

View File

@ -18,6 +18,7 @@ import {
doubleUpscaylAtom,
gpuIdAtom,
saveImageAsAtom,
userStatsAtom,
} from "../../atoms/user-settings-atom";
import useLogger from "../hooks/use-logger";
import {
@ -89,6 +90,7 @@ const Sidebar = ({
const useCustomWidth = useAtomValue(useCustomWidthAtom);
const tileSize = useAtomValue(tileSizeAtom);
const [showSidebar, setShowSidebar] = useAtom(showSidebarAtom);
const setUserStats = useSetAtom(userStatsAtom);
const upscaylHandler = async () => {
logit("🔄 Resetting Upscaled Image Path");
@ -114,6 +116,13 @@ const Sidebar = ({
tileSize,
},
);
setUserStats((prev) => ({
...prev,
totalUpscayls: prev.totalUpscayls + 1,
lastUsedAt: new Date().getTime(),
doubleUpscayls: prev.doubleUpscayls + 1,
imageUpscayls: prev.imageUpscayls + 1,
}));
logit("🏁 DOUBLE_UPSCAYL");
} else if (batchMode) {
// Batch Upscayl
@ -134,6 +143,12 @@ const Sidebar = ({
tileSize,
},
);
setUserStats((prev) => ({
...prev,
totalUpscayls: prev.totalUpscayls + 1,
lastUsedAt: new Date().getTime(),
batchUpscayls: prev.doubleUpscayls + 1,
}));
logit("🏁 FOLDER_UPSCAYL");
} else {
// Single Image Upscayl
@ -151,6 +166,12 @@ const Sidebar = ({
useCustomWidth,
tileSize,
});
setUserStats((prev) => ({
...prev,
totalUpscayls: prev.totalUpscayls + 1,
lastUsedAt: new Date().getTime(),
imageUpscayls: prev.imageUpscayls + 1,
}));
logit("🏁 UPSCAYL");
}
} else {

View File

@ -16,6 +16,7 @@ import { useAtom, useAtomValue } from "jotai";
import { selectedModelIdAtom } from "@/atoms/user-settings-atom";
import { customModelIdsAtom } from "@/atoms/models-list-atom";
import useTranslation from "@/components/hooks/use-translation";
import posthog from "posthog-js";
const SelectModelDialog = () => {
const t = useTranslation();
@ -28,6 +29,12 @@ const SelectModelDialog = () => {
const handleModelSelect = (model: ModelId | string) => {
setSelectedModelId(model);
setOpen(false);
posthog.capture("model_selected", {
$ip: "0.0.0.0",
$geoip_disable: true,
model,
});
};
const handleZoom = (event: React.MouseEvent, model: ModelId) => {
@ -61,12 +68,12 @@ const SelectModelDialog = () => {
className="btn !h-auto !w-full !flex-col !items-start !rounded-sm !p-4"
onClick={() => handleModelSelect(modelId)}
>
<div className="font-semibold">
<p className="font-semibold">
{t(`APP.MODEL_SELECTION.MODELS.${modelId}.NAME`)}
</div>
<div className="mb-2 text-left font-normal leading-normal text-opacity-50">
</p>
<p className="mb-2 text-left font-normal leading-normal text-base-content/70">
{t(`APP.MODEL_SELECTION.MODELS.${modelId}.DESCRIPTION`)}
</div>
</p>
<div className="relative h-52 w-full overflow-hidden rounded-sm">
<div className="flex h-full w-full">
<img

View File

@ -270,11 +270,6 @@ function UpscaylSteps({
: t("APP.SCALE_SELECTION.START_BUTTON_TITLE")}
</button>
</div>
<Tooltip
className="z-[999] max-w-sm break-words !bg-secondary"
id="tooltip"
/>
</div>
);
}

View File

@ -160,12 +160,19 @@
"START_BUTTON_TITLE": "Upscayl 🚀",
"IN_PROGRESS_BUTTON_TITLE": "Upscayling ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "Reset Image",
"LENS_VIEW_TITLE": "Lens View",
"SLIDER_VIEW_TITLE": "Slider View",
"ZOOM_AMOUNT_TITLE": "Zoom Amount",
"LENS_SIZE_TITLE": "Lens Size"
"LENS_SIZE_TITLE": "Lens Size",
"TOTAL_UPSCAYLS": "Total Upscayls",
"TOTAL_BATCH_UPSCAYLS": "Total Batch Upscayls",
"TOTAL_IMAGE_UPSCAYLS": "Total Image Upscayls",
"TOTAL_DOUBLE_UPSCAYLS": "Total Double Upscayls",
"AVERAGE_UPSCAYL_TIME": "Average Upscayl Time",
"LAST_UPSCAYL_DURATION": "Last Upscayl Duration",
"LAST_USED_AT": "Last Used At"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "Batch Upscayl In Progress:",
@ -178,7 +185,7 @@
"SELECT_IMAGE": "Select an Image",
"SELECT_FOLDER_DESCRIPTION": "Make sure that the folder doesn't contain anything except PNG, JPG, JPEG & WEBP images.",
"SELECT_IMAGES_DESCRIPTION": "Select or drag and drop a PNG, JPG, JPEG or WEBP image.",
"PASTE_IMAGE_DESCRIPTION": "Hit Ctrl + V or Cmd + V to Paste image from Clipboard"
"PASTE_IMAGE_DESCRIPTION": "Hit Ctrl+V or ⌘+V to Paste image from Clipboard"
},
"PROGRESS": {
"PROCESSING_TITLE": "Processing the image...",
@ -254,6 +261,7 @@
"NEXT_BUTTON_TITLE": "Next",
"BACK_BUTTON_TITLE": "Back",
"GET_STARTED_BUTTON_TITLE": "Get Started",
"SETTINGS_NOTE": "You can always change these settings later.",
"STEP_1": {
"TITLE": "Welcome to Upscayl 🎉",
"DESCRIPTION": "Let's get you started with a few quick steps."

View File

@ -161,12 +161,19 @@
"START_BUTTON_TITLE": "Upscayl 🚀",
"IN_PROGRESS_BUTTON_TITLE": "Aumentando ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "Restablecer imagen",
"LENS_VIEW_TITLE": "Vista de lente",
"SLIDER_VIEW_TITLE": "Vista de deslizador",
"ZOOM_AMOUNT_TITLE": "Cantidad de zoom",
"LENS_SIZE_TITLE": "Tamaño de lente"
"LENS_SIZE_TITLE": "Tamaño de lente",
"TOTAL_UPSCAYLS": "Total de aumentos",
"TOTAL_BATCH_UPSCAYLS": "Total de aumentos por lotes",
"TOTAL_IMAGE_UPSCAYLS": "Total de imágenes aumentadas",
"TOTAL_DOUBLE_UPSCAYLS": "Total de dobles aumentos",
"AVERAGE_UPSCAYL_TIME": "Tiempo promedio de aumento",
"LAST_UPSCAYL_DURATION": "Duración del último aumento",
"LAST_USED_AT": "Último uso"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "Aumento por lotes en progreso:",
@ -179,7 +186,7 @@
"SELECT_IMAGE": "Selecciona una imagen",
"SELECT_FOLDER_DESCRIPTION": "Asegúrate de que la carpeta no contenga nada excepto imágenes PNG, JPG, JPEG y WEBP.",
"SELECT_IMAGES_DESCRIPTION": "Selecciona o arrastra y suelta una imagen PNG, JPG, JPEG o WEBP.",
"PASTE_IMAGE_DESCRIPTION": "Presiona Ctrl + V o Cmd + V para pegar la imagen desde el portapapeles"
"PASTE_IMAGE_DESCRIPTION": "Presiona Ctrl+V o ⌘+V para pegar la imagen desde el portapapeles"
},
"PROGRESS": {
"PROCESSING_TITLE": "Procesando la imagen...",
@ -255,6 +262,7 @@
"NEXT_BUTTON_TITLE": "Siguiente",
"BACK_BUTTON_TITLE": "Atrás",
"GET_STARTED_BUTTON_TITLE": "Comenzar",
"SETTINGS_NOTE": "Siempre puedes cambiar estos ajustes más tarde.",
"STEP_1": {
"TITLE": "Bienvenido a Upscayl 🎉",
"DESCRIPTION": "Vamos a empezar con unos pocos pasos rápidos."

View File

@ -160,12 +160,19 @@
"START_BUTTON_TITLE": "Suréchantillonner 🚀",
"IN_PROGRESS_BUTTON_TITLE": "Suréchantillonnage ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "Réinitialiser l'image",
"LENS_VIEW_TITLE": "Vue de la lentille",
"SLIDER_VIEW_TITLE": "Vue du curseur",
"ZOOM_AMOUNT_TITLE": "Niveau de zoom",
"LENS_SIZE_TITLE": "Taille de la lentille"
"LENS_SIZE_TITLE": "Taille de la lentille",
"TOTAL_UPSCAYLS": "Total des suréchantillonnages",
"TOTAL_BATCH_UPSCAYLS": "Total des suréchantillonnages par lot",
"TOTAL_IMAGE_UPSCAYLS": "Total des suréchantillonnages d'images",
"TOTAL_DOUBLE_UPSCAYLS": "Total des doubles suréchantillonnages",
"AVERAGE_UPSCAYL_TIME": "Temps moyen de suréchantillonnage",
"LAST_UPSCAYL_DURATION": "Durée du dernier suréchantillonnage",
"LAST_USED_AT": "Dernière utilisation"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "Suréchantillonnage par lot en cours :",
@ -178,7 +185,7 @@
"SELECT_IMAGE": "Sélectionnez une image à suréchantillonner",
"SELECT_FOLDER_DESCRIPTION": "Assurez-vous que le dossier ne contient rien d'autre que des images PNG, JPG, JPEG et WEBP.",
"SELECT_IMAGES_DESCRIPTION": "Sélectionnez ou glissez-déposez une image PNG, JPG, JPEG ou WEBP.",
"PASTE_IMAGE_DESCRIPTION": "Appuyez sur Ctrl + V ou Cmd + V pour coller l'image depuis le presse-papiers"
"PASTE_IMAGE_DESCRIPTION": "Appuyez sur Ctrl+V ou ⌘+V pour coller l'image depuis le presse-papiers"
},
"PROGRESS": {
"PROCESSING_TITLE": "Traitement de l'image...",
@ -254,6 +261,7 @@
"NEXT_BUTTON_TITLE": "Suivant",
"BACK_BUTTON_TITLE": "Retour",
"GET_STARTED_BUTTON_TITLE": "Commencer",
"SETTINGS_NOTE": "Vous pouvez toujours modifier ces paramètres plus tard.",
"STEP_1": {
"TITLE": "Bienvenue sur Upscayl 🎉",
"DESCRIPTION": "Commençons par quelques étapes rapides."

View File

@ -160,12 +160,19 @@
"START_BUTTON_TITLE": "Upscayl 🚀",
"IN_PROGRESS_BUTTON_TITLE": "Upscayl中 ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "画像をリセット",
"LENS_VIEW_TITLE": "レンズビュー",
"SLIDER_VIEW_TITLE": "スライダービュー",
"ZOOM_AMOUNT_TITLE": "ズーム量",
"LENS_SIZE_TITLE": "レンズサイズ"
"LENS_SIZE_TITLE": "レンズサイズ",
"TOTAL_UPSCAYLS": "総アップスケール数",
"TOTAL_BATCH_UPSCAYLS": "総バッチアップスケール数",
"TOTAL_IMAGE_UPSCAYLS": "総画像アップスケール数",
"TOTAL_DOUBLE_UPSCAYLS": "総ダブルアップスケール数",
"AVERAGE_UPSCAYL_TIME": "平均アップスケール時間",
"LAST_UPSCAYL_DURATION": "最後のアップスケール時間",
"LAST_USED_AT": "最終使用日時"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "バッチUpscayl進行中",
@ -178,7 +185,7 @@
"SELECT_IMAGE": "Upscaylする画像を選択",
"SELECT_FOLDER_DESCRIPTION": "フォルダにはPNG、JPG、JPEG、WEBPの画像以外のものが含まれていないことを確認してください。",
"SELECT_IMAGES_DESCRIPTION": "PNG、JPG、JPEG、またはWEBP画像を選択するか、ドラッグアンドドロップしてください。",
"PASTE_IMAGE_DESCRIPTION": "Ctrl + V または Cmd + V を押してクリップボードから画像を貼り付けます"
"PASTE_IMAGE_DESCRIPTION": "Ctrl+V または ⌘+V を押してクリップボードから画像を貼り付けます"
},
"PROGRESS": {
"PROCESSING_TITLE": "画像を処理中...",
@ -254,6 +261,7 @@
"NEXT_BUTTON_TITLE": "次へ",
"BACK_BUTTON_TITLE": "戻る",
"GET_STARTED_BUTTON_TITLE": "始める",
"SETTINGS_NOTE": "これらの設定は後でいつでも変更できます。",
"STEP_1": {
"TITLE": "Upscaylへようこそ 🎉",
"DESCRIPTION": "いくつかの簡単なステップで始めましょう。"

View File

@ -160,12 +160,19 @@
"START_BUTTON_TITLE": "Увеличить 🚀",
"IN_PROGRESS_BUTTON_TITLE": "Увеличение ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "Сбросить изображение",
"LENS_VIEW_TITLE": "Просмотр через линзу",
"SLIDER_VIEW_TITLE": "Просмотр с ползунком",
"ZOOM_AMOUNT_TITLE": "Увеличение",
"LENS_SIZE_TITLE": "Размер линзы"
"LENS_SIZE_TITLE": "Размер линзы",
"TOTAL_UPSCAYLS": "Всего увеличений",
"TOTAL_BATCH_UPSCAYLS": "Всего пакетных увеличений",
"TOTAL_IMAGE_UPSCAYLS": "Всего увеличений изображений",
"TOTAL_DOUBLE_UPSCAYLS": "Всего двойных увеличений",
"AVERAGE_UPSCAYL_TIME": "Среднее время увеличения",
"LAST_UPSCAYL_DURATION": "Длительность последнего увеличения",
"LAST_USED_AT": "Последнее использование"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "Пакетное увеличение в процессе:",
@ -178,7 +185,7 @@
"SELECT_IMAGE": "Выберите изображение для увеличения",
"SELECT_FOLDER_DESCRIPTION": "Убедитесь, что в папке нет ничего, кроме изображений PNG, JPG, JPEG и WEBP.",
"SELECT_IMAGES_DESCRIPTION": "Выберите или перетащите изображение в формате PNG, JPG, JPEG или WEBP.",
"PASTE_IMAGE_DESCRIPTION": "Нажмите Ctrl + V или Cmd + V, чтобы вставить изображение из буфера обмена"
"PASTE_IMAGE_DESCRIPTION": "Нажмите Ctrl+V или ⌘+V, чтобы вставить изображение из буфера обмена"
},
"PROGRESS": {
"PROCESSING_TITLE": "Обработка изображения...",
@ -254,6 +261,7 @@
"NEXT_BUTTON_TITLE": "Далее",
"BACK_BUTTON_TITLE": "Назад",
"GET_STARTED_BUTTON_TITLE": "Начать",
"SETTINGS_NOTE": "Вы всегда можете изменить эти настройки позже.",
"STEP_1": {
"TITLE": "Добро пожаловать в Upscayl 🎉",
"DESCRIPTION": "Давайте начнем с нескольких быстрых шагов."

View File

@ -160,12 +160,19 @@
"START_BUTTON_TITLE": "升图!🚀",
"IN_PROGRESS_BUTTON_TITLE": "正在升图 ⏳"
},
"IMAGE_OPTIONS": {
"MORE_OPTIONS_DRAWER": {
"RESET_BUTTON_TITLE": "重置图片",
"LENS_VIEW_TITLE": "透镜视图",
"SLIDER_VIEW_TITLE": "滑块视图",
"ZOOM_AMOUNT_TITLE": "缩放比例",
"LENS_SIZE_TITLE": "透镜尺寸"
"LENS_SIZE_TITLE": "透镜尺寸",
"TOTAL_UPSCAYLS": "总升图次数",
"TOTAL_BATCH_UPSCAYLS": "总批量升图次数",
"TOTAL_IMAGE_UPSCAYLS": "总图片升图次数",
"TOTAL_DOUBLE_UPSCAYLS": "总双重升图次数",
"AVERAGE_UPSCAYL_TIME": "平均升图时间",
"LAST_UPSCAYL_DURATION": "上次升图时长",
"LAST_USED_AT": "上次使用时间"
},
"PROGRESS_BAR": {
"BATCH_UPSCAYL_IN_PROGRESS_TITLE": "批量阿普升图中",
@ -178,7 +185,7 @@
"SELECT_IMAGE": "选择要增强的图片",
"SELECT_FOLDER_DESCRIPTION": "文件夹中只支持 PNG、JPG、JPEG 和 WEBP 图片。",
"SELECT_IMAGES_DESCRIPTION": "选择或拖放 PNG、JPG、JPEG 或 WEBP 图片。",
"PASTE_IMAGE_DESCRIPTION": "按 Ctrl + V 或 Cmd + V 来粘贴图像"
"PASTE_IMAGE_DESCRIPTION": "按 Ctrl+V 或 ⌘+V 来粘贴图像"
},
"PROGRESS": {
"PROCESSING_TITLE": "正在处理图片...",
@ -254,6 +261,7 @@
"NEXT_BUTTON_TITLE": "下一步",
"BACK_BUTTON_TITLE": "上一步",
"GET_STARTED_BUTTON_TITLE": "开始使用",
"SETTINGS_NOTE": "你可以随时更改这些设置。",
"STEP_1": {
"TITLE": "欢迎使用阿普升图 🎉",
"DESCRIPTION": "让我们通过几个快速步骤来开始吧。"

View File

@ -4,22 +4,10 @@ import { AppProps } from "next/app";
import { Provider } from "jotai";
import "react-tooltip/dist/react-tooltip.css";
import { Toaster } from "@/components/ui/toaster";
import posthog from "posthog-js";
import { useEffect } from "react";
import { PostHogProvider } from "posthog-js/react";
import { Tooltip } from "react-tooltip";
import PostHogProviderWrapper from "@/components/posthog-provider-wrapper";
const MyApp = ({ Component, pageProps }: AppProps) => {
useEffect(() => {
posthog.init("phc_QMcmlmComdofjfaRPzoN4KV9ziV2KgOwAOVyu4J3dIc", {
api_host: "https://us.i.posthog.com",
person_profiles: "identified_only",
// Enable debug mode in development
loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug();
},
});
}, []);
return (
<>
<Head>
@ -27,10 +15,14 @@ const MyApp = ({ Component, pageProps }: AppProps) => {
</Head>
<Provider>
<PostHogProvider client={posthog}>
<PostHogProviderWrapper>
<Component {...pageProps} data-theme="upscayl" />
<Toaster />
</PostHogProvider>
<Tooltip
className="z-[999] max-w-sm break-words !bg-secondary"
id="tooltip"
/>
</PostHogProviderWrapper>
</Provider>
</>
);

View File

@ -8,6 +8,7 @@ import {
savedOutputPathAtom,
progressAtom,
rememberOutputFolderAtom,
userStatsAtom,
} from "../atoms/user-settings-atom";
import useLogger from "../components/hooks/use-logger";
import { useToast } from "@/components/ui/use-toast";
@ -44,6 +45,7 @@ const Home = () => {
const setProgress = useSetAtom(progressAtom);
const [doubleUpscaylCounter, setDoubleUpscaylCounter] = useState(0);
const setModelIds = useSetAtom(customModelIdsAtom);
const setUserStats = useSetAtom(userStatsAtom);
const selectImageHandler = async () => {
resetImagePaths();
@ -226,6 +228,14 @@ const Home = () => {
window.electron.on(ELECTRON_COMMANDS.UPSCAYL_DONE, (_, data: string) => {
setProgress("");
setUpscaledImagePath(data);
setUserStats((prev) => ({
...prev,
lastUpscaylDuration: new Date().getTime() - prev.lastUsedAt,
averageUpscaylTime:
(prev.averageUpscaylTime * prev.totalUpscayls +
(new Date().getTime() - prev.lastUsedAt)) /
(prev.totalUpscayls + 1),
}));
logit("upscaledImagePath: ", data);
logit(`💯 UPSCAYL_DONE: `, data);
});
@ -236,6 +246,14 @@ const Home = () => {
setProgress("");
setUpscaledBatchFolderPath(data);
logit(`💯 FOLDER_UPSCAYL_DONE: `, data);
setUserStats((prev) => ({
...prev,
lastUpscaylDuration: new Date().getTime() - prev.lastUsedAt,
averageUpscaylTime:
(prev.averageUpscaylTime * prev.totalUpscayls +
(new Date().getTime() - prev.lastUsedAt)) /
(prev.totalUpscayls + 1),
}));
},
);
// DOUBLE UPSCAYL DONE
@ -246,6 +264,14 @@ const Home = () => {
setTimeout(() => setUpscaledImagePath(data), 500);
setDoubleUpscaylCounter(0);
logit(`💯 DOUBLE_UPSCAYL_DONE: `, data);
setUserStats((prev) => ({
...prev,
lastUpscaylDuration: new Date().getTime() - prev.lastUsedAt,
averageUpscaylTime:
(prev.averageUpscaylTime * prev.totalUpscayls +
(new Date().getTime() - prev.lastUsedAt)) /
(prev.totalUpscayls + 1),
}));
},
);
// CUSTOM FOLDER LISTENER

View File

@ -6,6 +6,18 @@ export interface IElectronAPI {
send: <T>(command, func?: T) => IpcRenderer;
invoke: (command, func?) => any;
platform: "mac" | "win" | "linux";
getSystemInfo: () => Promise<{
platform: string | undefined;
release: string;
arch: string | undefined;
model: string;
cpuCount: number;
gpu: {
vendor: any;
model: any;
};
}>;
getAppVersion: () => Promise<string>;
}
declare global {