From 76737cfc9e462165cb256601acd883116959a5fa Mon Sep 17 00:00:00 2001
From: Gregor Adams <1148334+pixelass@users.noreply.github.com>
Date: Wed, 21 Feb 2024 12:04:46 +0100
Subject: [PATCH 1/2] test: setup playwright (#48)
## Summary
This PR establishes the foundation for end-to-end (E2E) testing in our
project by integrating Playwright. It's a crucial step towards building
a tested and stable application, focusing on simplifying our setup to
kickstart the testing process effectively.
## Changes
- **Integrated Playwright:** Sets up Playwright for E2E testing,
enabling us to begin automating tests for our application.
- **Codebase Cleanup:** Removed unused files and dependencies to
facilitate a smoother build process and ensure the testing environment
is focused only on active features.
- **Preparation for Feature Reintegration:** This cleanup paves the way
for a structured reintroduction of features, ensuring each is thoroughly
tested before becoming part of the main codebase again.
## Rationale
Following a significant refactor and restructuring of our source code,
this move is aimed at ensuring the new structure not only supports
development mode but is also testable and buildable. By starting with a
lean codebase, we aim to methodically reintroduce features, ensuring
each is compatible with our new structure and passes E2E tests.
## Next Steps
With the testing framework in place, we will gradually reintroduce
features, testing each thoroughly to maintain the integrity and
stability of our application.
### Issues Closed
- closes #22
---
package-lock.json | 60 ++
package.json | 7 +-
playwright-report/index.html | 69 ++
playwright.config.ts | 10 +
playwright/index.test.ts | 33 +
src/client/organisms/delete-confirm/index.tsx | 68 --
src/client/organisms/folder-drop/index.tsx | 52 --
src/client/organisms/folder-field/index.tsx | 55 --
.../live-painting/drawing-canvas.tsx | 196 ------
.../organisms/live-painting/output-canvas.tsx | 57 --
.../live-painting/update-properties.tsx | 128 ----
src/client/organisms/modals/add-dataset.tsx | 167 -----
.../organisms/modals/caption/batch-edit.tsx | 177 ------
src/client/organisms/modals/caption/index.tsx | 568 -----------------
src/client/organisms/model-card/index.tsx | 309 ---------
src/client/organisms/password-field/index.tsx | 49 --
src/client/organisms/text-field/index.tsx | 28 -
.../organisms/zoomable-image-stage/index.tsx | 152 -----
src/client/pages/[locale]/dataset.tsx | 548 ----------------
src/client/pages/[locale]/feedback.tsx | 115 ----
src/client/pages/[locale]/home.tsx | 247 --------
src/client/pages/[locale]/inventory.tsx | 58 --
src/client/pages/[locale]/livepainting.tsx | 66 --
src/client/pages/[locale]/marketplace.tsx | 595 ------------------
src/client/pages/[locale]/settings.tsx | 155 -----
src/client/pages/[locale]/training.tsx | 58 --
26 files changed, 177 insertions(+), 3850 deletions(-)
create mode 100644 playwright-report/index.html
create mode 100644 playwright.config.ts
create mode 100644 playwright/index.test.ts
delete mode 100644 src/client/organisms/delete-confirm/index.tsx
delete mode 100644 src/client/organisms/folder-drop/index.tsx
delete mode 100644 src/client/organisms/folder-field/index.tsx
delete mode 100644 src/client/organisms/live-painting/drawing-canvas.tsx
delete mode 100644 src/client/organisms/live-painting/output-canvas.tsx
delete mode 100644 src/client/organisms/live-painting/update-properties.tsx
delete mode 100644 src/client/organisms/modals/add-dataset.tsx
delete mode 100644 src/client/organisms/modals/caption/batch-edit.tsx
delete mode 100644 src/client/organisms/modals/caption/index.tsx
delete mode 100644 src/client/organisms/model-card/index.tsx
delete mode 100644 src/client/organisms/password-field/index.tsx
delete mode 100644 src/client/organisms/text-field/index.tsx
delete mode 100644 src/client/organisms/zoomable-image-stage/index.tsx
delete mode 100644 src/client/pages/[locale]/dataset.tsx
delete mode 100644 src/client/pages/[locale]/feedback.tsx
delete mode 100644 src/client/pages/[locale]/home.tsx
delete mode 100644 src/client/pages/[locale]/inventory.tsx
delete mode 100644 src/client/pages/[locale]/livepainting.tsx
delete mode 100644 src/client/pages/[locale]/marketplace.tsx
delete mode 100644 src/client/pages/[locale]/training.tsx
diff --git a/package-lock.json b/package-lock.json
index 827700b78..b84776b8a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
"@mui/joy": "5.0.0-beta.24",
"@mui/material": "5.15.6",
"@next/mdx": "14.1.0",
+ "@playwright/test": "^1.41.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
@@ -5085,6 +5086,21 @@
"url": "https://opencollective.com/unts"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
+ "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -20938,6 +20954,50 @@
"node": ">=4"
}
},
+ "node_modules/playwright": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
+ "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
+ "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
diff --git a/package.json b/package.json
index 31dbca1e0..f427bbcd0 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"build": "nextron build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dev": "nextron",
- "eslint": "eslint \"{renderer,main}/{**/*,*}{.,.config.,.test.,.e2e.}{js,mjs,ts,tsx}\"",
+ "eslint": "eslint \"src/{**/*,*}.{ts,tsx}\"",
"postinstall": "electron-builder install-app-deps",
"ncu": "npx npm-check-updates@latest -u",
"ncu:check": "npx npm-check-updates@latest",
@@ -21,7 +21,9 @@
"toc": "npx markdown-toc README.md -i",
"tsc:noEmit": "tsc --noEmit",
"caption:test": "ts-node-esm main/captions/misc.ts",
- "test:unit": "jest --runInBand --config jest.config.unit.mjs --verbose"
+ "test:unit": "jest --runInBand --config jest.config.unit.mjs --verbose",
+ "pretest:e2e": "nextron build --no-pack",
+ "test:e2e": "npx playwright test"
},
"dependencies": {
"electron-context-menu": "3.6.1",
@@ -43,6 +45,7 @@
"@mui/joy": "5.0.0-beta.24",
"@mui/material": "5.15.6",
"@next/mdx": "14.1.0",
+ "@playwright/test": "^1.41.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
diff --git a/playwright-report/index.html b/playwright-report/index.html
new file mode 100644
index 000000000..76188316c
--- /dev/null
+++ b/playwright-report/index.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 000000000..f8805f1b4
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,10 @@
+import type { PlaywrightTestConfig } from "@playwright/test";
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: "./playwright",
+};
+
+export default config;
diff --git a/playwright/index.test.ts b/playwright/index.test.ts
new file mode 100644
index 000000000..b8e9357e6
--- /dev/null
+++ b/playwright/index.test.ts
@@ -0,0 +1,33 @@
+import type { ElectronApplication, Page } from "@playwright/test";
+import { test, expect } from "@playwright/test";
+import { _electron as electron } from "playwright";
+
+let electronApp: ElectronApplication;
+let page: Page;
+
+test.beforeAll(async () => {
+ // Use package.main
+ electronApp = await electron.launch({ args: ["."] });
+ const isPackaged = await electronApp.evaluate(async ({ app }) => app.isPackaged);
+
+ expect(isPackaged).toBe(false);
+});
+
+test.afterAll(async () => {
+ await electronApp.close();
+});
+
+test("Renders the first page", async () => {
+ page = await electronApp.firstWindow();
+ const title = await page.title();
+ expect(title).toBe("Blibla");
+});
+
+test("Allows switching the language", async () => {
+ page = await electronApp.firstWindow();
+
+ await expect(page.getByTestId("language-selector-list")).toBeVisible();
+ await expect(page.getByText("Deutsch")).toBeVisible();
+ await page.getByText("Deutsch").click();
+ await expect(page.getByText("Sprache")).toBeVisible();
+});
diff --git a/src/client/organisms/delete-confirm/index.tsx b/src/client/organisms/delete-confirm/index.tsx
deleted file mode 100644
index f9e02bbea..000000000
--- a/src/client/organisms/delete-confirm/index.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import CancelIcon from "@mui/icons-material/Cancel";
-import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
-import Button from "@mui/joy/Button";
-import IconButton from "@mui/joy/IconButton";
-import Sheet from "@mui/joy/Sheet";
-import { useAtom } from "jotai";
-import { useTranslation } from "next-i18next";
-import { useState } from "react";
-
-import { datasetsAtom } from "@/ions/atoms";
-
-export function DeleteConfirm({ projectId }: { projectId: string }) {
- const [confirm, setConfirm] = useState(false);
- const [, setDatasets] = useAtom(datasetsAtom);
- const { t } = useTranslation(["common"]);
- return confirm ? (
-
- }
- onClick={() => {
- setConfirm(false);
- }}
- >
- {t("common:cancel")}
-
- }
- onClick={async () => {
- await window.ipc.deleteDataset(projectId);
- await window.ipc.getDatasets().then((datasets_: any) => {
- setDatasets(datasets_);
- });
- }}
- >
- {t("common:delete")}
-
-
- ) : (
- {
- setConfirm(true);
- }}
- >
-
-
- );
-}
diff --git a/src/client/organisms/folder-drop/index.tsx b/src/client/organisms/folder-drop/index.tsx
deleted file mode 100644
index d849617cd..000000000
--- a/src/client/organisms/folder-drop/index.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import Box from "@mui/joy/Box";
-import type { ReactNode } from "react";
-import { useState } from "react";
-
-export function FolderDrop({
- children,
- onDrop,
-}: {
- children?: ReactNode;
- onDrop?(path: string): void;
-}) {
- const [dragCounter, setDragCounter] = useState(0);
-
- const isDragOver = dragCounter > 0;
-
- return (
- {
- event.preventDefault();
- }}
- onDragEnter={event => {
- event.preventDefault();
- setDragCounter(previousState => previousState + 1);
- }}
- onDragLeave={event => {
- event.preventDefault();
- setDragCounter(previousState => previousState - 1);
- }}
- onDrop={event => {
- event.preventDefault();
- setDragCounter(0);
- const folder = event.dataTransfer.items[0];
- if (folder?.kind === "file" && folder.webkitGetAsEntry()?.isDirectory && onDrop) {
- const file = folder.getAsFile();
- if (file?.path) {
- onDrop(file.path);
- }
- }
- }}
- >
- {children}
-
- );
-}
diff --git a/src/client/organisms/folder-field/index.tsx b/src/client/organisms/folder-field/index.tsx
deleted file mode 100644
index 2bc380560..000000000
--- a/src/client/organisms/folder-field/index.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import FolderIcon from "@mui/icons-material/Folder";
-import FormControl from "@mui/joy/FormControl";
-import FormHelperText from "@mui/joy/FormHelperText";
-import FormLabel from "@mui/joy/FormLabel";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import type { InputProps } from "@mui/joy/Input";
-import { useTranslation } from "next-i18next";
-import type { ReactNode } from "react";
-import type { Except } from "type-fest";
-
-export function FolderField({
- helpText,
- error,
- label,
- onSelect,
- ...properties
-}: Except & {
- helpText?: ReactNode;
- label?: ReactNode;
- error?: string;
- onSelect?(value: string): Promise | void;
-}) {
- const { t } = useTranslation(["common"]);
- return (
-
- {label && {label}}
- {
- try {
- const directory_ = await window.ipc.getDirectory();
- if (directory_ && onSelect) {
- onSelect(directory_);
- }
- } catch (error) {
- console.log(error);
- }
- }}
- >
-
-
- }
- />
- {(error || helpText) && {error || helpText}}
-
- );
-}
diff --git a/src/client/organisms/live-painting/drawing-canvas.tsx b/src/client/organisms/live-painting/drawing-canvas.tsx
deleted file mode 100644
index 44eb119f7..000000000
--- a/src/client/organisms/live-painting/drawing-canvas.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import Brush from "@mui/icons-material/Brush";
-import Clear from "@mui/icons-material/Clear";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import Stack from "@mui/joy/Stack";
-import { Box } from "@mui/material";
-import type { MouseEvent } from "react";
-import { useRef, useEffect, useState } from "react";
-
-interface DrawingCanvasProperties {
- width?: number;
- height?: number;
-}
-
-export function DrawingCanvas({ width = 512, height = 512 }: DrawingCanvasProperties) {
- const canvasReference = useRef(null);
- const [isDrawing, setIsDrawing] = useState(false);
- const [strokeColor, setStrokeColor] = useState("#00ff00");
- const [brushSize, setBrushSize] = useState(30);
- const [backgroundColor] = useState("#ffffff");
- const brushReference = useRef(null);
-
- function startDrawing({ nativeEvent }: MouseEvent) {
- const context = canvasReference.current?.getContext("2d");
- if (context) {
- const { offsetX, offsetY } = nativeEvent;
- context.strokeStyle = strokeColor;
- context.lineWidth = brushSize;
- context.beginPath();
- context.moveTo(offsetX, offsetY);
- setIsDrawing(true);
- }
- }
-
- function finishDrawing() {
- const context = canvasReference.current?.getContext("2d");
- if (context) {
- context.closePath();
- setIsDrawing(false);
- }
- }
-
- function draw({ nativeEvent }: MouseEvent) {
- const { offsetX, offsetY } = nativeEvent;
- if (brushReference.current) {
- brushReference.current.style.transform = `translate3d(calc(${offsetX}px - 50%), calc(${offsetY}px - 50%), 0)`;
- }
-
- if (!isDrawing) {
- return;
- }
-
- const context = canvasReference.current?.getContext("2d");
- if (context) {
- context.strokeStyle = strokeColor;
- context.lineWidth = brushSize;
- context.lineTo(offsetX, offsetY);
- context.stroke();
- }
- }
-
- function clearCanvas() {
- if (canvasReference.current) {
- const canvas = canvasReference.current;
- const context = canvas?.getContext("2d");
-
- if (context) {
- context.clearRect(0, 0, canvas.width, canvas.height);
- context.fillStyle = backgroundColor;
- context.fillRect(0, 0, canvas.width, canvas.height);
- }
- }
- }
-
- useEffect(() => {
- // Keep this function local to this side effect
- // it simplifies the usage and reduces the number of required hooks
- let animationFrameId: number;
- async function captureAndSendCanvasData() {
- const drawingCanvas = canvasReference.current;
-
- if (drawingCanvas) {
- const data = drawingCanvas.toDataURL();
- window.ipc.send("live-painting:input", data);
- }
-
- animationFrameId = requestAnimationFrame(captureAndSendCanvasData);
- }
-
- if (canvasReference.current) {
- const dpr = window.devicePixelRatio || 1;
- const canvas = canvasReference.current;
-
- canvas.width = width * dpr;
- canvas.height = height * dpr;
-
- const context = canvas.getContext("2d");
- if (context) {
- context.scale(dpr, dpr);
- }
-
- animationFrameId = requestAnimationFrame(captureAndSendCanvasData);
- }
-
- return () => {
- cancelAnimationFrame(animationFrameId);
- };
- }, [height, width]);
-
- // Only handle adjustments here
- // Never paint in this side effect
- useEffect(() => {
- if (brushReference.current) {
- brushReference.current.style.width = `${brushSize}px`;
- brushReference.current.style.height = `${brushSize}px`;
- }
-
- if (canvasReference.current) {
- const canvas = canvasReference.current;
-
- const context = canvas.getContext("2d");
- if (context) {
- context.fillStyle = backgroundColor;
- context.lineCap = "round";
- context.lineJoin = "round"; // This makes the corner round
- context.strokeStyle = strokeColor;
- context.lineWidth = brushSize;
- }
- }
- }, [backgroundColor, brushSize, strokeColor]);
-
- return (
-
-
- {
- setStrokeColor(event.target.value);
- }}
- />
-
- }
- slotProps={{ input: { min: 0 } }}
- onChange={event => {
- setBrushSize(Number.parseInt(event.target.value, 10));
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/client/organisms/live-painting/output-canvas.tsx b/src/client/organisms/live-painting/output-canvas.tsx
deleted file mode 100644
index f307e2e27..000000000
--- a/src/client/organisms/live-painting/output-canvas.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { useRef, useEffect } from "react";
-
-interface OutputCanvasProperties {
- width?: number;
- height?: number;
-}
-
-export function OutputCanvas({ width = 512, height = 512 }: OutputCanvasProperties) {
- const canvasReference = useRef(null);
-
- useEffect(() => {
- if (canvasReference.current) {
- const dpr = window.devicePixelRatio || 1;
- const canvas = canvasReference.current;
-
- canvas.width = width * dpr;
- canvas.height = height * dpr;
- canvas.style.width = `${width}px`;
- canvas.style.height = `${height}px`;
- }
- }, [height, width]);
-
- useEffect(() => {
- const image = new Image();
-
- if (!canvasReference.current) {
- return;
- }
-
- const canvas = canvasReference.current;
- const context = canvas.getContext("2d");
-
- function handleLoad() {
- context!.drawImage(image, 0, 0, canvas.width, canvas.height);
- }
-
- image.addEventListener("load", handleLoad);
-
- function handleImageGenerated(base64Image: string) {
- if (!base64Image.trim() || image.src === base64Image) {
- return;
- }
-
- console.log("tick");
- image.src = `${base64Image}`;
- }
-
- const unsubscribe = window.ipc.on("image-generated", handleImageGenerated);
-
- return () => {
- unsubscribe();
- image.removeEventListener("load", handleLoad);
- };
- }, []);
-
- return ;
-}
diff --git a/src/client/organisms/live-painting/update-properties.tsx b/src/client/organisms/live-painting/update-properties.tsx
deleted file mode 100644
index 37a46d267..000000000
--- a/src/client/organisms/live-painting/update-properties.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import Recycling from "@mui/icons-material/Recycling";
-import FormControl from "@mui/joy/FormControl";
-import FormLabel from "@mui/joy/FormLabel";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import Stack from "@mui/joy/Stack";
-import type { ChangeEvent } from "react";
-import { useEffect, useState } from "react";
-
-export function UpdateProperties() {
- const [prompt, setPrompt] = useState("");
- const [seed, setSeed] = useState("0");
- const [size, setSize] = useState({ width: 512, height: 512 });
- const [strength, setStrength] = useState("1");
- const [guidance, setGuidance] = useState("0");
-
- // Handle input changes
- function handlePromptChange(event: ChangeEvent) {
- setPrompt(event.target.value);
- }
-
- function handleSeedChange(event: ChangeEvent) {
- setSeed(event.target.value);
- }
-
- function handleSizeChange(dimension: "height" | "width", event: ChangeEvent) {
- setSize(previousSize => ({
- ...previousSize,
- [dimension]: Number.parseInt(event.target.value, 10),
- }));
- }
-
- function handleStrengthChange(event: ChangeEvent) {
- setStrength(event.target.value);
- }
-
- function handleGuidanceChange(event: ChangeEvent) {
- setGuidance(event.target.value);
- }
-
- function randomSeed() {
- setSeed(Math.floor(Math.random() * 100_000_000 + 1).toString());
- }
-
- // Use effect to send updates
- useEffect(() => {
- const properties = {
- prompt,
- seed: seed === "" ? 0 : Number.parseInt(seed, 10),
- size,
- strength: Number.parseFloat(strength),
- guidance_scale: Number.parseFloat(guidance),
- };
- window.ipc.send("live-painting:update-properties", properties);
- }, [prompt, seed, size, strength, guidance]);
-
- return (
-
-
- Prompt
-
-
-
-
- Strength
- {
- handleStrengthChange(event);
- }}
- />
-
-
- {/*
- Guidance
- {
- handleGuidanceChange(event);
- }}
- />
- */}
-
-
- Seed
-
-
-
- }
- onChange={handleSeedChange}
- />
-
-
- Width
- {
- handleSizeChange("width", event);
- }}
- />
-
-
- Height
- {
- handleSizeChange("height", event);
- }}
- />
-
-
- );
-}
diff --git a/src/client/organisms/modals/add-dataset.tsx b/src/client/organisms/modals/add-dataset.tsx
deleted file mode 100644
index c072d8940..000000000
--- a/src/client/organisms/modals/add-dataset.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
-import FolderIcon from "@mui/icons-material/Folder";
-import RefreshIcon from "@mui/icons-material/Refresh";
-import Button from "@mui/joy/Button";
-import CircularProgress from "@mui/joy/CircularProgress";
-import FormControl from "@mui/joy/FormControl";
-import FormHelperText from "@mui/joy/FormHelperText";
-import FormLabel from "@mui/joy/FormLabel";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import Modal from "@mui/joy/Modal";
-import type { ModalProps } from "@mui/joy/Modal";
-import ModalClose from "@mui/joy/ModalClose";
-import ModalDialog from "@mui/joy/ModalDialog";
-import Stack from "@mui/joy/Stack";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai";
-import { useTranslation } from "next-i18next";
-import { useEffect, useState } from "react";
-import { useForm } from "react-hook-form";
-import type { Except } from "type-fest";
-
-import { directoryAtom, datasetsAtom } from "@/ions/atoms";
-import { adjectives, colors, generateRandomName, nouns } from "@/ions/utils/get-random-name";
-
-function AddDatasetForm({ onClose }: { onClose: ModalProps["onClose"] }) {
- const [, setDatasets] = useAtom(datasetsAtom);
- const [directory, setDirectory] = useAtom(directoryAtom);
-
- const [loading, setLoading] = useState(false);
- const [, setError] = useState(null);
-
- const { t } = useTranslation(["common"]);
-
- const { register, formState, handleSubmit, setValue, clearErrors } = useForm({
- defaultValues: {
- name: generateRandomName([adjectives, colors, nouns], {
- humanize: true,
- separator: " ",
- }),
- directory,
- },
- });
-
- useEffect(
- () => () => {
- setDirectory("");
- },
- [setDirectory]
- );
-
- useEffect(() => {
- clearErrors("directory");
- }, [clearErrors]);
-
- return (
-
- );
-}
-
-export function AddDatasetModal({ onClose, ...properties }: Except) {
- const { t } = useTranslation(["common"]);
-
- return (
-
-
-
-
-
-
- );
-}
diff --git a/src/client/organisms/modals/caption/batch-edit.tsx b/src/client/organisms/modals/caption/batch-edit.tsx
deleted file mode 100644
index 0974aaa3f..000000000
--- a/src/client/organisms/modals/caption/batch-edit.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import FindReplaceIcon from "@mui/icons-material/FindReplace";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import DialogActions from "@mui/joy/DialogActions";
-import FormControl from "@mui/joy/FormControl";
-import FormLabel from "@mui/joy/FormLabel";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import Modal from "@mui/joy/Modal";
-import ModalClose from "@mui/joy/ModalClose";
-import ModalDialog from "@mui/joy/ModalDialog";
-import Stack from "@mui/joy/Stack";
-import SvgIcon from "@mui/joy/SvgIcon";
-import Tooltip from "@mui/joy/Tooltip";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai/index";
-import { useTranslation } from "next-i18next";
-import React, { useId, useState } from "react";
-import { useForm } from "react-hook-form";
-
-import { editCaptionScopeAtom, imagesAtom } from "@/ions/atoms";
-import { EditCaptionScope } from "@/organisms/modals/caption/index";
-
-export function RegexIcon() {
- return (
-
-
-
- );
-}
-
-export function TextSearchIcon() {
- return (
-
-
-
- );
-}
-
-export function SuffixIcon() {
- return (
-
-
-
- );
-}
-
-export function PrefixIcon() {
- return (
-
-
-
- );
-}
-
-export function BatchEditModal({
- open,
- onClose,
-}: {
- onClose(): void | Promise;
- open: boolean;
-}) {
- const FindReplaceId = useId();
- const { t } = useTranslation(["common"]);
- const [regex, setRegex] = useState(false);
- const [images, setImages] = useAtom(imagesAtom);
- const [editCaptionScope] = useAtom(editCaptionScopeAtom);
- const { register, reset, handleSubmit } = useForm({
- defaultValues: {
- find: "",
- replace: "",
- prefix: "",
- suffix: "",
- },
- });
-
- return (
-
-
-
-
- {t("common:batchEdit")}:
- {
- const newData = images.map(image => {
- if (editCaptionScope === "empty" && image.caption) {
- return image;
- }
-
- if (editCaptionScope === "selected" && !image.selected) {
- return image;
- }
-
- if (data.find) {
- const pattern = regex ? new RegExp(data.find, "ig") : data.find;
- image.caption = image.caption.replace(pattern, data.replace);
- }
-
- image.caption = data.prefix + image.caption + data.suffix;
- return image;
- });
- setImages(newData);
- window.ipc.batchEditCaption(newData);
- reset();
- onClose();
- })}
- >
-
-
-
- {t("common:searchAndReplace")}
-
-
- }
- endDecorator={
-
- {
- setRegex(previousState => !previousState);
- }}
- >
-
-
-
- }
- />
- }
- />
-
-
-
- {t("common:prefix")}
- } />
-
-
- {t("common:suffix")}
- } />
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/client/organisms/modals/caption/index.tsx b/src/client/organisms/modals/caption/index.tsx
deleted file mode 100644
index 517536e73..000000000
--- a/src/client/organisms/modals/caption/index.tsx
+++ /dev/null
@@ -1,568 +0,0 @@
-import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
-import CheckAllIcon from "@mui/icons-material/DoneAll";
-import ErrorIcon from "@mui/icons-material/Error";
-import RuleIcon from "@mui/icons-material/Rule";
-import StyleIcon from "@mui/icons-material/Style";
-import VisibilityIcon from "@mui/icons-material/Visibility";
-import WarningIcon from "@mui/icons-material/Warning";
-import Alert from "@mui/joy/Alert";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import FormControl from "@mui/joy/FormControl";
-import FormLabel from "@mui/joy/FormLabel";
-import Link from "@mui/joy/Link";
-import Modal from "@mui/joy/Modal";
-import ModalClose from "@mui/joy/ModalClose";
-import ModalDialog from "@mui/joy/ModalDialog";
-import Option from "@mui/joy/Option";
-import Select from "@mui/joy/Select";
-import Slider from "@mui/joy/Slider";
-import Stack from "@mui/joy/Stack";
-import { styled } from "@mui/joy/styles";
-import SvgIcon from "@mui/joy/SvgIcon";
-import Tab from "@mui/joy/Tab";
-import TabList from "@mui/joy/TabList";
-import TabPanel from "@mui/joy/TabPanel";
-import Tabs from "@mui/joy/Tabs";
-import Textarea from "@mui/joy/Textarea";
-import ToggleButtonGroup from "@mui/joy/ToggleButtonGroup";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai/index";
-import dynamic from "next/dynamic";
-import { Trans, useTranslation } from "next-i18next";
-import React, { useEffect, useMemo, useState } from "react";
-import useSWR from "swr";
-
-import {
- CAPTIONS,
- DOWNLOADS,
- GPT_VISION_OPTIONS,
- OPENAI_API_KEY,
-} from "../../../../main/helpers/constants";
-
-import { captioningErrorAtom, imagesAtom, editCaptionScopeAtom } from "@/ions/atoms";
-import { PasswordField } from "@/organisms/password-field";
-
-export const CodeMirror = dynamic(
- () => import("react-codemirror2").then(module_ => module_.Controlled),
- { ssr: false }
-);
-export const StyledEditor = styled(CodeMirror)({
- height: "100%",
- ">.CodeMirror": {
- height: "100%",
- },
-});
-
-export const defaultGptOptions = {
- batchSize: 4,
- parallel: true,
- guidelines: `Please caption these images, separate groups by comma, ensure logical groups: "black torn wide pants, red stained sweater" instead of "black, torn, wide pants and red, stained sweater"`,
- exampleResponse: `[
- "a photo of a young man, red hair, blue torn overalls with brass buttons, orange t-shirt with holes, white background",
- "a watercolor painting of an elderly woman, grey hair, yellow and blue floral print sundress with puffy sleeves, pink high heels, looking at a castle in the distance"
-]`,
-};
-
-export function EmptyCaptionIcon() {
- return (
-
-
-
- );
-}
-
-export function useFilteredImages() {
- const [filterScope] = useAtom(editCaptionScopeAtom);
- const [images] = useAtom(imagesAtom);
- return useMemo(() => {
- switch (filterScope) {
- case "empty": {
- return images.filter(image => !image.caption);
- }
-
- case "selected": {
- return images.filter(image => image.selected);
- }
-
- case "all": {
- return images;
- }
-
- default: {
- return [];
- }
- }
- }, [filterScope, images]);
-}
-
-export function EditCaptionScope() {
- const { t } = useTranslation(["common"]);
- const [value, setValue] = useAtom(editCaptionScopeAtom);
-
- return (
-
- {t("common:scopeForEditing")}
- {
- if (newValue) {
- setValue(newValue);
- }
- }}
- >
- }>
- {t("common:all")}
-
- }>
- {t("common:empty")}
-
- }>
- {t("common:selected")}
-
-
-
- );
-}
-
-export function GPTVCaptionModal({
- onClose,
- onStart,
-}: {
- onClose(): void | Promise;
- onStart(): void | Promise;
- onDone(): void | Promise;
-}) {
- const [openAiApiKey, setOpenAiApiKey] = useState("");
- const [gptVisionOptions, setGptVisionOptions] = useState(defaultGptOptions);
- const [confirmGpt, setConfirmGpt] = useState(false);
- const { t } = useTranslation(["common"]);
- const [, setCaptioningError] = useAtom(captioningErrorAtom);
- const { data: openApiKeyData } = useSWR(OPENAI_API_KEY);
- const { data: gptVisionData } = useSWR(GPT_VISION_OPTIONS);
- const filteredImages = useFilteredImages();
-
- useEffect(() => {
- if (openApiKeyData) {
- setOpenAiApiKey(openApiKeyData);
- }
- }, [openApiKeyData]);
-
- useEffect(() => {
- if (gptVisionData) {
- setGptVisionOptions(
- gptVisionData as {
- batchSize: number;
- guidelines: string;
- exampleResponse: string;
- parallel: boolean;
- }
- );
- }
- }, [gptVisionData]);
-
- return (
-
- }
- sx={{ flex: 1 }}
- onClick={async () => {
- setConfirmGpt(!confirmGpt);
- }}
- >
- {t("common:pages.dataset.customCaptionsWithGPTVision")}
-
-
- {confirmGpt && (
-
- }>
-
-
- ),
- }}
- />
-
-
- {!openAiApiKey && (
- }>
-
- {t("common:pages.dataset.enterKeyToUseGPTVision")}{" "}
-
- {t("common:getApiKey")}
-
-
-
- )}
- }
- sx={{ flex: 1 }}
- onClick={async () => {
- onStart();
- onClose();
- try {
- await window.ipc.handleRunGPTV(
- filteredImages.map(image => image.image),
- {
- ...gptVisionOptions,
- exampleResponse: JSON.parse(
- gptVisionOptions.exampleResponse
- ),
- }
- );
- } catch (error) {
- console.log(".... error");
- console.log(error);
- setCaptioningError((error as Error).message);
- }
- }}
- >
- {t("common:pages.dataset.proceedWithGPTVision")}
-
-
- )}
-
- {
- setOpenAiApiKey(event.target.value);
- }}
- onBlur={event => {
- window.ipc.fetch(OPENAI_API_KEY, {
- method: "POST",
- data: event.target.value,
- });
- }}
- />
-
-
-
-
-
- {t("common:batch")}
- {
- setGptVisionOptions(previousState => ({
- ...previousState,
- batchSize: value as number,
- }));
- }}
- onChangeCommitted={(_event, value) => {
- window.ipc.fetch(GPT_VISION_OPTIONS, {
- method: "POST",
- data: {
- ...gptVisionOptions,
- batchSize: value,
- },
- });
- }}
- />
-
- {t("common:guideline")}
-
- {
- setGptVisionOptions({
- ...gptVisionOptions,
- guidelines: value,
- });
- window.ipc.fetch(GPT_VISION_OPTIONS, {
- method: "POST",
- data: {
- ...gptVisionOptions,
- guidelines: value,
- },
- });
- }}
- />
-
- {t("common:exampleResponse")}
-
- {
- setGptVisionOptions({
- ...gptVisionOptions,
- exampleResponse: value,
- });
- window.ipc.fetch(GPT_VISION_OPTIONS, {
- method: "POST",
- data: {
- ...gptVisionOptions,
- exampleResponse: value,
- },
- });
- }}
- />
-
-
-
- );
-}
-
-export function WD14CaptionModal({
- onClose,
- onStart,
-}: {
- onClose(): void | Promise;
- onStart(): void | Promise;
- onDone(): void | Promise;
-}) {
- const [options, setOptions] = useState({ batchSize: 10, model: "", exclude: "" });
- const { t } = useTranslation(["common"]);
- const [, setCaptioningError] = useAtom(captioningErrorAtom);
- const { data: loadingModel } = useSWR("SmilingWolf/wd-v1-4-convnextv2-tagger-v2/model");
- const { data: loadingCSV } = useSWR("SmilingWolf/wd-v1-4-convnextv2-tagger-v2/selected_tags");
- const { data: checkpointsData } = useSWR(CAPTIONS, () => window.ipc.getModels("captions"));
- const isInstalled = Boolean(checkpointsData?.length);
- const model =
- "https://huggingface.co/SmilingWolf/wd-v1-4-convnextv2-tagger-v2/resolve/main/model.onnx";
- const csv =
- "https://huggingface.co/SmilingWolf/wd-v1-4-convnextv2-tagger-v2/resolve/main/selected_tags.csv";
-
- useEffect(() => {
- if (checkpointsData) {
- setOptions(previousState => ({ ...previousState, model: checkpointsData[0] }));
- }
- }, [checkpointsData]);
-
- const filteredImages = useFilteredImages();
-
- return (
-
- {!isInstalled && (
- }
- endDecorator={
- }
- onClick={async () => {
- const storeKey = `${DOWNLOADS}.SmilingWolf/wd-v1-4-convnextv2-tagger-v2.model.onnx`;
-
- await window.ipc.downloadModel("wd14", model, {
- id: "SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
- storeKey,
- });
- await window.ipc.downloadModel("wd14", csv, {
- id: "SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
- storeKey,
- });
- }}
- >
- {t("common:download")}
-
- }
- sx={{
- ".MuiAlert-endDecorator": {
- alignSelf: "flex-start",
- mt: -0.5,
- mr: -0.5,
- },
- }}
- >
- {t("common:pages.dataset.oneTimeDownloadNote")}
-
- )}
- }
- sx={{ flex: 1 }}
- onClick={async () => {
- onStart();
- onClose();
- try {
- await window.ipc.handleRunWd14(
- filteredImages.map(image => image.image),
- {
- ...options,
- exclude: options.exclude.split(",").map(entry => entry.trim()),
- }
- );
- } catch (error) {
- setCaptioningError((error as Error).message);
- }
- }}
- >
- {t("common:pages.dataset.generateTagsWithWD14")}
-
-
-
-
- {t("common:model")}
-
-
-
- {t("common:excludedTags")}
-
-
- {t("common:batch")}
- {
- setOptions(previousState => ({
- ...previousState,
- batchSize: value as number,
- }));
- }}
- />
-
-
-
- );
-}
-
-export function CaptionModal({
- open,
- onClose,
- onStart,
- onDone,
-}: {
- onClose(): void | Promise;
- onStart(): void | Promise;
- onDone(): void | Promise;
- open: boolean;
-}) {
- const { t } = useTranslation(["common"]);
-
- return (
-
-
-
-
- {t("common:pages.dataset.chooseCaptioningMethod")}:
-
-
- WD14
- GPT Vision
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/client/organisms/model-card/index.tsx b/src/client/organisms/model-card/index.tsx
deleted file mode 100644
index d5729e481..000000000
--- a/src/client/organisms/model-card/index.tsx
+++ /dev/null
@@ -1,309 +0,0 @@
-import { ClickAwayListener } from "@mui/base";
-import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
-import CheckIcon from "@mui/icons-material/Check";
-import DownloadIcon from "@mui/icons-material/Download";
-import LinkIcon from "@mui/icons-material/Link";
-import WorkspacePremiumIcon from "@mui/icons-material/WorkspacePremium";
-import Badge from "@mui/joy/Badge";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import ButtonGroup from "@mui/joy/ButtonGroup";
-import Card from "@mui/joy/Card";
-import CardContent from "@mui/joy/CardContent";
-import CircularProgress from "@mui/joy/CircularProgress";
-import IconButton from "@mui/joy/IconButton";
-import Menu from "@mui/joy/Menu";
-import MenuItem from "@mui/joy/MenuItem";
-import Tooltip from "@mui/joy/Tooltip";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai";
-import { useTranslation } from "next-i18next";
-import type { MouseEvent as ReactMouseEvent } from "react";
-import { useEffect, useRef, useState } from "react";
-import useSWR from "swr";
-
-import { DOWNLOADS } from "../../../main/helpers/constants";
-
-import { captionsAtom, checkpointsAtom, lorasAtom } from "@/ions/atoms";
-import { fetcher } from "@/ions/swr/fetcher";
-
-export const architectureMap = {
- "sd-2-1": "SD 2.1",
- "sd-xl-turbo": "SDXL Turbo",
- "sd-2-1-turbo": "SD Turbo",
- "sd-1-5": "SD 1.5",
- "sd-xl-1-0": "SDXL",
-};
-
-const modelAtoms = {
- loras: lorasAtom,
- checkpoints: checkpointsAtom,
- wd14: captionsAtom,
-};
-
-// TODO this component has too much business logic.
-export function ModelCard({
- id,
- title,
- author,
- caption,
- files,
- type,
- license,
- architecture,
- link,
- image,
-}: {
- id: string;
- author: string;
- license: string;
- link: string;
- type: "loras" | "checkpoints" | "wd14";
- caption?: string;
- architecture: string;
- files: Array<{ filename: string; variant?: string; required?: boolean }>;
- title: string;
- image?: string;
-}) {
- const [isDownloading, setIsDownloading] = useState(false);
- const { t } = useTranslation(["common"]);
- const [isDownloadOptionsOpen, setIsDownloadOptionsOpen] = useState(false);
-
- const anchorReference = useRef(null);
- const [selectedIndex, setSelectedIndex] = useState(0);
- const [checkpoints] = useAtom(modelAtoms[type]);
-
- const selectedFile = files[selectedIndex];
- const installed =
- type === "wd14"
- ? Boolean(selectedFile) && checkpoints.includes([id, selectedFile.filename].join("/"))
- : Boolean(selectedFile) && checkpoints.includes(selectedFile.filename);
-
- const hasVersion =
- type === "wd14"
- ? files.some(({ filename }) => checkpoints.includes([id, filename].join("/")))
- : files.some(({ filename }) => checkpoints.includes(filename));
-
- const hasMultipleVersions = files.filter(item => !item.required).length > 1;
-
- function handleMenuItemClick(event: ReactMouseEvent, index: number) {
- setSelectedIndex(index);
- setIsDownloadOptionsOpen(false);
- }
-
- const storeKey = `${DOWNLOADS}.${id}.${selectedFile.filename}`;
- const { data } = useSWR(storeKey, fetcher, {
- refreshInterval: 1000,
- });
-
- useEffect(() => {
- setIsDownloading(Boolean(data));
- }, [data]);
-
- let buttonText = t("common:download");
-
- if (isDownloading) {
- buttonText = t("common:downloading");
- } else if (installed) {
- buttonText = t("common:installed");
- }
-
- return (
-
- <>
-
- }
- sx={{ display: "block" }}
- anchorOrigin={{
- vertical: "top",
- horizontal: "left",
- }}
- slotProps={{
- badge: {
- sx: {
- mt: -1,
- borderRadius: 0,
- boxShadow: "none",
- },
- },
- }}
- >
-
- {title}
-
-
- {author}
-
-
-
-
-
-
-
- {image && (
-
- )}
- {caption && (
-
- {caption}
-
- )}
-
-
-
- }>
- {license}
-
-
- {
- setIsDownloadOptionsOpen(false);
- }}
- >
-
-
-
- :
- }
- onClick={async () => {
- setIsDownloading(true);
- try {
- await window.ipc.fetch(storeKey, {
- method: "POST",
- data: true,
- });
-
- if (files.every(file => file.required)) {
- console.log("all required");
- for (const file of files) {
- await window.ipc.downloadModel(
- type,
- `${link}/resolve/main/${file.filename}?download=true`,
- { id, storeKey }
- );
- }
- } else {
- await window.ipc.downloadModel(
- type,
- `${link}/resolve/main/${selectedFile.filename}?download=true`,
- { id, storeKey }
- );
- }
- } catch (error) {
- console.log(error);
- } finally {
- setIsDownloading(false);
- }
- }}
- >
- {buttonText}
-
-
- {hasMultipleVersions && (
- {
- setIsDownloadOptionsOpen(!isDownloadOptionsOpen);
- }}
- >
-
-
- )}
-
-
-
-
-
- >
-
- );
-}
diff --git a/src/client/organisms/password-field/index.tsx b/src/client/organisms/password-field/index.tsx
deleted file mode 100644
index 0225d1763..000000000
--- a/src/client/organisms/password-field/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import VisibilityIcon from "@mui/icons-material/Visibility";
-import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
-import FormControl from "@mui/joy/FormControl";
-import FormHelperText from "@mui/joy/FormHelperText";
-import FormLabel from "@mui/joy/FormLabel";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import type { InputProps } from "@mui/joy/Input";
-import { useTranslation } from "next-i18next";
-import type { ReactNode } from "react";
-import { useState } from "react";
-import type { Except } from "type-fest";
-
-export function PasswordField({
- helpText,
- error,
- label,
- ...properties
-}: Except & {
- helpText?: ReactNode;
- label?: ReactNode;
- error?: string;
-}) {
- const [showKey, setShowKey] = useState(false);
- const { t } = useTranslation(["common"]);
- return (
-
- {label && {label}}
- {
- setShowKey(!showKey);
- }}
- >
- {showKey ? : }
-
- }
- />
- {(error || helpText) && {error || helpText}}
-
- );
-}
diff --git a/src/client/organisms/text-field/index.tsx b/src/client/organisms/text-field/index.tsx
deleted file mode 100644
index d44847260..000000000
--- a/src/client/organisms/text-field/index.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import FormControl from "@mui/joy/FormControl";
-import FormHelperText from "@mui/joy/FormHelperText";
-import FormLabel from "@mui/joy/FormLabel";
-import Input from "@mui/joy/Input";
-import type { InputProps } from "@mui/joy/Input";
-import type { ReactNode } from "react";
-
-export function TextField({
- helpText,
- error,
- label,
- ...properties
-}: InputProps & {
- helpText?: ReactNode;
- label?: ReactNode;
- error?: string;
-}) {
- return (
-
- {label && {label}}
-
- {(error || helpText) && {error || helpText}}
-
- );
-}
diff --git a/src/client/organisms/zoomable-image-stage/index.tsx b/src/client/organisms/zoomable-image-stage/index.tsx
deleted file mode 100644
index 8f1954a5a..000000000
--- a/src/client/organisms/zoomable-image-stage/index.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
-import SearchOffIcon from "@mui/icons-material/SearchOff";
-import ZoomInIcon from "@mui/icons-material/ZoomIn";
-import ZoomOutIcon from "@mui/icons-material/ZoomOut";
-import Box from "@mui/joy/Box";
-import ButtonGroup from "@mui/joy/ButtonGroup";
-import IconButton from "@mui/joy/IconButton";
-import Sheet from "@mui/joy/Sheet";
-import { useAtom } from "jotai/index";
-import { useTranslation } from "next-i18next";
-import React from "react";
-import {
- type ReactZoomPanPinchContentRef,
- TransformComponent,
- TransformWrapper,
-} from "react-zoom-pan-pinch";
-
-import { imagesAtom, selectedImageAtom } from "@/ions/atoms";
-
-export function ZoomControls({ zoomIn, zoomOut, resetTransform }: ReactZoomPanPinchContentRef) {
- const { t } = useTranslation(["common"]);
- return (
-
-
- {
- zoomIn();
- }}
- >
-
-
- {
- zoomOut();
- }}
- >
-
-
- {
- resetTransform();
- }}
- >
-
-
-
-
- );
-}
-
-export function ZoomImageStage() {
- const [images] = useAtom(imagesAtom);
- const [selectedImage, setSelectedImage] = useAtom(selectedImageAtom);
- const { t } = useTranslation(["common"]);
- return (
-
- {images[selectedImage] && (
-
-
- {utils => (
-
-
-
- {
- setSelectedImage(
- (images.length + selectedImage - 1) % images.length
- );
- utils.resetTransform();
- }}
- >
-
-
- {
- setSelectedImage((selectedImage + 1) % images.length);
- utils.resetTransform();
- }}
- >
-
-
-
-
-
-
-
- )}
-
-
- )}
-
- );
-}
diff --git a/src/client/pages/[locale]/dataset.tsx b/src/client/pages/[locale]/dataset.tsx
deleted file mode 100644
index c0d92cc07..000000000
--- a/src/client/pages/[locale]/dataset.tsx
+++ /dev/null
@@ -1,548 +0,0 @@
-import ChecklistIcon from "@mui/icons-material/Checklist";
-import DeselectIcon from "@mui/icons-material/Deselect";
-import EditIcon from "@mui/icons-material/Edit";
-import EditNoteIcon from "@mui/icons-material/EditNote";
-import ErrorIcon from "@mui/icons-material/Error";
-import FolderOpenIcon from "@mui/icons-material/FolderOpen";
-import PhotoFilterIcon from "@mui/icons-material/PhotoFilter";
-import SelectAllIcon from "@mui/icons-material/SelectAll";
-import Badge from "@mui/joy/Badge";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import Checkbox from "@mui/joy/Checkbox";
-import CircularProgress from "@mui/joy/CircularProgress";
-import FormControl from "@mui/joy/FormControl";
-import FormLabel from "@mui/joy/FormLabel";
-import Grid from "@mui/joy/Grid";
-import IconButton from "@mui/joy/IconButton";
-import Input from "@mui/joy/Input";
-import Sheet from "@mui/joy/Sheet";
-import Snackbar from "@mui/joy/Snackbar";
-import Stack from "@mui/joy/Stack";
-import Textarea from "@mui/joy/Textarea";
-import Tooltip from "@mui/joy/Tooltip";
-import { useAtom } from "jotai";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useRouter } from "next/router";
-import { useTranslation } from "next-i18next";
-import type { CSSProperties } from "react";
-import React, { useCallback, useEffect, useRef, useState } from "react";
-import AutoSizer from "react-virtualized-auto-sizer";
-import { FixedSizeGrid } from "react-window";
-import useSWR from "swr";
-
-import { CAPTION, CAPTION_RUNNING, DATASET, FOLDER } from "../../../main/helpers/constants";
-
-import { ScreenReaderOnly } from "@/atoms/screen-reader-only";
-import {
- canSelectImagesAtom,
- captioningErrorAtom,
- captionRunningAtom,
- directoryAtom,
- imagesAtom,
- projectAtom,
- selectedImageAtom,
-} from "@/ions/atoms";
-import { useColumns } from "@/ions/hooks/columns";
-import { useKeyboardControlledImagesNavigation } from "@/ions/hooks/keyboard-controlled-images-navigation";
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { CustomScrollbarsVirtualList } from "@/organisms/custom-scrollbars";
-import { CaptionModal, useFilteredImages } from "@/organisms/modals/caption";
-import { BatchEditModal } from "@/organisms/modals/caption/batch-edit";
-import { ZoomImageStage } from "@/organisms/zoomable-image-stage";
-
-export function ImageGridCell({
- columnIndex,
- rowIndex,
- style,
-}: {
- columnIndex: number;
- rowIndex: number;
- style: CSSProperties;
-}) {
- const reference = useRef(null);
- const [images, setImages] = useAtom(imagesAtom);
- const [selectedImage, setSelectedImage] = useAtom(selectedImageAtom);
- const columnCount = useColumns({ xs: 2, sm: 3, md: 4, lg: 6 });
- const { t } = useTranslation(["common"]);
- const [canSelectImages] = useAtom(canSelectImagesAtom);
-
- const index = rowIndex * columnCount + columnIndex;
- const image = images[index];
-
- return (
- image && (
-
- {canSelectImages && image && (
- ({
- position: "absolute",
- top: 0,
- left: 0,
- zIndex: theme.zIndex.badge + 1,
- })}
- >
- {
- setImages(previousState =>
- previousState.map(image_ =>
- image_ === image
- ? {
- ...image_,
- selected: event.target.checked,
- }
- : image_
- )
- );
- }}
- />
-
- )}
-
-
-
-
- )
- );
-}
-
-export function CaptioningError() {
- const [captioningError, setCaptioningError] = useAtom(captioningErrorAtom);
-
- if (!captioningError) {
- return null;
- }
-
- return (
- }
- sx={{
- maxWidth: 600,
- ".MuiSnackbar-startDecorator": { alignSelf: "flex-start" },
- ".MuiSnackbar-endDecorator": { alignSelf: "flex-end" },
- }}
- endDecorator={
-
- }
- onClose={() => {
- setCaptioningError(false);
- }}
- >
- {captioningError}
-
- );
-}
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { query } = useRouter();
- const id = query.id as string | undefined;
- const [images, setImages] = useAtom(imagesAtom);
- const [selectedImage, setSelectedImage] = useAtom(selectedImageAtom);
- const [dataset, setDataset] = useAtom(projectAtom);
- const columnCount = useColumns({ xs: 2, sm: 3, md: 4, lg: 6 });
- const [caption, setCaption] = useState("");
- const [name, setName] = useState("");
- const { t } = useTranslation(["common"]);
- const [batchModalOpen, setBatchModalOpen] = useState(false);
- const [captionModalOpen, setCaptionModalOpen] = useState(false);
- const [progress, setProgress] = useState(0);
- const [progressCount, setProgressCount] = useState("");
- const [, setDirectory] = useAtom(directoryAtom);
- const [captionRunning, setCaptionRunning] = useAtom(captionRunningAtom);
- const [canSelectImages, setCanSelectImages] = useAtom(canSelectImagesAtom);
-
- const { data: captionRunningData } = useSWR(CAPTION_RUNNING);
-
- const { data: datasetData } = useSWR(captionRunningData ? DATASET : undefined, () => {
- if (id) {
- return window.ipc.getDataset(id);
- }
- });
- const filteredImages = useFilteredImages();
- const saveCaptionToFile = useCallback(async () => {
- const image = images[selectedImage];
- if (image) {
- setImages(
- images.map(image_ =>
- image_.image === image.image ? { ...image_, caption } : image_
- )
- );
- await window.ipc.saveCaption({ ...image, caption });
- }
- }, [images, selectedImage, caption, setImages]);
-
- useKeyboardControlledImagesNavigation({ onBeforeChange: saveCaptionToFile });
-
- useEffect(() => {
- console.log({ captionRunningData });
- if (typeof captionRunningData === "boolean") {
- setCaptionRunning(captionRunningData);
- }
- }, [captionRunningData, setCaptionRunning]);
-
- useEffect(() => {
- if (datasetData) {
- setDataset(datasetData.dataset);
- setImages(datasetData.images);
- setName(datasetData.dataset.name);
- setDirectory(datasetData.dataset.files);
- }
- // When datasetData is undefined (initial call) we manually get it from IPC
- // Check if id has is available from query
- else if (id) {
- window.ipc.getDataset(id).then(datasetData_ => {
- setDataset(datasetData_.dataset);
- setImages(datasetData_.images);
- setName(datasetData_.dataset.name);
- setDirectory(datasetData_.dataset.files);
- });
- }
- }, [datasetData, setDataset, setImages, setDirectory, id]);
-
- // TODO: probably needs adjustment to new mechanism
- // Track progress of potential captioning progress
- useEffect(() => {
- const unsubscribe = window.ipc.on(
- `${CAPTION}:updated`,
- ({
- progress: progress_,
- counter,
- totalCount,
- done,
- }: {
- progress: number;
- counter: number;
- totalCount: number;
- done: boolean;
- }) => {
- setProgress(progress_);
- setProgressCount(`${counter}/${totalCount}`);
- if (done) {
- setCaptionRunning(false);
- }
- }
- );
- return () => {
- unsubscribe();
- };
- }, [setCaptionRunning]);
-
- // Set initially selected image to 0
- useEffect(() => {
- setSelectedImage(0);
- }, [setSelectedImage]);
-
- // Set caption when an image changes
- useEffect(() => {
- const image = images[selectedImage];
- if (image) {
- setCaption(image.caption ?? "");
- }
- }, [selectedImage, images]);
-
- return (
- <>
-
- {`Captain | ${t("common:dataset")}`}
-
-
- {
- setCaptionRunning(true);
- setProgress(0);
- setProgressCount(`0/${filteredImages.length}`);
- window.ipc.fetch(CAPTION_RUNNING, { method: "POST", data: true });
- }}
- onDone={async () => {
- console.log("done");
- }}
- onClose={() => {
- setCaptionModalOpen(false);
- }}
- />
- {
- setBatchModalOpen(false);
- }}
- />
-
-
-
- {dataset && (
- }
- onChange={event => {
- setName(event.target.value);
- }}
- onBlur={async event => {
- await window.ipc.updateDataset(dataset.id, {
- name: event.target.value,
- });
- }}
- />
- )}
-
-
- }
- sx={{
- width: { xs: 36, lg: "auto" },
- px: 1.5,
- whiteSpace: "nowrap",
- justifyContent: "flex-start",
- overflow: "hidden",
- }}
- onClick={() => {
- setCanSelectImages(previousState => !previousState);
- }}
- >
- {t("common:selectImages")}
-
-
-
- }
- sx={{
- width: { xs: 36, lg: "auto" },
- px: 1.5,
- whiteSpace: "nowrap",
- justifyContent: "flex-start",
- overflow: "hidden",
- }}
- onClick={() => {
- setBatchModalOpen(true);
- }}
- >
- {t("common:batchEdit")}
-
-
-
- }
- sx={{
- width: { xs: 36, lg: "auto" },
- px: 1.5,
- whiteSpace: "nowrap",
- justifyContent: "flex-start",
- overflow: "hidden",
- }}
- onClick={() => {
- if (dataset) {
- window.ipc.send(`${FOLDER}:open`, dataset.files);
- }
- }}
- >
- {t("common:openFolder")}
-
-
-
-
- ) : (
-
- )
- }
- sx={{
- position: "relative",
- width: { xs: 36, md: "auto" },
- px: 1.5,
- whiteSpace: "nowrap",
- justifyContent: "flex-start",
- overflow: "hidden",
- "&.Mui-disabled": {
- color: captionRunning ? "common.white" : undefined,
- },
- ".MuiButton-startDecorator": { position: "relative", zIndex: 1 },
- }}
- onClick={() => {
- if (!captionRunning) {
- setCaptionModalOpen(true);
- }
- }}
- >
- {captionRunning && (
-
- )}
-
- {captionRunning ? progressCount : t("common:autoCaption")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t("common:caption")}
-
-
- {canSelectImages && (
-
-
- {
- setImages(previousState =>
- previousState.map(image_ => ({
- ...image_,
- selected: true,
- }))
- );
- }}
- >
-
-
-
-
- {
- setImages(previousState =>
- previousState.map(image_ => ({
- ...image_,
- selected: false,
- }))
- );
- }}
- >
-
-
-
-
- )}
-
-
- {({ height, width }) => {
- const columnWidth = width / columnCount;
-
- return (
-
- {ImageGridCell}
-
- );
- }}
-
-
-
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/feedback.tsx b/src/client/pages/[locale]/feedback.tsx
deleted file mode 100644
index 91e89d2d0..000000000
--- a/src/client/pages/[locale]/feedback.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import Container from "@mui/joy/Container";
-import FormControl from "@mui/joy/FormControl";
-import FormLabel from "@mui/joy/FormLabel";
-import Sheet from "@mui/joy/Sheet";
-import Textarea from "@mui/joy/Textarea";
-import Typography from "@mui/joy/Typography";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useTranslation } from "next-i18next";
-import { useForm } from "react-hook-form";
-
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { CustomScrollbars } from "@/organisms/custom-scrollbars";
-import { Lottie } from "@/organisms/lottie";
-
-function FeedbackForm() {
- const {
- register,
- handleSubmit,
- formState: { errors },
- } = useForm({
- defaultValues: { body: "" },
- });
- const { t } = useTranslation(["common"]);
-
- return (
- {
- await window.ipc.sendFeedback(data);
- })}
- >
-
- {t("common:pages.feedback.giveFeedback")}
-
-
-
-
-
-
- );
-}
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { t } = useTranslation(["common"]);
- return (
- <>
-
- {`Captain | ${t("common:feedback")}`}
-
-
-
-
-
- {t("common:feedback")}
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/home.tsx b/src/client/pages/[locale]/home.tsx
deleted file mode 100644
index d1194e4e6..000000000
--- a/src/client/pages/[locale]/home.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-import AddToPhotosIcon from "@mui/icons-material/AddToPhotos";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import Grid from "@mui/joy/Grid";
-import LinearProgress from "@mui/joy/LinearProgress";
-import Sheet from "@mui/joy/Sheet";
-import Tooltip from "@mui/joy/Tooltip";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import Link from "next/link";
-import { useTranslation } from "next-i18next";
-import { useEffect, useState } from "react";
-import useSWR from "swr";
-
-import { APP, INSTALLING_PYTHON } from "../../../main/helpers/constants";
-
-import { StyledImage } from "@/atoms/image/styled";
-import { directoryAtom, imagesAtom, datasetsAtom } from "@/ions/atoms";
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { CustomScrollbars } from "@/organisms/custom-scrollbars";
-import { DeleteConfirm } from "@/organisms/delete-confirm";
-import { FolderDrop } from "@/organisms/folder-drop";
-import { Lottie } from "@/organisms/lottie";
-import { AddDatasetModal } from "@/organisms/modals/add-dataset";
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const [datasets, setDatasets] = useAtom(datasetsAtom);
- const [, setImages] = useAtom(imagesAtom);
- const [, setDirectory] = useAtom(directoryAtom);
- const {
- t,
- i18n: { language: locale },
- } = useTranslation(["common"]);
- const [newDatasetOpen, setNewDatasetOpen] = useState(false);
- const [appReady, setAppReady] = useState(false);
-
- const { data: pythonInstallingData } = useSWR(INSTALLING_PYTHON);
-
- function handleCloseNewDataset() {
- setNewDatasetOpen(false);
- }
-
- function handleOpenNewDataset() {
- setNewDatasetOpen(true);
- }
-
- useEffect(() => {
- window.ipc.getDatasets().then(datasets_ => {
- setDatasets(datasets_);
- });
- }, [setDatasets]);
-
- useEffect(() => {
- setImages([]);
- }, [setImages]);
-
- useEffect(() => {
- if (typeof pythonInstallingData === "boolean") {
- setAppReady(!pythonInstallingData);
- }
- }, [pythonInstallingData]);
-
- useEffect(() => {
- const unsubscribe = window.ipc.on(`${APP}:ready`, () => {
- console.log("APP IS READY");
- setAppReady(true);
- });
- return () => {
- unsubscribe();
- };
- }, []);
-
- return (
- <>
-
- {`Captain | ${t("common:datasets")}`}
-
-
-
-
- {t("common:datasets")}
-
-
- }
- onClick={handleOpenNewDataset}
- >
- {t("common:new")}
-
-
-
-
-
- {
- if (appReady) {
- setDirectory(path);
- handleOpenNewDataset();
- }
- }}
- >
- {datasets.length > 0 && appReady ? (
-
-
- {datasets?.map(dataset => (
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
- ) : (
-
- {appReady ? (
-
-
-
- {t("common:noDatasets")}
-
-
- {t("common:pages.datasets.dropToAdd")}
-
-
- ) : (
-
-
-
-
- {t("common:initialSetup")}
-
-
- {t("common:settingUpApp")}
-
-
- )}
-
- )}
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/inventory.tsx b/src/client/pages/[locale]/inventory.tsx
deleted file mode 100644
index 618e1f003..000000000
--- a/src/client/pages/[locale]/inventory.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import Box from "@mui/joy/Box";
-import Sheet from "@mui/joy/Sheet";
-import Stack from "@mui/joy/Stack";
-import Typography from "@mui/joy/Typography";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useTranslation } from "next-i18next";
-
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { Lottie } from "@/organisms/lottie";
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { t } = useTranslation(["common"]);
- return (
- <>
-
- {`Captain | ${t("common:inventory")}`}
-
-
-
-
-
- {t("common:inventory")}
-
-
-
-
-
-
- {t("common:comingSoon")}
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/livepainting.tsx b/src/client/pages/[locale]/livepainting.tsx
deleted file mode 100644
index 4ba76778a..000000000
--- a/src/client/pages/[locale]/livepainting.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import Grid from "@mui/joy/Grid";
-import Sheet from "@mui/joy/Sheet";
-import Stack from "@mui/joy/Stack";
-import Typography from "@mui/joy/Typography";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useTranslation } from "next-i18next";
-
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { DrawingCanvas } from "@/organisms/live-painting/drawing-canvas";
-import { OutputCanvas } from "@/organisms/live-painting/output-canvas";
-import { UpdateProperties } from "@/organisms/live-painting/update-properties";
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { t } = useTranslation(["common"]);
- return (
- <>
-
- {`Captain | ${t("common:livepainting")}`}
-
-
-
-
-
- {t("common:livepainting")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/marketplace.tsx b/src/client/pages/[locale]/marketplace.tsx
deleted file mode 100644
index d6945725c..000000000
--- a/src/client/pages/[locale]/marketplace.tsx
+++ /dev/null
@@ -1,595 +0,0 @@
-import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
-import ChevronRightIcon from "@mui/icons-material/ChevronRight";
-import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
-import Box from "@mui/joy/Box";
-import Button from "@mui/joy/Button";
-import CircularProgress from "@mui/joy/CircularProgress";
-import Container from "@mui/joy/Container";
-import IconButton from "@mui/joy/IconButton";
-import Sheet from "@mui/joy/Sheet";
-import Typography from "@mui/joy/Typography";
-import { useAtom } from "jotai";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useTranslation } from "next-i18next";
-import { useEffect, useRef, useState } from "react";
-import useSWR from "swr";
-
-export const DEFAULT_MARKETPLACE_URL =
- "https://gist.githubusercontent.com/pixelass/004948226b2ce96b0c6be1ae459f3f42/raw/695bf222fee0c109135ea5b91902046b401175ba/captain-marketplace-test.json";
-
-import {
- CAPTIONS,
- CHECKPOINTS,
- LORAS,
- MARKETPLACE_INDEX,
- MARKETPLACE_INDEX_DATA,
-} from "../../../main/helpers/constants";
-
-import { captionsAtom, checkpointsAtom, lorasAtom } from "@/ions/atoms";
-import { useScrollPosition } from "@/ions/hooks/scroll-position";
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { CustomScrollbars } from "@/organisms/custom-scrollbars";
-import { Lottie } from "@/organisms/lottie";
-import { ModelCard } from "@/organisms/model-card";
-
-interface NestedObjectWithOptionalId {
- [key: string]: NestedObjectWithOptionalId | string | boolean | number | null | undefined;
- id?: string;
-}
-
-// Define a type for objects that definitely have an id property
-interface ObjectWithId {
- id: string;
- [key: string]: any;
-}
-
-interface ModelInformation {
- previews: Preview[];
- info: ModelInfo;
- id: string;
-}
-
-interface Preview {
- type: string;
- content: string;
-}
-
-interface ModelInfo {
- type: string;
- architecture: string;
- title: string;
- author: string;
- link: string;
- license: string;
- files: FileInfo[];
-}
-
-interface FileInfo {
- filename: string;
- variant?: string;
- required?: boolean;
-}
-
-function findAllParentObjectsWithId(object: NestedObjectWithOptionalId, parents: T[] = []): T[] {
- if (object && typeof object === "object" && "id" in object) {
- // Use a type guard to check if the object has a string id
- const objectWithId: ObjectWithId | undefined =
- typeof object.id === "string" ? { ...object, id: object.id } : undefined;
- if (objectWithId) {
- parents.push(objectWithId as T);
- }
- }
-
- if (object && typeof object === "object") {
- for (const key in object) {
- if (Object.hasOwn(object, key)) {
- const child = object[key];
- // Check if child is a non-null object but not an array (as arrays might not have id properties in the same sense)
- if (child && typeof child === "object" && !Array.isArray(child)) {
- findAllParentObjectsWithId(child, parents);
- }
- }
- }
- }
-
- return parents;
-}
-
-export function CheckpointsSection() {
- const { t } = useTranslation(["common"]);
- const [, setCheckpoints] = useAtom(checkpointsAtom);
- // TODO create a type for the models
- const [stableDiffusionModels, setStableDiffusionModels] = useState([]);
- const scrollReference = useRef(null);
- const scrollPosition = useScrollPosition(scrollReference);
-
- const { data: marketPlaceData } = useSWR(MARKETPLACE_INDEX_DATA);
- const { data: checkpointsData } = useSWR(CHECKPOINTS, () =>
- window.ipc.getModels("checkpoints")
- );
-
- useEffect(() => {
- if (marketPlaceData && marketPlaceData["stable-diffusion"]?.checkpoint) {
- setStableDiffusionModels(
- findAllParentObjectsWithId(
- marketPlaceData["stable-diffusion"]?.checkpoint
- )
- );
- }
- }, [marketPlaceData]);
-
- useEffect(() => {
- if (checkpointsData) {
- setCheckpoints(checkpointsData);
- }
- }, [checkpointsData, setCheckpoints]);
-
- return (
-
-
-
- {t("common:pages.settings.stableDiffusionCheckpoints")}
-
-
-
-
- {
- if (scrollReference.current) {
- scrollReference.current.scrollBy({
- left: -scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
- {
- if (scrollReference.current) {
- // Scroll one full width of the scrollRef Element to the right
- scrollReference.current.scrollBy({
- left: scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
-
-
-
- {stableDiffusionModels.map(stableDiffusionModel => (
-
- item.type === "image"
- )?.content
- }
- />
-
- ))}
-
-
-
- );
-}
-
-export function LorasSection() {
- const { t } = useTranslation(["common"]);
- const [, setLoras] = useAtom(lorasAtom);
- // TODO create a type for the models
- const [stableDiffusionModels, setStableDiffusionModels] = useState([]);
- const scrollReference = useRef(null);
- const scrollPosition = useScrollPosition(scrollReference);
-
- const { data: marketPlaceData } = useSWR(MARKETPLACE_INDEX_DATA);
- const { data: loasData } = useSWR(LORAS, () => window.ipc.getModels("loras"));
-
- useEffect(() => {
- if (marketPlaceData && marketPlaceData["stable-diffusion"]?.lora) {
- setStableDiffusionModels(
- findAllParentObjectsWithId(
- marketPlaceData["stable-diffusion"]?.lora
- )
- );
- }
- }, [marketPlaceData]);
-
- useEffect(() => {
- if (loasData) {
- setLoras(loasData);
- }
- }, [loasData, setLoras]);
-
- return (
-
-
-
- {t("common:pages.settings.stableDiffusionLoras")}
-
-
-
-
- {
- if (scrollReference.current) {
- scrollReference.current.scrollBy({
- left: -scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
- {
- if (scrollReference.current) {
- // Scroll one full width of the scrollRef Element to the right
- scrollReference.current.scrollBy({
- left: scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
-
-
-
- {stableDiffusionModels.map(stableDiffusionModel => (
-
- item.type === "image"
- )?.content
- }
- />
-
- ))}
-
-
-
- );
-}
-
-export function CaptionsSection() {
- const { t } = useTranslation(["common"]);
- // TODO create a type for the models
- const [, setInstalledCaptionModels] = useAtom(captionsAtom);
- const [captionModels, setCaptionModels] = useState([]);
- const scrollReference = useRef(null);
- const scrollPosition = useScrollPosition(scrollReference);
-
- const { data: marketPlaceData } = useSWR(MARKETPLACE_INDEX_DATA);
- const { data: checkpointsData } = useSWR(CAPTIONS, () => window.ipc.getModels("captions"));
-
- useEffect(() => {
- if (marketPlaceData?.caption) {
- setCaptionModels(findAllParentObjectsWithId(marketPlaceData.caption));
- }
- }, [marketPlaceData]);
- useEffect(() => {
- if (checkpointsData) {
- setInstalledCaptionModels(checkpointsData);
- }
- }, [checkpointsData, setInstalledCaptionModels]);
-
- return (
-
-
- {t("common:caption")}
-
-
-
- {
- if (scrollReference.current) {
- scrollReference.current.scrollBy({
- left: -scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
- {
- if (scrollReference.current) {
- // Scroll one full width of the scrollRef Element to the right
- scrollReference.current.scrollBy({
- left: scrollReference.current.clientWidth,
- behavior: "smooth",
- });
- }
- }}
- >
-
-
-
-
-
- {captionModels.map(captionModel => (
-
- item.type === "text")
- ?.content
- }
- />
-
- ))}
-
-
-
- );
-}
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { t } = useTranslation(["common"]);
- const [marketplaceDownloading, setMarketplaceDownloading] = useState(false);
-
- useEffect(() => {
- const unsubscribe = window.ipc.on(`${MARKETPLACE_INDEX}:updated`, (_event, data) => {
- setMarketplaceDownloading(false);
- });
- return () => {
- unsubscribe();
- };
- }, []);
-
- return (
- <>
-
- {`Captain | ${t("common:marketplace")}`}
-
-
-
-
- {t("common:marketplace")}
-
-
- :
- }
- onClick={() => {
- setMarketplaceDownloading(true);
- window.ipc.downloadMarketplace(DEFAULT_MARKETPLACE_URL);
- }}
- >
- {t("common:updateMarketplace")}
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
diff --git a/src/client/pages/[locale]/settings.tsx b/src/client/pages/[locale]/settings.tsx
index fdc0c9f36..793ba8e16 100644
--- a/src/client/pages/[locale]/settings.tsx
+++ b/src/client/pages/[locale]/settings.tsx
@@ -12,14 +12,11 @@ import Typography from "@mui/joy/Typography";
import type { InferGetStaticPropsType } from "next";
import Head from "next/head";
import { useTranslation } from "next-i18next";
-import { useState } from "react";
import { makeStaticProperties } from "@/ions/i18n/get-static";
import { ColorModeSelector } from "@/organisms/color-mode-selector";
import { CustomScrollbars } from "@/organisms/custom-scrollbars";
-import { FolderField } from "@/organisms/folder-field";
import { LanguageSelect } from "@/organisms/language-select";
-import { PasswordField } from "@/organisms/password-field";
export function UserPreferences() {
const { t } = useTranslation(["common"]);
@@ -60,158 +57,6 @@ export function UserPreferences() {
);
}
-export function OpenAISettings() {
- const { t } = useTranslation(["common"]);
-
- return (
-
- {t("common:pages.settings.openAiSettings")}
-
-
-
-
- {t("common:openAiApiKey")}
-
- {t("common:pages.settings.openAiApiKeyDescription")}
-
-
-
- {
- setOpenAiApiKey(event.target.value);
- }}
- onBlur={event => {
- window.ipc.fetch(OPENAI_API_KEY, {
- method: "POST",
- data: event.target.value,
- });
- }} */
- />
-
-
-
-
-
- );
-}
-
-export function StableDiffusionSettings() {
- const { t } = useTranslation(["common"]);
- const [sdSettings, setSdSettings] = useState({ checkpoints: "", loras: "" });
-
- return (
-
- {t("common:pages.settings.stableDiffusionSettings")}
-
-
-
-
-
- {t("common:pages.settings.stableDiffusionCheckpoints")}
-
-
- {t("common:pages.settings.stableDiffusionCheckpointsDescription")}
-
-
-
- {
- setSdSettings(previousValue => ({
- ...previousValue,
- checkpoints: event.target.value,
- }));
- }}
- /* //
- onSelect={async value => {
- await window.ipc.fetch(STABLE_DIFFUSION_SETTINGS, {
- method: "PATCH",
- data: { checkpoints: value },
- });
- setSdSettings(previousValue => ({
- ...previousValue,
- checkpoints: value,
- }));
- }}
- onBlur={event => {
- window.ipc.fetch(STABLE_DIFFUSION_SETTINGS, {
- method: "PATCH",
- data: { checkpoints: event.target.value },
- });
- }} */
- />
-
-
-
-
-
- {t("common:pages.settings.stableDiffusionLoras")}
-
-
- {t("common:pages.settings.stableDiffusionLorasDescription")}
-
-
-
- {
- setSdSettings(previousValue => ({
- ...previousValue,
- loras: event.target.value,
- }));
- }}
- /* //
- onSelect={async value => {
- await window.ipc.fetch(STABLE_DIFFUSION_SETTINGS, {
- method: "PATCH",
- data: { loras: value },
- });
- setSdSettings(previousValue => ({
- ...previousValue,
- loras: value,
- }));
- }}
- onBlur={event => {
- window.ipc.fetch(STABLE_DIFFUSION_SETTINGS, {
- method: "PATCH",
- data: { loras: event.target.value },
- });
- }} */
- />
-
-
-
-
-
- );
-}
-
-export function RunPodSettings() {
- const { t } = useTranslation(["common"]);
- return (
-
- {t("common:pages.settings.runPodSettings")}
-
-
-
-
- {t("common:comingSoon")}
-
-
-
-
-
- );
-}
-
export default function Page(_properties: InferGetStaticPropsType) {
const { t } = useTranslation(["common"]);
return (
diff --git a/src/client/pages/[locale]/training.tsx b/src/client/pages/[locale]/training.tsx
deleted file mode 100644
index f1f6e4708..000000000
--- a/src/client/pages/[locale]/training.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import Box from "@mui/joy/Box";
-import Sheet from "@mui/joy/Sheet";
-import Stack from "@mui/joy/Stack";
-import Typography from "@mui/joy/Typography";
-import type { InferGetStaticPropsType } from "next";
-import Head from "next/head";
-import { useTranslation } from "next-i18next";
-
-import { makeStaticProperties } from "@/ions/i18n/get-static";
-import { Lottie } from "@/organisms/lottie";
-
-export default function Page(_properties: InferGetStaticPropsType) {
- const { t } = useTranslation(["common"]);
- return (
- <>
-
- {`Captain | ${t("common:training")}`}
-
-
-
-
-
- {t("common:training")}
-
-
-
-
-
-
- {t("common:comingSoon")}
-
-
-
-
- >
- );
-}
-
-export const getStaticProps = makeStaticProperties(["common"]);
-
-export { getStaticPaths } from "@/ions/i18n/get-static";
From 382b8ce344d2f5ef1ad6dc15020d307a3ef9bbd9 Mon Sep 17 00:00:00 2001
From: Gregor Adams <1148334+pixelass@users.noreply.github.com>
Date: Wed, 21 Feb 2024 15:36:18 +0100
Subject: [PATCH 2/2] chore: set pr title (#50)
## Motivation
## Issues closed
---
.github/workflows/set-pr-title.yml | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 .github/workflows/set-pr-title.yml
diff --git a/.github/workflows/set-pr-title.yml b/.github/workflows/set-pr-title.yml
new file mode 100644
index 000000000..6e9b5b05a
--- /dev/null
+++ b/.github/workflows/set-pr-title.yml
@@ -0,0 +1,30 @@
+name: Set PR Title to First Commit Message
+
+on:
+ pull_request:
+ types: [opened, synchronize]
+
+jobs:
+ set-pr-title:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ # Fetches the entire commit history so that we can access the first commit
+ fetch-depth: 0
+
+ - name: Get first commit message
+ run: |
+ # Get the SHA of the first commit in the PR
+ FIRST_COMMIT_SHA=$(git log --reverse --format="%H" "${{ github.event.pull_request.base.sha }}..HEAD" | head -n 1)
+ # Get the message of the first commit in the PR
+ FIRST_COMMIT_MESSAGE=$(git log -n 1 --format=%B "$FIRST_COMMIT_SHA")
+ echo "FIRST_COMMIT_MESSAGE=$FIRST_COMMIT_MESSAGE" >> $GITHUB_ENV
+
+ - name: Update PR title
+ run: gh pr edit "$PR_NUMBER" --title "$FIRST_COMMIT_MESSAGE"
+ env:
+ FIRST_COMMIT_MESSAGE: ${{ env.FIRST_COMMIT_MESSAGE }}
+ GITHUB_TOKEN: ${{ secrets.PIXELASS_PAT_BLIBLA }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}