2022-12-01 05:33:20 +00:00
|
|
|
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
|
|
|
|
|
|
|
|
import argparse
|
2023-04-06 04:06:05 +00:00
|
|
|
import re
|
2023-03-03 06:02:00 +00:00
|
|
|
import shlex
|
2023-05-18 14:48:23 +00:00
|
|
|
import sys
|
2022-12-01 05:33:20 +00:00
|
|
|
import time
|
2023-07-03 16:17:45 +00:00
|
|
|
from typing import Union, get_type_hints, Optional
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2023-05-17 18:13:12 +00:00
|
|
|
from pydantic import BaseModel, ValidationError
|
2022-12-01 05:33:20 +00:00
|
|
|
from pydantic.fields import Field
|
|
|
|
|
2023-06-07 03:19:10 +00:00
|
|
|
# This should come early so that the logger can pick up its configuration options
|
|
|
|
from .services.config import InvokeAIAppConfig
|
2023-06-07 02:57:49 +00:00
|
|
|
from invokeai.backend.util.logging import InvokeAILogger
|
2023-06-07 03:19:10 +00:00
|
|
|
config = InvokeAIAppConfig.get_config()
|
|
|
|
config.parse_args()
|
|
|
|
logger = InvokeAILogger().getLogger(config=config)
|
2023-07-08 00:47:29 +00:00
|
|
|
from invokeai.version.invokeai_version import __version__
|
|
|
|
|
|
|
|
# we call this early so that the message appears before other invokeai initialization messages
|
|
|
|
if config.version:
|
|
|
|
print(f'InvokeAI version {__version__}')
|
|
|
|
sys.exit(0)
|
2023-06-07 03:19:10 +00:00
|
|
|
|
2023-06-26 18:16:10 +00:00
|
|
|
from invokeai.app.services.board_image_record_storage import (
|
|
|
|
SqliteBoardImageRecordStorage,
|
|
|
|
)
|
|
|
|
from invokeai.app.services.board_images import (
|
|
|
|
BoardImagesService,
|
|
|
|
BoardImagesServiceDependencies,
|
|
|
|
)
|
|
|
|
from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage
|
|
|
|
from invokeai.app.services.boards import BoardService, BoardServiceDependencies
|
2023-05-25 00:47:45 +00:00
|
|
|
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
|
2023-06-26 18:16:10 +00:00
|
|
|
from invokeai.app.services.images import ImageService, ImageServiceDependencies
|
2023-05-25 00:47:45 +00:00
|
|
|
from invokeai.app.services.metadata import CoreMetadataService
|
2023-05-26 23:10:02 +00:00
|
|
|
from invokeai.app.services.resource_name import SimpleNameService
|
2023-05-25 00:47:45 +00:00
|
|
|
from invokeai.app.services.urls import LocalUrlService
|
2023-06-10 14:13:54 +00:00
|
|
|
from .services.default_graphs import (default_text_to_image_graph_id,
|
|
|
|
create_system_graphs)
|
2023-04-06 04:06:05 +00:00
|
|
|
from .services.latent_storage import DiskLatentsStorage, ForwardCacheLatentsStorage
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-05-26 02:40:45 +00:00
|
|
|
from .cli.commands import (BaseCommand, CliContext, ExitCli,
|
|
|
|
SortedHelpFormatter, add_graph_parsers, add_parsers)
|
2023-03-27 16:20:24 +00:00
|
|
|
from .cli.completer import set_autocompleter
|
2023-03-03 06:02:00 +00:00
|
|
|
from .invocations.baseinvocation import BaseInvocation
|
|
|
|
from .services.events import EventServiceBase
|
2023-05-26 02:40:45 +00:00
|
|
|
from .services.graph import (Edge, EdgeConnection, GraphExecutionState,
|
|
|
|
GraphInvocation, LibraryGraph,
|
|
|
|
are_connection_types_compatible)
|
2023-05-21 10:05:33 +00:00
|
|
|
from .services.image_file_storage import DiskImageFileStorage
|
2022-12-01 05:33:20 +00:00
|
|
|
from .services.invocation_queue import MemoryInvocationQueue
|
|
|
|
from .services.invocation_services import InvocationServices
|
2023-03-03 06:02:00 +00:00
|
|
|
from .services.invoker import Invoker
|
2023-05-26 02:40:45 +00:00
|
|
|
from .services.model_manager_service import ModelManagerService
|
2023-03-03 06:02:00 +00:00
|
|
|
from .services.processor import DefaultInvocationProcessor
|
2023-05-26 02:40:45 +00:00
|
|
|
from .services.restoration_services import RestorationServices
|
2023-03-03 06:02:00 +00:00
|
|
|
from .services.sqlite import SqliteItemStorage
|
2023-06-07 03:19:10 +00:00
|
|
|
|
2023-07-04 22:05:01 +00:00
|
|
|
import torch
|
|
|
|
if torch.backends.mps.is_available():
|
|
|
|
import invokeai.backend.util.mps_fixes
|
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
class CliCommand(BaseModel):
|
|
|
|
command: Union[BaseCommand.get_commands() + BaseInvocation.get_invocations()] = Field(discriminator="type") # type: ignore
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class InvalidArgs(Exception):
|
|
|
|
pass
|
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
def add_invocation_args(command_parser):
|
|
|
|
# Add linking capability
|
|
|
|
command_parser.add_argument(
|
|
|
|
"--link",
|
|
|
|
"-l",
|
|
|
|
action="append",
|
|
|
|
nargs=3,
|
2023-04-06 04:06:05 +00:00
|
|
|
help="A link in the format 'source_node source_field dest_field'. source_node can be relative to history (e.g. -1)",
|
2023-03-04 22:46:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
command_parser.add_argument(
|
|
|
|
"--link_node",
|
|
|
|
"-ln",
|
|
|
|
action="append",
|
|
|
|
help="A link from all fields in the specified node. Node can be relative to history (e.g. -1)",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
def get_command_parser(services: InvocationServices) -> argparse.ArgumentParser:
|
2022-12-01 05:33:20 +00:00
|
|
|
# Create invocation parser
|
2023-05-04 02:30:30 +00:00
|
|
|
parser = argparse.ArgumentParser(formatter_class=SortedHelpFormatter)
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
def exit(*args, **kwargs):
|
|
|
|
raise InvalidArgs
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
parser.exit = exit
|
2023-03-03 06:02:00 +00:00
|
|
|
subparsers = parser.add_subparsers(dest="type")
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
# Create subparsers for each invocation
|
|
|
|
invocations = BaseInvocation.get_all_subclasses()
|
2023-03-04 22:46:02 +00:00
|
|
|
add_parsers(subparsers, invocations, add_arguments=add_invocation_args)
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
# Create subparsers for each command
|
|
|
|
commands = BaseCommand.get_all_subclasses()
|
|
|
|
add_parsers(subparsers, commands, exclude_fields=["type"])
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
# Create subparsers for exposed CLI graphs
|
|
|
|
# TODO: add a way to identify these graphs
|
|
|
|
text_to_image = services.graph_library.get(default_text_to_image_graph_id)
|
|
|
|
add_graph_parsers(subparsers, [text_to_image], add_arguments=add_invocation_args)
|
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
return parser
|
|
|
|
|
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
class NodeField():
|
|
|
|
alias: str
|
|
|
|
node_path: str
|
|
|
|
field: str
|
|
|
|
field_type: type
|
|
|
|
|
|
|
|
def __init__(self, alias: str, node_path: str, field: str, field_type: type):
|
|
|
|
self.alias = alias
|
|
|
|
self.node_path = node_path
|
|
|
|
self.field = field
|
|
|
|
self.field_type = field_type
|
|
|
|
|
|
|
|
|
|
|
|
def fields_from_type_hints(hints: dict[str, type], node_path: str) -> dict[str,NodeField]:
|
|
|
|
return {k:NodeField(alias=k, node_path=node_path, field=k, field_type=v) for k, v in hints.items()}
|
|
|
|
|
|
|
|
|
|
|
|
def get_node_input_field(graph: LibraryGraph, field_alias: str, node_id: str) -> NodeField:
|
|
|
|
"""Gets the node field for the specified field alias"""
|
|
|
|
exposed_input = next(e for e in graph.exposed_inputs if e.alias == field_alias)
|
|
|
|
node_type = type(graph.graph.get_node(exposed_input.node_path))
|
|
|
|
return NodeField(alias=exposed_input.alias, node_path=f'{node_id}.{exposed_input.node_path}', field=exposed_input.field, field_type=get_type_hints(node_type)[exposed_input.field])
|
|
|
|
|
|
|
|
|
|
|
|
def get_node_output_field(graph: LibraryGraph, field_alias: str, node_id: str) -> NodeField:
|
|
|
|
"""Gets the node field for the specified field alias"""
|
|
|
|
exposed_output = next(e for e in graph.exposed_outputs if e.alias == field_alias)
|
|
|
|
node_type = type(graph.graph.get_node(exposed_output.node_path))
|
|
|
|
node_output_type = node_type.get_output_type()
|
|
|
|
return NodeField(alias=exposed_output.alias, node_path=f'{node_id}.{exposed_output.node_path}', field=exposed_output.field, field_type=get_type_hints(node_output_type)[exposed_output.field])
|
|
|
|
|
|
|
|
|
|
|
|
def get_node_inputs(invocation: BaseInvocation, context: CliContext) -> dict[str, NodeField]:
|
|
|
|
"""Gets the inputs for the specified invocation from the context"""
|
|
|
|
node_type = type(invocation)
|
|
|
|
if node_type is not GraphInvocation:
|
|
|
|
return fields_from_type_hints(get_type_hints(node_type), invocation.id)
|
|
|
|
else:
|
|
|
|
graph: LibraryGraph = context.invoker.services.graph_library.get(context.graph_nodes[invocation.id])
|
|
|
|
return {e.alias: get_node_input_field(graph, e.alias, invocation.id) for e in graph.exposed_inputs}
|
|
|
|
|
|
|
|
|
|
|
|
def get_node_outputs(invocation: BaseInvocation, context: CliContext) -> dict[str, NodeField]:
|
|
|
|
"""Gets the outputs for the specified invocation from the context"""
|
|
|
|
node_type = type(invocation)
|
|
|
|
if node_type is not GraphInvocation:
|
|
|
|
return fields_from_type_hints(get_type_hints(node_type.get_output_type()), invocation.id)
|
|
|
|
else:
|
|
|
|
graph: LibraryGraph = context.invoker.services.graph_library.get(context.graph_nodes[invocation.id])
|
|
|
|
return {e.alias: get_node_output_field(graph, e.alias, invocation.id) for e in graph.exposed_outputs}
|
|
|
|
|
|
|
|
|
2023-03-03 06:02:00 +00:00
|
|
|
def generate_matching_edges(
|
2023-04-14 06:41:06 +00:00
|
|
|
a: BaseInvocation, b: BaseInvocation, context: CliContext
|
2023-03-15 06:09:30 +00:00
|
|
|
) -> list[Edge]:
|
2022-12-01 05:33:20 +00:00
|
|
|
"""Generates all possible edges between two invocations"""
|
2023-04-14 06:41:06 +00:00
|
|
|
afields = get_node_outputs(a, context)
|
|
|
|
bfields = get_node_inputs(b, context)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
matching_fields = set(afields.keys()).intersection(bfields.keys())
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
# Remove invalid fields
|
2023-03-03 06:02:00 +00:00
|
|
|
invalid_fields = set(["type", "id"])
|
2022-12-01 05:33:20 +00:00
|
|
|
matching_fields = matching_fields.difference(invalid_fields)
|
|
|
|
|
2023-04-06 04:06:05 +00:00
|
|
|
# Validate types
|
2023-04-14 06:41:06 +00:00
|
|
|
matching_fields = [f for f in matching_fields if are_connection_types_compatible(afields[f].field_type, bfields[f].field_type)]
|
2023-04-06 04:06:05 +00:00
|
|
|
|
2023-03-03 06:02:00 +00:00
|
|
|
edges = [
|
2023-03-15 06:09:30 +00:00
|
|
|
Edge(
|
2023-04-14 06:41:06 +00:00
|
|
|
source=EdgeConnection(node_id=afields[alias].node_path, field=afields[alias].field),
|
|
|
|
destination=EdgeConnection(node_id=bfields[alias].node_path, field=bfields[alias].field)
|
2023-03-03 06:02:00 +00:00
|
|
|
)
|
2023-04-14 06:41:06 +00:00
|
|
|
for alias in matching_fields
|
2023-03-03 06:02:00 +00:00
|
|
|
]
|
2022-12-01 05:33:20 +00:00
|
|
|
return edges
|
|
|
|
|
|
|
|
|
2023-03-09 03:25:03 +00:00
|
|
|
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)
|
2023-03-15 06:09:30 +00:00
|
|
|
while not context.get_session().is_complete():
|
2023-03-09 03:25:03 +00:00
|
|
|
# Wait some time
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
|
|
# Print any errors
|
|
|
|
if context.session.has_error():
|
|
|
|
for n in context.session.errors:
|
2023-04-29 14:48:50 +00:00
|
|
|
context.invoker.services.logger.error(
|
2023-03-15 06:09:30 +00:00
|
|
|
f"Error in node {n} (source node {context.session.prepared_source_mapping[n]}): {context.session.errors[n]}"
|
2023-03-09 03:25:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
raise SessionError()
|
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
def invoke_cli():
|
2023-07-08 00:47:29 +00:00
|
|
|
logger.info(f'InvokeAI version {__version__}')
|
2023-05-17 18:13:12 +00:00
|
|
|
# get the optional list of invocations to execute on the command line
|
|
|
|
parser = config.get_parser()
|
|
|
|
parser.add_argument('commands',nargs='*')
|
|
|
|
invocation_commands = parser.parse_args().commands
|
2023-05-18 14:48:23 +00:00
|
|
|
|
|
|
|
# get the optional file to read commands from.
|
|
|
|
# Simplest is to use it for STDIN
|
|
|
|
if infile := config.from_file:
|
|
|
|
sys.stdin = open(infile,"r")
|
2023-05-17 18:13:12 +00:00
|
|
|
|
2023-05-12 01:24:29 +00:00
|
|
|
model_manager = ModelManagerService(config,logger)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
events = EventServiceBase()
|
2023-05-04 02:30:30 +00:00
|
|
|
output_folder = config.output_path
|
Partial migration of UI to nodes API (#3195)
* feat(ui): add axios client generator and simple example
* fix(ui): update client & nodes test code w/ new Edge type
* chore(ui): organize generated files
* chore(ui): update .eslintignore, .prettierignore
* chore(ui): update openapi.json
* feat(backend): fixes for nodes/generator
* feat(ui): generate object args for api client
* feat(ui): more nodes api prototyping
* feat(ui): nodes cancel
* chore(ui): regenerate api client
* fix(ui): disable OG web server socket connection
* fix(ui): fix scrollbar styles typing and prop
just noticed the typo, and made the types stronger.
* feat(ui): add socketio types
* feat(ui): wip nodes
- extract api client method arg types instead of manually declaring them
- update example to display images
- general tidy up
* start building out node translations from frontend state and add notes about missing features
* use reference to sampler_name
* use reference to sampler_name
* add optional apiUrl prop
* feat(ui): start hooking up dynamic txt2img node generation, create middleware for session invocation
* feat(ui): write separate nodes socket layer, txt2img generating and rendering w single node
* feat(ui): img2img implementation
* feat(ui): get intermediate images working but types are stubbed out
* chore(ui): add support for package mode
* feat(ui): add nodes mode script
* feat(ui): handle random seeds
* fix(ui): fix middleware types
* feat(ui): add rtk action type guard
* feat(ui): disable NodeAPITest
This was polluting the network/socket logs.
* feat(ui): fix parameters panel border color
This commit should be elsewhere but I don't want to break my flow
* feat(ui): make thunk types more consistent
* feat(ui): add type guards for outputs
* feat(ui): load images on socket connect
Rudimentary
* chore(ui): bump redux-toolkit
* docs(ui): update readme
* chore(ui): regenerate api client
* chore(ui): add typescript as dev dependency
I am having trouble with TS versions after vscode updated and now uses TS 5. `madge` has installed 3.9.10 and for whatever reason my vscode wants to use that. Manually specifying 4.9.5 and then setting vscode to use that as the workspace TS fixes the issue.
* feat(ui): begin migrating gallery to nodes
Along the way, migrate to use RTK `createEntityAdapter` for gallery images, and separate `results` and `uploads` into separate slices. Much cleaner this way.
* feat(ui): clean up & comment results slice
* fix(ui): separate thunk for initial gallery load so it properly gets index 0
* feat(ui): POST upload working
* fix(ui): restore removed type
* feat(ui): patch api generation for headers access
* chore(ui): regenerate api
* feat(ui): wip gallery migration
* feat(ui): wip gallery migration
* chore(ui): regenerate api
* feat(ui): wip refactor socket events
* feat(ui): disable panels based on app props
* feat(ui): invert logic to be disabled
* disable panels when app mounts
* feat(ui): add support to disableTabs
* docs(ui): organise and update docs
* lang(ui): add toast strings
* feat(ui): wip events, comments, and general refactoring
* feat(ui): add optional token for auth
* feat(ui): export StatusIndicator and ModelSelect for header use
* feat(ui) working on making socket URL dynamic
* feat(ui): dynamic middleware loading
* feat(ui): prep for socket jwt
* feat(ui): migrate cancelation
also updated action names to be event-like instead of declaration-like
sorry, i was scattered and this commit has a lot of unrelated stuff in it.
* fix(ui): fix img2img type
* chore(ui): regenerate api client
* feat(ui): improve InvocationCompleteEvent types
* feat(ui): increase StatusIndicator font size
* fix(ui): fix middleware order for multi-node graphs
* feat(ui): add exampleGraphs object w/ iterations example
* feat(ui): generate iterations graph
* feat(ui): update ModelSelect for nodes API
* feat(ui): add hi-res functionality for txt2img generations
* feat(ui): "subscribe" to particular nodes
feels like a dirty hack but oh well it works
* feat(ui): first steps to node editor ui
* fix(ui): disable event subscription
it is not fully baked just yet
* feat(ui): wip node editor
* feat(ui): remove extraneous field types
* feat(ui): nodes before deleting stuff
* feat(ui): cleanup nodes ui stuff
* feat(ui): hook up nodes to redux
* fix(ui): fix handle
* fix(ui): add basic node edges & connection validation
* feat(ui): add connection validation styling
* feat(ui): increase edge width
* feat(ui): it blends
* feat(ui): wip model handling and graph topology validation
* feat(ui): validation connections w/ graphlib
* docs(ui): update nodes doc
* feat(ui): wip node editor
* chore(ui): rebuild api, update types
* add redux-dynamic-middlewares as a dependency
* feat(ui): add url host transformation
* feat(ui): handle already-connected fields
* feat(ui): rewrite SqliteItemStore in sqlalchemy
* fix(ui): fix sqlalchemy dynamic model instantiation
* feat(ui, nodes): metadata wip
* feat(ui, nodes): models
* feat(ui, nodes): more metadata wip
* feat(ui): wip range/iterate
* fix(nodes): fix sqlite typing
* feat(ui): export new type for invoke component
* tests(nodes): fix test instantiation of ImageField
* feat(nodes): fix LoadImageInvocation
* feat(nodes): add `title` ui hint
* feat(nodes): make ImageField attrs optional
* feat(ui): wip nodes etc
* feat(nodes): roll back sqlalchemy
* fix(nodes): partially address feedback
* fix(backend): roll back changes to pngwriter
* feat(nodes): wip address metadata feedback
* feat(nodes): add seeded rng to RandomRange
* feat(nodes): address feedback
* feat(nodes): move GET images error handling to DiskImageStorage
* feat(nodes): move GET images error handling to DiskImageStorage
* fix(nodes): fix image output schema customization
* feat(ui): img2img/txt2img -> linear
- remove txt2img and img2img tabs
- add linear tab
- add initial image selection to linear parameters accordion
* feat(ui): tidy graph builders
* feat(ui): tidy misc
* feat(ui): improve invocation union types
* feat(ui): wip metadata viewer recall
* feat(ui): move fonts to normal deps
* feat(nodes): fix broken upload
* feat(nodes): add metadata module + tests, thumbnails
- `MetadataModule` is stateless and needed in places where the `InvocationContext` is not available, so have not made it a `service`
- Handles loading/parsing/building metadata, and creating png info objects
- added tests for MetadataModule
- Lifted thumbnail stuff to util
* fix(nodes): revert change to RandomRangeInvocation
* feat(nodes): address feedback
- make metadata a service
- rip out pydantic validation, implement metadata parsing as simple functions
- update tests
- address other minor feedback items
* fix(nodes): fix other tests
* fix(nodes): add metadata service to cli
* fix(nodes): fix latents/image field parsing
* feat(nodes): customise LatentsField schema
* feat(nodes): move metadata parsing to frontend
* fix(nodes): fix metadata test
---------
Co-authored-by: maryhipp <maryhipp@gmail.com>
Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
2023-04-22 03:10:20 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
# TODO: build a file/path manager?
|
2023-05-25 01:53:02 +00:00
|
|
|
if config.use_memory_db:
|
|
|
|
db_location = ":memory:"
|
|
|
|
else:
|
2023-06-04 00:24:41 +00:00
|
|
|
db_location = config.db_path
|
2023-06-08 11:17:54 +00:00
|
|
|
db_location.parent.mkdir(parents=True,exist_ok=True)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-05-25 01:12:22 +00:00
|
|
|
logger.info(f'InvokeAI database location is "{db_location}"')
|
|
|
|
|
2023-05-25 00:47:45 +00:00
|
|
|
graph_execution_manager = SqliteItemStorage[GraphExecutionState](
|
|
|
|
filename=db_location, table_name="graph_executions"
|
|
|
|
)
|
|
|
|
|
|
|
|
urls = LocalUrlService()
|
|
|
|
metadata = CoreMetadataService()
|
|
|
|
image_record_storage = SqliteImageRecordStorage(db_location)
|
|
|
|
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
|
2023-05-26 23:10:02 +00:00
|
|
|
names = SimpleNameService()
|
2023-05-25 00:47:45 +00:00
|
|
|
|
2023-06-26 18:16:10 +00:00
|
|
|
board_record_storage = SqliteBoardRecordStorage(db_location)
|
|
|
|
board_image_record_storage = SqliteBoardImageRecordStorage(db_location)
|
|
|
|
|
|
|
|
boards = BoardService(
|
|
|
|
services=BoardServiceDependencies(
|
|
|
|
board_image_record_storage=board_image_record_storage,
|
|
|
|
board_record_storage=board_record_storage,
|
|
|
|
image_record_storage=image_record_storage,
|
|
|
|
url=urls,
|
|
|
|
logger=logger,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
board_images = BoardImagesService(
|
|
|
|
services=BoardImagesServiceDependencies(
|
|
|
|
board_image_record_storage=board_image_record_storage,
|
|
|
|
board_record_storage=board_record_storage,
|
|
|
|
image_record_storage=image_record_storage,
|
|
|
|
url=urls,
|
|
|
|
logger=logger,
|
|
|
|
)
|
2023-05-25 00:47:45 +00:00
|
|
|
)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-06-26 18:16:10 +00:00
|
|
|
images = ImageService(
|
|
|
|
services=ImageServiceDependencies(
|
|
|
|
board_image_record_storage=board_image_record_storage,
|
|
|
|
image_record_storage=image_record_storage,
|
|
|
|
image_file_storage=image_file_storage,
|
|
|
|
metadata=metadata,
|
|
|
|
url=urls,
|
|
|
|
logger=logger,
|
|
|
|
names=names,
|
|
|
|
graph_execution_manager=graph_execution_manager,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-02-25 04:11:28 +00:00
|
|
|
services = InvocationServices(
|
remove factory pattern
Factory pattern is now removed. Typical usage of the InvokeAIGenerator is now:
```
from invokeai.backend.generator import (
InvokeAIGeneratorBasicParams,
Txt2Img,
Img2Img,
Inpaint,
)
params = InvokeAIGeneratorBasicParams(
model_name = 'stable-diffusion-1.5',
steps = 30,
scheduler = 'k_lms',
cfg_scale = 8.0,
height = 640,
width = 640
)
print ('=== TXT2IMG TEST ===')
txt2img = Txt2Img(manager, params)
outputs = txt2img.generate(prompt='banana sushi', iterations=2)
for i in outputs:
print(f'image={output.image}, seed={output.seed}, model={output.params.model_name}, hash={output.model_hash}, steps={output.params.steps}')
```
The `params` argument is optional, so if you wish to accept default
parameters and selectively override them, just do this:
```
outputs = Txt2Img(manager).generate(prompt='banana sushi',
steps=50,
scheduler='k_heun',
model_name='stable-diffusion-2.1'
)
```
2023-03-11 00:33:04 +00:00
|
|
|
model_manager=model_manager,
|
2023-03-03 06:02:00 +00:00
|
|
|
events=events,
|
2023-04-06 04:06:05 +00:00
|
|
|
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f'{output_folder}/latents')),
|
2023-05-25 00:47:45 +00:00
|
|
|
images=images,
|
2023-06-26 18:16:10 +00:00
|
|
|
boards=boards,
|
|
|
|
board_images=board_images,
|
2023-03-03 06:02:00 +00:00
|
|
|
queue=MemoryInvocationQueue(),
|
2023-04-14 06:41:06 +00:00
|
|
|
graph_library=SqliteItemStorage[LibraryGraph](
|
|
|
|
filename=db_location, table_name="graphs"
|
|
|
|
),
|
2023-05-25 00:47:45 +00:00
|
|
|
graph_execution_manager=graph_execution_manager,
|
2023-03-03 06:02:00 +00:00
|
|
|
processor=DefaultInvocationProcessor(),
|
2023-04-29 14:48:50 +00:00
|
|
|
restoration=RestorationServices(config,logger=logger),
|
|
|
|
logger=logger,
|
2023-05-04 02:30:30 +00:00
|
|
|
configuration=config,
|
2022-12-01 05:33:20 +00:00
|
|
|
)
|
2023-05-18 21:17:45 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
system_graphs = create_system_graphs(services.graph_library)
|
|
|
|
system_graph_names = set([g.name for g in system_graphs])
|
2023-05-18 21:17:45 +00:00
|
|
|
set_autocompleter(services)
|
2023-04-14 06:41:06 +00:00
|
|
|
|
2023-02-25 04:11:28 +00:00
|
|
|
invoker = Invoker(services)
|
2023-02-27 18:01:07 +00:00
|
|
|
session: GraphExecutionState = invoker.create_execution_state()
|
2023-04-14 06:41:06 +00:00
|
|
|
parser = get_command_parser(services)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-04-06 04:06:05 +00:00
|
|
|
re_negid = re.compile('^-[0-9]+$')
|
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
# Uncomment to print out previous sessions at startup
|
2023-02-25 04:11:28 +00:00
|
|
|
# print(services.session_manager.list())
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
context = CliContext(invoker, session, parser)
|
2023-05-04 02:30:30 +00:00
|
|
|
set_autocompleter(services)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-05-17 18:13:12 +00:00
|
|
|
command_line_args_exist = len(invocation_commands) > 0
|
|
|
|
done = False
|
|
|
|
|
|
|
|
while not done:
|
2022-12-01 05:33:20 +00:00
|
|
|
try:
|
2023-05-17 18:13:12 +00:00
|
|
|
if command_line_args_exist:
|
|
|
|
cmd_input = invocation_commands.pop(0)
|
|
|
|
done = len(invocation_commands) == 0
|
|
|
|
else:
|
|
|
|
cmd_input = input("invoke> ")
|
2023-03-26 04:24:27 +00:00
|
|
|
except (KeyboardInterrupt, EOFError):
|
2022-12-01 05:33:20 +00:00
|
|
|
# Ctrl-c exits
|
|
|
|
break
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Refresh the state of the session
|
2023-04-14 06:41:06 +00:00
|
|
|
#history = list(get_graph_execution_history(context.session))
|
|
|
|
history = list(reversed(context.nodes_added))
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
# Split the command for piping
|
2023-03-03 06:02:00 +00:00
|
|
|
cmds = cmd_input.split("|")
|
2023-04-14 06:41:06 +00:00
|
|
|
start_id = len(context.nodes_added)
|
2022-12-01 05:33:20 +00:00
|
|
|
current_id = start_id
|
|
|
|
new_invocations = list()
|
|
|
|
for cmd in cmds:
|
2023-03-03 06:02:00 +00:00
|
|
|
if cmd is None or cmd.strip() == "":
|
|
|
|
raise InvalidArgs("Empty command")
|
2023-02-27 18:01:07 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
# Parse args to create invocation
|
2023-03-09 03:25:03 +00:00
|
|
|
args = vars(context.parser.parse_args(shlex.split(cmd.strip())))
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
# Override defaults
|
2023-03-04 22:46:02 +00:00
|
|
|
for field_name, field_default in context.defaults.items():
|
2022-12-01 05:33:20 +00:00
|
|
|
if field_name in args:
|
|
|
|
args[field_name] = field_default
|
|
|
|
|
|
|
|
# Parse invocation
|
2023-04-14 06:41:06 +00:00
|
|
|
command: CliCommand = None # type:ignore
|
2023-07-03 16:17:45 +00:00
|
|
|
system_graph: Optional[LibraryGraph] = None
|
2023-04-14 06:41:06 +00:00
|
|
|
if args['type'] in system_graph_names:
|
|
|
|
system_graph = next(filter(lambda g: g.name == args['type'], system_graphs))
|
|
|
|
invocation = GraphInvocation(graph=system_graph.graph, id=str(current_id))
|
|
|
|
for exposed_input in system_graph.exposed_inputs:
|
|
|
|
if exposed_input.alias in args:
|
|
|
|
node = invocation.graph.get_node(exposed_input.node_path)
|
|
|
|
field = exposed_input.field
|
|
|
|
setattr(node, field, args[exposed_input.alias])
|
|
|
|
command = CliCommand(command = invocation)
|
|
|
|
context.graph_nodes[invocation.id] = system_graph.id
|
|
|
|
else:
|
|
|
|
args["id"] = current_id
|
|
|
|
command = CliCommand(command=args)
|
|
|
|
|
|
|
|
if command is None:
|
|
|
|
continue
|
2023-03-04 22:46:02 +00:00
|
|
|
|
|
|
|
# Run any CLI commands immediately
|
|
|
|
if isinstance(command.command, BaseCommand):
|
2023-03-09 03:25:03 +00:00
|
|
|
# Invoke all current nodes to preserve operation order
|
|
|
|
invoke_all(context)
|
|
|
|
|
|
|
|
# Run the command
|
2023-03-04 22:46:02 +00:00
|
|
|
command.command.run(context)
|
|
|
|
continue
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
# TODO: handle linking with library graphs
|
2022-12-01 05:33:20 +00:00
|
|
|
# Pipe previous command output (if there was a previous command)
|
2023-03-15 06:09:30 +00:00
|
|
|
edges: list[Edge] = list()
|
2022-12-01 05:33:20 +00:00
|
|
|
if len(history) > 0 or current_id != start_id:
|
2023-03-03 06:02:00 +00:00
|
|
|
from_id = (
|
|
|
|
history[0] if current_id == start_id else str(current_id - 1)
|
|
|
|
)
|
|
|
|
from_node = (
|
|
|
|
next(filter(lambda n: n[0].id == from_id, new_invocations))[0]
|
|
|
|
if current_id != start_id
|
2023-03-09 03:25:03 +00:00
|
|
|
else context.session.graph.get_node(from_id)
|
2023-03-03 06:02:00 +00:00
|
|
|
)
|
|
|
|
matching_edges = generate_matching_edges(
|
2023-04-14 06:41:06 +00:00
|
|
|
from_node, command.command, context
|
2023-03-03 06:02:00 +00:00
|
|
|
)
|
2022-12-01 05:33:20 +00:00
|
|
|
edges.extend(matching_edges)
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
# Parse provided links
|
2023-03-03 06:02:00 +00:00
|
|
|
if "link_node" in args and args["link_node"]:
|
|
|
|
for link in args["link_node"]:
|
2023-04-06 04:06:05 +00:00
|
|
|
node_id = link
|
|
|
|
if re_negid.match(node_id):
|
|
|
|
node_id = str(current_id + int(node_id))
|
|
|
|
|
|
|
|
link_node = context.session.graph.get_node(node_id)
|
2023-03-03 06:02:00 +00:00
|
|
|
matching_edges = generate_matching_edges(
|
2023-04-14 06:41:06 +00:00
|
|
|
link_node, command.command, context
|
2023-03-03 06:02:00 +00:00
|
|
|
)
|
2023-03-15 06:09:30 +00:00
|
|
|
matching_destinations = [e.destination for e in matching_edges]
|
|
|
|
edges = [e for e in edges if e.destination not in matching_destinations]
|
2022-12-01 05:33:20 +00:00
|
|
|
edges.extend(matching_edges)
|
2023-03-03 06:02:00 +00:00
|
|
|
|
|
|
|
if "link" in args and args["link"]:
|
|
|
|
for link in args["link"]:
|
2023-04-06 04:06:05 +00:00
|
|
|
edges = [e for e in edges if e.destination.node_id != command.command.id or e.destination.field != link[2]]
|
|
|
|
|
|
|
|
node_id = link[0]
|
|
|
|
if re_negid.match(node_id):
|
|
|
|
node_id = str(current_id + int(node_id))
|
|
|
|
|
2023-04-14 06:41:06 +00:00
|
|
|
# TODO: handle missing input/output
|
|
|
|
node_output = get_node_outputs(context.session.graph.get_node(node_id), context)[link[1]]
|
|
|
|
node_input = get_node_inputs(command.command, context)[link[2]]
|
|
|
|
|
2023-03-03 06:02:00 +00:00
|
|
|
edges.append(
|
2023-03-15 06:09:30 +00:00
|
|
|
Edge(
|
2023-04-14 06:41:06 +00:00
|
|
|
source=EdgeConnection(node_id=node_output.node_path, field=node_output.field),
|
|
|
|
destination=EdgeConnection(node_id=node_input.node_path, field=node_input.field)
|
2023-03-03 06:02:00 +00:00
|
|
|
)
|
|
|
|
)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
new_invocations.append((command.command, edges))
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
current_id = current_id + 1
|
|
|
|
|
2023-03-09 03:25:03 +00:00
|
|
|
# Add the node to the session
|
2023-04-14 06:41:06 +00:00
|
|
|
context.add_node(command.command)
|
2023-03-09 03:25:03 +00:00
|
|
|
for edge in edges:
|
2023-03-04 22:46:02 +00:00
|
|
|
print(edge)
|
2023-04-14 06:41:06 +00:00
|
|
|
context.add_edge(edge)
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2023-03-09 03:25:03 +00:00
|
|
|
# Execute all remaining nodes
|
|
|
|
invoke_all(context)
|
2022-12-01 05:33:20 +00:00
|
|
|
|
|
|
|
except InvalidArgs:
|
2023-04-29 14:48:50 +00:00
|
|
|
invoker.services.logger.warning('Invalid command, use "help" to list commands')
|
2022-12-01 05:33:20 +00:00
|
|
|
continue
|
|
|
|
|
2023-05-17 18:13:12 +00:00
|
|
|
except ValidationError:
|
|
|
|
invoker.services.logger.warning('Invalid command arguments, run "<command> --help" for summary')
|
|
|
|
|
2023-03-09 03:25:03 +00:00
|
|
|
except SessionError:
|
|
|
|
# Start a new session
|
2023-04-29 14:48:50 +00:00
|
|
|
invoker.services.logger.warning("Session error: creating a new session")
|
2023-04-14 06:41:06 +00:00
|
|
|
context.reset()
|
2023-03-09 03:25:03 +00:00
|
|
|
|
2023-03-04 22:46:02 +00:00
|
|
|
except ExitCli:
|
|
|
|
break
|
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
except SystemExit:
|
|
|
|
continue
|
2023-03-03 06:02:00 +00:00
|
|
|
|
2022-12-01 05:33:20 +00:00
|
|
|
invoker.stop()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
invoke_cli()
|