Merge branch 'main' into refactor/model-manager-3

This commit is contained in:
Lincoln Stein 2023-11-26 13:33:41 -05:00 committed by GitHub
commit 60eae7443a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 662 additions and 585 deletions

View File

@ -20,10 +20,16 @@ module.exports = {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'], plugins: [
'react',
'@typescript-eslint',
'eslint-plugin-react-hooks',
'i18next',
],
root: true, root: true,
rules: { rules: {
curly: 'error', curly: 'error',
'i18next/no-literal-string': 2,
'react/jsx-no-bind': ['error', { allowBind: true }], 'react/jsx-no-bind': ['error', { allowBind: true }],
'react/jsx-curly-brace-presence': [ 'react/jsx-curly-brace-presence': [
'error', 'error',

View File

@ -1,87 +0,0 @@
# Generated axios API client
- [Generated axios API client](#generated-axios-api-client)
- [Generation](#generation)
- [Generate the API client from the nodes web server](#generate-the-api-client-from-the-nodes-web-server)
- [Generate the API client from JSON](#generate-the-api-client-from-json)
- [Getting the JSON from the nodes web server](#getting-the-json-from-the-nodes-web-server)
- [Getting the JSON with a python script](#getting-the-json-with-a-python-script)
- [Generate the API client](#generate-the-api-client)
- [The generated client](#the-generated-client)
- [API client customisation](#api-client-customisation)
This API client is generated by an [openapi code generator](https://github.com/ferdikoomen/openapi-typescript-codegen).
All files in `invokeai/frontend/web/src/services/api/` are made by the generator.
## Generation
The axios client may be generated by from the OpenAPI schema from the nodes web server, or from JSON.
### Generate the API client from the nodes web server
We need to start the nodes web server, which serves the OpenAPI schema to the generator.
1. Start the nodes web server.
```bash
# from the repo root
python scripts/invokeai-web.py
```
2. Generate the API client.
```bash
# from invokeai/frontend/web/
yarn api:web
```
### Generate the API client from JSON
The JSON can be acquired from the nodes web server, or with a python script.
#### Getting the JSON from the nodes web server
Start the nodes web server as described above, then download the file.
```bash
# from invokeai/frontend/web/
curl http://localhost:9090/openapi.json -o openapi.json
```
#### Getting the JSON with a python script
Run this python script from the repo root, so it can access the nodes server modules.
The script will output `openapi.json` in the repo root. Then we need to move it to `invokeai/frontend/web/`.
```bash
# from the repo root
python invokeai/app/util/generate_openapi_json.py
mv invokeai/app/util/openapi.json invokeai/frontend/web/services/fixtures/
```
#### Generate the API client
Now we can generate the API client from the JSON.
```bash
# from invokeai/frontend/web/
yarn api:file
```
## The generated client
The client will be written to `invokeai/frontend/web/services/api/`:
- `axios` client
- TS types
- An easily parseable schema, which we can use to generate UI
## API client customisation
The generator has a default `request.ts` file that implements a base `axios` client. The generated client uses this base client.
One shortcoming of this is base client is it does not provide response headers unless the response body is empty. To fix this, we provide our own lightly-patched `request.ts`.
To access the headers, call `getHeaders(response)` on any response from the generated api client. This function is exported from `invokeai/frontend/web/src/services/util/getHeaders.ts`.

View File

@ -1,21 +0,0 @@
# Events
Events via `socket.io`
## `actions.ts`
Redux actions for all socket events. Payloads all include a timestamp, and optionally some other data.
Any reducer (or middleware) can respond to the actions.
## `middleware.ts`
Redux middleware for events.
Handles dispatching the event actions. Only put logic here if it can't really go anywhere else.
For example, on connect we want to load images to the gallery if it's not populated. This requires dispatching a thunk, so we need to directly dispatch this in the middleware.
## `types.ts`
Hand-written types for the socket events. Cannot generate these from the server, but fortunately they are few and simple.

View File

@ -1,17 +0,0 @@
# Node Editor Design
WIP
nodes
everything in `src/features/nodes/`
have a look at `state.nodes.invocation`
- on socket connect, if no schema saved, fetch `localhost:9090/openapi.json`, save JSON to `state.nodes.schema`
- on fulfilled schema fetch, `parseSchema()` the schema. this outputs a `Record<string, Invocation>` which is saved to `state.nodes.invocations` - `Invocation` is like a template for the node
- when you add a node, the the `Invocation` template is passed to `InvocationComponent.tsx` to build the UI component for that node
- inputs/outputs have field types - and each field type gets an `FieldComponent` which includes a dispatcher to write state changes to redux `nodesSlice`
- `reactflow` sends changes to nodes/edges to redux
- to invoke, `buildNodesGraph()` state, then send this
- changed onClick Invoke button actions to build the schema, then when schema builds it dispatches the actual network request to create the session - see `session.ts`

View File

@ -1,17 +0,0 @@
# Package Scripts
WIP walkthrough of `package.json` scripts.
## `theme` & `theme:watch`
These run the Chakra CLI to generate types for the theme, or watch for code change and re-generate the types.
The CLI essentially monkeypatches Chakra's files in `node_modules`.
## `postinstall`
The `postinstall` script patches a few packages and runs the Chakra CLI to generate types for the theme.
### Patch `@chakra-ui/cli`
See: <https://github.com/chakra-ui/chakra-ui/issues/7394>

View File

@ -1,33 +1,98 @@
# InvokeAI Web UI # InvokeAI Web UI
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
- [InvokeAI Web UI](#invokeai-web-ui) - [InvokeAI Web UI](#invokeai-web-ui)
- [Stack](#stack) - [Core Libraries](#core-libraries)
- [Redux Toolkit](#redux-toolkit)
- [Socket\.IO](#socketio)
- [Chakra UI](#chakra-ui)
- [KonvaJS](#konvajs)
- [Vite](#vite)
- [i18next & Weblate](#i18next--weblate)
- [openapi-typescript](#openapi-typescript)
- [Client Types Generation](#client-types-generation)
- [Package Scripts](#package-scripts)
- [Contributing](#contributing) - [Contributing](#contributing)
- [Dev Environment](#dev-environment) - [Dev Environment](#dev-environment)
- [VSCode Remote Dev](#vscode-remote-dev)
- [Production builds](#production-builds) - [Production builds](#production-builds)
The UI is a fairly straightforward Typescript React app. The only really fancy stuff is the Unified Canvas. <!-- /code_chunk_output -->
Code in `invokeai/frontend/web/` if you want to have a look. The UI is a fairly straightforward Typescript React app.
## Stack ## Core Libraries
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK: The app makes heavy use of a handful of libraries.
- `createAsyncThunk` for HTTP requests
- `createEntityAdapter` for fetching images and models
- `createListenerMiddleware` for workflows
The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md. ### Redux Toolkit
Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help). [Redux Toolkit](https://github.com/reduxjs/redux-toolkit) is used for state management and fetching/caching:
[Chakra-UI](https://github.com/chakra-ui/chakra-ui) for components and styling. - `RTK-Query` for data fetching and caching
- `createAsyncThunk` for a couple other HTTP requests
- `createEntityAdapter` to normalize things like images and models
- `createListenerMiddleware` for async workflows
[Konva](https://github.com/konvajs/react-konva) for the canvas, but we are pushing the limits of what is feasible with it (and HTML canvas in general). We plan to rebuild it with [PixiJS](https://github.com/pixijs/pixijs) to take advantage of WebGL's improved raster handling. We use [redux-remember](https://github.com/zewish/redux-remember) for persistence.
[Vite](https://vitejs.dev/) for bundling. ### Socket\.IO
Localisation is via [i18next](https://github.com/i18next/react-i18next), but translation happens on our [Weblate](https://hosted.weblate.org/engage/invokeai/) project. Only the English source strings should be changed on this repo. [Socket\.IO](https://github.com/socketio/socket.io) is used for server-to-client events, like generation process and queue state changes.
### Chakra UI
[Chakra UI](https://github.com/chakra-ui/chakra-ui) is our primary UI library, but we also use a few components from [Mantine v6](https://v6.mantine.dev/).
### KonvaJS
[KonvaJS](https://github.com/konvajs/react-konva) powers the canvas. In the future, we'd like to explore [PixiJS](https://github.com/pixijs/pixijs) or WebGPU.
### Vite
[Vite](https://github.com/vitejs/vite) is our bundler.
### i18next & Weblate
We use [i18next](https://github.com/i18next/react-i18next) for localisation, but translation to languages other than English happens on our [Weblate](https://hosted.weblate.org/engage/invokeai/) project. **Only the English source strings should be changed on this repo.**
### openapi-typescript
[openapi-typescript](https://github.com/drwpow/openapi-typescript) is used to generate types from the server's OpenAPI schema. See TYPES_CODEGEN.md.
## Client Types Generation
We use [`openapi-typescript`](https://github.com/drwpow/openapi-typescript) to generate types from the app's OpenAPI schema.
The generated types are written to `invokeai/frontend/web/src/services/api/schema.d.ts`. This file is committed to the repo.
The server must be started and available at <http://127.0.0.1:9090>.
```sh
# from the repo root, start the server
python scripts/invokeai-web.py
# from invokeai/frontend/web/, run the script
yarn typegen
```
## Package Scripts
See `package.json` for all scripts.
Run with `yarn <script name>`.
- `dev`: run the frontend in dev mode, enabling hot reloading
- `build`: run all checks (madge, eslint, prettier, tsc) and then build the frontend
- `typegen`: generate types from the OpenAPI schema (see [Client Types Generation](#client-types-generation))
- `lint:madge`: check frontend for circular dependencies
- `lint:eslint`: check frontend for code quality
- `lint:prettier`: check frontend for code formatting
- `lint:tsc`: check frontend for type issues
- `lint`: run all checks concurrently
- `fix`: run `eslint` and `prettier`, fixing fixable issues
## Contributing ## Contributing

View File

@ -19,7 +19,6 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky",
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"", "dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
"dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"", "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"",
"build": "yarn run lint && vite build", "build": "yarn run lint && vite build",
@ -30,7 +29,7 @@
"lint:prettier": "prettier --check .", "lint:prettier": "prettier --check .",
"lint:tsc": "tsc --noEmit", "lint:tsc": "tsc --noEmit",
"lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"yarn run lint:eslint\" \"yarn run lint:prettier\" \"yarn run lint:tsc\" \"yarn run lint:madge\"", "lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"yarn run lint:eslint\" \"yarn run lint:prettier\" \"yarn run lint:tsc\" \"yarn run lint:madge\"",
"fix": "eslint --fix . && prettier --loglevel warn --write . && tsc --noEmit", "fix": "eslint --fix . && prettier --loglevel warn --write .",
"lint-staged": "lint-staged", "lint-staged": "lint-staged",
"postinstall": "patch-package && yarn run theme", "postinstall": "patch-package && yarn run theme",
"theme": "chakra-cli tokens src/theme/theme.ts", "theme": "chakra-cli tokens src/theme/theme.ts",
@ -80,7 +79,6 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nanostores": "^0.9.4", "nanostores": "^0.9.4",
"new-github-issue-url": "^1.0.0", "new-github-issue-url": "^1.0.0",
"openapi-fetch": "^0.8.1",
"overlayscrollbars": "^2.4.4", "overlayscrollbars": "^2.4.4",
"overlayscrollbars-react": "^0.5.3", "overlayscrollbars-react": "^0.5.3",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
@ -133,6 +131,7 @@
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.53.0", "eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3", "husky": "^8.0.3",

View File

@ -90,7 +90,16 @@
"openInNewTab": "In einem neuem Tab öffnen", "openInNewTab": "In einem neuem Tab öffnen",
"statusProcessing": "wird bearbeitet", "statusProcessing": "wird bearbeitet",
"linear": "Linear", "linear": "Linear",
"imagePrompt": "Bild Prompt" "imagePrompt": "Bild Prompt",
"checkpoint": "Checkpoint",
"inpaint": "inpaint",
"simple": "Einfach",
"template": "Vorlage",
"outputs": "Ausgabe",
"data": "Daten",
"safetensors": "Safetensors",
"outpaint": "outpaint",
"details": "Details"
}, },
"gallery": { "gallery": {
"generations": "Erzeugungen", "generations": "Erzeugungen",
@ -110,7 +119,6 @@
"preparingDownload": "bereite Download vor", "preparingDownload": "bereite Download vor",
"preparingDownloadFailed": "Problem beim Download vorbereiten", "preparingDownloadFailed": "Problem beim Download vorbereiten",
"deleteImage": "Lösche Bild", "deleteImage": "Lösche Bild",
"images": "Bilder",
"copy": "Kopieren", "copy": "Kopieren",
"download": "Runterladen", "download": "Runterladen",
"setCurrentImage": "Setze aktuelle Bild", "setCurrentImage": "Setze aktuelle Bild",
@ -120,7 +128,8 @@
"downloadSelection": "Auswahl herunterladen", "downloadSelection": "Auswahl herunterladen",
"currentlyInUse": "Dieses Bild wird derzeit in den folgenden Funktionen verwendet:", "currentlyInUse": "Dieses Bild wird derzeit in den folgenden Funktionen verwendet:",
"deleteImagePermanent": "Gelöschte Bilder können nicht wiederhergestellt werden.", "deleteImagePermanent": "Gelöschte Bilder können nicht wiederhergestellt werden.",
"autoAssignBoardOnClick": "Board per Klick automatisch zuweisen" "autoAssignBoardOnClick": "Board per Klick automatisch zuweisen",
"noImageSelected": "Kein Bild ausgewählt"
}, },
"hotkeys": { "hotkeys": {
"keyboardShortcuts": "Tastenkürzel", "keyboardShortcuts": "Tastenkürzel",
@ -454,7 +463,7 @@
"quickAdd": "Schnell hinzufügen", "quickAdd": "Schnell hinzufügen",
"simpleModelDesc": "Geben Sie einen Pfad zu einem lokalen Diffusers-Modell, einem lokalen Checkpoint-/Safetensors-Modell, einer HuggingFace-Repo-ID oder einer Checkpoint-/Diffusers-Modell-URL an.", "simpleModelDesc": "Geben Sie einen Pfad zu einem lokalen Diffusers-Modell, einem lokalen Checkpoint-/Safetensors-Modell, einer HuggingFace-Repo-ID oder einer Checkpoint-/Diffusers-Modell-URL an.",
"modelDeleted": "Modell gelöscht", "modelDeleted": "Modell gelöscht",
"inpainting": "v1 Ausmalen", "inpainting": "v1 Inpainting",
"modelUpdateFailed": "Modellaktualisierung fehlgeschlagen", "modelUpdateFailed": "Modellaktualisierung fehlgeschlagen",
"useCustomConfig": "Benutzerdefinierte Konfiguration verwenden", "useCustomConfig": "Benutzerdefinierte Konfiguration verwenden",
"settings": "Einstellungen", "settings": "Einstellungen",
@ -473,7 +482,10 @@
"variant": "Variante", "variant": "Variante",
"loraModels": "LoRAs", "loraModels": "LoRAs",
"modelDeleteFailed": "Modell konnte nicht gelöscht werden", "modelDeleteFailed": "Modell konnte nicht gelöscht werden",
"mergedModelName": "Zusammengeführter Modellname" "mergedModelName": "Zusammengeführter Modellname",
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
"formMessageDiffusersModelLocation": "Diffusers Modell Speicherort",
"noModelSelected": "Kein Modell ausgewählt"
}, },
"parameters": { "parameters": {
"images": "Bilder", "images": "Bilder",
@ -683,7 +695,8 @@
"exitViewer": "Betrachten beenden", "exitViewer": "Betrachten beenden",
"menu": "Menü", "menu": "Menü",
"loadMore": "Mehr laden", "loadMore": "Mehr laden",
"invokeProgressBar": "Invoke Fortschrittsanzeige" "invokeProgressBar": "Invoke Fortschrittsanzeige",
"mode": "Modus"
}, },
"boards": { "boards": {
"autoAddBoard": "Automatisches Hinzufügen zum Ordner", "autoAddBoard": "Automatisches Hinzufügen zum Ordner",
@ -701,7 +714,11 @@
"changeBoard": "Ordner wechseln", "changeBoard": "Ordner wechseln",
"loading": "Laden...", "loading": "Laden...",
"clearSearch": "Suche leeren", "clearSearch": "Suche leeren",
"bottomMessage": "Durch das Löschen dieses Ordners und seiner Bilder werden alle Funktionen zurückgesetzt, die sie derzeit verwenden." "bottomMessage": "Durch das Löschen dieses Ordners und seiner Bilder werden alle Funktionen zurückgesetzt, die sie derzeit verwenden.",
"deleteBoardOnly": "Nur Ordner löschen",
"deleteBoard": "Löschen Ordner",
"deleteBoardAndImages": "Löschen Ordner und Bilder",
"deletedBoardsCannotbeRestored": "Gelöschte Ordner könnte nicht wiederhergestellt werden"
}, },
"controlnet": { "controlnet": {
"showAdvanced": "Zeige Erweitert", "showAdvanced": "Zeige Erweitert",
@ -786,7 +803,8 @@
"canny": "Canny", "canny": "Canny",
"hedDescription": "Ganzheitlich verschachtelte Kantenerkennung", "hedDescription": "Ganzheitlich verschachtelte Kantenerkennung",
"scribble": "Scribble", "scribble": "Scribble",
"maxFaces": "Maximal Anzahl Gesichter" "maxFaces": "Maximal Anzahl Gesichter",
"unstarImage": "Markierung aufheben"
}, },
"queue": { "queue": {
"status": "Status", "status": "Status",
@ -840,7 +858,8 @@
"pauseTooltip": "Pause von Prozessor", "pauseTooltip": "Pause von Prozessor",
"back": "Hinten", "back": "Hinten",
"resumeSucceeded": "Prozessor wieder aufgenommen", "resumeSucceeded": "Prozessor wieder aufgenommen",
"resumeTooltip": "Prozessor wieder aufnehmen" "resumeTooltip": "Prozessor wieder aufnehmen",
"time": "Zeit"
}, },
"metadata": { "metadata": {
"negativePrompt": "Negativ Beschreibung", "negativePrompt": "Negativ Beschreibung",
@ -868,7 +887,8 @@
"vae": "VAE", "vae": "VAE",
"workflow": "Arbeitsablauf", "workflow": "Arbeitsablauf",
"scheduler": "Scheduler", "scheduler": "Scheduler",
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden" "noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden",
"recallParameters": "Recall Parameters"
}, },
"popovers": { "popovers": {
"noiseUseCPU": { "noiseUseCPU": {
@ -944,7 +964,9 @@
"booleanCollection": "Boolesche Werte Sammlung", "booleanCollection": "Boolesche Werte Sammlung",
"cannotConnectToSelf": "Es kann keine Verbindung zu sich selbst hergestellt werden", "cannotConnectToSelf": "Es kann keine Verbindung zu sich selbst hergestellt werden",
"colorCodeEdges": "Farbkodierte Kanten", "colorCodeEdges": "Farbkodierte Kanten",
"addNodeToolTip": "Knoten hinzufügen (Umschalt+A, Leertaste)" "addNodeToolTip": "Knoten hinzufügen (Umschalt+A, Leertaste)",
"boardField": "Ordner",
"boardFieldDescription": "Ein Galerie Ordner"
}, },
"hrf": { "hrf": {
"enableHrf": "Aktivieren Sie die Korrektur für hohe Auflösungen", "enableHrf": "Aktivieren Sie die Korrektur für hohe Auflösungen",
@ -968,6 +990,8 @@
"selectModel": "Wählen ein Modell aus", "selectModel": "Wählen ein Modell aus",
"noRefinerModelsInstalled": "Keine SDXL Refiner-Modelle installiert", "noRefinerModelsInstalled": "Keine SDXL Refiner-Modelle installiert",
"noLoRAsInstalled": "Keine LoRAs installiert", "noLoRAsInstalled": "Keine LoRAs installiert",
"selectLoRA": "Wählen ein LoRA aus" "selectLoRA": "Wählen ein LoRA aus",
"esrganModel": "ESRGAN Modell",
"addLora": "LoRA hinzufügen"
} }
} }

View File

@ -1,6 +1,7 @@
{ {
"accessibility": { "accessibility": {
"copyMetadataJson": "Copy metadata JSON", "copyMetadataJson": "Copy metadata JSON",
"createIssue":"Create Issue",
"exitViewer": "Exit Viewer", "exitViewer": "Exit Viewer",
"flipHorizontally": "Flip Horizontally", "flipHorizontally": "Flip Horizontally",
"flipVertically": "Flip Vertically", "flipVertically": "Flip Vertically",
@ -12,6 +13,7 @@
"nextImage": "Next Image", "nextImage": "Next Image",
"previousImage": "Previous Image", "previousImage": "Previous Image",
"reset": "Reset", "reset": "Reset",
"resetUI":"$t(accessibility.reset) UI",
"rotateClockwise": "Rotate Clockwise", "rotateClockwise": "Rotate Clockwise",
"rotateCounterClockwise": "Rotate Counter-Clockwise", "rotateCounterClockwise": "Rotate Counter-Clockwise",
"showGalleryPanel": "Show Gallery Panel", "showGalleryPanel": "Show Gallery Panel",
@ -38,6 +40,8 @@
"loading": "Loading...", "loading": "Loading...",
"menuItemAutoAdd": "Auto-add to this Board", "menuItemAutoAdd": "Auto-add to this Board",
"move": "Move", "move": "Move",
"movingImagesToBoard_one": "Moving {{count}} image to board:",
"movingImagesToBoard_other": "Moving {{count}} images to board:",
"myBoard": "My Board", "myBoard": "My Board",
"noMatching": "No matching Boards", "noMatching": "No matching Boards",
"searchBoard": "Search Boards...", "searchBoard": "Search Boards...",
@ -49,11 +53,13 @@
"common": { "common": {
"accept": "Accept", "accept": "Accept",
"advanced": "Advanced", "advanced": "Advanced",
"ai": "ai",
"areYouSure": "Are you sure?", "areYouSure": "Are you sure?",
"auto": "Auto", "auto": "Auto",
"back": "Back", "back": "Back",
"batch": "Batch Manager", "batch": "Batch Manager",
"cancel": "Cancel", "cancel": "Cancel",
"copyError":"$t(gallery.copy) Error",
"close": "Close", "close": "Close",
"on": "On", "on": "On",
"checkpoint": "Checkpoint", "checkpoint": "Checkpoint",
@ -67,6 +73,10 @@
"darkMode": "Dark Mode", "darkMode": "Dark Mode",
"discordLabel": "Discord", "discordLabel": "Discord",
"dontAskMeAgain": "Don't ask me again", "dontAskMeAgain": "Don't ask me again",
"error": "Error",
"file": "File",
"folder": "Folder",
"format":"format",
"generate": "Generate", "generate": "Generate",
"githubLabel": "Github", "githubLabel": "Github",
"hotkeysLabel": "Hotkeys", "hotkeysLabel": "Hotkeys",
@ -74,6 +84,8 @@
"imageFailedToLoad": "Unable to Load Image", "imageFailedToLoad": "Unable to Load Image",
"img2img": "Image To Image", "img2img": "Image To Image",
"inpaint": "inpaint", "inpaint": "inpaint",
"input": "Input",
"installed": "Installed",
"langArabic": "العربية", "langArabic": "العربية",
"langBrPortuguese": "Português do Brasil", "langBrPortuguese": "Português do Brasil",
"langDutch": "Nederlands", "langDutch": "Nederlands",
@ -101,6 +113,7 @@
"nodeEditor": "Node Editor", "nodeEditor": "Node Editor",
"nodes": "Workflow Editor", "nodes": "Workflow Editor",
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.", "nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
"notInstalled": "Not $t(common.installed)",
"openInNewTab": "Open in New Tab", "openInNewTab": "Open in New Tab",
"outpaint": "outpaint", "outpaint": "outpaint",
"outputs": "Outputs", "outputs": "Outputs",
@ -114,6 +127,7 @@
"safetensors": "Safetensors", "safetensors": "Safetensors",
"settingsLabel": "Settings", "settingsLabel": "Settings",
"simple": "Simple", "simple": "Simple",
"somethingWentWrong": "Something went wrong",
"statusConnected": "Connected", "statusConnected": "Connected",
"statusConvertingModel": "Converting Model", "statusConvertingModel": "Converting Model",
"statusDisconnected": "Disconnected", "statusDisconnected": "Disconnected",
@ -252,6 +266,7 @@
"embedding": { "embedding": {
"addEmbedding": "Add Embedding", "addEmbedding": "Add Embedding",
"incompatibleModel": "Incompatible base model:", "incompatibleModel": "Incompatible base model:",
"noEmbeddingsLoaded": "No Embeddings Loaded",
"noMatchingEmbedding": "No matching Embeddings" "noMatchingEmbedding": "No matching Embeddings"
}, },
"queue": { "queue": {
@ -330,7 +345,8 @@
"enableFailed": "Problem Enabling Invocation Cache", "enableFailed": "Problem Enabling Invocation Cache",
"disable": "Disable", "disable": "Disable",
"disableSucceeded": "Invocation Cache Disabled", "disableSucceeded": "Invocation Cache Disabled",
"disableFailed": "Problem Disabling Invocation Cache" "disableFailed": "Problem Disabling Invocation Cache",
"useCache": "Use Cache"
}, },
"gallery": { "gallery": {
"allImagesLoaded": "All Images Loaded", "allImagesLoaded": "All Images Loaded",
@ -339,6 +355,9 @@
"autoSwitchNewImages": "Auto-Switch to New Images", "autoSwitchNewImages": "Auto-Switch to New Images",
"copy": "Copy", "copy": "Copy",
"currentlyInUse": "This image is currently in use in the following features:", "currentlyInUse": "This image is currently in use in the following features:",
"drop":"Drop",
"dropOrUpload":"$t(gallery.drop) or Upload",
"dropToUpload":"$t(gallery.drop) to Upload",
"deleteImage": "Delete Image", "deleteImage": "Delete Image",
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
"deleteImagePermanent": "Deleted images cannot be restored.", "deleteImagePermanent": "Deleted images cannot be restored.",
@ -348,7 +367,7 @@
"galleryImageSize": "Image Size", "galleryImageSize": "Image Size",
"gallerySettings": "Gallery Settings", "gallerySettings": "Gallery Settings",
"generations": "Generations", "generations": "Generations",
"images": "Images", "image": "image",
"loading": "Loading", "loading": "Loading",
"loadMore": "Load More", "loadMore": "Load More",
"maintainAspectRatio": "Maintain Aspect Ratio", "maintainAspectRatio": "Maintain Aspect Ratio",
@ -360,6 +379,7 @@
"singleColumnLayout": "Single Column Layout", "singleColumnLayout": "Single Column Layout",
"unableToLoad": "Unable to load Gallery", "unableToLoad": "Unable to load Gallery",
"uploads": "Uploads", "uploads": "Uploads",
"deleteSelection": "Delete Selection",
"downloadSelection": "Download Selection", "downloadSelection": "Download Selection",
"preparingDownload": "Preparing Download", "preparingDownload": "Preparing Download",
"preparingDownloadFailed": "Problem Preparing Download" "preparingDownloadFailed": "Problem Preparing Download"
@ -629,6 +649,7 @@
"closeAdvanced": "Close Advanced", "closeAdvanced": "Close Advanced",
"config": "Config", "config": "Config",
"configValidationMsg": "Path to the config file of your model.", "configValidationMsg": "Path to the config file of your model.",
"conversionNotSupported": "Conversion Not Supported",
"convert": "Convert", "convert": "Convert",
"convertingModelBegin": "Converting Model. Please wait.", "convertingModelBegin": "Converting Model. Please wait.",
"convertToDiffusers": "Convert To Diffusers", "convertToDiffusers": "Convert To Diffusers",
@ -754,6 +775,7 @@
"esrganModel": "ESRGAN Model", "esrganModel": "ESRGAN Model",
"loading": "loading", "loading": "loading",
"noLoRAsAvailable": "No LoRAs available", "noLoRAsAvailable": "No LoRAs available",
"noLoRAsLoaded":"No LoRAs Loaded",
"noMatchingLoRAs": "No matching LoRAs", "noMatchingLoRAs": "No matching LoRAs",
"noMatchingModels": "No matching Models", "noMatchingModels": "No matching Models",
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
@ -765,6 +787,7 @@
"nodes": { "nodes": {
"addNode": "Add Node", "addNode": "Add Node",
"addNodeToolTip": "Add Node (Shift+A, Space)", "addNodeToolTip": "Add Node (Shift+A, Space)",
"addLinearView":"Add to Linear View",
"animatedEdges": "Animated Edges", "animatedEdges": "Animated Edges",
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes", "animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
"boardField": "Board", "boardField": "Board",
@ -885,6 +908,7 @@
"noMatchingNodes": "No matching nodes", "noMatchingNodes": "No matching nodes",
"noNodeSelected": "No node selected", "noNodeSelected": "No node selected",
"nodeOpacity": "Node Opacity", "nodeOpacity": "Node Opacity",
"nodeVersion": "Node Version",
"noOutputRecorded": "No outputs recorded", "noOutputRecorded": "No outputs recorded",
"noOutputSchemaName": "No output schema name found in ref object", "noOutputSchemaName": "No output schema name found in ref object",
"notes": "Notes", "notes": "Notes",
@ -892,6 +916,7 @@
"oNNXModelField": "ONNX Model", "oNNXModelField": "ONNX Model",
"oNNXModelFieldDescription": "ONNX model field.", "oNNXModelFieldDescription": "ONNX model field.",
"outputField": "Output Field", "outputField": "Output Field",
"outputFieldInInput": "Output field in input",
"outputFields": "Output Fields", "outputFields": "Output Fields",
"outputNode": "Output node", "outputNode": "Output node",
"outputSchemaNotFound": "Output schema not found", "outputSchemaNotFound": "Output schema not found",
@ -937,10 +962,14 @@
"uNetFieldDescription": "UNet submodel.", "uNetFieldDescription": "UNet submodel.",
"unhandledInputProperty": "Unhandled input property", "unhandledInputProperty": "Unhandled input property",
"unhandledOutputProperty": "Unhandled output property", "unhandledOutputProperty": "Unhandled output property",
"unknownField": "Unknown Field", "unknownField": "Unknown field",
"unknownFieldType": "$(nodes.unknownField) type",
"unknownNode": "Unknown Node", "unknownNode": "Unknown Node",
"unknownNodeType":"$t(nodes.unknownNode) type",
"unknownTemplate": "Unknown Template", "unknownTemplate": "Unknown Template",
"unknownInput": "Unknown input",
"unkownInvocation": "Unknown Invocation type", "unkownInvocation": "Unknown Invocation type",
"unknownOutput": "Unknown output",
"updateNode": "Update Node", "updateNode": "Update Node",
"updateAllNodes": "Update All Nodes", "updateAllNodes": "Update All Nodes",
"updateApp": "Update App", "updateApp": "Update App",
@ -1082,6 +1111,7 @@
"upscaling": "Upscaling", "upscaling": "Upscaling",
"unmasked": "Unmasked", "unmasked": "Unmasked",
"useAll": "Use All", "useAll": "Use All",
"useSize": "Use Size",
"useCpuNoise": "Use CPU Noise", "useCpuNoise": "Use CPU Noise",
"cpuNoise": "CPU Noise", "cpuNoise": "CPU Noise",
"gpuNoise": "GPU Noise", "gpuNoise": "GPU Noise",
@ -1171,7 +1201,8 @@
"clearIntermediatesWithCount_other": "Clear {{count}} Intermediates", "clearIntermediatesWithCount_other": "Clear {{count}} Intermediates",
"intermediatesCleared_one": "Cleared {{count}} Intermediate", "intermediatesCleared_one": "Cleared {{count}} Intermediate",
"intermediatesCleared_other": "Cleared {{count}} Intermediates", "intermediatesCleared_other": "Cleared {{count}} Intermediates",
"intermediatesClearedFailed": "Problem Clearing Intermediates" "intermediatesClearedFailed": "Problem Clearing Intermediates",
"reloadingIn": "Reloading in"
}, },
"toast": { "toast": {
"addedToBoard": "Added to board", "addedToBoard": "Added to board",
@ -1199,6 +1230,7 @@
"initialImageNotSet": "Initial Image Not Set", "initialImageNotSet": "Initial Image Not Set",
"initialImageNotSetDesc": "Could not load initial image", "initialImageNotSetDesc": "Could not load initial image",
"initialImageSet": "Initial Image Set", "initialImageSet": "Initial Image Set",
"invalidUpload": "Invalid Upload",
"loadedWithWarnings": "Workflow Loaded with Warnings", "loadedWithWarnings": "Workflow Loaded with Warnings",
"maskSavedAssets": "Mask Saved to Assets", "maskSavedAssets": "Mask Saved to Assets",
"maskSentControlnetAssets": "Mask Sent to ControlNet & Assets", "maskSentControlnetAssets": "Mask Sent to ControlNet & Assets",
@ -1517,7 +1549,7 @@
"clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?", "clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?",
"clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.", "clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.",
"clearHistory": "Clear History", "clearHistory": "Clear History",
"clearMask": "Clear Mask", "clearMask": "Clear Mask (Shift+C)",
"colorPicker": "Color Picker", "colorPicker": "Color Picker",
"copyToClipboard": "Copy to Clipboard", "copyToClipboard": "Copy to Clipboard",
"cursorPosition": "Cursor Position", "cursorPosition": "Cursor Position",
@ -1544,6 +1576,7 @@
"redo": "Redo", "redo": "Redo",
"resetView": "Reset View", "resetView": "Reset View",
"saveBoxRegionOnly": "Save Box Region Only", "saveBoxRegionOnly": "Save Box Region Only",
"saveMask":"Save $t(unifiedCanvas.mask)",
"saveToGallery": "Save To Gallery", "saveToGallery": "Save To Gallery",
"scaledBoundingBox": "Scaled Bounding Box", "scaledBoundingBox": "Scaled Bounding Box",
"showCanvasDebugInfo": "Show Additional Canvas Info", "showCanvasDebugInfo": "Show Additional Canvas Info",

View File

@ -98,7 +98,6 @@
"deleteImage": "Eliminar Imagen", "deleteImage": "Eliminar Imagen",
"deleteImageBin": "Las imágenes eliminadas se enviarán a la papelera de tu sistema operativo.", "deleteImageBin": "Las imágenes eliminadas se enviarán a la papelera de tu sistema operativo.",
"deleteImagePermanent": "Las imágenes eliminadas no se pueden restaurar.", "deleteImagePermanent": "Las imágenes eliminadas no se pueden restaurar.",
"images": "Imágenes",
"assets": "Activos", "assets": "Activos",
"autoAssignBoardOnClick": "Asignación automática de tableros al hacer clic" "autoAssignBoardOnClick": "Asignación automática de tableros al hacer clic"
}, },

View File

@ -89,7 +89,9 @@
"t2iAdapter": "Adattatore T2I", "t2iAdapter": "Adattatore T2I",
"controlAdapter": "Adattatore di Controllo", "controlAdapter": "Adattatore di Controllo",
"controlNet": "ControlNet", "controlNet": "ControlNet",
"auto": "Automatico" "auto": "Automatico",
"simple": "Semplice",
"details": "Dettagli"
}, },
"gallery": { "gallery": {
"generations": "Generazioni", "generations": "Generazioni",
@ -108,7 +110,6 @@
"deleteImage": "Elimina l'immagine", "deleteImage": "Elimina l'immagine",
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.", "deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
"deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.", "deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.",
"images": "Immagini",
"assets": "Risorse", "assets": "Risorse",
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic", "autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.", "featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
@ -120,7 +121,8 @@
"setCurrentImage": "Imposta come immagine corrente", "setCurrentImage": "Imposta come immagine corrente",
"preparingDownload": "Preparazione del download", "preparingDownload": "Preparazione del download",
"preparingDownloadFailed": "Problema durante la preparazione del download", "preparingDownloadFailed": "Problema durante la preparazione del download",
"downloadSelection": "Scarica gli elementi selezionati" "downloadSelection": "Scarica gli elementi selezionati",
"noImageSelected": "Nessuna immagine selezionata"
}, },
"hotkeys": { "hotkeys": {
"keyboardShortcuts": "Tasti rapidi", "keyboardShortcuts": "Tasti rapidi",
@ -474,7 +476,8 @@
"closeAdvanced": "Chiudi Avanzate", "closeAdvanced": "Chiudi Avanzate",
"modelType": "Tipo di modello", "modelType": "Tipo di modello",
"customConfigFileLocation": "Posizione del file di configurazione personalizzato", "customConfigFileLocation": "Posizione del file di configurazione personalizzato",
"vaePrecision": "Precisione VAE" "vaePrecision": "Precisione VAE",
"noModelSelected": "Nessun modello selezionato"
}, },
"parameters": { "parameters": {
"images": "Immagini", "images": "Immagini",
@ -601,7 +604,9 @@
"seamlessX": "Senza cuciture X", "seamlessX": "Senza cuciture X",
"seamlessY": "Senza cuciture Y", "seamlessY": "Senza cuciture Y",
"imageActions": "Azioni Immagine", "imageActions": "Azioni Immagine",
"aspectRatioFree": "Libere" "aspectRatioFree": "Libere",
"maskEdge": "Maschera i bordi",
"unmasked": "No maschera"
}, },
"settings": { "settings": {
"models": "Modelli", "models": "Modelli",
@ -642,7 +647,10 @@
"clearIntermediatesWithCount_one": "Cancella {{count}} immagine intermedia", "clearIntermediatesWithCount_one": "Cancella {{count}} immagine intermedia",
"clearIntermediatesWithCount_many": "Cancella {{count}} immagini intermedie", "clearIntermediatesWithCount_many": "Cancella {{count}} immagini intermedie",
"clearIntermediatesWithCount_other": "Cancella {{count}} immagini intermedie", "clearIntermediatesWithCount_other": "Cancella {{count}} immagini intermedie",
"clearIntermediatesDisabled": "La coda deve essere vuota per cancellare le immagini intermedie" "clearIntermediatesDisabled": "La coda deve essere vuota per cancellare le immagini intermedie",
"enableNSFWChecker": "Abilita controllo NSFW",
"enableInvisibleWatermark": "Abilita filigrana invisibile",
"enableInformationalPopovers": "Abilita testo informativo a comparsa"
}, },
"toast": { "toast": {
"tempFoldersEmptied": "Cartella temporanea svuotata", "tempFoldersEmptied": "Cartella temporanea svuotata",
@ -727,7 +735,8 @@
"setCanvasInitialImage": "Imposta come immagine iniziale della tela", "setCanvasInitialImage": "Imposta come immagine iniziale della tela",
"workflowLoaded": "Flusso di lavoro caricato", "workflowLoaded": "Flusso di lavoro caricato",
"setIPAdapterImage": "Imposta come immagine per l'Adattatore IP", "setIPAdapterImage": "Imposta come immagine per l'Adattatore IP",
"problemSavingMaskDesc": "Impossibile salvare la maschera" "problemSavingMaskDesc": "Impossibile salvare la maschera",
"setAsCanvasInitialImage": "Imposta come immagine iniziale della tela"
}, },
"tooltip": { "tooltip": {
"feature": { "feature": {
@ -828,7 +837,8 @@
"modifyConfig": "Modifica configurazione", "modifyConfig": "Modifica configurazione",
"menu": "Menu", "menu": "Menu",
"showGalleryPanel": "Mostra il pannello Galleria", "showGalleryPanel": "Mostra il pannello Galleria",
"loadMore": "Carica altro" "loadMore": "Carica altro",
"mode": "Modalità"
}, },
"ui": { "ui": {
"hideProgressImages": "Nascondi avanzamento immagini", "hideProgressImages": "Nascondi avanzamento immagini",
@ -1048,7 +1058,11 @@
"noMatching": "Nessuna bacheca corrispondente", "noMatching": "Nessuna bacheca corrispondente",
"selectBoard": "Seleziona una Bacheca", "selectBoard": "Seleziona una Bacheca",
"uncategorized": "Non categorizzato", "uncategorized": "Non categorizzato",
"downloadBoard": "Scarica la bacheca" "downloadBoard": "Scarica la bacheca",
"deleteBoardOnly": "Elimina solo la Bacheca",
"deleteBoard": "Elimina Bacheca",
"deleteBoardAndImages": "Elimina Bacheca e Immagini",
"deletedBoardsCannotbeRestored": "Le bacheche eliminate non possono essere ripristinate"
}, },
"controlnet": { "controlnet": {
"contentShuffleDescription": "Rimescola il contenuto di un'immagine", "contentShuffleDescription": "Rimescola il contenuto di un'immagine",
@ -1089,7 +1103,7 @@
"none": "Nessuno", "none": "Nessuno",
"incompatibleBaseModel": "Modello base incompatibile:", "incompatibleBaseModel": "Modello base incompatibile:",
"pidiDescription": "Elaborazione immagini PIDI", "pidiDescription": "Elaborazione immagini PIDI",
"fill": "Riempire", "fill": "Riempie",
"colorMapDescription": "Genera una mappa dei colori dall'immagine", "colorMapDescription": "Genera una mappa dei colori dall'immagine",
"lineartAnimeDescription": "Elaborazione lineart in stile anime", "lineartAnimeDescription": "Elaborazione lineart in stile anime",
"imageResolution": "Risoluzione dell'immagine", "imageResolution": "Risoluzione dell'immagine",
@ -1183,7 +1197,9 @@
"clearQueueAlertDialog2": "Sei sicuro di voler cancellare la coda?", "clearQueueAlertDialog2": "Sei sicuro di voler cancellare la coda?",
"item": "Elemento", "item": "Elemento",
"graphFailedToQueue": "Impossibile mettere in coda il grafico", "graphFailedToQueue": "Impossibile mettere in coda il grafico",
"queueMaxExceeded": "È stato superato il limite massimo di {{max_queue_size}} e {{skip}} elementi verrebbero saltati" "queueMaxExceeded": "È stato superato il limite massimo di {{max_queue_size}} e {{skip}} elementi verrebbero saltati",
"batchFieldValues": "Valori Campi Lotto",
"time": "Tempo"
}, },
"embedding": { "embedding": {
"noMatchingEmbedding": "Nessun Incorporamento corrispondente", "noMatchingEmbedding": "Nessun Incorporamento corrispondente",
@ -1199,7 +1215,9 @@
"selectModel": "Seleziona un modello", "selectModel": "Seleziona un modello",
"selectLoRA": "Seleziona un LoRA", "selectLoRA": "Seleziona un LoRA",
"noRefinerModelsInstalled": "Nessun modello SDXL Refiner installato", "noRefinerModelsInstalled": "Nessun modello SDXL Refiner installato",
"noLoRAsInstalled": "Nessun LoRA installato" "noLoRAsInstalled": "Nessun LoRA installato",
"esrganModel": "Modello ESRGAN",
"addLora": "Aggiungi LoRA"
}, },
"invocationCache": { "invocationCache": {
"disable": "Disabilita", "disable": "Disabilita",
@ -1231,7 +1249,8 @@
"promptsWithCount_one": "{{count}} Prompt", "promptsWithCount_one": "{{count}} Prompt",
"promptsWithCount_many": "{{count}} Prompt", "promptsWithCount_many": "{{count}} Prompt",
"promptsWithCount_other": "{{count}} Prompt", "promptsWithCount_other": "{{count}} Prompt",
"dynamicPrompts": "Prompt dinamici" "dynamicPrompts": "Prompt dinamici",
"promptsPreview": "Anteprima dei prompt"
}, },
"popovers": { "popovers": {
"paramScheduler": { "paramScheduler": {

View File

@ -660,7 +660,7 @@
"queueTotal": "合計 {{total}}", "queueTotal": "合計 {{total}}",
"resumeSucceeded": "処理が再開されました", "resumeSucceeded": "処理が再開されました",
"resumeTooltip": "処理を再開", "resumeTooltip": "処理を再開",
"resume": "再", "resume": "再",
"status": "ステータス", "status": "ステータス",
"pruneSucceeded": "キューから完了アイテム{{item_count}}件を削除しました", "pruneSucceeded": "キューから完了アイテム{{item_count}}件を削除しました",
"cancelTooltip": "現在のアイテムをキャンセル", "cancelTooltip": "現在のアイテムをキャンセル",

View File

@ -110,7 +110,6 @@
"deleteImageBin": "Verwijderde afbeeldingen worden naar de prullenbak van je besturingssysteem gestuurd.", "deleteImageBin": "Verwijderde afbeeldingen worden naar de prullenbak van je besturingssysteem gestuurd.",
"deleteImagePermanent": "Verwijderde afbeeldingen kunnen niet worden hersteld.", "deleteImagePermanent": "Verwijderde afbeeldingen kunnen niet worden hersteld.",
"assets": "Eigen onderdelen", "assets": "Eigen onderdelen",
"images": "Afbeeldingen",
"autoAssignBoardOnClick": "Ken automatisch bord toe bij klikken", "autoAssignBoardOnClick": "Ken automatisch bord toe bij klikken",
"featuresWillReset": "Als je deze afbeelding verwijdert, dan worden deze functies onmiddellijk teruggezet.", "featuresWillReset": "Als je deze afbeelding verwijdert, dan worden deze functies onmiddellijk teruggezet.",
"loading": "Bezig met laden", "loading": "Bezig met laden",

View File

@ -101,7 +101,6 @@
"deleteImagePermanent": "Удаленные изображения невозможно восстановить.", "deleteImagePermanent": "Удаленные изображения невозможно восстановить.",
"deleteImageBin": "Удаленные изображения будут отправлены в корзину вашей операционной системы.", "deleteImageBin": "Удаленные изображения будут отправлены в корзину вашей операционной системы.",
"deleteImage": "Удалить изображение", "deleteImage": "Удалить изображение",
"images": "Изображения",
"assets": "Ресурсы", "assets": "Ресурсы",
"autoAssignBoardOnClick": "Авто-назначение доски по клику" "autoAssignBoardOnClick": "Авто-назначение доски по клику"
}, },

View File

@ -90,7 +90,16 @@
"controlAdapter": "Control Adapter", "controlAdapter": "Control Adapter",
"controlNet": "ControlNet", "controlNet": "ControlNet",
"on": "开", "on": "开",
"auto": "自动" "auto": "自动",
"checkpoint": "Checkpoint",
"inpaint": "内补重绘",
"simple": "简单",
"template": "模板",
"outputs": "输出",
"data": "数据",
"safetensors": "Safetensors",
"outpaint": "外扩绘制",
"details": "详情"
}, },
"gallery": { "gallery": {
"generations": "生成的图像", "generations": "生成的图像",
@ -109,7 +118,6 @@
"deleteImage": "删除图片", "deleteImage": "删除图片",
"deleteImageBin": "被删除的图片会发送到你操作系统的回收站。", "deleteImageBin": "被删除的图片会发送到你操作系统的回收站。",
"deleteImagePermanent": "删除的图片无法被恢复。", "deleteImagePermanent": "删除的图片无法被恢复。",
"images": "图片",
"assets": "素材", "assets": "素材",
"autoAssignBoardOnClick": "点击后自动分配面板", "autoAssignBoardOnClick": "点击后自动分配面板",
"featuresWillReset": "如果您删除该图像,这些功能会立即被重置。", "featuresWillReset": "如果您删除该图像,这些功能会立即被重置。",
@ -121,7 +129,8 @@
"setCurrentImage": "设为当前图像", "setCurrentImage": "设为当前图像",
"preparingDownload": "准备下载", "preparingDownload": "准备下载",
"preparingDownloadFailed": "准备下载时出现问题", "preparingDownloadFailed": "准备下载时出现问题",
"downloadSelection": "下载所选内容" "downloadSelection": "下载所选内容",
"noImageSelected": "无选中的图像"
}, },
"hotkeys": { "hotkeys": {
"keyboardShortcuts": "键盘快捷键", "keyboardShortcuts": "键盘快捷键",
@ -475,7 +484,9 @@
"oliveModels": "Olive", "oliveModels": "Olive",
"loraModels": "LoRA", "loraModels": "LoRA",
"alpha": "Alpha", "alpha": "Alpha",
"vaePrecision": "VAE 精度" "vaePrecision": "VAE 精度",
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
"noModelSelected": "无选中的模型"
}, },
"parameters": { "parameters": {
"images": "图像", "images": "图像",
@ -602,7 +613,9 @@
"seamlessX&Y": "无缝 X & Y", "seamlessX&Y": "无缝 X & Y",
"aspectRatioFree": "自由", "aspectRatioFree": "自由",
"seamlessX": "无缝 X", "seamlessX": "无缝 X",
"seamlessY": "无缝 Y" "seamlessY": "无缝 Y",
"maskEdge": "遮罩边缘",
"unmasked": "取消遮罩"
}, },
"settings": { "settings": {
"models": "模型", "models": "模型",
@ -639,7 +652,10 @@
"clearIntermediatesDesc1": "清除中间产物会重置您的画布和 ControlNet 状态。", "clearIntermediatesDesc1": "清除中间产物会重置您的画布和 ControlNet 状态。",
"intermediatesClearedFailed": "清除中间产物时出现问题", "intermediatesClearedFailed": "清除中间产物时出现问题",
"clearIntermediatesWithCount_other": "清除 {{count}} 个中间产物", "clearIntermediatesWithCount_other": "清除 {{count}} 个中间产物",
"clearIntermediatesDisabled": "队列为空才能清理中间产物" "clearIntermediatesDisabled": "队列为空才能清理中间产物",
"enableNSFWChecker": "启用成人内容检测器",
"enableInvisibleWatermark": "启用不可见水印",
"enableInformationalPopovers": "启用信息弹窗"
}, },
"toast": { "toast": {
"tempFoldersEmptied": "临时文件夹已清空", "tempFoldersEmptied": "临时文件夹已清空",
@ -705,7 +721,7 @@
"modelAddFailed": "模型添加失败", "modelAddFailed": "模型添加失败",
"problemDownloadingCanvas": "下载画布时出现问题", "problemDownloadingCanvas": "下载画布时出现问题",
"problemMergingCanvas": "合并画布时出现问题", "problemMergingCanvas": "合并画布时出现问题",
"setCanvasInitialImage": "设画布初始图像", "setCanvasInitialImage": "设画布初始图像",
"imageUploaded": "图像已上传", "imageUploaded": "图像已上传",
"addedToBoard": "已添加到面板", "addedToBoard": "已添加到面板",
"workflowLoaded": "工作流已加载", "workflowLoaded": "工作流已加载",
@ -722,7 +738,8 @@
"canvasSavedGallery": "画布已保存到图库", "canvasSavedGallery": "画布已保存到图库",
"imageUploadFailed": "图像上传失败", "imageUploadFailed": "图像上传失败",
"problemImportingMask": "导入遮罩时出现问题", "problemImportingMask": "导入遮罩时出现问题",
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型" "baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型",
"setAsCanvasInitialImage": "设为画布初始图像"
}, },
"unifiedCanvas": { "unifiedCanvas": {
"layer": "图层", "layer": "图层",
@ -808,7 +825,8 @@
"toggleAutoscroll": "切换自动缩放", "toggleAutoscroll": "切换自动缩放",
"menu": "菜单", "menu": "菜单",
"showGalleryPanel": "显示图库浮窗", "showGalleryPanel": "显示图库浮窗",
"loadMore": "加载更多" "loadMore": "加载更多",
"mode": "模式"
}, },
"ui": { "ui": {
"showProgressImages": "显示处理中的图片", "showProgressImages": "显示处理中的图片",
@ -1031,7 +1049,9 @@
"integerPolymorphic": "整数多态", "integerPolymorphic": "整数多态",
"latentsPolymorphic": "Latents 多态", "latentsPolymorphic": "Latents 多态",
"conditioningField": "条件", "conditioningField": "条件",
"latentsField": "Latents" "latentsField": "Latents",
"updateAllNodes": "更新所有节点",
"unableToUpdateNodes_other": "{{count}} 个节点无法完成更新"
}, },
"controlnet": { "controlnet": {
"resize": "直接缩放", "resize": "直接缩放",
@ -1117,7 +1137,8 @@
"openPose": "Openpose", "openPose": "Openpose",
"controlAdapter_other": "Control Adapters", "controlAdapter_other": "Control Adapters",
"lineartAnime": "Lineart Anime", "lineartAnime": "Lineart Anime",
"canny": "Canny" "canny": "Canny",
"unstarImage": "取消收藏图像"
}, },
"queue": { "queue": {
"status": "状态", "status": "状态",
@ -1176,7 +1197,9 @@
"queueTotal": "总计 {{total}}", "queueTotal": "总计 {{total}}",
"enqueueing": "队列中的批次", "enqueueing": "队列中的批次",
"queueMaxExceeded": "超出最大值 {{max_queue_size}},将跳过 {{skip}}", "queueMaxExceeded": "超出最大值 {{max_queue_size}},将跳过 {{skip}}",
"graphFailedToQueue": "节点图加入队列失败" "graphFailedToQueue": "节点图加入队列失败",
"batchFieldValues": "批处理值",
"time": "时间"
}, },
"sdxl": { "sdxl": {
"refinerStart": "Refiner 开始作用时机", "refinerStart": "Refiner 开始作用时机",
@ -1234,7 +1257,9 @@
"selectModel": "选择一个模型", "selectModel": "选择一个模型",
"selectLoRA": "选择一个 LoRA", "selectLoRA": "选择一个 LoRA",
"noRefinerModelsInstalled": "无已安装的 SDXL Refiner 模型", "noRefinerModelsInstalled": "无已安装的 SDXL Refiner 模型",
"noLoRAsInstalled": "无已安装的 LoRA" "noLoRAsInstalled": "无已安装的 LoRA",
"esrganModel": "ESRGAN 模型",
"addLora": "添加 LoRA"
}, },
"boards": { "boards": {
"autoAddBoard": "自动添加面板", "autoAddBoard": "自动添加面板",
@ -1252,7 +1277,11 @@
"changeBoard": "更改面板", "changeBoard": "更改面板",
"loading": "加载中...", "loading": "加载中...",
"clearSearch": "清除检索", "clearSearch": "清除检索",
"downloadBoard": "下载面板" "downloadBoard": "下载面板",
"deleteBoardOnly": "仅删除面板",
"deleteBoard": "删除面板",
"deleteBoardAndImages": "删除面板和图像",
"deletedBoardsCannotbeRestored": "已删除的面板无法被恢复"
}, },
"embedding": { "embedding": {
"noMatchingEmbedding": "不匹配的 Embedding", "noMatchingEmbedding": "不匹配的 Embedding",
@ -1271,7 +1300,8 @@
"combinatorial": "组合生成", "combinatorial": "组合生成",
"maxPrompts": "最大提示词数", "maxPrompts": "最大提示词数",
"dynamicPrompts": "动态提示词", "dynamicPrompts": "动态提示词",
"promptsWithCount_other": "{{count}} 个提示词" "promptsWithCount_other": "{{count}} 个提示词",
"promptsPreview": "提示词预览"
}, },
"popovers": { "popovers": {
"compositingMaskAdjustments": { "compositingMaskAdjustments": {

View File

@ -1,61 +1,19 @@
import fs from 'node:fs'; import fs from 'node:fs';
import openapiTS from 'openapi-typescript'; import openapiTS from 'openapi-typescript';
import { COLORS } from './colors.js';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json'; const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.d.ts'; const OUTPUT_FILE = 'src/services/api/schema.d.ts';
async function main() { async function main() {
process.stdout.write( process.stdout.write(
`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...\n\n` `Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`
); );
const types = await openapiTS(OPENAPI_URL, { const types = await openapiTS(OPENAPI_URL, {
exportType: true, exportType: true,
transform: (schemaObject, metadata) => { transform: (schemaObject) => {
if ('format' in schemaObject && schemaObject.format === 'binary') { if ('format' in schemaObject && schemaObject.format === 'binary') {
return schemaObject.nullable ? 'Blob | null' : 'Blob'; return schemaObject.nullable ? 'Blob | null' : 'Blob';
} }
/**
* Because invocations may have required fields that accept connection input, the generated
* types may be incorrect.
*
* For example, the ImageResizeInvocation has a required `image` field, but because it accepts
* connection input, it should be optional on instantiation of the field.
*
* To handle this, the schema exposes an `input` property that can be used to determine if the
* field accepts connection input. If it does, we can make the field optional.
*/
if ('class' in schemaObject && schemaObject.class === 'invocation') {
// We only want to make fields optional if they are required
if (!Array.isArray(schemaObject?.required)) {
schemaObject.required = [];
}
schemaObject.required.forEach((prop) => {
const acceptsConnection = ['any', 'connection'].includes(
schemaObject.properties?.[prop]?.['input']
);
if (acceptsConnection) {
// remove this prop from the required array
const invocationName = metadata.path.split('/').pop();
console.log(
`Making connectable field optional: ${COLORS.fg.green}${invocationName}.${COLORS.fg.cyan}${prop}${COLORS.reset}`
);
schemaObject.required = schemaObject.required.filter(
(r) => r !== prop
);
}
});
return;
}
// Check if we are generating types for an invocation output
if ('class' in schemaObject && schemaObject.class === 'output') {
// modify output types
}
}, },
}); });
fs.writeFileSync(OUTPUT_FILE, types); fs.writeFileSync(OUTPUT_FILE, types);

View File

@ -20,6 +20,7 @@ import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import GlobalHotkeys from './GlobalHotkeys'; import GlobalHotkeys from './GlobalHotkeys';
import PreselectedImage from './PreselectedImage'; import PreselectedImage from './PreselectedImage';
import Toaster from './Toaster'; import Toaster from './Toaster';
import { useSocketIO } from '../hooks/useSocketIO';
const DEFAULT_CONFIG = {}; const DEFAULT_CONFIG = {};
@ -33,10 +34,12 @@ interface Props {
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => { const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
const language = useAppSelector(languageSelector); const language = useAppSelector(languageSelector);
const logger = useLogger('system'); const logger = useLogger('system');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
// singleton!
useSocketIO();
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
localStorage.clear(); localStorage.clear();
location.reload(); location.reload();

View File

@ -2,6 +2,7 @@ import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
import newGithubIssueUrl from 'new-github-issue-url'; import newGithubIssueUrl from 'new-github-issue-url';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaCopy, FaExternalLinkAlt } from 'react-icons/fa'; import { FaCopy, FaExternalLinkAlt } from 'react-icons/fa';
import { FaArrowRotateLeft } from 'react-icons/fa6'; import { FaArrowRotateLeft } from 'react-icons/fa6';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';
@ -13,6 +14,7 @@ type Props = {
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => { const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
const toast = useToast(); const toast = useToast();
const { t } = useTranslation();
const handleCopy = useCallback(() => { const handleCopy = useCallback(() => {
const text = JSON.stringify(serializeError(error), null, 2); const text = JSON.stringify(serializeError(error), null, 2);
@ -53,7 +55,7 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
p: 16, p: 16,
}} }}
> >
<Heading>Something went wrong</Heading> <Heading>{t('common.somethingWentWrong')}</Heading>
<Flex <Flex
layerStyle="second" layerStyle="second"
sx={{ sx={{
@ -80,13 +82,15 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
leftIcon={<FaArrowRotateLeft />} leftIcon={<FaArrowRotateLeft />}
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
> >
Reset UI {t('accessibility.resetUI')}
</IAIButton> </IAIButton>
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}> <IAIButton leftIcon={<FaCopy />} onClick={handleCopy}>
Copy Error {t('common.copyError')}
</IAIButton> </IAIButton>
<Link href={url} isExternal> <Link href={url} isExternal>
<IAIButton leftIcon={<FaExternalLinkAlt />}>Create Issue</IAIButton> <IAIButton leftIcon={<FaExternalLinkAlt />}>
{t('accessibility.createIssue')}
</IAIButton>
</Link> </Link>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -1,26 +1,27 @@
import { Middleware } from '@reduxjs/toolkit'; import { Middleware } from '@reduxjs/toolkit';
import { $socketOptions } from 'app/hooks/useSocketIO';
import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { $projectId } from 'app/store/nanostores/projectId';
import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
import { store } from 'app/store/store'; import { store } from 'app/store/store';
import { PartialAppConfig } from 'app/types/invokeai'; import { PartialAppConfig } from 'app/types/invokeai';
import React, { import React, {
lazy,
memo,
PropsWithChildren, PropsWithChildren,
ReactNode, ReactNode,
lazy,
memo,
useEffect, useEffect,
} from 'react'; } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares'; import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import { $authToken, $baseUrl, $projectId } from 'services/api/client'; import { ManagerOptions, SocketOptions } from 'socket.io-client';
import { socketMiddleware } from 'services/events/middleware';
import Loading from '../../common/components/Loading/Loading'; import Loading from '../../common/components/Loading/Loading';
import '../../i18n';
import AppDndContext from '../../features/dnd/components/AppDndContext'; import AppDndContext from '../../features/dnd/components/AppDndContext';
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI'; import '../../i18n';
import { $headerComponent } from 'app/store/nanostores/headerComponent';
import {
$queueId,
DEFAULT_QUEUE_ID,
} from 'features/queue/store/queueNanoStore';
const App = lazy(() => import('./App')); const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
@ -38,6 +39,8 @@ interface Props extends PropsWithChildren {
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters'; action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
}; };
customStarUi?: CustomStarUi; customStarUi?: CustomStarUi;
socketOptions?: Partial<ManagerOptions & SocketOptions>;
isDebugging?: boolean;
} }
const InvokeAIUI = ({ const InvokeAIUI = ({
@ -50,6 +53,8 @@ const InvokeAIUI = ({
queueId, queueId,
selectedImage, selectedImage,
customStarUi, customStarUi,
socketOptions,
isDebugging = false,
}: Props) => { }: Props) => {
useEffect(() => { useEffect(() => {
// configure API client token // configure API client token
@ -82,9 +87,7 @@ const InvokeAIUI = ({
// rebuild socket middleware with token and apiUrl // rebuild socket middleware with token and apiUrl
if (middleware && middleware.length > 0) { if (middleware && middleware.length > 0) {
addMiddleware(socketMiddleware(), ...middleware); addMiddleware(...middleware);
} else {
addMiddleware(socketMiddleware());
} }
return () => { return () => {
@ -116,6 +119,24 @@ const InvokeAIUI = ({
}; };
}, [headerComponent]); }, [headerComponent]);
useEffect(() => {
if (socketOptions) {
$socketOptions.set(socketOptions);
}
return () => {
$socketOptions.set({});
};
}, [socketOptions]);
useEffect(() => {
if (isDebugging) {
$isDebugging.set(isDebugging);
}
return () => {
$isDebugging.set(false);
};
}, [isDebugging]);
return ( return (
<React.StrictMode> <React.StrictMode>
<Provider store={store}> <Provider store={store}>

View File

@ -0,0 +1,109 @@
import { useStore } from '@nanostores/react';
import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { useAppDispatch } from 'app/store/storeHooks';
import { MapStore, WritableAtom, atom, map } from 'nanostores';
import { useEffect } from 'react';
import {
ClientToServerEvents,
ServerToClientEvents,
} from 'services/events/types';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client';
// Inject socket options and url into window for debugging
declare global {
interface Window {
$socketOptions?: MapStore<Partial<ManagerOptions & SocketOptions>>;
$socketUrl?: WritableAtom<string>;
}
}
const makeSocketOptions = (): Partial<ManagerOptions & SocketOptions> => {
const socketOptions: Parameters<typeof io>[0] = {
timeout: 60000,
path: '/ws/socket.io',
autoConnect: false, // achtung! removing this breaks the dynamic middleware
forceNew: true,
};
// if building in package mode, replace socket url with open api base url minus the http protocol
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
const authToken = $authToken.get();
if (authToken) {
// TODO: handle providing jwt to socket.io
socketOptions.auth = { token: authToken };
}
socketOptions.transports = ['websocket', 'polling'];
}
return socketOptions;
};
const makeSocketUrl = (): string => {
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
let socketUrl = `${wsProtocol}://${window.location.host}`;
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
const baseUrl = $baseUrl.get();
if (baseUrl) {
//eslint-disable-next-line
socketUrl = baseUrl.replace(/^https?\:\/\//i, '');
}
}
return socketUrl;
};
const makeSocket = (): Socket<ServerToClientEvents, ClientToServerEvents> => {
const socketOptions = makeSocketOptions();
const socketUrl = $socketUrl.get();
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
socketUrl,
{ ...socketOptions, ...$socketOptions.get() }
);
return socket;
};
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
export const $socketUrl = atom<string>(makeSocketUrl());
export const $isSocketInitialized = atom<boolean>(false);
/**
* Initializes the socket.io connection and sets up event listeners.
*/
export const useSocketIO = () => {
const dispatch = useAppDispatch();
const socketOptions = useStore($socketOptions);
const socketUrl = useStore($socketUrl);
const baseUrl = useStore($baseUrl);
const authToken = useStore($authToken);
useEffect(() => {
if ($isSocketInitialized.get()) {
// Singleton!
return;
}
const socket = makeSocket();
setEventListeners({ dispatch, socket });
socket.connect();
if ($isDebugging.get()) {
window.$socketOptions = $socketOptions;
window.$socketUrl = $socketUrl;
console.log('Socket initialized', socket);
}
$isSocketInitialized.set(true);
return () => {
if ($isDebugging.get()) {
window.$socketOptions = undefined;
window.$socketUrl = undefined;
console.log('Socket teardown', socket);
}
socket.disconnect();
$isSocketInitialized.set(false);
};
}, [dispatch, socketOptions, socketUrl, baseUrl, authToken]);
};

View File

@ -0,0 +1,6 @@
import { atom } from 'nanostores';
/**
* The user's auth token.
*/
export const $authToken = atom<string | undefined>();

View File

@ -0,0 +1,6 @@
import { atom } from 'nanostores';
/**
* The OpenAPI base url.
*/
export const $baseUrl = atom<string | undefined>();

View File

@ -0,0 +1,3 @@
import { atom } from 'nanostores';
export const $isDebugging = atom<boolean>(false);

View File

@ -0,0 +1,6 @@
import { atom } from 'nanostores';
/**
* The optional project-id header.
*/
export const $projectId = atom<string | undefined>();

View File

@ -12,14 +12,14 @@ import deleteImageModalReducer from 'features/deleteImageModal/store/slice';
import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import galleryReducer from 'features/gallery/store/gallerySlice'; import galleryReducer from 'features/gallery/store/gallerySlice';
import loraReducer from 'features/lora/store/loraSlice'; import loraReducer from 'features/lora/store/loraSlice';
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
import nodesReducer from 'features/nodes/store/nodesSlice'; import nodesReducer from 'features/nodes/store/nodesSlice';
import generationReducer from 'features/parameters/store/generationSlice'; import generationReducer from 'features/parameters/store/generationSlice';
import postprocessingReducer from 'features/parameters/store/postprocessingSlice'; import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
import queueReducer from 'features/queue/store/queueSlice';
import sdxlReducer from 'features/sdxl/store/sdxlSlice'; import sdxlReducer from 'features/sdxl/store/sdxlSlice';
import configReducer from 'features/system/store/configSlice'; import configReducer from 'features/system/store/configSlice';
import systemReducer from 'features/system/store/systemSlice'; import systemReducer from 'features/system/store/systemSlice';
import queueReducer from 'features/queue/store/queueSlice';
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import uiReducer from 'features/ui/store/uiSlice'; import uiReducer from 'features/ui/store/uiSlice';
import dynamicMiddlewares from 'redux-dynamic-middlewares'; import dynamicMiddlewares from 'redux-dynamic-middlewares';

View File

@ -1,6 +1,7 @@
import { Box, Flex, Heading } from '@chakra-ui/react'; import { Box, Flex, Heading } from '@chakra-ui/react';
import { memo } from 'react'; import { memo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
type ImageUploadOverlayProps = { type ImageUploadOverlayProps = {
isDragAccept: boolean; isDragAccept: boolean;
@ -9,6 +10,7 @@ type ImageUploadOverlayProps = {
}; };
const ImageUploadOverlay = (props: ImageUploadOverlayProps) => { const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
const { t } = useTranslation();
const { const {
isDragAccept, isDragAccept,
isDragReject: _isDragAccept, isDragReject: _isDragAccept,
@ -76,11 +78,13 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
}} }}
> >
{isDragAccept ? ( {isDragAccept ? (
<Heading size="lg">Drop to Upload</Heading> <Heading size="lg">{t('gallery.dropToUpload')}</Heading>
) : ( ) : (
<> <>
<Heading size="lg">Invalid Upload</Heading> <Heading size="lg">{t('toast.invalidUpload')}</Heading>
<Heading size="md">Must be single JPEG or PNG image</Heading> <Heading size="md">
{t('toast.uploadFailedInvalidUploadDesc')}
</Heading>
</> </>
)} )}
</Flex> </Flex>

View File

@ -1,5 +1,5 @@
import { $authToken } from 'app/store/nanostores/authToken';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { $authToken } from 'services/api/client';
/** /**
* Converts an image URL to a Blob by creating an <img /> element, drawing it to canvas * Converts an image URL to a Blob by creating an <img /> element, drawing it to canvas

View File

@ -1,7 +1,7 @@
import { skipToken } from '@reduxjs/toolkit/dist/query'; import { skipToken } from '@reduxjs/toolkit/dist/query';
import { $authToken } from 'app/store/nanostores/authToken';
import { memo } from 'react'; import { memo } from 'react';
import { Image } from 'react-konva'; import { Image } from 'react-konva';
import { $authToken } from 'services/api/client';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import useImage from 'use-image'; import useImage from 'use-image';
import { CanvasImage } from '../store/canvasTypes'; import { CanvasImage } from '../store/canvasTypes';

View File

@ -155,10 +155,10 @@ const IAICanvasMaskOptions = () => {
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} /> <IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
</Box> </Box>
<IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}> <IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}>
Save Mask {t('unifiedCanvas.saveMask')}
</IAIButton> </IAIButton>
<IAIButton size="sm" leftIcon={<FaTrash />} onClick={handleClearMask}> <IAIButton size="sm" leftIcon={<FaTrash />} onClick={handleClearMask}>
{t('unifiedCanvas.clearMask')} (Shift+C) {t('unifiedCanvas.clearMask')}
</IAIButton> </IAIButton>
</Flex> </Flex>
</IAIPopover> </IAIPopover>

View File

@ -110,8 +110,10 @@ const ChangeBoardModal = () => {
<AlertDialogBody> <AlertDialogBody>
<Flex sx={{ flexDir: 'column', gap: 4 }}> <Flex sx={{ flexDir: 'column', gap: 4 }}>
<Text> <Text>
Moving {`${imagesToChange.length}`} image {t('boards.movingImagesToBoard', {
{`${imagesToChange.length > 1 ? 's' : ''}`} to board: count: imagesToChange.length,
})}
:
</Text> </Text>
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
placeholder={ placeholder={

View File

@ -124,10 +124,10 @@ const DeleteImageModal = () => {
</AlertDialogBody> </AlertDialogBody>
<AlertDialogFooter> <AlertDialogFooter>
<IAIButton ref={cancelRef} onClick={handleClose}> <IAIButton ref={cancelRef} onClick={handleClose}>
Cancel {t('boards.cancel')}
</IAIButton> </IAIButton>
<IAIButton colorScheme="error" onClick={handleDelete} ml={3}> <IAIButton colorScheme="error" onClick={handleDelete} ml={3}>
Delete {t('controlnet.delete')}
</IAIButton> </IAIButton>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -1,6 +1,7 @@
import { Box, ChakraProps, Flex, Heading, Image, Text } from '@chakra-ui/react'; import { Box, ChakraProps, Flex, Heading, Image, Text } from '@chakra-ui/react';
import { memo } from 'react'; import { memo } from 'react';
import { TypesafeDraggableData } from '../types'; import { TypesafeDraggableData } from '../types';
import { useTranslation } from 'react-i18next';
type OverlayDragImageProps = { type OverlayDragImageProps = {
dragData: TypesafeDraggableData | null; dragData: TypesafeDraggableData | null;
@ -26,6 +27,7 @@ const STYLES: ChakraProps['sx'] = {
}; };
const DragPreview = (props: OverlayDragImageProps) => { const DragPreview = (props: OverlayDragImageProps) => {
const { t } = useTranslation();
if (!props.dragData) { if (!props.dragData) {
return null; return null;
} }
@ -89,7 +91,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
}} }}
> >
<Heading>{props.dragData.payload.imageDTOs.length}</Heading> <Heading>{props.dragData.payload.imageDTOs.length}</Heading>
<Heading size="sm">Images</Heading> <Heading size="sm">{t('parameters.images')}</Heading>
</Flex> </Flex>
); );
} }

View File

@ -121,7 +121,7 @@ const ParamEmbeddingPopover = (props: Props) => {
_dark: { color: 'base.700' }, _dark: { color: 'base.700' },
}} }}
> >
<Text>No Embeddings Loaded</Text> <Text>{t('embedding.noEmbeddingsLoaded')}</Text>
</Flex> </Flex>
) : ( ) : (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect

View File

@ -32,6 +32,7 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import AutoAddIcon from '../AutoAddIcon'; import AutoAddIcon from '../AutoAddIcon';
import BoardContextMenu from '../BoardContextMenu'; import BoardContextMenu from '../BoardContextMenu';
import { useTranslation } from 'react-i18next';
interface GalleryBoardProps { interface GalleryBoardProps {
board: BoardDTO; board: BoardDTO;
@ -143,7 +144,7 @@ const GalleryBoard = ({
const handleChange = useCallback((newBoardName: string) => { const handleChange = useCallback((newBoardName: string) => {
setLocalBoardName(newBoardName); setLocalBoardName(newBoardName);
}, []); }, []);
const { t } = useTranslation();
return ( return (
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}> <Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
<Flex <Flex
@ -292,7 +293,9 @@ const GalleryBoard = ({
<IAIDroppable <IAIDroppable
data={droppableData} data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>} dropLabel={
<Text fontSize="md">{t('unifiedCanvas.move')}</Text>
}
/> />
</Flex> </Flex>
</Tooltip> </Tooltip>

View File

@ -19,6 +19,7 @@ import {
useGetBoardAssetsTotalQuery, useGetBoardAssetsTotalQuery,
useGetBoardImagesTotalQuery, useGetBoardImagesTotalQuery,
} from 'services/api/endpoints/boards'; } from 'services/api/endpoints/boards';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
isSelected: boolean; isSelected: boolean;
@ -71,7 +72,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
}), }),
[] []
); );
const { t } = useTranslation();
return ( return (
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}> <Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
<Flex <Flex
@ -161,7 +162,9 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
/> />
<IAIDroppable <IAIDroppable
data={droppableData} data={droppableData}
dropLabel={<Text fontSize="md">Move</Text>} dropLabel={
<Text fontSize="md">{t('unifiedCanvas.move')}</Text>
}
/> />
</Flex> </Flex>
</Tooltip> </Tooltip>

View File

@ -35,6 +35,7 @@ import {
FaCode, FaCode,
FaHourglassHalf, FaHourglassHalf,
FaQuoteRight, FaQuoteRight,
FaRulerVertical,
FaSeedling, FaSeedling,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6'; import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
@ -95,8 +96,12 @@ const CurrentImageButtons = () => {
const toaster = useAppToaster(); const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const { recallBothPrompts, recallSeed, recallAllParameters } = const {
useRecallParameters(); recallBothPrompts,
recallSeed,
recallWidthAndHeight,
recallAllParameters,
} = useRecallParameters();
const { currentData: imageDTO } = useGetImageDTOQuery( const { currentData: imageDTO } = useGetImageDTOQuery(
lastSelectedImage?.image_name ?? skipToken lastSelectedImage?.image_name ?? skipToken
@ -117,6 +122,8 @@ const CurrentImageButtons = () => {
dispatch(workflowLoadRequested(workflow)); dispatch(workflowLoadRequested(workflow));
}, [dispatch, workflow]); }, [dispatch, workflow]);
useHotkeys('w', handleLoadWorkflow, [workflow]);
const handleClickUseAllParameters = useCallback(() => { const handleClickUseAllParameters = useCallback(() => {
recallAllParameters(metadata); recallAllParameters(metadata);
}, [metadata, recallAllParameters]); }, [metadata, recallAllParameters]);
@ -127,7 +134,7 @@ const CurrentImageButtons = () => {
recallSeed(metadata?.seed); recallSeed(metadata?.seed);
}, [metadata?.seed, recallSeed]); }, [metadata?.seed, recallSeed]);
useHotkeys('s', handleUseSeed, [imageDTO]); useHotkeys('s', handleUseSeed, [metadata]);
const handleUsePrompt = useCallback(() => { const handleUsePrompt = useCallback(() => {
recallBothPrompts( recallBothPrompts(
@ -144,9 +151,13 @@ const CurrentImageButtons = () => {
recallBothPrompts, recallBothPrompts,
]); ]);
useHotkeys('p', handleUsePrompt, [imageDTO]); useHotkeys('p', handleUsePrompt, [metadata]);
useHotkeys('w', handleLoadWorkflow, [workflow]); const handleUseSize = useCallback(() => {
recallWidthAndHeight(metadata?.width, metadata?.height);
}, [metadata?.width, metadata?.height, recallWidthAndHeight]);
useHotkeys('d', handleUseSize, [metadata]);
const handleSendToImageToImage = useCallback(() => { const handleSendToImageToImage = useCallback(() => {
dispatch(sentImageToImg2Img()); dispatch(sentImageToImg2Img());
@ -267,6 +278,19 @@ const CurrentImageButtons = () => {
isDisabled={metadata?.seed === null || metadata?.seed === undefined} isDisabled={metadata?.seed === null || metadata?.seed === undefined}
onClick={handleUseSeed} onClick={handleUseSeed}
/> />
<IAIIconButton
isLoading={isLoadingMetadata}
icon={<FaRulerVertical />}
tooltip={`${t('parameters.useSize')} (D)`}
aria-label={`${t('parameters.useSize')} (D)`}
isDisabled={
metadata?.height === null ||
metadata?.height === undefined ||
metadata?.width === null ||
metadata?.width === undefined
}
onClick={handleUseSize}
/>
<IAIIconButton <IAIIconButton
isLoading={isLoadingMetadata} isLoading={isLoadingMetadata}
icon={<FaAsterisk />} icon={<FaAsterisk />}

View File

@ -111,7 +111,7 @@ const MultipleSelectionMenuItems = () => {
icon={<FaTrash />} icon={<FaTrash />}
onClickCapture={handleDeleteSelection} onClickCapture={handleDeleteSelection}
> >
Delete Selection {t('gallery.deleteSelection')}
</MenuItem> </MenuItem>
</> </>
); );

View File

@ -113,7 +113,7 @@ const ImageGalleryContent = () => {
leftIcon={<FaImages />} leftIcon={<FaImages />}
data-testid="images-tab" data-testid="images-tab"
> >
{t('gallery.images')} {t('parameters.images')}
</Tab> </Tab>
<Tab <Tab
as={IAIButton} as={IAIButton}

View File

@ -49,7 +49,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
}} }}
> >
<Flex gap={2}> <Flex gap={2}>
<Text fontWeight="semibold">File:</Text> <Text fontWeight="semibold">{t('common.file')}:</Text>
<Link href={image.image_url} isExternal maxW="calc(100% - 3rem)"> <Link href={image.image_url} isExternal maxW="calc(100% - 3rem)">
{image.image_name} {image.image_name}
<ExternalLinkIcon mx="2px" /> <ExternalLinkIcon mx="2px" />

View File

@ -165,7 +165,7 @@ export default function FoundModelsList() {
}, },
}} }}
> >
Installed {t('common.installed')}
</Text> </Text>
)} )}
</Flex> </Flex>
@ -215,7 +215,7 @@ export default function FoundModelsList() {
/> />
<Flex p={2} gap={2}> <Flex p={2} gap={2}>
<Text sx={{ fontWeight: 600 }}> <Text sx={{ fontWeight: 600 }}>
Models Found: {foundModels.length} {t('modelManager.modelsFound')}: {foundModels.length}
</Text> </Text>
<Text <Text
sx={{ sx={{
@ -226,7 +226,7 @@ export default function FoundModelsList() {
}, },
}} }}
> >
Not Installed: {filteredModels.length} {t('common.notInstalled')}: {filteredModels.length}
</Text> </Text>
</Flex> </Flex>

View File

@ -76,7 +76,7 @@ function SearchFolderForm() {
_dark: { color: 'base.300' }, _dark: { color: 'base.300' },
}} }}
> >
Folder {t('common.folder')}
</Text> </Text>
{!searchFolder ? ( {!searchFolder ? (
<IAIInput <IAIInput

View File

@ -114,7 +114,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
{model.model_name} {model.model_name}
</Text> </Text>
<Text fontSize="sm" color="base.400"> <Text fontSize="sm" color="base.400">
{MODEL_TYPE_MAP[model.base_model]} Model {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')}
</Text> </Text>
</Flex> </Flex>
{![''].includes(model.base_model) ? ( {![''].includes(model.base_model) ? (
@ -128,7 +128,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
_dark: { bg: 'error.400' }, _dark: { bg: 'error.400' },
}} }}
> >
Conversion Not Supported {t('modelManager.conversionNotSupported')}
</Badge> </Badge>
)} )}
</Flex> </Flex>

View File

@ -95,7 +95,7 @@ export default function DiffusersModelEdit(props: DiffusersModelEditProps) {
{model.model_name} {model.model_name}
</Text> </Text>
<Text fontSize="sm" color="base.400"> <Text fontSize="sm" color="base.400">
{MODEL_TYPE_MAP[model.base_model]} Model {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')}
</Text> </Text>
</Flex> </Flex>
<Divider /> <Divider />

View File

@ -95,8 +95,8 @@ export default function LoRAModelEdit(props: LoRAModelEditProps) {
{model.model_name} {model.model_name}
</Text> </Text>
<Text fontSize="sm" color="base.400"> <Text fontSize="sm" color="base.400">
{MODEL_TYPE_MAP[model.base_model]} Model {' '} {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')} {' '}
{LORA_MODEL_FORMAT_MAP[model.model_format]} format {LORA_MODEL_FORMAT_MAP[model.model_format]} {t('common.format')}
</Text> </Text>
</Flex> </Flex>
<Divider /> <Divider />

View File

@ -10,6 +10,7 @@ import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { stateSelector } from 'app/store/store'; import { stateSelector } from 'app/store/store';
import { useTranslation } from 'react-i18next';
const selector = createSelector(stateSelector, ({ system, gallery }) => { const selector = createSelector(stateSelector, ({ system, gallery }) => {
const imageDTO = gallery.selection[gallery.selection.length - 1]; const imageDTO = gallery.selection[gallery.selection.length - 1];
@ -66,7 +67,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
const handleMouseLeave = useCallback(() => { const handleMouseLeave = useCallback(() => {
setIsHovering(false); setIsHovering(false);
}, []); }, []);
const { t } = useTranslation();
return ( return (
<NodeWrapper <NodeWrapper
nodeId={props.nodeProps.id} nodeId={props.nodeProps.id}
@ -99,7 +100,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
_dark: { color: 'base.200' }, _dark: { color: 'base.200' },
}} }}
> >
Current Image {t('nodes.currentImage')}
</Text> </Text>
</Flex> </Flex>
<Flex <Flex

View File

@ -4,8 +4,10 @@ import { useEmbedWorkflow } from 'features/nodes/hooks/useEmbedWorkflow';
import { useWithWorkflow } from 'features/nodes/hooks/useWithWorkflow'; import { useWithWorkflow } from 'features/nodes/hooks/useWithWorkflow';
import { nodeEmbedWorkflowChanged } from 'features/nodes/store/nodesSlice'; import { nodeEmbedWorkflowChanged } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => { const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const withWorkflow = useWithWorkflow(nodeId); const withWorkflow = useWithWorkflow(nodeId);
const embedWorkflow = useEmbedWorkflow(nodeId); const embedWorkflow = useEmbedWorkflow(nodeId);
@ -27,7 +29,9 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
return ( return (
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}> <FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Workflow</FormLabel> <FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>
{t('metadata.workflow')}
</FormLabel>
<Checkbox <Checkbox
className="nopan" className="nopan"
size="sm" size="sm"

View File

@ -75,7 +75,7 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
const { status, progress, progressImage } = nodeExecutionState; const { status, progress, progressImage } = nodeExecutionState;
const { t } = useTranslation(); const { t } = useTranslation();
if (status === NodeStatus.PENDING) { if (status === NodeStatus.PENDING) {
return <Text>Pending</Text>; return <Text>{t('queue.pending')}</Text>;
} }
if (status === NodeStatus.IN_PROGRESS) { if (status === NodeStatus.IN_PROGRESS) {
if (progressImage) { if (progressImage) {

View File

@ -3,6 +3,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { memo } from 'react'; import { memo } from 'react';
import NodeCollapseButton from '../common/NodeCollapseButton'; import NodeCollapseButton from '../common/NodeCollapseButton';
import NodeWrapper from '../common/NodeWrapper'; import NodeWrapper from '../common/NodeWrapper';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -19,6 +20,7 @@ const InvocationNodeUnknownFallback = ({
type, type,
selected, selected,
}: Props) => { }: Props) => {
const { t } = useTranslation();
return ( return (
<NodeWrapper nodeId={nodeId} selected={selected}> <NodeWrapper nodeId={nodeId} selected={selected}>
<Flex <Flex
@ -61,7 +63,7 @@ const InvocationNodeUnknownFallback = ({
}} }}
> >
<Box> <Box>
<Text as="span">Unknown node type: </Text> <Text as="span">{t('nodes.unknownNodeType')}: </Text>
<Text as="span" fontWeight={600}> <Text as="span" fontWeight={600}>
{type} {type}
</Text> </Text>

View File

@ -4,8 +4,10 @@ import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate'; import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate';
import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice'; import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => { const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const hasImageOutput = useHasImageOutput(nodeId); const hasImageOutput = useHasImageOutput(nodeId);
const isIntermediate = useIsIntermediate(nodeId); const isIntermediate = useIsIntermediate(nodeId);
@ -27,7 +29,9 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
return ( return (
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}> <FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Save to Gallery</FormLabel> <FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>
{t('hotkeys.saveToGallery.title')}
</FormLabel>
<Checkbox <Checkbox
className="nopan" className="nopan"
size="sm" size="sm"

View File

@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
import { useUseCache } from 'features/nodes/hooks/useUseCache'; import { useUseCache } from 'features/nodes/hooks/useUseCache';
import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice'; import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => { const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -18,10 +19,12 @@ const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
}, },
[dispatch, nodeId] [dispatch, nodeId]
); );
const { t } = useTranslation();
return ( return (
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}> <FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>Use Cache</FormLabel> <FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>
{t('invocationCache.useCache')}
</FormLabel>
<Checkbox <Checkbox
className="nopan" className="nopan"
size="sm" size="sm"

View File

@ -79,7 +79,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
icon={<FaPlus />} icon={<FaPlus />}
onClick={handleExposeField} onClick={handleExposeField}
> >
Add to Linear View {t('nodes.addLinearView')}
</MenuItem> </MenuItem>
); );
} }
@ -90,7 +90,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
icon={<FaMinus />} icon={<FaMinus />}
onClick={handleUnexposeField} onClick={handleUnexposeField}
> >
Remove from Linear View {t('nodes.removeLinearView')}
</MenuItem> </MenuItem>
); );
} }
@ -102,6 +102,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
isExposed, isExposed,
mayExpose, mayExpose,
nodeId, nodeId,
t,
]); ]);
const renderMenuFunc = useCallback( const renderMenuFunc = useCallback(

View File

@ -49,8 +49,16 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
{fieldTemplate.description} {fieldTemplate.description}
</Text> </Text>
)} )}
{fieldTemplate && <Text>Type: {FIELDS[fieldTemplate.type].title}</Text>} {fieldTemplate && (
{isInputTemplate && <Text>Input: {startCase(fieldTemplate.input)}</Text>} <Text>
{t('parameters.type')}: {FIELDS[fieldTemplate.type].title}
</Text>
)}
{isInputTemplate && (
<Text>
{t('common.input')}: {startCase(fieldTemplate.input)}
</Text>
)}
</Flex> </Flex>
); );
}; };

View File

@ -7,6 +7,7 @@ import EditableFieldTitle from './EditableFieldTitle';
import FieldContextMenu from './FieldContextMenu'; import FieldContextMenu from './FieldContextMenu';
import FieldHandle from './FieldHandle'; import FieldHandle from './FieldHandle';
import InputFieldRenderer from './InputFieldRenderer'; import InputFieldRenderer from './InputFieldRenderer';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
nodeId: string; nodeId: string;
@ -14,6 +15,7 @@ interface Props {
} }
const InputField = ({ nodeId, fieldName }: Props) => { const InputField = ({ nodeId, fieldName }: Props) => {
const { t } = useTranslation();
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input'); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input');
const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName); const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName);
@ -49,7 +51,7 @@ const InputField = ({ nodeId, fieldName }: Props) => {
<FormControl <FormControl
sx={{ color: 'error.400', textAlign: 'left', fontSize: 'sm' }} sx={{ color: 'error.400', textAlign: 'left', fontSize: 'sm' }}
> >
Unknown input: {fieldName} {t('nodes.unknownInput')}: {fieldName}
</FormControl> </FormControl>
</InputFieldWrapper> </InputFieldWrapper>
); );

View File

@ -18,6 +18,7 @@ import VaeModelInputField from './inputs/VaeModelInputField';
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField'; import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
import T2IAdapterModelInputField from './inputs/T2IAdapterModelInputField'; import T2IAdapterModelInputField from './inputs/T2IAdapterModelInputField';
import BoardInputField from './inputs/BoardInputField'; import BoardInputField from './inputs/BoardInputField';
import { useTranslation } from 'react-i18next';
type InputFieldProps = { type InputFieldProps = {
nodeId: string; nodeId: string;
@ -25,11 +26,16 @@ type InputFieldProps = {
}; };
const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => { const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
const { t } = useTranslation();
const field = useFieldData(nodeId, fieldName); const field = useFieldData(nodeId, fieldName);
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input'); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input');
if (fieldTemplate?.fieldKind === 'output') { if (fieldTemplate?.fieldKind === 'output') {
return <Box p={2}>Output field in input: {field?.type}</Box>; return (
<Box p={2}>
{t('nodes.outputFieldInInput')}: {field?.type}
</Box>
);
} }
if ( if (
@ -249,7 +255,7 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
_dark: { color: 'error.300' }, _dark: { color: 'error.300' },
}} }}
> >
Unknown field type: {field?.type} {t('nodes.unknownFieldType')}: {field?.type}
</Text> </Text>
</Box> </Box>
); );

View File

@ -5,6 +5,7 @@ import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
import { PropsWithChildren, memo } from 'react'; import { PropsWithChildren, memo } from 'react';
import FieldHandle from './FieldHandle'; import FieldHandle from './FieldHandle';
import FieldTooltipContent from './FieldTooltipContent'; import FieldTooltipContent from './FieldTooltipContent';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
nodeId: string; nodeId: string;
@ -12,6 +13,7 @@ interface Props {
} }
const OutputField = ({ nodeId, fieldName }: Props) => { const OutputField = ({ nodeId, fieldName }: Props) => {
const { t } = useTranslation();
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'output'); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'output');
const { const {
@ -28,7 +30,7 @@ const OutputField = ({ nodeId, fieldName }: Props) => {
<FormControl <FormControl
sx={{ color: 'error.400', textAlign: 'right', fontSize: 'sm' }} sx={{ color: 'error.400', textAlign: 'right', fontSize: 'sm' }}
> >
Unknown output: {fieldName} {t('nodes.unknownOutput')}: {fieldName}
</FormControl> </FormControl>
</OutputFieldWrapper> </OutputFieldWrapper>
); );

View File

@ -16,6 +16,7 @@ import {
ImagePolymorphicInputFieldValue, ImagePolymorphicInputFieldValue,
} from 'features/nodes/types/types'; } from 'features/nodes/types/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaUndo } from 'react-icons/fa'; import { FaUndo } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import { PostUploadAction } from 'services/api/types'; import { PostUploadAction } from 'services/api/types';
@ -103,18 +104,24 @@ const ImageInputFieldComponent = (
export default memo(ImageInputFieldComponent); export default memo(ImageInputFieldComponent);
const UploadElement = memo(() => ( const UploadElement = memo(() => {
<Text fontSize={16} fontWeight={600}> const { t } = useTranslation();
Drop or Upload return (
</Text> <Text fontSize={16} fontWeight={600}>
)); {t('gallery.dropOrUpload')}
</Text>
);
});
UploadElement.displayName = 'UploadElement'; UploadElement.displayName = 'UploadElement';
const DropLabel = memo(() => ( const DropLabel = memo(() => {
<Text fontSize={16} fontWeight={600}> const { t } = useTranslation();
Drop return (
</Text> <Text fontSize={16} fontWeight={600}>
)); {t('gallery.drop')}
</Text>
);
});
DropLabel.displayName = 'DropLabel'; DropLabel.displayName = 'DropLabel';

View File

@ -91,7 +91,7 @@ const LoRAModelInputFieldComponent = (
return ( return (
<Flex sx={{ justifyContent: 'center', p: 2 }}> <Flex sx={{ justifyContent: 'center', p: 2 }}>
<Text sx={{ fontSize: 'sm', color: 'base.500', _dark: 'base.700' }}> <Text sx={{ fontSize: 'sm', color: 'base.500', _dark: 'base.700' }}>
No LoRAs Loaded {t('models.noLoRAsLoaded')}
</Text> </Text>
</Flex> </Flex>
); );

View File

@ -90,7 +90,7 @@ const Content = (props: {
<EditableNodeTitle nodeId={props.node.data.id} /> <EditableNodeTitle nodeId={props.node.data.id} />
<HStack> <HStack>
<FormControl> <FormControl>
<FormLabel>Node Type</FormLabel> <FormLabel>{t('nodes.nodeType')}</FormLabel>
<Text fontSize="sm" fontWeight={600}> <Text fontSize="sm" fontWeight={600}>
{props.template.title} {props.template.title}
</Text> </Text>
@ -102,7 +102,7 @@ const Content = (props: {
w="full" w="full"
> >
<FormControl isInvalid={needsUpdate}> <FormControl isInvalid={needsUpdate}>
<FormLabel>Node Version</FormLabel> <FormLabel>{t('nodes.nodeVersion')}</FormLabel>
<Text fontSize="sm" fontWeight={600}> <Text fontSize="sm" fontWeight={600}>
{props.node.data.version} {props.node.data.version}
</Text> </Text>

View File

@ -5,11 +5,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaUndo, FaUpload } from 'react-icons/fa'; import { useHotkeys } from 'react-hotkeys-hook';
import InitialImage from './InitialImage'; import { useTranslation } from 'react-i18next';
import { FaRulerVertical, FaUndo, FaUpload } from 'react-icons/fa';
import { PostUploadAction } from 'services/api/types'; import { PostUploadAction } from 'services/api/types';
import InitialImage from './InitialImage';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -17,6 +20,7 @@ const selector = createSelector(
const { initialImage } = state.generation; const { initialImage } = state.generation;
return { return {
isResetButtonDisabled: !initialImage, isResetButtonDisabled: !initialImage,
initialImage,
}; };
}, },
defaultSelectorOptions defaultSelectorOptions
@ -27,7 +31,9 @@ const postUploadAction: PostUploadAction = {
}; };
const InitialImageDisplay = () => { const InitialImageDisplay = () => {
const { isResetButtonDisabled } = useAppSelector(selector); const { recallWidthAndHeight } = useRecallParameters();
const { t } = useTranslation();
const { isResetButtonDisabled, initialImage } = useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
@ -38,6 +44,14 @@ const InitialImageDisplay = () => {
dispatch(clearInitialImage()); dispatch(clearInitialImage());
}, [dispatch]); }, [dispatch]);
const handleUseSizeInitialImage = useCallback(() => {
if (initialImage) {
recallWidthAndHeight(initialImage.width, initialImage.height);
}
}, [initialImage, recallWidthAndHeight]);
useHotkeys('shift+d', handleUseSizeInitialImage, [initialImage]);
return ( return (
<Flex <Flex
layerStyle="first" layerStyle="first"
@ -73,7 +87,7 @@ const InitialImageDisplay = () => {
}, },
}} }}
> >
Initial Image {t('metadata.initImage')}
</Text> </Text>
<Spacer /> <Spacer />
<IAIIconButton <IAIIconButton
@ -82,6 +96,13 @@ const InitialImageDisplay = () => {
icon={<FaUpload />} icon={<FaUpload />}
{...getUploadButtonProps()} {...getUploadButtonProps()}
/> />
<IAIIconButton
tooltip={`${t('parameters.useSize')} (Shift+D)`}
aria-label={`${t('parameters.useSize')} (Shift+D)`}
icon={<FaRulerVertical />}
onClick={handleUseSizeInitialImage}
isDisabled={isResetButtonDisabled}
/>
<IAIIconButton <IAIIconButton
tooltip="Reset Initial Image" tooltip="Reset Initial Image"
aria-label="Reset Initial Image" aria-label="Reset Initial Image"

View File

@ -373,6 +373,26 @@ export const useRecallParameters = () => {
[dispatch, parameterSetToast, parameterNotSetToast] [dispatch, parameterSetToast, parameterNotSetToast]
); );
/**
* Recall width and height with toast
*/
const recallWidthAndHeight = useCallback(
(width: unknown, height: unknown) => {
if (!isValidWidth(width)) {
allParameterNotSetToast();
return;
}
if (!isValidHeight(height)) {
allParameterNotSetToast();
return;
}
dispatch(setHeight(height));
dispatch(setWidth(width));
allParameterSetToast();
},
[dispatch, allParameterSetToast, allParameterNotSetToast]
);
/** /**
* Recall strength with toast * Recall strength with toast
*/ */
@ -966,6 +986,7 @@ export const useRecallParameters = () => {
recallSteps, recallSteps,
recallWidth, recallWidth,
recallHeight, recallHeight,
recallWidthAndHeight,
recallStrength, recallStrength,
recallHrfEnabled, recallHrfEnabled,
recallHrfStrength, recallHrfStrength,

View File

@ -106,7 +106,7 @@ const QueueItemComponent = ({ queueItemDTO }: Props) => {
flexDir="column" flexDir="column"
> >
<Heading size="sm" color="error.500" _dark={{ color: 'error.400' }}> <Heading size="sm" color="error.500" _dark={{ color: 'error.400' }}>
Error {t('common.error')}
</Heading> </Heading>
<pre>{queueItem.error}</pre> <pre>{queueItem.error}</pre>
</Flex> </Flex>

View File

@ -1,3 +1,5 @@
/* eslint-disable i18next/no-literal-string */
import { Flex, Image, Text } from '@chakra-ui/react'; import { Flex, Image, Text } from '@chakra-ui/react';
import InvokeAILogoImage from 'assets/images/logo.png'; import InvokeAILogoImage from 'assets/images/logo.png';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';

View File

@ -423,7 +423,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
<Flex justifyContent="center"> <Flex justifyContent="center">
<Text fontSize="lg"> <Text fontSize="lg">
<Text> <Text>
{t('settings.resetComplete')} Reloading in {countdown}... {t('settings.resetComplete')} {t('settings.reloadingIn')}{' '}
{countdown}...
</Text> </Text>
</Text> </Text>
</Flex> </Flex>

View File

@ -1,43 +0,0 @@
import { atom, computed } from 'nanostores';
import createClient from 'openapi-fetch';
import { paths } from 'services/api/schema';
/**
* We use nanostores to store the token and base url for very simple reactivity
*/
/**
* The user's auth token.
*/
export const $authToken = atom<string | undefined>();
/**
* The OpenAPI base url.
*/
export const $baseUrl = atom<string | undefined>();
/**
* The optional project-id header.
*/
export const $projectId = atom<string | undefined>();
/**
* Autogenerated, type-safe fetch client for the API. Used when RTK Query is not an option.
* Dynamically updates when the token or base url changes.
* Use `$client.get()` to get the client.
*
* @example
* const { get, post, del } = $client.get();
*/
export const $client = computed(
[$authToken, $baseUrl, $projectId],
(authToken, baseUrl, projectId) =>
createClient<paths>({
headers: {
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
...(projectId ? { 'project-id': projectId } : {}),
},
// do not include `api/v1` in the base url for this client
baseUrl: `${baseUrl ?? ''}`,
})
);

View File

@ -4,7 +4,7 @@ import {
ThunkDispatch, ThunkDispatch,
createEntityAdapter, createEntityAdapter,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { $queueId } from 'features/queue/store/queueNanoStore'; import { $queueId } from 'app/store/nanostores/queueId';
import { listParamsReset } from 'features/queue/store/queueSlice'; import { listParamsReset } from 'features/queue/store/queueSlice';
import queryString from 'query-string'; import queryString from 'query-string';
import { ApiTagDescription, api } from '..'; import { ApiTagDescription, api } from '..';

View File

@ -6,7 +6,9 @@ import {
createApi, createApi,
fetchBaseQuery, fetchBaseQuery,
} from '@reduxjs/toolkit/query/react'; } from '@reduxjs/toolkit/query/react';
import { $authToken, $baseUrl, $projectId } from 'services/api/client'; import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $projectId } from 'app/store/nanostores/projectId';
export const tagTypes = [ export const tagTypes = [
'AppVersion', 'AppVersion',

File diff suppressed because one or more lines are too long

View File

@ -1,63 +0,0 @@
import { Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
import { AppThunkDispatch, RootState } from 'app/store/store';
import { $authToken, $baseUrl } from 'services/api/client';
import {
ClientToServerEvents,
ServerToClientEvents,
} from 'services/events/types';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { Socket, io } from 'socket.io-client';
export const socketMiddleware = () => {
let areListenersSet = false;
const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
let socketUrl = `${wsProtocol}://${window.location.host}`;
const socketOptions: Parameters<typeof io>[0] = {
timeout: 60000,
path: '/ws/socket.io',
autoConnect: false, // achtung! removing this breaks the dynamic middleware
};
// if building in package mode, replace socket url with open api base url minus the http protocol
if (['nodes', 'package'].includes(import.meta.env.MODE)) {
const baseUrl = $baseUrl.get();
if (baseUrl) {
//eslint-disable-next-line
socketUrl = baseUrl.replace(/^https?\:\/\//i, '');
}
const authToken = $authToken.get();
if (authToken) {
// TODO: handle providing jwt to socket.io
socketOptions.auth = { token: authToken };
}
socketOptions.transports = ['websocket', 'polling'];
}
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
socketUrl,
socketOptions
);
const middleware: Middleware =
(storeApi: MiddlewareAPI<AppThunkDispatch, RootState>) =>
(next) =>
(action) => {
// Set listeners for `connect` and `disconnect` events once
// Must happen in middleware to get access to `dispatch`
if (!areListenersSet) {
setEventListeners({ storeApi, socket });
areListenersSet = true;
socket.connect();
}
next(action);
};
return middleware;
};

View File

@ -1,7 +1,5 @@
import { MiddlewareAPI } from '@reduxjs/toolkit'; import { $queueId } from 'app/store/nanostores/queueId';
import { logger } from 'app/logging/logger'; import { AppDispatch } from 'app/store/store';
import { AppDispatch, RootState } from 'app/store/store';
import { $queueId } from 'features/queue/store/queueNanoStore';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
@ -23,20 +21,16 @@ import { ClientToServerEvents, ServerToClientEvents } from '../types';
type SetEventListenersArg = { type SetEventListenersArg = {
socket: Socket<ServerToClientEvents, ClientToServerEvents>; socket: Socket<ServerToClientEvents, ClientToServerEvents>;
storeApi: MiddlewareAPI<AppDispatch, RootState>; dispatch: AppDispatch;
}; };
export const setEventListeners = (arg: SetEventListenersArg) => { export const setEventListeners = (arg: SetEventListenersArg) => {
const { socket, storeApi } = arg; const { socket, dispatch } = arg;
const { dispatch } = storeApi;
/** /**
* Connect * Connect
*/ */
socket.on('connect', () => { socket.on('connect', () => {
const log = logger('socketio');
log.debug('Connected');
dispatch(socketConnected()); dispatch(socketConnected());
const queue_id = $queueId.get(); const queue_id = $queueId.get();
socket.emit('subscribe_queue', { queue_id }); socket.emit('subscribe_queue', { queue_id });

View File

@ -3517,6 +3517,14 @@ eslint-config-prettier@^9.0.0:
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f"
integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==
eslint-plugin-i18next@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-i18next/-/eslint-plugin-i18next-6.0.3.tgz#a388f9982deb040102c1c4498e4dc38d9bd6ffd9"
integrity sha512-RtQXYfg6PZCjejIQ/YG+dUj/x15jPhufJ9hUDGH0kCpJ6CkVMAWOQ9exU1CrbPmzeykxLjrXkjAaOZF/V7+DOA==
dependencies:
lodash "^4.17.21"
requireindex "~1.1.0"
eslint-plugin-react-hooks@^4.6.0: eslint-plugin-react-hooks@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
@ -5028,23 +5036,11 @@ open@^8.4.0:
is-docker "^2.1.1" is-docker "^2.1.1"
is-wsl "^2.2.0" is-wsl "^2.2.0"
openapi-fetch@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.8.1.tgz#a2bda1f72a8311e92cc789d1c8fec7b2d8ca28b6"
integrity sha512-xmzMaBCydPTMd0TKy4P2DYx/JOe9yjXtPIky1n1GV7nJJdZ3IZgSHvAWVbe06WsPD8EreR7E97IAiskPr6sa2g==
dependencies:
openapi-typescript-helpers "^0.0.4"
openapi-types@^12.1.3: openapi-types@^12.1.3:
version "12.1.3" version "12.1.3"
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==
openapi-typescript-helpers@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.4.tgz#ffe7c4868f094fcc8502dbdcddc6c32ce8011aee"
integrity sha512-Q0MTapapFAG993+dx8lNw33X6P/6EbFr31yNymJHq56fNc6dODyRm8tWyRnGxuC74lyl1iCRMV6nQCGQsfVNKg==
openapi-typescript@^6.7.0: openapi-typescript@^6.7.0:
version "6.7.0" version "6.7.0"
resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-6.7.0.tgz#6d1a4dfc0db60b61573a3ea3c52984a79c638c67" resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-6.7.0.tgz#6d1a4dfc0db60b61573a3ea3c52984a79c638c67"
@ -5629,6 +5625,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
requireindex@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162"
integrity sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==
requirejs-config-file@^4.0.0: requirejs-config-file@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc"