mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into refactor/nodes-on-generator
This commit is contained in:
commit
cde0b6ae8d
@ -102,6 +102,29 @@ def generate_matching_edges(
|
|||||||
return edges
|
return edges
|
||||||
|
|
||||||
|
|
||||||
|
class SessionError(Exception):
|
||||||
|
"""Raised when a session error has occurred"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def invoke_all(context: CliContext):
|
||||||
|
"""Runs all invocations in the specified session"""
|
||||||
|
context.invoker.invoke(context.session, invoke_all=True)
|
||||||
|
while not context.session.is_complete():
|
||||||
|
# Wait some time
|
||||||
|
session = context.get_session()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Print any errors
|
||||||
|
if context.session.has_error():
|
||||||
|
for n in context.session.errors:
|
||||||
|
print(
|
||||||
|
f"Error in node {n} (source node {context.session.prepared_source_mapping[n]}): {session.errors[n]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
raise SessionError()
|
||||||
|
|
||||||
|
|
||||||
def invoke_cli():
|
def invoke_cli():
|
||||||
args = Args()
|
args = Args()
|
||||||
config = args.parse_args()
|
config = args.parse_args()
|
||||||
@ -130,7 +153,6 @@ def invoke_cli():
|
|||||||
|
|
||||||
invoker = Invoker(services)
|
invoker = Invoker(services)
|
||||||
session: GraphExecutionState = invoker.create_execution_state()
|
session: GraphExecutionState = invoker.create_execution_state()
|
||||||
|
|
||||||
parser = get_command_parser()
|
parser = get_command_parser()
|
||||||
|
|
||||||
# Uncomment to print out previous sessions at startup
|
# Uncomment to print out previous sessions at startup
|
||||||
@ -147,8 +169,7 @@ def invoke_cli():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Refresh the state of the session
|
# Refresh the state of the session
|
||||||
session = invoker.services.graph_execution_manager.get(session.id)
|
history = list(get_graph_execution_history(context.session))
|
||||||
history = list(get_graph_execution_history(session))
|
|
||||||
|
|
||||||
# Split the command for piping
|
# Split the command for piping
|
||||||
cmds = cmd_input.split("|")
|
cmds = cmd_input.split("|")
|
||||||
@ -160,7 +181,7 @@ def invoke_cli():
|
|||||||
raise InvalidArgs("Empty command")
|
raise InvalidArgs("Empty command")
|
||||||
|
|
||||||
# Parse args to create invocation
|
# Parse args to create invocation
|
||||||
args = vars(parser.parse_args(shlex.split(cmd.strip())))
|
args = vars(context.parser.parse_args(shlex.split(cmd.strip())))
|
||||||
|
|
||||||
# Override defaults
|
# Override defaults
|
||||||
for field_name, field_default in context.defaults.items():
|
for field_name, field_default in context.defaults.items():
|
||||||
@ -172,11 +193,11 @@ def invoke_cli():
|
|||||||
command = CliCommand(command=args)
|
command = CliCommand(command=args)
|
||||||
|
|
||||||
# Run any CLI commands immediately
|
# Run any CLI commands immediately
|
||||||
# TODO: this won't behave as expected if piping and using e.g. history,
|
|
||||||
# since invocations are gathered and then run together at the end.
|
|
||||||
# This is more efficient if the CLI is running against a distributed
|
|
||||||
# backend, so it's preferable not to change that behavior.
|
|
||||||
if isinstance(command.command, BaseCommand):
|
if isinstance(command.command, BaseCommand):
|
||||||
|
# Invoke all current nodes to preserve operation order
|
||||||
|
invoke_all(context)
|
||||||
|
|
||||||
|
# Run the command
|
||||||
command.command.run(context)
|
command.command.run(context)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -189,7 +210,7 @@ def invoke_cli():
|
|||||||
from_node = (
|
from_node = (
|
||||||
next(filter(lambda n: n[0].id == from_id, new_invocations))[0]
|
next(filter(lambda n: n[0].id == from_id, new_invocations))[0]
|
||||||
if current_id != start_id
|
if current_id != start_id
|
||||||
else session.graph.get_node(from_id)
|
else context.session.graph.get_node(from_id)
|
||||||
)
|
)
|
||||||
matching_edges = generate_matching_edges(
|
matching_edges = generate_matching_edges(
|
||||||
from_node, command.command
|
from_node, command.command
|
||||||
@ -199,7 +220,7 @@ def invoke_cli():
|
|||||||
# Parse provided links
|
# Parse provided links
|
||||||
if "link_node" in args and args["link_node"]:
|
if "link_node" in args and args["link_node"]:
|
||||||
for link in args["link_node"]:
|
for link in args["link_node"]:
|
||||||
link_node = session.graph.get_node(link)
|
link_node = context.session.graph.get_node(link)
|
||||||
matching_edges = generate_matching_edges(
|
matching_edges = generate_matching_edges(
|
||||||
link_node, command.command
|
link_node, command.command
|
||||||
)
|
)
|
||||||
@ -223,37 +244,24 @@ def invoke_cli():
|
|||||||
|
|
||||||
current_id = current_id + 1
|
current_id = current_id + 1
|
||||||
|
|
||||||
# Command line was parsed successfully
|
# Add the node to the session
|
||||||
# Add the invocations to the session
|
context.session.add_node(command.command)
|
||||||
for invocation in new_invocations:
|
for edge in edges:
|
||||||
session.add_node(invocation[0])
|
|
||||||
for edge in invocation[1]:
|
|
||||||
print(edge)
|
print(edge)
|
||||||
session.add_edge(edge)
|
context.session.add_edge(edge)
|
||||||
|
|
||||||
# Execute all available invocations
|
# Execute all remaining nodes
|
||||||
invoker.invoke(session, invoke_all=True)
|
invoke_all(context)
|
||||||
while not session.is_complete():
|
|
||||||
# Wait some time
|
|
||||||
session = context.get_session()
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
# Print any errors
|
|
||||||
if session.has_error():
|
|
||||||
for n in session.errors:
|
|
||||||
print(
|
|
||||||
f"Error in node {n} (source node {session.prepared_source_mapping[n]}): {session.errors[n]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start a new session
|
|
||||||
print("Creating a new session")
|
|
||||||
session = invoker.create_execution_state()
|
|
||||||
context.session = session
|
|
||||||
|
|
||||||
except InvalidArgs:
|
except InvalidArgs:
|
||||||
print('Invalid command, use "help" to list commands')
|
print('Invalid command, use "help" to list commands')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
except SessionError:
|
||||||
|
# Start a new session
|
||||||
|
print("Session error: creating a new session")
|
||||||
|
context.session = context.invoker.create_execution_state()
|
||||||
|
|
||||||
except ExitCli:
|
except ExitCli:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -347,6 +347,7 @@ class Generator:
|
|||||||
h_symmetry_time_pct=h_symmetry_time_pct,
|
h_symmetry_time_pct=h_symmetry_time_pct,
|
||||||
v_symmetry_time_pct=v_symmetry_time_pct,
|
v_symmetry_time_pct=v_symmetry_time_pct,
|
||||||
attention_maps_callback=attention_maps_callback,
|
attention_maps_callback=attention_maps_callback,
|
||||||
|
seed=seed,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
results = []
|
results = []
|
||||||
@ -537,9 +538,7 @@ class Generator:
|
|||||||
if self.variation_amount > 0:
|
if self.variation_amount > 0:
|
||||||
random.seed() # reset RNG to an actually random state, so we can get a random seed for variations
|
random.seed() # reset RNG to an actually random state, so we can get a random seed for variations
|
||||||
seed = random.randrange(0, np.iinfo(np.uint32).max)
|
seed = random.randrange(0, np.iinfo(np.uint32).max)
|
||||||
return (seed, initial_noise)
|
return (seed, initial_noise)
|
||||||
else:
|
|
||||||
return (seed, None)
|
|
||||||
|
|
||||||
def get_perlin_noise(self, width, height):
|
def get_perlin_noise(self, width, height):
|
||||||
fixdevice = "cpu" if (self.model.device.type == "mps") else self.model.device
|
fixdevice = "cpu" if (self.model.device.type == "mps") else self.model.device
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
invokeai.backend.generator.img2img descends from .generator
|
invokeai.backend.generator.img2img descends from .generator
|
||||||
"""
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
from accelerate.utils import set_seed
|
||||||
from diffusers import logging
|
from diffusers import logging
|
||||||
|
|
||||||
from ..stable_diffusion import (
|
from ..stable_diffusion import (
|
||||||
@ -35,6 +37,7 @@ class Img2Img(Generator):
|
|||||||
h_symmetry_time_pct=None,
|
h_symmetry_time_pct=None,
|
||||||
v_symmetry_time_pct=None,
|
v_symmetry_time_pct=None,
|
||||||
attention_maps_callback=None,
|
attention_maps_callback=None,
|
||||||
|
seed=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -65,6 +68,7 @@ class Img2Img(Generator):
|
|||||||
# FIXME: use x_T for initial seeded noise
|
# FIXME: use x_T for initial seeded noise
|
||||||
# We're not at the moment because the pipeline automatically resizes init_image if
|
# We're not at the moment because the pipeline automatically resizes init_image if
|
||||||
# necessary, which the x_T input might not match.
|
# necessary, which the x_T input might not match.
|
||||||
|
# In the meantime, reset the seed prior to generating pipeline output so we at least get the same result.
|
||||||
logging.set_verbosity_error() # quench safety check warnings
|
logging.set_verbosity_error() # quench safety check warnings
|
||||||
pipeline_output = pipeline.img2img_from_embeddings(
|
pipeline_output = pipeline.img2img_from_embeddings(
|
||||||
init_image,
|
init_image,
|
||||||
@ -73,6 +77,7 @@ class Img2Img(Generator):
|
|||||||
conditioning_data,
|
conditioning_data,
|
||||||
noise_func=self.get_noise_like,
|
noise_func=self.get_noise_like,
|
||||||
callback=step_callback,
|
callback=step_callback,
|
||||||
|
seed=seed
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
pipeline_output.attention_map_saver is not None
|
pipeline_output.attention_map_saver is not None
|
||||||
@ -83,7 +88,9 @@ class Img2Img(Generator):
|
|||||||
|
|
||||||
return make_image
|
return make_image
|
||||||
|
|
||||||
def get_noise_like(self, like: torch.Tensor):
|
def get_noise_like(self, like: torch.Tensor, seed: Optional[int]):
|
||||||
|
if seed is not None:
|
||||||
|
set_seed(seed)
|
||||||
device = like.device
|
device = like.device
|
||||||
if device.type == "mps":
|
if device.type == "mps":
|
||||||
x = torch.randn_like(like, device="cpu").to(device)
|
x = torch.randn_like(like, device="cpu").to(device)
|
||||||
|
@ -223,6 +223,7 @@ class Inpaint(Img2Img):
|
|||||||
inpaint_height=None,
|
inpaint_height=None,
|
||||||
inpaint_fill: tuple(int) = (0x7F, 0x7F, 0x7F, 0xFF),
|
inpaint_fill: tuple(int) = (0x7F, 0x7F, 0x7F, 0xFF),
|
||||||
attention_maps_callback=None,
|
attention_maps_callback=None,
|
||||||
|
seed=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@ -319,6 +320,7 @@ class Inpaint(Img2Img):
|
|||||||
conditioning_data=conditioning_data,
|
conditioning_data=conditioning_data,
|
||||||
noise_func=self.get_noise_like,
|
noise_func=self.get_noise_like,
|
||||||
callback=step_callback,
|
callback=step_callback,
|
||||||
|
seed=seed
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -690,6 +690,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
callback: Callable[[PipelineIntermediateState], None] = None,
|
callback: Callable[[PipelineIntermediateState], None] = None,
|
||||||
run_id=None,
|
run_id=None,
|
||||||
noise_func=None,
|
noise_func=None,
|
||||||
|
seed=None,
|
||||||
) -> InvokeAIStableDiffusionPipelineOutput:
|
) -> InvokeAIStableDiffusionPipelineOutput:
|
||||||
if isinstance(init_image, PIL.Image.Image):
|
if isinstance(init_image, PIL.Image.Image):
|
||||||
init_image = image_resized_to_grid_as_tensor(init_image.convert("RGB"))
|
init_image = image_resized_to_grid_as_tensor(init_image.convert("RGB"))
|
||||||
@ -703,7 +704,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
device=self._model_group.device_for(self.unet),
|
device=self._model_group.device_for(self.unet),
|
||||||
dtype=self.unet.dtype,
|
dtype=self.unet.dtype,
|
||||||
)
|
)
|
||||||
noise = noise_func(initial_latents)
|
noise = noise_func(initial_latents, seed)
|
||||||
|
|
||||||
return self.img2img_from_latents_and_embeddings(
|
return self.img2img_from_latents_and_embeddings(
|
||||||
initial_latents,
|
initial_latents,
|
||||||
@ -731,9 +732,11 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
device=self._model_group.device_for(self.unet),
|
device=self._model_group.device_for(self.unet),
|
||||||
)
|
)
|
||||||
result_latents, result_attention_maps = self.latents_from_embeddings(
|
result_latents, result_attention_maps = self.latents_from_embeddings(
|
||||||
initial_latents,
|
latents=initial_latents if strength < 1.0 else torch.zeros_like(
|
||||||
num_inference_steps,
|
initial_latents, device=initial_latents.device, dtype=initial_latents.dtype
|
||||||
conditioning_data,
|
),
|
||||||
|
num_inference_steps=num_inference_steps,
|
||||||
|
conditioning_data=conditioning_data,
|
||||||
timesteps=timesteps,
|
timesteps=timesteps,
|
||||||
noise=noise,
|
noise=noise,
|
||||||
run_id=run_id,
|
run_id=run_id,
|
||||||
@ -779,6 +782,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
callback: Callable[[PipelineIntermediateState], None] = None,
|
callback: Callable[[PipelineIntermediateState], None] = None,
|
||||||
run_id=None,
|
run_id=None,
|
||||||
noise_func=None,
|
noise_func=None,
|
||||||
|
seed=None,
|
||||||
) -> InvokeAIStableDiffusionPipelineOutput:
|
) -> InvokeAIStableDiffusionPipelineOutput:
|
||||||
device = self._model_group.device_for(self.unet)
|
device = self._model_group.device_for(self.unet)
|
||||||
latents_dtype = self.unet.dtype
|
latents_dtype = self.unet.dtype
|
||||||
@ -802,7 +806,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
init_image_latents = self.non_noised_latents_from_image(
|
init_image_latents = self.non_noised_latents_from_image(
|
||||||
init_image, device=device, dtype=latents_dtype
|
init_image, device=device, dtype=latents_dtype
|
||||||
)
|
)
|
||||||
noise = noise_func(init_image_latents)
|
noise = noise_func(init_image_latents, seed)
|
||||||
|
|
||||||
if mask.dim() == 3:
|
if mask.dim() == 3:
|
||||||
mask = mask.unsqueeze(0)
|
mask = mask.unsqueeze(0)
|
||||||
@ -831,9 +835,11 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result_latents, result_attention_maps = self.latents_from_embeddings(
|
result_latents, result_attention_maps = self.latents_from_embeddings(
|
||||||
init_image_latents,
|
latents=init_image_latents if strength < 1.0 else torch.zeros_like(
|
||||||
num_inference_steps,
|
init_image_latents, device=init_image_latents.device, dtype=init_image_latents.dtype
|
||||||
conditioning_data,
|
),
|
||||||
|
num_inference_steps=num_inference_steps,
|
||||||
|
conditioning_data=conditioning_data,
|
||||||
noise=noise,
|
noise=noise,
|
||||||
timesteps=timesteps,
|
timesteps=timesteps,
|
||||||
additional_guidance=guidance,
|
additional_guidance=guidance,
|
||||||
|
188
invokeai/frontend/web/dist/assets/App-2afdb3c5.js
vendored
Normal file
188
invokeai/frontend/web/dist/assets/App-2afdb3c5.js
vendored
Normal file
File diff suppressed because one or more lines are too long
188
invokeai/frontend/web/dist/assets/App-36f81b03.js
vendored
188
invokeai/frontend/web/dist/assets/App-36f81b03.js
vendored
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
|||||||
import{j as y,cM as Ie,r as _,cN as bt,q as Vr,cO as o,cP as b,cQ as v,cR as S,cS as Lr,cT as ut,cU as vt,cV as ft,cW as mt,n as ht,cX as gt,E as pt}from"./index-b928084d.js";import{d as yt,i as St,T as xt,j as $t,s as kt,g as _t}from"./scrollbar-f59ed469.js";var Or=`
|
import{j as y,cM as Ie,r as _,cN as bt,q as Vr,cO as o,cP as b,cQ as v,cR as S,cS as Lr,cT as ut,cU as vt,cV as ft,cW as mt,n as ht,cX as gt,E as pt}from"./index-61f10aa8.js";import{d as yt,i as St,T as xt,j as $t,s as kt,g as _t}from"./scrollbar-7e342734.js";var Or=`
|
||||||
:root {
|
:root {
|
||||||
--chakra-vh: 100vh;
|
--chakra-vh: 100vh;
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
9
invokeai/frontend/web/dist/assets/scrollbar-7e342734.js
vendored
Normal file
9
invokeai/frontend/web/dist/assets/scrollbar-7e342734.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
invokeai/frontend/web/dist/index.html
vendored
2
invokeai/frontend/web/dist/index.html
vendored
@ -12,7 +12,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="./assets/index-b928084d.js"></script>
|
<script type="module" crossorigin src="./assets/index-61f10aa8.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index-5483945c.css">
|
<link rel="stylesheet" href="./assets/index-5483945c.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
4
invokeai/frontend/web/dist/locales/en.json
vendored
4
invokeai/frontend/web/dist/locales/en.json
vendored
@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"accessibility": {
|
||||||
|
"modelSelect": "Model Select",
|
||||||
|
"invokeProgressBar": "Invoke progress bar"
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
"themeLabel": "Theme",
|
"themeLabel": "Theme",
|
||||||
|
1
invokeai/frontend/web/dist/locales/es.json
vendored
1
invokeai/frontend/web/dist/locales/es.json
vendored
@ -363,7 +363,6 @@
|
|||||||
"convertToDiffusersHelpText6": "¿Desea transformar este modelo?",
|
"convertToDiffusersHelpText6": "¿Desea transformar este modelo?",
|
||||||
"convertToDiffusersSaveLocation": "Guardar ubicación",
|
"convertToDiffusersSaveLocation": "Guardar ubicación",
|
||||||
"v1": "v1",
|
"v1": "v1",
|
||||||
"v2": "v2",
|
|
||||||
"statusConverting": "Adaptar",
|
"statusConverting": "Adaptar",
|
||||||
"modelConverted": "Modelo adaptado",
|
"modelConverted": "Modelo adaptado",
|
||||||
"sameFolder": "La misma carpeta",
|
"sameFolder": "La misma carpeta",
|
||||||
|
4
invokeai/frontend/web/dist/locales/fr.json
vendored
4
invokeai/frontend/web/dist/locales/fr.json
vendored
@ -45,7 +45,9 @@
|
|||||||
"statusUpscaling": "Mise à échelle",
|
"statusUpscaling": "Mise à échelle",
|
||||||
"statusUpscalingESRGAN": "Mise à échelle (ESRGAN)",
|
"statusUpscalingESRGAN": "Mise à échelle (ESRGAN)",
|
||||||
"statusLoadingModel": "Chargement du modèle",
|
"statusLoadingModel": "Chargement du modèle",
|
||||||
"statusModelChanged": "Modèle changé"
|
"statusModelChanged": "Modèle changé",
|
||||||
|
"discordLabel": "Discord",
|
||||||
|
"githubLabel": "Github"
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
"generations": "Générations",
|
"generations": "Générations",
|
||||||
|
1
invokeai/frontend/web/dist/locales/he.json
vendored
1
invokeai/frontend/web/dist/locales/he.json
vendored
@ -92,7 +92,6 @@
|
|||||||
"modelThree": "מודל 3",
|
"modelThree": "מודל 3",
|
||||||
"mergedModelName": "שם מודל ממוזג",
|
"mergedModelName": "שם מודל ממוזג",
|
||||||
"v1": "v1",
|
"v1": "v1",
|
||||||
"v2": "v2",
|
|
||||||
"invokeRoot": "תיקיית InvokeAI",
|
"invokeRoot": "תיקיית InvokeAI",
|
||||||
"customConfig": "תצורה מותאמת אישית",
|
"customConfig": "תצורה מותאמת אישית",
|
||||||
"pathToCustomConfig": "נתיב לתצורה מותאמת אישית",
|
"pathToCustomConfig": "נתיב לתצורה מותאמת אישית",
|
||||||
|
1
invokeai/frontend/web/dist/locales/it.json
vendored
1
invokeai/frontend/web/dist/locales/it.json
vendored
@ -361,7 +361,6 @@
|
|||||||
"convertToDiffusersHelpText5": "Assicurati di avere spazio su disco sufficiente. I modelli generalmente variano tra 4 GB e 7 GB di dimensioni.",
|
"convertToDiffusersHelpText5": "Assicurati di avere spazio su disco sufficiente. I modelli generalmente variano tra 4 GB e 7 GB di dimensioni.",
|
||||||
"convertToDiffusersHelpText6": "Vuoi convertire questo modello?",
|
"convertToDiffusersHelpText6": "Vuoi convertire questo modello?",
|
||||||
"convertToDiffusersSaveLocation": "Ubicazione salvataggio",
|
"convertToDiffusersSaveLocation": "Ubicazione salvataggio",
|
||||||
"v2": "v2",
|
|
||||||
"inpainting": "v1 Inpainting",
|
"inpainting": "v1 Inpainting",
|
||||||
"customConfig": "Configurazione personalizzata",
|
"customConfig": "Configurazione personalizzata",
|
||||||
"statusConverting": "Conversione in corso",
|
"statusConverting": "Conversione in corso",
|
||||||
|
3
invokeai/frontend/web/dist/locales/nl.json
vendored
3
invokeai/frontend/web/dist/locales/nl.json
vendored
@ -302,7 +302,7 @@
|
|||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
"nameValidationMsg": "Geef een naam voor je model",
|
"nameValidationMsg": "Geef een naam voor je model",
|
||||||
"description": "Beschrijving",
|
"description": "Beschrijving",
|
||||||
"descriptionValidationMsg": "Voeg een beschrijving toe voor je model",
|
"descriptionValidationMsg": "Voeg een beschrijving toe voor je model.",
|
||||||
"config": "Configuratie",
|
"config": "Configuratie",
|
||||||
"configValidationMsg": "Pad naar het configuratiebestand van je model.",
|
"configValidationMsg": "Pad naar het configuratiebestand van je model.",
|
||||||
"modelLocation": "Locatie model",
|
"modelLocation": "Locatie model",
|
||||||
@ -364,7 +364,6 @@
|
|||||||
"convertToDiffusersHelpText5": "Zorg ervoor dat je genoeg schijfruimte hebt. Modellen nemen gewoonlijk ongeveer 4 - 7 GB ruimte in beslag.",
|
"convertToDiffusersHelpText5": "Zorg ervoor dat je genoeg schijfruimte hebt. Modellen nemen gewoonlijk ongeveer 4 - 7 GB ruimte in beslag.",
|
||||||
"convertToDiffusersSaveLocation": "Bewaarlocatie",
|
"convertToDiffusersSaveLocation": "Bewaarlocatie",
|
||||||
"v1": "v1",
|
"v1": "v1",
|
||||||
"v2": "v2",
|
|
||||||
"inpainting": "v1-inpainting",
|
"inpainting": "v1-inpainting",
|
||||||
"customConfig": "Eigen configuratie",
|
"customConfig": "Eigen configuratie",
|
||||||
"pathToCustomConfig": "Pad naar eigen configuratie",
|
"pathToCustomConfig": "Pad naar eigen configuratie",
|
||||||
|
50
invokeai/frontend/web/dist/locales/pt_BR.json
vendored
50
invokeai/frontend/web/dist/locales/pt_BR.json
vendored
@ -358,7 +358,6 @@
|
|||||||
"convertToDiffusersHelpText6": "Você deseja converter este modelo?",
|
"convertToDiffusersHelpText6": "Você deseja converter este modelo?",
|
||||||
"convertToDiffusersSaveLocation": "Local para Salvar",
|
"convertToDiffusersSaveLocation": "Local para Salvar",
|
||||||
"v1": "v1",
|
"v1": "v1",
|
||||||
"v2": "v2",
|
|
||||||
"inpainting": "v1 Inpainting",
|
"inpainting": "v1 Inpainting",
|
||||||
"customConfig": "Configuração personalizada",
|
"customConfig": "Configuração personalizada",
|
||||||
"pathToCustomConfig": "Caminho para configuração personalizada",
|
"pathToCustomConfig": "Caminho para configuração personalizada",
|
||||||
@ -381,7 +380,19 @@
|
|||||||
"allModels": "Todos os Modelos",
|
"allModels": "Todos os Modelos",
|
||||||
"repoIDValidationMsg": "Repositório Online do seu Modelo",
|
"repoIDValidationMsg": "Repositório Online do seu Modelo",
|
||||||
"convert": "Converter",
|
"convert": "Converter",
|
||||||
"convertToDiffusersHelpText2": "Este processo irá substituir sua entrada de Gerenciador de Modelos por uma versão Diffusers do mesmo modelo."
|
"convertToDiffusersHelpText2": "Este processo irá substituir sua entrada de Gerenciador de Modelos por uma versão Diffusers do mesmo modelo.",
|
||||||
|
"mergedModelCustomSaveLocation": "Caminho Personalizado",
|
||||||
|
"mergedModelSaveLocation": "Local de Salvamento",
|
||||||
|
"interpolationType": "Tipo de Interpolação",
|
||||||
|
"ignoreMismatch": "Ignorar Divergências entre Modelos Selecionados",
|
||||||
|
"invokeAIFolder": "Pasta Invoke AI",
|
||||||
|
"weightedSum": "Soma Ponderada",
|
||||||
|
"sigmoid": "Sigmóide",
|
||||||
|
"inverseSigmoid": "Sigmóide Inversa",
|
||||||
|
"modelMergeHeaderHelp1": "Você pode mesclar até três modelos diferentes para criar uma mistura que atenda às suas necessidades.",
|
||||||
|
"modelMergeInterpAddDifferenceHelp": "Neste modo, o Modelo 3 é primeiro subtraído do Modelo 2. A versão resultante é mesclada com o Modelo 1 com a taxa alpha definida acima.",
|
||||||
|
"modelMergeAlphaHelp": "Alpha controla a força da mistura dos modelos. Valores de alpha mais baixos resultam em uma influência menor do segundo modelo.",
|
||||||
|
"modelMergeHeaderHelp2": "Apenas Diffusers estão disponíveis para mesclagem. Se você deseja mesclar um modelo de checkpoint, por favor, converta-o para Diffusers primeiro."
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"images": "Imagems",
|
"images": "Imagems",
|
||||||
@ -441,7 +452,22 @@
|
|||||||
"info": "Informações",
|
"info": "Informações",
|
||||||
"deleteImage": "Apagar Imagem",
|
"deleteImage": "Apagar Imagem",
|
||||||
"initialImage": "Imagem inicial",
|
"initialImage": "Imagem inicial",
|
||||||
"showOptionsPanel": "Mostrar Painel de Opções"
|
"showOptionsPanel": "Mostrar Painel de Opções",
|
||||||
|
"vSymmetryStep": "V Passo de Simetria",
|
||||||
|
"hSymmetryStep": "H Passo de Simetria",
|
||||||
|
"symmetry": "Simetria",
|
||||||
|
"copyImage": "Copiar imagem",
|
||||||
|
"negativePrompts": "Indicações negativas",
|
||||||
|
"hiresStrength": "Força da Alta Resolução",
|
||||||
|
"denoisingStrength": "A força de remoção de ruído",
|
||||||
|
"imageToImage": "Imagem para Imagem",
|
||||||
|
"cancel": {
|
||||||
|
"setType": "Definir tipo de cancelamento",
|
||||||
|
"isScheduled": "Cancelando",
|
||||||
|
"schedule": "Cancelar após a iteração atual",
|
||||||
|
"immediate": "Cancelar imediatamente"
|
||||||
|
},
|
||||||
|
"general": "Geral"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"models": "Modelos",
|
"models": "Modelos",
|
||||||
@ -454,7 +480,8 @@
|
|||||||
"resetWebUI": "Reiniciar Interface",
|
"resetWebUI": "Reiniciar Interface",
|
||||||
"resetWebUIDesc1": "Reiniciar a interface apenas reinicia o cache local do broswer para imagens e configurações lembradas. Não apaga nenhuma imagem do disco.",
|
"resetWebUIDesc1": "Reiniciar a interface apenas reinicia o cache local do broswer para imagens e configurações lembradas. Não apaga nenhuma imagem do disco.",
|
||||||
"resetWebUIDesc2": "Se as imagens não estão aparecendo na galeria ou algo mais não está funcionando, favor tentar reiniciar antes de postar um problema no GitHub.",
|
"resetWebUIDesc2": "Se as imagens não estão aparecendo na galeria ou algo mais não está funcionando, favor tentar reiniciar antes de postar um problema no GitHub.",
|
||||||
"resetComplete": "A interface foi reiniciada. Atualize a página para carregar."
|
"resetComplete": "A interface foi reiniciada. Atualize a página para carregar.",
|
||||||
|
"useSlidersForAll": "Usar deslizadores para todas as opções"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"tempFoldersEmptied": "Pasta de Arquivos Temporários Esvaziada",
|
"tempFoldersEmptied": "Pasta de Arquivos Temporários Esvaziada",
|
||||||
@ -546,5 +573,20 @@
|
|||||||
"betaDarkenOutside": "Escurecer Externamente",
|
"betaDarkenOutside": "Escurecer Externamente",
|
||||||
"betaLimitToBox": "Limitar Para a Caixa",
|
"betaLimitToBox": "Limitar Para a Caixa",
|
||||||
"betaPreserveMasked": "Preservar Máscarado"
|
"betaPreserveMasked": "Preservar Máscarado"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"feature": {
|
||||||
|
"seed": "O valor da semente afeta o ruído inicial a partir do qual a imagem é formada. Você pode usar as sementes já existentes de imagens anteriores. 'Limiar de ruído' é usado para mitigar artefatos em valores CFG altos (experimente a faixa de 0-10), e o Perlin para adicionar ruído Perlin durante a geração: ambos servem para adicionar variação às suas saídas.",
|
||||||
|
"gallery": "A galeria exibe as gerações da pasta de saída conforme elas são criadas. As configurações são armazenadas em arquivos e acessadas pelo menu de contexto.",
|
||||||
|
"other": "Essas opções ativam modos alternativos de processamento para o Invoke. 'Seamless tiling' criará padrões repetidos na saída. 'High resolution' é uma geração em duas etapas com img2img: use essa configuração quando desejar uma imagem maior e mais coerente sem artefatos. Levará mais tempo do que o txt2img usual.",
|
||||||
|
"boundingBox": "A caixa delimitadora é a mesma que as configurações de largura e altura para Texto para Imagem ou Imagem para Imagem. Apenas a área na caixa será processada.",
|
||||||
|
"upscale": "Use o ESRGAN para ampliar a imagem imediatamente após a geração.",
|
||||||
|
"seamCorrection": "Controla o tratamento das emendas visíveis que ocorrem entre as imagens geradas no canvas.",
|
||||||
|
"faceCorrection": "Correção de rosto com GFPGAN ou Codeformer: o algoritmo detecta rostos na imagem e corrige quaisquer defeitos. Um valor alto mudará mais a imagem, resultando em rostos mais atraentes. Codeformer com uma fidelidade maior preserva a imagem original às custas de uma correção de rosto mais forte.",
|
||||||
|
"prompt": "Este é o campo de prompt. O prompt inclui objetos de geração e termos estilísticos. Você também pode adicionar peso (importância do token) no prompt, mas comandos e parâmetros de CLI não funcionarão.",
|
||||||
|
"infillAndScaling": "Gerencie os métodos de preenchimento (usados em áreas mascaradas ou apagadas do canvas) e a escala (útil para tamanhos de caixa delimitadora pequenos).",
|
||||||
|
"imageToImage": "Image to Image carrega qualquer imagem como inicial, que é então usada para gerar uma nova junto com o prompt. Quanto maior o valor, mais a imagem resultante mudará. Valores de 0.0 a 1.0 são possíveis, a faixa recomendada é de 0.25 a 0.75",
|
||||||
|
"variations": "Experimente uma variação com um valor entre 0,1 e 1,0 para mudar o resultado para uma determinada semente. Variações interessantes da semente estão entre 0,1 e 0,3."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
invokeai/frontend/web/dist/locales/zh_Hant.json
vendored
25
invokeai/frontend/web/dist/locales/zh_Hant.json
vendored
@ -1 +1,24 @@
|
|||||||
{}
|
{
|
||||||
|
"common": {
|
||||||
|
"nodes": "節點",
|
||||||
|
"img2img": "圖片轉圖片",
|
||||||
|
"langSimplifiedChinese": "簡體中文",
|
||||||
|
"statusError": "錯誤",
|
||||||
|
"statusDisconnected": "已中斷連線",
|
||||||
|
"statusConnected": "已連線",
|
||||||
|
"back": "返回",
|
||||||
|
"load": "載入",
|
||||||
|
"close": "關閉",
|
||||||
|
"langEnglish": "英語",
|
||||||
|
"settingsLabel": "設定",
|
||||||
|
"upload": "上傳",
|
||||||
|
"langArabic": "阿拉伯語",
|
||||||
|
"greenTheme": "綠色",
|
||||||
|
"lightTheme": "淺色",
|
||||||
|
"darkTheme": "深色",
|
||||||
|
"discordLabel": "Discord",
|
||||||
|
"nodesDesc": "使用Node生成圖像的系統正在開發中。敬請期待有關於這項功能的更新。",
|
||||||
|
"reportBugLabel": "回報錯誤",
|
||||||
|
"githubLabel": "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"accessibility": {
|
||||||
|
"modelSelect": "Model Select",
|
||||||
|
"invokeProgressBar": "Invoke progress bar"
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
"themeLabel": "Theme",
|
"themeLabel": "Theme",
|
||||||
|
@ -68,6 +68,7 @@ export default function IAISimpleMenu(props: IAIMenuProps) {
|
|||||||
<MenuButton
|
<MenuButton
|
||||||
as={menuType === 'icon' ? IconButton : Button}
|
as={menuType === 'icon' ? IconButton : Button}
|
||||||
tooltip={iconTooltip}
|
tooltip={iconTooltip}
|
||||||
|
aria-label={iconTooltip}
|
||||||
icon={isOpen ? <MdArrowDropUp /> : <MdArrowDropDown />}
|
icon={isOpen ? <MdArrowDropUp /> : <MdArrowDropDown />}
|
||||||
paddingX={0}
|
paddingX={0}
|
||||||
paddingY={menuType === 'regular' ? 2 : 0}
|
paddingY={menuType === 'regular' ? 2 : 0}
|
||||||
|
38
invokeai/frontend/web/src/component.tsx
Normal file
38
invokeai/frontend/web/src/component.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import React, { lazy } from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
|
import { store } from './app/store';
|
||||||
|
import { persistor } from './persistor';
|
||||||
|
import '@fontsource/inter/100.css';
|
||||||
|
import '@fontsource/inter/200.css';
|
||||||
|
import '@fontsource/inter/300.css';
|
||||||
|
import '@fontsource/inter/400.css';
|
||||||
|
import '@fontsource/inter/500.css';
|
||||||
|
import '@fontsource/inter/600.css';
|
||||||
|
import '@fontsource/inter/700.css';
|
||||||
|
import '@fontsource/inter/800.css';
|
||||||
|
import '@fontsource/inter/900.css';
|
||||||
|
|
||||||
|
import Loading from './Loading';
|
||||||
|
|
||||||
|
// Localization
|
||||||
|
import './i18n';
|
||||||
|
|
||||||
|
const App = lazy(() => import('./app/App'));
|
||||||
|
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
||||||
|
|
||||||
|
export default function Component() {
|
||||||
|
return (
|
||||||
|
<React.StrictMode>
|
||||||
|
<Provider store={store}>
|
||||||
|
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||||
|
<React.Suspense fallback={<Loading showText />}>
|
||||||
|
<ThemeLocaleProvider>
|
||||||
|
<App />
|
||||||
|
</ThemeLocaleProvider>
|
||||||
|
</React.Suspense>
|
||||||
|
</PersistGate>
|
||||||
|
</Provider>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
@ -6,6 +6,7 @@ import IAISelect from 'common/components/IAISelect';
|
|||||||
import { isEqual, map } from 'lodash';
|
import { isEqual, map } from 'lodash';
|
||||||
|
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { activeModelSelector, systemSelector } from '../store/systemSelectors';
|
import { activeModelSelector, systemSelector } from '../store/systemSelectors';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
@ -24,6 +25,7 @@ const selector = createSelector(
|
|||||||
|
|
||||||
const ModelSelect = () => {
|
const ModelSelect = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { models, isProcessing } = useAppSelector(selector);
|
const { models, isProcessing } = useAppSelector(selector);
|
||||||
const activeModel = useAppSelector(activeModelSelector);
|
const activeModel = useAppSelector(activeModelSelector);
|
||||||
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
|
const handleChangeModel = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
@ -38,6 +40,7 @@ const ModelSelect = () => {
|
|||||||
>
|
>
|
||||||
<IAISelect
|
<IAISelect
|
||||||
style={{ fontSize: 'sm' }}
|
style={{ fontSize: 'sm' }}
|
||||||
|
aria-label={t('accessibility.modelSelect')}
|
||||||
tooltip={activeModel.description}
|
tooltip={activeModel.description}
|
||||||
isDisabled={isProcessing}
|
isDisabled={isProcessing}
|
||||||
value={activeModel.name}
|
value={activeModel.name}
|
||||||
|
@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { useAppSelector } from 'app/storeHooks';
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
import { SystemState } from 'features/system/store/systemSlice';
|
import { SystemState } from 'features/system/store/systemSlice';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants';
|
import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants';
|
||||||
import { systemSelector } from '../store/systemSelectors';
|
import { systemSelector } from '../store/systemSelectors';
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ const progressBarSelector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const ProgressBar = () => {
|
const ProgressBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { isProcessing, currentStep, totalSteps, currentStatusHasSteps } =
|
const { isProcessing, currentStep, totalSteps, currentStatusHasSteps } =
|
||||||
useAppSelector(progressBarSelector);
|
useAppSelector(progressBarSelector);
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ const ProgressBar = () => {
|
|||||||
return (
|
return (
|
||||||
<Progress
|
<Progress
|
||||||
value={value}
|
value={value}
|
||||||
|
aria-label={t('accessibility.invokeProgressBar')}
|
||||||
isIndeterminate={isProcessing && !currentStatusHasSteps}
|
isIndeterminate={isProcessing && !currentStatusHasSteps}
|
||||||
height={PROGRESS_BAR_THICKNESS}
|
height={PROGRESS_BAR_THICKNESS}
|
||||||
zIndex={99}
|
zIndex={99}
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
TabPanels,
|
TabPanels,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
VisuallyHidden,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store';
|
import { RootState } from 'app/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
@ -164,7 +165,12 @@ export default function InvokeTabs() {
|
|||||||
label={tabDict[key as keyof typeof tabDict].tooltip}
|
label={tabDict[key as keyof typeof tabDict].tooltip}
|
||||||
placement="end"
|
placement="end"
|
||||||
>
|
>
|
||||||
<Tab>{tabDict[key as keyof typeof tabDict].title}</Tab>
|
<Tab>
|
||||||
|
<VisuallyHidden>
|
||||||
|
{tabDict[key as keyof typeof tabDict].tooltip}
|
||||||
|
</VisuallyHidden>
|
||||||
|
{tabDict[key as keyof typeof tabDict].title}
|
||||||
|
</Tab>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -61,6 +61,7 @@ export default function UnifiedCanvasLayerSelect() {
|
|||||||
return (
|
return (
|
||||||
<IAISelect
|
<IAISelect
|
||||||
tooltip={`${t('unifiedCanvas.layer')} (Q)`}
|
tooltip={`${t('unifiedCanvas.layer')} (Q)`}
|
||||||
|
aria-label={`${t('unifiedCanvas.layer')} (Q)`}
|
||||||
tooltipProps={{ hasArrow: true, placement: 'top' }}
|
tooltipProps={{ hasArrow: true, placement: 'top' }}
|
||||||
value={layer}
|
value={layer}
|
||||||
validValues={LAYER_NAMES_DICT}
|
validValues={LAYER_NAMES_DICT}
|
||||||
|
@ -1,37 +1,7 @@
|
|||||||
import React, { lazy } from 'react';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
|
||||||
import { store } from './app/store';
|
|
||||||
import { persistor } from './persistor';
|
|
||||||
import '@fontsource/inter/100.css';
|
|
||||||
import '@fontsource/inter/200.css';
|
|
||||||
import '@fontsource/inter/300.css';
|
|
||||||
import '@fontsource/inter/400.css';
|
|
||||||
import '@fontsource/inter/500.css';
|
|
||||||
import '@fontsource/inter/600.css';
|
|
||||||
import '@fontsource/inter/700.css';
|
|
||||||
import '@fontsource/inter/800.css';
|
|
||||||
import '@fontsource/inter/900.css';
|
|
||||||
|
|
||||||
import Loading from './Loading';
|
import Component from './component';
|
||||||
|
|
||||||
// Localization
|
|
||||||
import './i18n';
|
|
||||||
|
|
||||||
const App = lazy(() => import('./app/App'));
|
|
||||||
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<Component />
|
||||||
<Provider store={store}>
|
|
||||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
|
||||||
<React.Suspense fallback={<Loading showText />}>
|
|
||||||
<ThemeLocaleProvider>
|
|
||||||
<App />
|
|
||||||
</ThemeLocaleProvider>
|
|
||||||
</React.Suspense>
|
|
||||||
</PersistGate>
|
|
||||||
</Provider>
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
);
|
||||||
|
File diff suppressed because one or more lines are too long
@ -63,6 +63,7 @@ dependencies = [
|
|||||||
"prompt-toolkit",
|
"prompt-toolkit",
|
||||||
"pypatchmatch",
|
"pypatchmatch",
|
||||||
"pyreadline3",
|
"pyreadline3",
|
||||||
|
"pytorch-lightning==1.7.7",
|
||||||
"realesrgan",
|
"realesrgan",
|
||||||
"requests==2.28.2",
|
"requests==2.28.2",
|
||||||
"rich~=13.3",
|
"rich~=13.3",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user