mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into refactor/model-manager-3
This commit is contained in:
commit
60eae7443a
@ -20,10 +20,16 @@ module.exports = {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'],
|
||||
plugins: [
|
||||
'react',
|
||||
'@typescript-eslint',
|
||||
'eslint-plugin-react-hooks',
|
||||
'i18next',
|
||||
],
|
||||
root: true,
|
||||
rules: {
|
||||
curly: 'error',
|
||||
'i18next/no-literal-string': 2,
|
||||
'react/jsx-no-bind': ['error', { allowBind: true }],
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
|
@ -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`.
|
@ -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.
|
@ -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`
|
@ -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>
|
@ -1,33 +1,98 @@
|
||||
# InvokeAI Web UI
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [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)
|
||||
- [Dev Environment](#dev-environment)
|
||||
- [VSCode Remote Dev](#vscode-remote-dev)
|
||||
- [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:
|
||||
- `createAsyncThunk` for HTTP requests
|
||||
- `createEntityAdapter` for fetching images and models
|
||||
- `createListenerMiddleware` for workflows
|
||||
The app makes heavy use of a handful of libraries.
|
||||
|
||||
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
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky",
|
||||
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
|
||||
"dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"",
|
||||
"build": "yarn run lint && vite build",
|
||||
@ -30,7 +29,7 @@
|
||||
"lint:prettier": "prettier --check .",
|
||||
"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\"",
|
||||
"fix": "eslint --fix . && prettier --loglevel warn --write . && tsc --noEmit",
|
||||
"fix": "eslint --fix . && prettier --loglevel warn --write .",
|
||||
"lint-staged": "lint-staged",
|
||||
"postinstall": "patch-package && yarn run theme",
|
||||
"theme": "chakra-cli tokens src/theme/theme.ts",
|
||||
@ -80,7 +79,6 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanostores": "^0.9.4",
|
||||
"new-github-issue-url": "^1.0.0",
|
||||
"openapi-fetch": "^0.8.1",
|
||||
"overlayscrollbars": "^2.4.4",
|
||||
"overlayscrollbars-react": "^0.5.3",
|
||||
"patch-package": "^8.0.0",
|
||||
@ -133,6 +131,7 @@
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-i18next": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"husky": "^8.0.3",
|
||||
|
@ -90,7 +90,16 @@
|
||||
"openInNewTab": "In einem neuem Tab öffnen",
|
||||
"statusProcessing": "wird bearbeitet",
|
||||
"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": {
|
||||
"generations": "Erzeugungen",
|
||||
@ -110,7 +119,6 @@
|
||||
"preparingDownload": "bereite Download vor",
|
||||
"preparingDownloadFailed": "Problem beim Download vorbereiten",
|
||||
"deleteImage": "Lösche Bild",
|
||||
"images": "Bilder",
|
||||
"copy": "Kopieren",
|
||||
"download": "Runterladen",
|
||||
"setCurrentImage": "Setze aktuelle Bild",
|
||||
@ -120,7 +128,8 @@
|
||||
"downloadSelection": "Auswahl herunterladen",
|
||||
"currentlyInUse": "Dieses Bild wird derzeit in den folgenden Funktionen verwendet:",
|
||||
"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": {
|
||||
"keyboardShortcuts": "Tastenkürzel",
|
||||
@ -454,7 +463,7 @@
|
||||
"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.",
|
||||
"modelDeleted": "Modell gelöscht",
|
||||
"inpainting": "v1 Ausmalen",
|
||||
"inpainting": "v1 Inpainting",
|
||||
"modelUpdateFailed": "Modellaktualisierung fehlgeschlagen",
|
||||
"useCustomConfig": "Benutzerdefinierte Konfiguration verwenden",
|
||||
"settings": "Einstellungen",
|
||||
@ -473,7 +482,10 @@
|
||||
"variant": "Variante",
|
||||
"loraModels": "LoRAs",
|
||||
"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": {
|
||||
"images": "Bilder",
|
||||
@ -683,7 +695,8 @@
|
||||
"exitViewer": "Betrachten beenden",
|
||||
"menu": "Menü",
|
||||
"loadMore": "Mehr laden",
|
||||
"invokeProgressBar": "Invoke Fortschrittsanzeige"
|
||||
"invokeProgressBar": "Invoke Fortschrittsanzeige",
|
||||
"mode": "Modus"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Automatisches Hinzufügen zum Ordner",
|
||||
@ -701,7 +714,11 @@
|
||||
"changeBoard": "Ordner wechseln",
|
||||
"loading": "Laden...",
|
||||
"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": {
|
||||
"showAdvanced": "Zeige Erweitert",
|
||||
@ -786,7 +803,8 @@
|
||||
"canny": "Canny",
|
||||
"hedDescription": "Ganzheitlich verschachtelte Kantenerkennung",
|
||||
"scribble": "Scribble",
|
||||
"maxFaces": "Maximal Anzahl Gesichter"
|
||||
"maxFaces": "Maximal Anzahl Gesichter",
|
||||
"unstarImage": "Markierung aufheben"
|
||||
},
|
||||
"queue": {
|
||||
"status": "Status",
|
||||
@ -840,7 +858,8 @@
|
||||
"pauseTooltip": "Pause von Prozessor",
|
||||
"back": "Hinten",
|
||||
"resumeSucceeded": "Prozessor wieder aufgenommen",
|
||||
"resumeTooltip": "Prozessor wieder aufnehmen"
|
||||
"resumeTooltip": "Prozessor wieder aufnehmen",
|
||||
"time": "Zeit"
|
||||
},
|
||||
"metadata": {
|
||||
"negativePrompt": "Negativ Beschreibung",
|
||||
@ -868,7 +887,8 @@
|
||||
"vae": "VAE",
|
||||
"workflow": "Arbeitsablauf",
|
||||
"scheduler": "Scheduler",
|
||||
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden"
|
||||
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden",
|
||||
"recallParameters": "Recall Parameters"
|
||||
},
|
||||
"popovers": {
|
||||
"noiseUseCPU": {
|
||||
@ -944,7 +964,9 @@
|
||||
"booleanCollection": "Boolesche Werte Sammlung",
|
||||
"cannotConnectToSelf": "Es kann keine Verbindung zu sich selbst hergestellt werden",
|
||||
"colorCodeEdges": "Farbkodierte Kanten",
|
||||
"addNodeToolTip": "Knoten hinzufügen (Umschalt+A, Leertaste)"
|
||||
"addNodeToolTip": "Knoten hinzufügen (Umschalt+A, Leertaste)",
|
||||
"boardField": "Ordner",
|
||||
"boardFieldDescription": "Ein Galerie Ordner"
|
||||
},
|
||||
"hrf": {
|
||||
"enableHrf": "Aktivieren Sie die Korrektur für hohe Auflösungen",
|
||||
@ -968,6 +990,8 @@
|
||||
"selectModel": "Wählen ein Modell aus",
|
||||
"noRefinerModelsInstalled": "Keine SDXL Refiner-Modelle installiert",
|
||||
"noLoRAsInstalled": "Keine LoRAs installiert",
|
||||
"selectLoRA": "Wählen ein LoRA aus"
|
||||
"selectLoRA": "Wählen ein LoRA aus",
|
||||
"esrganModel": "ESRGAN Modell",
|
||||
"addLora": "LoRA hinzufügen"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"accessibility": {
|
||||
"copyMetadataJson": "Copy metadata JSON",
|
||||
"createIssue":"Create Issue",
|
||||
"exitViewer": "Exit Viewer",
|
||||
"flipHorizontally": "Flip Horizontally",
|
||||
"flipVertically": "Flip Vertically",
|
||||
@ -12,6 +13,7 @@
|
||||
"nextImage": "Next Image",
|
||||
"previousImage": "Previous Image",
|
||||
"reset": "Reset",
|
||||
"resetUI":"$t(accessibility.reset) UI",
|
||||
"rotateClockwise": "Rotate Clockwise",
|
||||
"rotateCounterClockwise": "Rotate Counter-Clockwise",
|
||||
"showGalleryPanel": "Show Gallery Panel",
|
||||
@ -38,6 +40,8 @@
|
||||
"loading": "Loading...",
|
||||
"menuItemAutoAdd": "Auto-add to this Board",
|
||||
"move": "Move",
|
||||
"movingImagesToBoard_one": "Moving {{count}} image to board:",
|
||||
"movingImagesToBoard_other": "Moving {{count}} images to board:",
|
||||
"myBoard": "My Board",
|
||||
"noMatching": "No matching Boards",
|
||||
"searchBoard": "Search Boards...",
|
||||
@ -49,11 +53,13 @@
|
||||
"common": {
|
||||
"accept": "Accept",
|
||||
"advanced": "Advanced",
|
||||
"ai": "ai",
|
||||
"areYouSure": "Are you sure?",
|
||||
"auto": "Auto",
|
||||
"back": "Back",
|
||||
"batch": "Batch Manager",
|
||||
"cancel": "Cancel",
|
||||
"copyError":"$t(gallery.copy) Error",
|
||||
"close": "Close",
|
||||
"on": "On",
|
||||
"checkpoint": "Checkpoint",
|
||||
@ -67,6 +73,10 @@
|
||||
"darkMode": "Dark Mode",
|
||||
"discordLabel": "Discord",
|
||||
"dontAskMeAgain": "Don't ask me again",
|
||||
"error": "Error",
|
||||
"file": "File",
|
||||
"folder": "Folder",
|
||||
"format":"format",
|
||||
"generate": "Generate",
|
||||
"githubLabel": "Github",
|
||||
"hotkeysLabel": "Hotkeys",
|
||||
@ -74,6 +84,8 @@
|
||||
"imageFailedToLoad": "Unable to Load Image",
|
||||
"img2img": "Image To Image",
|
||||
"inpaint": "inpaint",
|
||||
"input": "Input",
|
||||
"installed": "Installed",
|
||||
"langArabic": "العربية",
|
||||
"langBrPortuguese": "Português do Brasil",
|
||||
"langDutch": "Nederlands",
|
||||
@ -101,6 +113,7 @@
|
||||
"nodeEditor": "Node 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.",
|
||||
"notInstalled": "Not $t(common.installed)",
|
||||
"openInNewTab": "Open in New Tab",
|
||||
"outpaint": "outpaint",
|
||||
"outputs": "Outputs",
|
||||
@ -114,6 +127,7 @@
|
||||
"safetensors": "Safetensors",
|
||||
"settingsLabel": "Settings",
|
||||
"simple": "Simple",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"statusConnected": "Connected",
|
||||
"statusConvertingModel": "Converting Model",
|
||||
"statusDisconnected": "Disconnected",
|
||||
@ -252,6 +266,7 @@
|
||||
"embedding": {
|
||||
"addEmbedding": "Add Embedding",
|
||||
"incompatibleModel": "Incompatible base model:",
|
||||
"noEmbeddingsLoaded": "No Embeddings Loaded",
|
||||
"noMatchingEmbedding": "No matching Embeddings"
|
||||
},
|
||||
"queue": {
|
||||
@ -330,7 +345,8 @@
|
||||
"enableFailed": "Problem Enabling Invocation Cache",
|
||||
"disable": "Disable",
|
||||
"disableSucceeded": "Invocation Cache Disabled",
|
||||
"disableFailed": "Problem Disabling Invocation Cache"
|
||||
"disableFailed": "Problem Disabling Invocation Cache",
|
||||
"useCache": "Use Cache"
|
||||
},
|
||||
"gallery": {
|
||||
"allImagesLoaded": "All Images Loaded",
|
||||
@ -339,6 +355,9 @@
|
||||
"autoSwitchNewImages": "Auto-Switch to New Images",
|
||||
"copy": "Copy",
|
||||
"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",
|
||||
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
|
||||
"deleteImagePermanent": "Deleted images cannot be restored.",
|
||||
@ -348,7 +367,7 @@
|
||||
"galleryImageSize": "Image Size",
|
||||
"gallerySettings": "Gallery Settings",
|
||||
"generations": "Generations",
|
||||
"images": "Images",
|
||||
"image": "image",
|
||||
"loading": "Loading",
|
||||
"loadMore": "Load More",
|
||||
"maintainAspectRatio": "Maintain Aspect Ratio",
|
||||
@ -360,6 +379,7 @@
|
||||
"singleColumnLayout": "Single Column Layout",
|
||||
"unableToLoad": "Unable to load Gallery",
|
||||
"uploads": "Uploads",
|
||||
"deleteSelection": "Delete Selection",
|
||||
"downloadSelection": "Download Selection",
|
||||
"preparingDownload": "Preparing Download",
|
||||
"preparingDownloadFailed": "Problem Preparing Download"
|
||||
@ -629,6 +649,7 @@
|
||||
"closeAdvanced": "Close Advanced",
|
||||
"config": "Config",
|
||||
"configValidationMsg": "Path to the config file of your model.",
|
||||
"conversionNotSupported": "Conversion Not Supported",
|
||||
"convert": "Convert",
|
||||
"convertingModelBegin": "Converting Model. Please wait.",
|
||||
"convertToDiffusers": "Convert To Diffusers",
|
||||
@ -754,6 +775,7 @@
|
||||
"esrganModel": "ESRGAN Model",
|
||||
"loading": "loading",
|
||||
"noLoRAsAvailable": "No LoRAs available",
|
||||
"noLoRAsLoaded":"No LoRAs Loaded",
|
||||
"noMatchingLoRAs": "No matching LoRAs",
|
||||
"noMatchingModels": "No matching Models",
|
||||
"noModelsAvailable": "No models available",
|
||||
@ -765,6 +787,7 @@
|
||||
"nodes": {
|
||||
"addNode": "Add Node",
|
||||
"addNodeToolTip": "Add Node (Shift+A, Space)",
|
||||
"addLinearView":"Add to Linear View",
|
||||
"animatedEdges": "Animated Edges",
|
||||
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
|
||||
"boardField": "Board",
|
||||
@ -885,6 +908,7 @@
|
||||
"noMatchingNodes": "No matching nodes",
|
||||
"noNodeSelected": "No node selected",
|
||||
"nodeOpacity": "Node Opacity",
|
||||
"nodeVersion": "Node Version",
|
||||
"noOutputRecorded": "No outputs recorded",
|
||||
"noOutputSchemaName": "No output schema name found in ref object",
|
||||
"notes": "Notes",
|
||||
@ -892,6 +916,7 @@
|
||||
"oNNXModelField": "ONNX Model",
|
||||
"oNNXModelFieldDescription": "ONNX model field.",
|
||||
"outputField": "Output Field",
|
||||
"outputFieldInInput": "Output field in input",
|
||||
"outputFields": "Output Fields",
|
||||
"outputNode": "Output node",
|
||||
"outputSchemaNotFound": "Output schema not found",
|
||||
@ -937,10 +962,14 @@
|
||||
"uNetFieldDescription": "UNet submodel.",
|
||||
"unhandledInputProperty": "Unhandled input property",
|
||||
"unhandledOutputProperty": "Unhandled output property",
|
||||
"unknownField": "Unknown Field",
|
||||
"unknownField": "Unknown field",
|
||||
"unknownFieldType": "$(nodes.unknownField) type",
|
||||
"unknownNode": "Unknown Node",
|
||||
"unknownNodeType":"$t(nodes.unknownNode) type",
|
||||
"unknownTemplate": "Unknown Template",
|
||||
"unknownInput": "Unknown input",
|
||||
"unkownInvocation": "Unknown Invocation type",
|
||||
"unknownOutput": "Unknown output",
|
||||
"updateNode": "Update Node",
|
||||
"updateAllNodes": "Update All Nodes",
|
||||
"updateApp": "Update App",
|
||||
@ -1082,6 +1111,7 @@
|
||||
"upscaling": "Upscaling",
|
||||
"unmasked": "Unmasked",
|
||||
"useAll": "Use All",
|
||||
"useSize": "Use Size",
|
||||
"useCpuNoise": "Use CPU Noise",
|
||||
"cpuNoise": "CPU Noise",
|
||||
"gpuNoise": "GPU Noise",
|
||||
@ -1171,7 +1201,8 @@
|
||||
"clearIntermediatesWithCount_other": "Clear {{count}} Intermediates",
|
||||
"intermediatesCleared_one": "Cleared {{count}} Intermediate",
|
||||
"intermediatesCleared_other": "Cleared {{count}} Intermediates",
|
||||
"intermediatesClearedFailed": "Problem Clearing Intermediates"
|
||||
"intermediatesClearedFailed": "Problem Clearing Intermediates",
|
||||
"reloadingIn": "Reloading in"
|
||||
},
|
||||
"toast": {
|
||||
"addedToBoard": "Added to board",
|
||||
@ -1199,6 +1230,7 @@
|
||||
"initialImageNotSet": "Initial Image Not Set",
|
||||
"initialImageNotSetDesc": "Could not load initial image",
|
||||
"initialImageSet": "Initial Image Set",
|
||||
"invalidUpload": "Invalid Upload",
|
||||
"loadedWithWarnings": "Workflow Loaded with Warnings",
|
||||
"maskSavedAssets": "Mask Saved to Assets",
|
||||
"maskSentControlnetAssets": "Mask Sent to ControlNet & Assets",
|
||||
@ -1517,7 +1549,7 @@
|
||||
"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.",
|
||||
"clearHistory": "Clear History",
|
||||
"clearMask": "Clear Mask",
|
||||
"clearMask": "Clear Mask (Shift+C)",
|
||||
"colorPicker": "Color Picker",
|
||||
"copyToClipboard": "Copy to Clipboard",
|
||||
"cursorPosition": "Cursor Position",
|
||||
@ -1544,6 +1576,7 @@
|
||||
"redo": "Redo",
|
||||
"resetView": "Reset View",
|
||||
"saveBoxRegionOnly": "Save Box Region Only",
|
||||
"saveMask":"Save $t(unifiedCanvas.mask)",
|
||||
"saveToGallery": "Save To Gallery",
|
||||
"scaledBoundingBox": "Scaled Bounding Box",
|
||||
"showCanvasDebugInfo": "Show Additional Canvas Info",
|
||||
|
@ -98,7 +98,6 @@
|
||||
"deleteImage": "Eliminar Imagen",
|
||||
"deleteImageBin": "Las imágenes eliminadas se enviarán a la papelera de tu sistema operativo.",
|
||||
"deleteImagePermanent": "Las imágenes eliminadas no se pueden restaurar.",
|
||||
"images": "Imágenes",
|
||||
"assets": "Activos",
|
||||
"autoAssignBoardOnClick": "Asignación automática de tableros al hacer clic"
|
||||
},
|
||||
|
@ -89,7 +89,9 @@
|
||||
"t2iAdapter": "Adattatore T2I",
|
||||
"controlAdapter": "Adattatore di Controllo",
|
||||
"controlNet": "ControlNet",
|
||||
"auto": "Automatico"
|
||||
"auto": "Automatico",
|
||||
"simple": "Semplice",
|
||||
"details": "Dettagli"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@ -108,7 +110,6 @@
|
||||
"deleteImage": "Elimina l'immagine",
|
||||
"deleteImagePermanent": "Le immagini eliminate non possono essere ripristinate.",
|
||||
"deleteImageBin": "Le immagini eliminate verranno spostate nel Cestino del tuo sistema operativo.",
|
||||
"images": "Immagini",
|
||||
"assets": "Risorse",
|
||||
"autoAssignBoardOnClick": "Assegna automaticamente la bacheca al clic",
|
||||
"featuresWillReset": "Se elimini questa immagine, quelle funzionalità verranno immediatamente ripristinate.",
|
||||
@ -120,7 +121,8 @@
|
||||
"setCurrentImage": "Imposta come immagine corrente",
|
||||
"preparingDownload": "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": {
|
||||
"keyboardShortcuts": "Tasti rapidi",
|
||||
@ -474,7 +476,8 @@
|
||||
"closeAdvanced": "Chiudi Avanzate",
|
||||
"modelType": "Tipo di modello",
|
||||
"customConfigFileLocation": "Posizione del file di configurazione personalizzato",
|
||||
"vaePrecision": "Precisione VAE"
|
||||
"vaePrecision": "Precisione VAE",
|
||||
"noModelSelected": "Nessun modello selezionato"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Immagini",
|
||||
@ -601,7 +604,9 @@
|
||||
"seamlessX": "Senza cuciture X",
|
||||
"seamlessY": "Senza cuciture Y",
|
||||
"imageActions": "Azioni Immagine",
|
||||
"aspectRatioFree": "Libere"
|
||||
"aspectRatioFree": "Libere",
|
||||
"maskEdge": "Maschera i bordi",
|
||||
"unmasked": "No maschera"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Modelli",
|
||||
@ -642,7 +647,10 @@
|
||||
"clearIntermediatesWithCount_one": "Cancella {{count}} immagine intermedia",
|
||||
"clearIntermediatesWithCount_many": "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": {
|
||||
"tempFoldersEmptied": "Cartella temporanea svuotata",
|
||||
@ -727,7 +735,8 @@
|
||||
"setCanvasInitialImage": "Imposta come immagine iniziale della tela",
|
||||
"workflowLoaded": "Flusso di lavoro caricato",
|
||||
"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": {
|
||||
"feature": {
|
||||
@ -828,7 +837,8 @@
|
||||
"modifyConfig": "Modifica configurazione",
|
||||
"menu": "Menu",
|
||||
"showGalleryPanel": "Mostra il pannello Galleria",
|
||||
"loadMore": "Carica altro"
|
||||
"loadMore": "Carica altro",
|
||||
"mode": "Modalità"
|
||||
},
|
||||
"ui": {
|
||||
"hideProgressImages": "Nascondi avanzamento immagini",
|
||||
@ -1048,7 +1058,11 @@
|
||||
"noMatching": "Nessuna bacheca corrispondente",
|
||||
"selectBoard": "Seleziona una Bacheca",
|
||||
"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": {
|
||||
"contentShuffleDescription": "Rimescola il contenuto di un'immagine",
|
||||
@ -1089,7 +1103,7 @@
|
||||
"none": "Nessuno",
|
||||
"incompatibleBaseModel": "Modello base incompatibile:",
|
||||
"pidiDescription": "Elaborazione immagini PIDI",
|
||||
"fill": "Riempire",
|
||||
"fill": "Riempie",
|
||||
"colorMapDescription": "Genera una mappa dei colori dall'immagine",
|
||||
"lineartAnimeDescription": "Elaborazione lineart in stile anime",
|
||||
"imageResolution": "Risoluzione dell'immagine",
|
||||
@ -1183,7 +1197,9 @@
|
||||
"clearQueueAlertDialog2": "Sei sicuro di voler cancellare la coda?",
|
||||
"item": "Elemento",
|
||||
"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": {
|
||||
"noMatchingEmbedding": "Nessun Incorporamento corrispondente",
|
||||
@ -1199,7 +1215,9 @@
|
||||
"selectModel": "Seleziona un modello",
|
||||
"selectLoRA": "Seleziona un LoRA",
|
||||
"noRefinerModelsInstalled": "Nessun modello SDXL Refiner installato",
|
||||
"noLoRAsInstalled": "Nessun LoRA installato"
|
||||
"noLoRAsInstalled": "Nessun LoRA installato",
|
||||
"esrganModel": "Modello ESRGAN",
|
||||
"addLora": "Aggiungi LoRA"
|
||||
},
|
||||
"invocationCache": {
|
||||
"disable": "Disabilita",
|
||||
@ -1231,7 +1249,8 @@
|
||||
"promptsWithCount_one": "{{count}} Prompt",
|
||||
"promptsWithCount_many": "{{count}} Prompt",
|
||||
"promptsWithCount_other": "{{count}} Prompt",
|
||||
"dynamicPrompts": "Prompt dinamici"
|
||||
"dynamicPrompts": "Prompt dinamici",
|
||||
"promptsPreview": "Anteprima dei prompt"
|
||||
},
|
||||
"popovers": {
|
||||
"paramScheduler": {
|
||||
|
@ -660,7 +660,7 @@
|
||||
"queueTotal": "合計 {{total}}",
|
||||
"resumeSucceeded": "処理が再開されました",
|
||||
"resumeTooltip": "処理を再開",
|
||||
"resume": "再会",
|
||||
"resume": "再開",
|
||||
"status": "ステータス",
|
||||
"pruneSucceeded": "キューから完了アイテム{{item_count}}件を削除しました",
|
||||
"cancelTooltip": "現在のアイテムをキャンセル",
|
||||
|
@ -110,7 +110,6 @@
|
||||
"deleteImageBin": "Verwijderde afbeeldingen worden naar de prullenbak van je besturingssysteem gestuurd.",
|
||||
"deleteImagePermanent": "Verwijderde afbeeldingen kunnen niet worden hersteld.",
|
||||
"assets": "Eigen onderdelen",
|
||||
"images": "Afbeeldingen",
|
||||
"autoAssignBoardOnClick": "Ken automatisch bord toe bij klikken",
|
||||
"featuresWillReset": "Als je deze afbeelding verwijdert, dan worden deze functies onmiddellijk teruggezet.",
|
||||
"loading": "Bezig met laden",
|
||||
|
@ -101,7 +101,6 @@
|
||||
"deleteImagePermanent": "Удаленные изображения невозможно восстановить.",
|
||||
"deleteImageBin": "Удаленные изображения будут отправлены в корзину вашей операционной системы.",
|
||||
"deleteImage": "Удалить изображение",
|
||||
"images": "Изображения",
|
||||
"assets": "Ресурсы",
|
||||
"autoAssignBoardOnClick": "Авто-назначение доски по клику"
|
||||
},
|
||||
|
@ -90,7 +90,16 @@
|
||||
"controlAdapter": "Control Adapter",
|
||||
"controlNet": "ControlNet",
|
||||
"on": "开",
|
||||
"auto": "自动"
|
||||
"auto": "自动",
|
||||
"checkpoint": "Checkpoint",
|
||||
"inpaint": "内补重绘",
|
||||
"simple": "简单",
|
||||
"template": "模板",
|
||||
"outputs": "输出",
|
||||
"data": "数据",
|
||||
"safetensors": "Safetensors",
|
||||
"outpaint": "外扩绘制",
|
||||
"details": "详情"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "生成的图像",
|
||||
@ -109,7 +118,6 @@
|
||||
"deleteImage": "删除图片",
|
||||
"deleteImageBin": "被删除的图片会发送到你操作系统的回收站。",
|
||||
"deleteImagePermanent": "删除的图片无法被恢复。",
|
||||
"images": "图片",
|
||||
"assets": "素材",
|
||||
"autoAssignBoardOnClick": "点击后自动分配面板",
|
||||
"featuresWillReset": "如果您删除该图像,这些功能会立即被重置。",
|
||||
@ -121,7 +129,8 @@
|
||||
"setCurrentImage": "设为当前图像",
|
||||
"preparingDownload": "准备下载",
|
||||
"preparingDownloadFailed": "准备下载时出现问题",
|
||||
"downloadSelection": "下载所选内容"
|
||||
"downloadSelection": "下载所选内容",
|
||||
"noImageSelected": "无选中的图像"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "键盘快捷键",
|
||||
@ -475,7 +484,9 @@
|
||||
"oliveModels": "Olive",
|
||||
"loraModels": "LoRA",
|
||||
"alpha": "Alpha",
|
||||
"vaePrecision": "VAE 精度"
|
||||
"vaePrecision": "VAE 精度",
|
||||
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
|
||||
"noModelSelected": "无选中的模型"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "图像",
|
||||
@ -602,7 +613,9 @@
|
||||
"seamlessX&Y": "无缝 X & Y",
|
||||
"aspectRatioFree": "自由",
|
||||
"seamlessX": "无缝 X",
|
||||
"seamlessY": "无缝 Y"
|
||||
"seamlessY": "无缝 Y",
|
||||
"maskEdge": "遮罩边缘",
|
||||
"unmasked": "取消遮罩"
|
||||
},
|
||||
"settings": {
|
||||
"models": "模型",
|
||||
@ -639,7 +652,10 @@
|
||||
"clearIntermediatesDesc1": "清除中间产物会重置您的画布和 ControlNet 状态。",
|
||||
"intermediatesClearedFailed": "清除中间产物时出现问题",
|
||||
"clearIntermediatesWithCount_other": "清除 {{count}} 个中间产物",
|
||||
"clearIntermediatesDisabled": "队列为空才能清理中间产物"
|
||||
"clearIntermediatesDisabled": "队列为空才能清理中间产物",
|
||||
"enableNSFWChecker": "启用成人内容检测器",
|
||||
"enableInvisibleWatermark": "启用不可见水印",
|
||||
"enableInformationalPopovers": "启用信息弹窗"
|
||||
},
|
||||
"toast": {
|
||||
"tempFoldersEmptied": "临时文件夹已清空",
|
||||
@ -705,7 +721,7 @@
|
||||
"modelAddFailed": "模型添加失败",
|
||||
"problemDownloadingCanvas": "下载画布时出现问题",
|
||||
"problemMergingCanvas": "合并画布时出现问题",
|
||||
"setCanvasInitialImage": "设为画布初始图像",
|
||||
"setCanvasInitialImage": "设定画布初始图像",
|
||||
"imageUploaded": "图像已上传",
|
||||
"addedToBoard": "已添加到面板",
|
||||
"workflowLoaded": "工作流已加载",
|
||||
@ -722,7 +738,8 @@
|
||||
"canvasSavedGallery": "画布已保存到图库",
|
||||
"imageUploadFailed": "图像上传失败",
|
||||
"problemImportingMask": "导入遮罩时出现问题",
|
||||
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型"
|
||||
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型",
|
||||
"setAsCanvasInitialImage": "设为画布初始图像"
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"layer": "图层",
|
||||
@ -808,7 +825,8 @@
|
||||
"toggleAutoscroll": "切换自动缩放",
|
||||
"menu": "菜单",
|
||||
"showGalleryPanel": "显示图库浮窗",
|
||||
"loadMore": "加载更多"
|
||||
"loadMore": "加载更多",
|
||||
"mode": "模式"
|
||||
},
|
||||
"ui": {
|
||||
"showProgressImages": "显示处理中的图片",
|
||||
@ -1031,7 +1049,9 @@
|
||||
"integerPolymorphic": "整数多态",
|
||||
"latentsPolymorphic": "Latents 多态",
|
||||
"conditioningField": "条件",
|
||||
"latentsField": "Latents"
|
||||
"latentsField": "Latents",
|
||||
"updateAllNodes": "更新所有节点",
|
||||
"unableToUpdateNodes_other": "{{count}} 个节点无法完成更新"
|
||||
},
|
||||
"controlnet": {
|
||||
"resize": "直接缩放",
|
||||
@ -1117,7 +1137,8 @@
|
||||
"openPose": "Openpose",
|
||||
"controlAdapter_other": "Control Adapters",
|
||||
"lineartAnime": "Lineart Anime",
|
||||
"canny": "Canny"
|
||||
"canny": "Canny",
|
||||
"unstarImage": "取消收藏图像"
|
||||
},
|
||||
"queue": {
|
||||
"status": "状态",
|
||||
@ -1176,7 +1197,9 @@
|
||||
"queueTotal": "总计 {{total}}",
|
||||
"enqueueing": "队列中的批次",
|
||||
"queueMaxExceeded": "超出最大值 {{max_queue_size}},将跳过 {{skip}}",
|
||||
"graphFailedToQueue": "节点图加入队列失败"
|
||||
"graphFailedToQueue": "节点图加入队列失败",
|
||||
"batchFieldValues": "批处理值",
|
||||
"time": "时间"
|
||||
},
|
||||
"sdxl": {
|
||||
"refinerStart": "Refiner 开始作用时机",
|
||||
@ -1234,7 +1257,9 @@
|
||||
"selectModel": "选择一个模型",
|
||||
"selectLoRA": "选择一个 LoRA",
|
||||
"noRefinerModelsInstalled": "无已安装的 SDXL Refiner 模型",
|
||||
"noLoRAsInstalled": "无已安装的 LoRA"
|
||||
"noLoRAsInstalled": "无已安装的 LoRA",
|
||||
"esrganModel": "ESRGAN 模型",
|
||||
"addLora": "添加 LoRA"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "自动添加面板",
|
||||
@ -1252,7 +1277,11 @@
|
||||
"changeBoard": "更改面板",
|
||||
"loading": "加载中...",
|
||||
"clearSearch": "清除检索",
|
||||
"downloadBoard": "下载面板"
|
||||
"downloadBoard": "下载面板",
|
||||
"deleteBoardOnly": "仅删除面板",
|
||||
"deleteBoard": "删除面板",
|
||||
"deleteBoardAndImages": "删除面板和图像",
|
||||
"deletedBoardsCannotbeRestored": "已删除的面板无法被恢复"
|
||||
},
|
||||
"embedding": {
|
||||
"noMatchingEmbedding": "不匹配的 Embedding",
|
||||
@ -1271,7 +1300,8 @@
|
||||
"combinatorial": "组合生成",
|
||||
"maxPrompts": "最大提示词数",
|
||||
"dynamicPrompts": "动态提示词",
|
||||
"promptsWithCount_other": "{{count}} 个提示词"
|
||||
"promptsWithCount_other": "{{count}} 个提示词",
|
||||
"promptsPreview": "提示词预览"
|
||||
},
|
||||
"popovers": {
|
||||
"compositingMaskAdjustments": {
|
||||
|
@ -1,61 +1,19 @@
|
||||
import fs from 'node:fs';
|
||||
import openapiTS from 'openapi-typescript';
|
||||
import { COLORS } from './colors.js';
|
||||
|
||||
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
|
||||
const OUTPUT_FILE = 'src/services/api/schema.d.ts';
|
||||
|
||||
async function main() {
|
||||
process.stdout.write(
|
||||
`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...\n\n`
|
||||
`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`
|
||||
);
|
||||
const types = await openapiTS(OPENAPI_URL, {
|
||||
exportType: true,
|
||||
transform: (schemaObject, metadata) => {
|
||||
transform: (schemaObject) => {
|
||||
if ('format' in schemaObject && schemaObject.format === 'binary') {
|
||||
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);
|
||||
|
@ -20,6 +20,7 @@ import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
||||
import GlobalHotkeys from './GlobalHotkeys';
|
||||
import PreselectedImage from './PreselectedImage';
|
||||
import Toaster from './Toaster';
|
||||
import { useSocketIO } from '../hooks/useSocketIO';
|
||||
|
||||
const DEFAULT_CONFIG = {};
|
||||
|
||||
@ -33,10 +34,12 @@ interface Props {
|
||||
|
||||
const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
const language = useAppSelector(languageSelector);
|
||||
|
||||
const logger = useLogger('system');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// singleton!
|
||||
useSocketIO();
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
|
@ -2,6 +2,7 @@ import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import newGithubIssueUrl from 'new-github-issue-url';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCopy, FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import { FaArrowRotateLeft } from 'react-icons/fa6';
|
||||
import { serializeError } from 'serialize-error';
|
||||
@ -13,6 +14,7 @@ type Props = {
|
||||
|
||||
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
const toast = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
const text = JSON.stringify(serializeError(error), null, 2);
|
||||
@ -53,7 +55,7 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
p: 16,
|
||||
}}
|
||||
>
|
||||
<Heading>Something went wrong</Heading>
|
||||
<Heading>{t('common.somethingWentWrong')}</Heading>
|
||||
<Flex
|
||||
layerStyle="second"
|
||||
sx={{
|
||||
@ -80,13 +82,15 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
leftIcon={<FaArrowRotateLeft />}
|
||||
onClick={resetErrorBoundary}
|
||||
>
|
||||
Reset UI
|
||||
{t('accessibility.resetUI')}
|
||||
</IAIButton>
|
||||
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}>
|
||||
Copy Error
|
||||
{t('common.copyError')}
|
||||
</IAIButton>
|
||||
<Link href={url} isExternal>
|
||||
<IAIButton leftIcon={<FaExternalLinkAlt />}>Create Issue</IAIButton>
|
||||
<IAIButton leftIcon={<FaExternalLinkAlt />}>
|
||||
{t('accessibility.createIssue')}
|
||||
</IAIButton>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,26 +1,27 @@
|
||||
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 { PartialAppConfig } from 'app/types/invokeai';
|
||||
import React, {
|
||||
lazy,
|
||||
memo,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
lazy,
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
|
||||
import { $authToken, $baseUrl, $projectId } from 'services/api/client';
|
||||
import { socketMiddleware } from 'services/events/middleware';
|
||||
import { ManagerOptions, SocketOptions } from 'socket.io-client';
|
||||
import Loading from '../../common/components/Loading/Loading';
|
||||
import '../../i18n';
|
||||
import AppDndContext from '../../features/dnd/components/AppDndContext';
|
||||
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI';
|
||||
import { $headerComponent } from 'app/store/nanostores/headerComponent';
|
||||
import {
|
||||
$queueId,
|
||||
DEFAULT_QUEUE_ID,
|
||||
} from 'features/queue/store/queueNanoStore';
|
||||
import '../../i18n';
|
||||
|
||||
const App = lazy(() => import('./App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||
@ -38,6 +39,8 @@ interface Props extends PropsWithChildren {
|
||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||
};
|
||||
customStarUi?: CustomStarUi;
|
||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||
isDebugging?: boolean;
|
||||
}
|
||||
|
||||
const InvokeAIUI = ({
|
||||
@ -50,6 +53,8 @@ const InvokeAIUI = ({
|
||||
queueId,
|
||||
selectedImage,
|
||||
customStarUi,
|
||||
socketOptions,
|
||||
isDebugging = false,
|
||||
}: Props) => {
|
||||
useEffect(() => {
|
||||
// configure API client token
|
||||
@ -82,9 +87,7 @@ const InvokeAIUI = ({
|
||||
|
||||
// rebuild socket middleware with token and apiUrl
|
||||
if (middleware && middleware.length > 0) {
|
||||
addMiddleware(socketMiddleware(), ...middleware);
|
||||
} else {
|
||||
addMiddleware(socketMiddleware());
|
||||
addMiddleware(...middleware);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@ -116,6 +119,24 @@ const InvokeAIUI = ({
|
||||
};
|
||||
}, [headerComponent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (socketOptions) {
|
||||
$socketOptions.set(socketOptions);
|
||||
}
|
||||
return () => {
|
||||
$socketOptions.set({});
|
||||
};
|
||||
}, [socketOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDebugging) {
|
||||
$isDebugging.set(isDebugging);
|
||||
}
|
||||
return () => {
|
||||
$isDebugging.set(false);
|
||||
};
|
||||
}, [isDebugging]);
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
|
109
invokeai/frontend/web/src/app/hooks/useSocketIO.ts
Normal file
109
invokeai/frontend/web/src/app/hooks/useSocketIO.ts
Normal 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]);
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
/**
|
||||
* The user's auth token.
|
||||
*/
|
||||
export const $authToken = atom<string | undefined>();
|
@ -0,0 +1,6 @@
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
/**
|
||||
* The OpenAPI base url.
|
||||
*/
|
||||
export const $baseUrl = atom<string | undefined>();
|
@ -0,0 +1,3 @@
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
export const $isDebugging = atom<boolean>(false);
|
@ -0,0 +1,6 @@
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
/**
|
||||
* The optional project-id header.
|
||||
*/
|
||||
export const $projectId = atom<string | undefined>();
|
@ -12,14 +12,14 @@ import deleteImageModalReducer from 'features/deleteImageModal/store/slice';
|
||||
import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||
import loraReducer from 'features/lora/store/loraSlice';
|
||||
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
|
||||
import nodesReducer from 'features/nodes/store/nodesSlice';
|
||||
import generationReducer from 'features/parameters/store/generationSlice';
|
||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||
import queueReducer from 'features/queue/store/queueSlice';
|
||||
import sdxlReducer from 'features/sdxl/store/sdxlSlice';
|
||||
import configReducer from 'features/system/store/configSlice';
|
||||
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 uiReducer from 'features/ui/store/uiSlice';
|
||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Box, Flex, Heading } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type ImageUploadOverlayProps = {
|
||||
isDragAccept: boolean;
|
||||
@ -9,6 +10,7 @@ type ImageUploadOverlayProps = {
|
||||
};
|
||||
|
||||
const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isDragAccept,
|
||||
isDragReject: _isDragAccept,
|
||||
@ -76,11 +78,13 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
||||
}}
|
||||
>
|
||||
{isDragAccept ? (
|
||||
<Heading size="lg">Drop to Upload</Heading>
|
||||
<Heading size="lg">{t('gallery.dropToUpload')}</Heading>
|
||||
) : (
|
||||
<>
|
||||
<Heading size="lg">Invalid Upload</Heading>
|
||||
<Heading size="md">Must be single JPEG or PNG image</Heading>
|
||||
<Heading size="lg">{t('toast.invalidUpload')}</Heading>
|
||||
<Heading size="md">
|
||||
{t('toast.uploadFailedInvalidUploadDesc')}
|
||||
</Heading>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
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
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
import { memo } from 'react';
|
||||
import { Image } from 'react-konva';
|
||||
import { $authToken } from 'services/api/client';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import useImage from 'use-image';
|
||||
import { CanvasImage } from '../store/canvasTypes';
|
||||
|
@ -155,10 +155,10 @@ const IAICanvasMaskOptions = () => {
|
||||
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||
</Box>
|
||||
<IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}>
|
||||
Save Mask
|
||||
{t('unifiedCanvas.saveMask')}
|
||||
</IAIButton>
|
||||
<IAIButton size="sm" leftIcon={<FaTrash />} onClick={handleClearMask}>
|
||||
{t('unifiedCanvas.clearMask')} (Shift+C)
|
||||
{t('unifiedCanvas.clearMask')}
|
||||
</IAIButton>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
|
@ -110,8 +110,10 @@ const ChangeBoardModal = () => {
|
||||
<AlertDialogBody>
|
||||
<Flex sx={{ flexDir: 'column', gap: 4 }}>
|
||||
<Text>
|
||||
Moving {`${imagesToChange.length}`} image
|
||||
{`${imagesToChange.length > 1 ? 's' : ''}`} to board:
|
||||
{t('boards.movingImagesToBoard', {
|
||||
count: imagesToChange.length,
|
||||
})}
|
||||
:
|
||||
</Text>
|
||||
<IAIMantineSearchableSelect
|
||||
placeholder={
|
||||
|
@ -124,10 +124,10 @@ const DeleteImageModal = () => {
|
||||
</AlertDialogBody>
|
||||
<AlertDialogFooter>
|
||||
<IAIButton ref={cancelRef} onClick={handleClose}>
|
||||
Cancel
|
||||
{t('boards.cancel')}
|
||||
</IAIButton>
|
||||
<IAIButton colorScheme="error" onClick={handleDelete} ml={3}>
|
||||
Delete
|
||||
{t('controlnet.delete')}
|
||||
</IAIButton>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Box, ChakraProps, Flex, Heading, Image, Text } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { TypesafeDraggableData } from '../types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type OverlayDragImageProps = {
|
||||
dragData: TypesafeDraggableData | null;
|
||||
@ -26,6 +27,7 @@ const STYLES: ChakraProps['sx'] = {
|
||||
};
|
||||
|
||||
const DragPreview = (props: OverlayDragImageProps) => {
|
||||
const { t } = useTranslation();
|
||||
if (!props.dragData) {
|
||||
return null;
|
||||
}
|
||||
@ -89,7 +91,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
|
||||
}}
|
||||
>
|
||||
<Heading>{props.dragData.payload.imageDTOs.length}</Heading>
|
||||
<Heading size="sm">Images</Heading>
|
||||
<Heading size="sm">{t('parameters.images')}</Heading>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
||||
_dark: { color: 'base.700' },
|
||||
}}
|
||||
>
|
||||
<Text>No Embeddings Loaded</Text>
|
||||
<Text>{t('embedding.noEmbeddingsLoaded')}</Text>
|
||||
</Flex>
|
||||
) : (
|
||||
<IAIMantineSearchableSelect
|
||||
|
@ -32,6 +32,7 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { BoardDTO } from 'services/api/types';
|
||||
import AutoAddIcon from '../AutoAddIcon';
|
||||
import BoardContextMenu from '../BoardContextMenu';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface GalleryBoardProps {
|
||||
board: BoardDTO;
|
||||
@ -143,7 +144,7 @@ const GalleryBoard = ({
|
||||
const handleChange = useCallback((newBoardName: string) => {
|
||||
setLocalBoardName(newBoardName);
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
|
||||
<Flex
|
||||
@ -292,7 +293,9 @@ const GalleryBoard = ({
|
||||
|
||||
<IAIDroppable
|
||||
data={droppableData}
|
||||
dropLabel={<Text fontSize="md">Move</Text>}
|
||||
dropLabel={
|
||||
<Text fontSize="md">{t('unifiedCanvas.move')}</Text>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
useGetBoardAssetsTotalQuery,
|
||||
useGetBoardImagesTotalQuery,
|
||||
} from 'services/api/endpoints/boards';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
isSelected: boolean;
|
||||
@ -71,7 +72,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none', userSelect: 'none' }}>
|
||||
<Flex
|
||||
@ -161,7 +162,9 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
|
||||
/>
|
||||
<IAIDroppable
|
||||
data={droppableData}
|
||||
dropLabel={<Text fontSize="md">Move</Text>}
|
||||
dropLabel={
|
||||
<Text fontSize="md">{t('unifiedCanvas.move')}</Text>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
FaCode,
|
||||
FaHourglassHalf,
|
||||
FaQuoteRight,
|
||||
FaRulerVertical,
|
||||
FaSeedling,
|
||||
} from 'react-icons/fa';
|
||||
import { FaCircleNodes, FaEllipsis } from 'react-icons/fa6';
|
||||
@ -95,8 +96,12 @@ const CurrentImageButtons = () => {
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
const {
|
||||
recallBothPrompts,
|
||||
recallSeed,
|
||||
recallWidthAndHeight,
|
||||
recallAllParameters,
|
||||
} = useRecallParameters();
|
||||
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(
|
||||
lastSelectedImage?.image_name ?? skipToken
|
||||
@ -117,6 +122,8 @@ const CurrentImageButtons = () => {
|
||||
dispatch(workflowLoadRequested(workflow));
|
||||
}, [dispatch, workflow]);
|
||||
|
||||
useHotkeys('w', handleLoadWorkflow, [workflow]);
|
||||
|
||||
const handleClickUseAllParameters = useCallback(() => {
|
||||
recallAllParameters(metadata);
|
||||
}, [metadata, recallAllParameters]);
|
||||
@ -127,7 +134,7 @@ const CurrentImageButtons = () => {
|
||||
recallSeed(metadata?.seed);
|
||||
}, [metadata?.seed, recallSeed]);
|
||||
|
||||
useHotkeys('s', handleUseSeed, [imageDTO]);
|
||||
useHotkeys('s', handleUseSeed, [metadata]);
|
||||
|
||||
const handleUsePrompt = useCallback(() => {
|
||||
recallBothPrompts(
|
||||
@ -144,9 +151,13 @@ const CurrentImageButtons = () => {
|
||||
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(() => {
|
||||
dispatch(sentImageToImg2Img());
|
||||
@ -267,6 +278,19 @@ const CurrentImageButtons = () => {
|
||||
isDisabled={metadata?.seed === null || metadata?.seed === undefined}
|
||||
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
|
||||
isLoading={isLoadingMetadata}
|
||||
icon={<FaAsterisk />}
|
||||
|
@ -111,7 +111,7 @@ const MultipleSelectionMenuItems = () => {
|
||||
icon={<FaTrash />}
|
||||
onClickCapture={handleDeleteSelection}
|
||||
>
|
||||
Delete Selection
|
||||
{t('gallery.deleteSelection')}
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
|
@ -113,7 +113,7 @@ const ImageGalleryContent = () => {
|
||||
leftIcon={<FaImages />}
|
||||
data-testid="images-tab"
|
||||
>
|
||||
{t('gallery.images')}
|
||||
{t('parameters.images')}
|
||||
</Tab>
|
||||
<Tab
|
||||
as={IAIButton}
|
||||
|
@ -49,7 +49,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
|
||||
}}
|
||||
>
|
||||
<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)">
|
||||
{image.image_name}
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
|
@ -165,7 +165,7 @@ export default function FoundModelsList() {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Installed
|
||||
{t('common.installed')}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
@ -215,7 +215,7 @@ export default function FoundModelsList() {
|
||||
/>
|
||||
<Flex p={2} gap={2}>
|
||||
<Text sx={{ fontWeight: 600 }}>
|
||||
Models Found: {foundModels.length}
|
||||
{t('modelManager.modelsFound')}: {foundModels.length}
|
||||
</Text>
|
||||
<Text
|
||||
sx={{
|
||||
@ -226,7 +226,7 @@ export default function FoundModelsList() {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Not Installed: {filteredModels.length}
|
||||
{t('common.notInstalled')}: {filteredModels.length}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
|
@ -76,7 +76,7 @@ function SearchFolderForm() {
|
||||
_dark: { color: 'base.300' },
|
||||
}}
|
||||
>
|
||||
Folder
|
||||
{t('common.folder')}
|
||||
</Text>
|
||||
{!searchFolder ? (
|
||||
<IAIInput
|
||||
|
@ -114,7 +114,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
||||
{model.model_name}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="base.400">
|
||||
{MODEL_TYPE_MAP[model.base_model]} Model
|
||||
{MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')}
|
||||
</Text>
|
||||
</Flex>
|
||||
{![''].includes(model.base_model) ? (
|
||||
@ -128,7 +128,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
||||
_dark: { bg: 'error.400' },
|
||||
}}
|
||||
>
|
||||
Conversion Not Supported
|
||||
{t('modelManager.conversionNotSupported')}
|
||||
</Badge>
|
||||
)}
|
||||
</Flex>
|
||||
|
@ -95,7 +95,7 @@ export default function DiffusersModelEdit(props: DiffusersModelEditProps) {
|
||||
{model.model_name}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="base.400">
|
||||
{MODEL_TYPE_MAP[model.base_model]} Model
|
||||
{MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Divider />
|
||||
|
@ -95,8 +95,8 @@ export default function LoRAModelEdit(props: LoRAModelEditProps) {
|
||||
{model.model_name}
|
||||
</Text>
|
||||
<Text fontSize="sm" color="base.400">
|
||||
{MODEL_TYPE_MAP[model.base_model]} Model ⋅{' '}
|
||||
{LORA_MODEL_FORMAT_MAP[model.model_format]} format
|
||||
{MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')} ⋅{' '}
|
||||
{LORA_MODEL_FORMAT_MAP[model.model_format]} {t('common.format')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Divider />
|
||||
|
@ -10,6 +10,7 @@ import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(stateSelector, ({ system, gallery }) => {
|
||||
const imageDTO = gallery.selection[gallery.selection.length - 1];
|
||||
@ -66,7 +67,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsHovering(false);
|
||||
}, []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<NodeWrapper
|
||||
nodeId={props.nodeProps.id}
|
||||
@ -99,7 +100,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
_dark: { color: 'base.200' },
|
||||
}}
|
||||
>
|
||||
Current Image
|
||||
{t('nodes.currentImage')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
|
@ -4,8 +4,10 @@ import { useEmbedWorkflow } from 'features/nodes/hooks/useEmbedWorkflow';
|
||||
import { useWithWorkflow } from 'features/nodes/hooks/useWithWorkflow';
|
||||
import { nodeEmbedWorkflowChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const withWorkflow = useWithWorkflow(nodeId);
|
||||
const embedWorkflow = useEmbedWorkflow(nodeId);
|
||||
@ -27,7 +29,9 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
|
||||
return (
|
||||
<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
|
||||
className="nopan"
|
||||
size="sm"
|
||||
|
@ -75,7 +75,7 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||
const { status, progress, progressImage } = nodeExecutionState;
|
||||
const { t } = useTranslation();
|
||||
if (status === NodeStatus.PENDING) {
|
||||
return <Text>Pending</Text>;
|
||||
return <Text>{t('queue.pending')}</Text>;
|
||||
}
|
||||
if (status === NodeStatus.IN_PROGRESS) {
|
||||
if (progressImage) {
|
||||
|
@ -3,6 +3,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { memo } from 'react';
|
||||
import NodeCollapseButton from '../common/NodeCollapseButton';
|
||||
import NodeWrapper from '../common/NodeWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -19,6 +20,7 @@ const InvocationNodeUnknownFallback = ({
|
||||
type,
|
||||
selected,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<NodeWrapper nodeId={nodeId} selected={selected}>
|
||||
<Flex
|
||||
@ -61,7 +63,7 @@ const InvocationNodeUnknownFallback = ({
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Text as="span">Unknown node type: </Text>
|
||||
<Text as="span">{t('nodes.unknownNodeType')}: </Text>
|
||||
<Text as="span" fontWeight={600}>
|
||||
{type}
|
||||
</Text>
|
||||
|
@ -4,8 +4,10 @@ import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
||||
import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate';
|
||||
import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const hasImageOutput = useHasImageOutput(nodeId);
|
||||
const isIntermediate = useIsIntermediate(nodeId);
|
||||
@ -27,7 +29,9 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
|
||||
return (
|
||||
<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
|
||||
className="nopan"
|
||||
size="sm"
|
||||
|
@ -3,6 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useUseCache } from 'features/nodes/hooks/useUseCache';
|
||||
import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -18,10 +19,12 @@ const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
},
|
||||
[dispatch, nodeId]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<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
|
||||
className="nopan"
|
||||
size="sm"
|
||||
|
@ -79,7 +79,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
icon={<FaPlus />}
|
||||
onClick={handleExposeField}
|
||||
>
|
||||
Add to Linear View
|
||||
{t('nodes.addLinearView')}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
@ -90,7 +90,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
icon={<FaMinus />}
|
||||
onClick={handleUnexposeField}
|
||||
>
|
||||
Remove from Linear View
|
||||
{t('nodes.removeLinearView')}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
@ -102,6 +102,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
isExposed,
|
||||
mayExpose,
|
||||
nodeId,
|
||||
t,
|
||||
]);
|
||||
|
||||
const renderMenuFunc = useCallback(
|
||||
|
@ -49,8 +49,16 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
||||
{fieldTemplate.description}
|
||||
</Text>
|
||||
)}
|
||||
{fieldTemplate && <Text>Type: {FIELDS[fieldTemplate.type].title}</Text>}
|
||||
{isInputTemplate && <Text>Input: {startCase(fieldTemplate.input)}</Text>}
|
||||
{fieldTemplate && (
|
||||
<Text>
|
||||
{t('parameters.type')}: {FIELDS[fieldTemplate.type].title}
|
||||
</Text>
|
||||
)}
|
||||
{isInputTemplate && (
|
||||
<Text>
|
||||
{t('common.input')}: {startCase(fieldTemplate.input)}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldContextMenu from './FieldContextMenu';
|
||||
import FieldHandle from './FieldHandle';
|
||||
import InputFieldRenderer from './InputFieldRenderer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@ -14,6 +15,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input');
|
||||
const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName);
|
||||
|
||||
@ -49,7 +51,7 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
<FormControl
|
||||
sx={{ color: 'error.400', textAlign: 'left', fontSize: 'sm' }}
|
||||
>
|
||||
Unknown input: {fieldName}
|
||||
{t('nodes.unknownInput')}: {fieldName}
|
||||
</FormControl>
|
||||
</InputFieldWrapper>
|
||||
);
|
||||
|
@ -18,6 +18,7 @@ import VaeModelInputField from './inputs/VaeModelInputField';
|
||||
import IPAdapterModelInputField from './inputs/IPAdapterModelInputField';
|
||||
import T2IAdapterModelInputField from './inputs/T2IAdapterModelInputField';
|
||||
import BoardInputField from './inputs/BoardInputField';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InputFieldProps = {
|
||||
nodeId: string;
|
||||
@ -25,11 +26,16 @@ type InputFieldProps = {
|
||||
};
|
||||
|
||||
const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
|
||||
const { t } = useTranslation();
|
||||
const field = useFieldData(nodeId, fieldName);
|
||||
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input');
|
||||
|
||||
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 (
|
||||
@ -249,7 +255,7 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
|
||||
_dark: { color: 'error.300' },
|
||||
}}
|
||||
>
|
||||
Unknown field type: {field?.type}
|
||||
{t('nodes.unknownFieldType')}: {field?.type}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import FieldHandle from './FieldHandle';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@ -12,6 +13,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const OutputField = ({ nodeId, fieldName }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'output');
|
||||
|
||||
const {
|
||||
@ -28,7 +30,7 @@ const OutputField = ({ nodeId, fieldName }: Props) => {
|
||||
<FormControl
|
||||
sx={{ color: 'error.400', textAlign: 'right', fontSize: 'sm' }}
|
||||
>
|
||||
Unknown output: {fieldName}
|
||||
{t('nodes.unknownOutput')}: {fieldName}
|
||||
</FormControl>
|
||||
</OutputFieldWrapper>
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
ImagePolymorphicInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUndo } from 'react-icons/fa';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { PostUploadAction } from 'services/api/types';
|
||||
@ -103,18 +104,24 @@ const ImageInputFieldComponent = (
|
||||
|
||||
export default memo(ImageInputFieldComponent);
|
||||
|
||||
const UploadElement = memo(() => (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
Drop or Upload
|
||||
</Text>
|
||||
));
|
||||
const UploadElement = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
{t('gallery.dropOrUpload')}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
UploadElement.displayName = 'UploadElement';
|
||||
|
||||
const DropLabel = memo(() => (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
Drop
|
||||
</Text>
|
||||
));
|
||||
const DropLabel = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
{t('gallery.drop')}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
DropLabel.displayName = 'DropLabel';
|
||||
|
@ -91,7 +91,7 @@ const LoRAModelInputFieldComponent = (
|
||||
return (
|
||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||
<Text sx={{ fontSize: 'sm', color: 'base.500', _dark: 'base.700' }}>
|
||||
No LoRAs Loaded
|
||||
{t('models.noLoRAsLoaded')}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
@ -90,7 +90,7 @@ const Content = (props: {
|
||||
<EditableNodeTitle nodeId={props.node.data.id} />
|
||||
<HStack>
|
||||
<FormControl>
|
||||
<FormLabel>Node Type</FormLabel>
|
||||
<FormLabel>{t('nodes.nodeType')}</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
{props.template.title}
|
||||
</Text>
|
||||
@ -102,7 +102,7 @@ const Content = (props: {
|
||||
w="full"
|
||||
>
|
||||
<FormControl isInvalid={needsUpdate}>
|
||||
<FormLabel>Node Version</FormLabel>
|
||||
<FormLabel>{t('nodes.nodeVersion')}</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
{props.node.data.version}
|
||||
</Text>
|
||||
|
@ -5,11 +5,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import InitialImage from './InitialImage';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaRulerVertical, FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import { PostUploadAction } from 'services/api/types';
|
||||
import InitialImage from './InitialImage';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
@ -17,6 +20,7 @@ const selector = createSelector(
|
||||
const { initialImage } = state.generation;
|
||||
return {
|
||||
isResetButtonDisabled: !initialImage,
|
||||
initialImage,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
@ -27,7 +31,9 @@ const postUploadAction: PostUploadAction = {
|
||||
};
|
||||
|
||||
const InitialImageDisplay = () => {
|
||||
const { isResetButtonDisabled } = useAppSelector(selector);
|
||||
const { recallWidthAndHeight } = useRecallParameters();
|
||||
const { t } = useTranslation();
|
||||
const { isResetButtonDisabled, initialImage } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
||||
@ -38,6 +44,14 @@ const InitialImageDisplay = () => {
|
||||
dispatch(clearInitialImage());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleUseSizeInitialImage = useCallback(() => {
|
||||
if (initialImage) {
|
||||
recallWidthAndHeight(initialImage.width, initialImage.height);
|
||||
}
|
||||
}, [initialImage, recallWidthAndHeight]);
|
||||
|
||||
useHotkeys('shift+d', handleUseSizeInitialImage, [initialImage]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
@ -73,7 +87,7 @@ const InitialImageDisplay = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Initial Image
|
||||
{t('metadata.initImage')}
|
||||
</Text>
|
||||
<Spacer />
|
||||
<IAIIconButton
|
||||
@ -82,6 +96,13 @@ const InitialImageDisplay = () => {
|
||||
icon={<FaUpload />}
|
||||
{...getUploadButtonProps()}
|
||||
/>
|
||||
<IAIIconButton
|
||||
tooltip={`${t('parameters.useSize')} (Shift+D)`}
|
||||
aria-label={`${t('parameters.useSize')} (Shift+D)`}
|
||||
icon={<FaRulerVertical />}
|
||||
onClick={handleUseSizeInitialImage}
|
||||
isDisabled={isResetButtonDisabled}
|
||||
/>
|
||||
<IAIIconButton
|
||||
tooltip="Reset Initial Image"
|
||||
aria-label="Reset Initial Image"
|
||||
|
@ -373,6 +373,26 @@ export const useRecallParameters = () => {
|
||||
[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
|
||||
*/
|
||||
@ -966,6 +986,7 @@ export const useRecallParameters = () => {
|
||||
recallSteps,
|
||||
recallWidth,
|
||||
recallHeight,
|
||||
recallWidthAndHeight,
|
||||
recallStrength,
|
||||
recallHrfEnabled,
|
||||
recallHrfStrength,
|
||||
|
@ -106,7 +106,7 @@ const QueueItemComponent = ({ queueItemDTO }: Props) => {
|
||||
flexDir="column"
|
||||
>
|
||||
<Heading size="sm" color="error.500" _dark={{ color: 'error.400' }}>
|
||||
Error
|
||||
{t('common.error')}
|
||||
</Heading>
|
||||
<pre>{queueItem.error}</pre>
|
||||
</Flex>
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
|
||||
import { Flex, Image, Text } from '@chakra-ui/react';
|
||||
import InvokeAILogoImage from 'assets/images/logo.png';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
@ -423,7 +423,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<Flex justifyContent="center">
|
||||
<Text fontSize="lg">
|
||||
<Text>
|
||||
{t('settings.resetComplete')} Reloading in {countdown}...
|
||||
{t('settings.resetComplete')} {t('settings.reloadingIn')}{' '}
|
||||
{countdown}...
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
|
@ -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 ?? ''}`,
|
||||
})
|
||||
);
|
@ -4,7 +4,7 @@ import {
|
||||
ThunkDispatch,
|
||||
createEntityAdapter,
|
||||
} 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 queryString from 'query-string';
|
||||
import { ApiTagDescription, api } from '..';
|
||||
|
@ -6,7 +6,9 @@ import {
|
||||
createApi,
|
||||
fetchBaseQuery,
|
||||
} 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 = [
|
||||
'AppVersion',
|
||||
|
140
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
140
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||
};
|
@ -1,7 +1,5 @@
|
||||
import { MiddlewareAPI } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { AppDispatch, RootState } from 'app/store/store';
|
||||
import { $queueId } from 'features/queue/store/queueNanoStore';
|
||||
import { $queueId } from 'app/store/nanostores/queueId';
|
||||
import { AppDispatch } from 'app/store/store';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { Socket } from 'socket.io-client';
|
||||
@ -23,20 +21,16 @@ import { ClientToServerEvents, ServerToClientEvents } from '../types';
|
||||
|
||||
type SetEventListenersArg = {
|
||||
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||
storeApi: MiddlewareAPI<AppDispatch, RootState>;
|
||||
dispatch: AppDispatch;
|
||||
};
|
||||
|
||||
export const setEventListeners = (arg: SetEventListenersArg) => {
|
||||
const { socket, storeApi } = arg;
|
||||
const { dispatch } = storeApi;
|
||||
const { socket, dispatch } = arg;
|
||||
|
||||
/**
|
||||
* Connect
|
||||
*/
|
||||
socket.on('connect', () => {
|
||||
const log = logger('socketio');
|
||||
log.debug('Connected');
|
||||
|
||||
dispatch(socketConnected());
|
||||
const queue_id = $queueId.get();
|
||||
socket.emit('subscribe_queue', { queue_id });
|
||||
|
@ -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"
|
||||
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:
|
||||
version "4.6.0"
|
||||
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-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:
|
||||
version "12.1.3"
|
||||
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3"
|
||||
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:
|
||||
version "6.7.0"
|
||||
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"
|
||||
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:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc"
|
||||
|
Loading…
Reference in New Issue
Block a user