2023-02-01 04:46:36 +00:00
# Copyright (c) 2023 Eugene Brodsky (https://github.com/ebr)
2023-01-08 08:09:04 +00:00
"""
Installer user interaction
"""
2023-01-30 08:15:05 +00:00
import os
2023-01-08 08:09:04 +00:00
import platform
2024-02-05 16:51:01 +00:00
from enum import Enum
2023-01-08 08:09:04 +00:00
from pathlib import Path
2024-03-26 02:34:00 +00:00
from typing import Optional
2023-01-08 08:09:04 +00:00
2024-03-20 10:43:13 +00:00
from prompt_toolkit import prompt
2024-02-05 23:58:55 +00:00
from prompt_toolkit . completion import FuzzyWordCompleter , PathCompleter
2023-01-13 06:57:06 +00:00
from prompt_toolkit . validation import Validator
2023-01-08 08:09:04 +00:00
from rich import box , print
2023-02-06 05:57:21 +00:00
from rich . console import Console , Group , group
2023-01-08 08:09:04 +00:00
from rich . panel import Panel
from rich . prompt import Confirm
from rich . style import Style
2023-01-14 16:24:18 +00:00
from rich . syntax import Syntax
2023-01-17 05:47:36 +00:00
from rich . text import Text
2023-01-08 08:09:04 +00:00
OS = platform . uname ( ) . system
ARCH = platform . uname ( ) . machine
2023-01-14 16:24:18 +00:00
if OS == " Windows " :
# Windows terminals look better without a background colour
console = Console ( style = Style ( color = " grey74 " ) )
else :
console = Console ( style = Style ( color = " grey74 " , bgcolor = " grey19 " ) )
2023-01-30 08:15:05 +00:00
2024-03-26 02:34:00 +00:00
def welcome ( available_releases : tuple [ list [ str ] , list [ str ] ] | None = None ) - > None :
2023-02-06 05:57:21 +00:00
@group ( )
def text ( ) :
2024-02-05 20:23:29 +00:00
if ( platform_specific := _platform_specific_help ( ) ) is not None :
2023-02-06 05:57:21 +00:00
yield platform_specific
yield " "
yield Text . from_markup (
" Some of the installation steps take a long time to run. Please be patient. If the script appears to hang for more than 10 minutes, please interrupt with [i]Control-C[/] and retry. " ,
justify = " center " ,
)
2024-02-06 00:00:29 +00:00
if available_releases is not None :
latest_stable = available_releases [ 0 ] [ 0 ]
last_pre = available_releases [ 1 ] [ 0 ]
yield " "
yield Text . from_markup (
f " [red3]🠶[/] Latest stable release (recommended): [b bright_white] { latest_stable } " , justify = " center "
)
yield Text . from_markup (
f " [red3]🠶[/] Last published pre-release version: [b bright_white] { last_pre } " , justify = " center "
)
2023-02-06 05:57:21 +00:00
2023-01-08 08:09:04 +00:00
console . rule ( )
print (
Panel (
2023-01-09 05:13:01 +00:00
title = " [bold wheat1]Welcome to the InvokeAI Installer " ,
2023-02-06 05:57:21 +00:00
renderable = text ( ) ,
2023-01-08 08:09:04 +00:00
box = box . DOUBLE ,
2023-02-06 05:57:21 +00:00
expand = True ,
2023-01-08 08:09:04 +00:00
padding = ( 1 , 2 ) ,
style = Style ( bgcolor = " grey23 " , color = " orange1 " ) ,
2023-01-09 05:13:01 +00:00
subtitle = f " [bold grey39] { OS } - { ARCH } " ,
2023-01-08 08:09:04 +00:00
)
)
console . line ( )
2023-07-27 14:54:01 +00:00
2024-03-26 03:24:06 +00:00
def installing_from_wheel ( wheel_filename : str ) - > None :
""" Display a message about installing from a wheel """
@group ( )
def text ( ) :
yield Text . from_markup ( f " You are installing from a wheel file: [bold] { wheel_filename } \n " )
yield Text . from_markup (
" [bold orange3]If you are not sure why you are doing this, you should cancel and install InvokeAI normally. "
)
console . print (
Panel (
title = " Installing from Wheel " ,
renderable = text ( ) ,
box = box . DOUBLE ,
expand = True ,
padding = ( 1 , 2 ) ,
)
)
should_proceed = Confirm . ask ( " Do you want to proceed? " )
if not should_proceed :
console . print ( " Installation cancelled. " )
exit ( )
2024-03-26 02:34:00 +00:00
def choose_version ( available_releases : tuple [ list [ str ] , list [ str ] ] | None = None ) - > str :
2024-02-05 23:58:55 +00:00
"""
Prompt the user to choose an Invoke version to install
"""
# short circuit if we couldn't get a version list
# still try to install the latest stable version
if available_releases is None :
return " stable "
2024-02-11 19:00:24 +00:00
console . print ( " :grey_question: [orange3]Please choose an Invoke version to install. " )
choices = available_releases [ 0 ] + available_releases [ 1 ]
response = prompt (
message = f " <Enter> to install the recommended release ( { choices [ 0 ] } ). <Tab> or type to pick a version: " ,
complete_while_typing = True ,
completer = FuzzyWordCompleter ( choices ) ,
)
2024-02-11 17:42:55 +00:00
console . print ( f " Version { choices [ 0 ] if response == ' ' else response } will be installed. " )
2024-02-08 14:56:56 +00:00
2024-02-05 23:58:55 +00:00
console . line ( )
return " stable " if response == " " else response
2023-02-01 04:13:37 +00:00
2024-02-06 01:28:12 +00:00
def confirm_install ( dest : Path ) - > bool :
if dest . exists ( ) :
print ( f " :stop_sign: Directory { dest } already exists! " )
print ( " Is this location correct? " )
default = False
else :
print ( f " :file_folder: InvokeAI will be installed in { dest } " )
default = True
dest_confirmed = Confirm . ask ( " Please confirm: " , default = default )
console . line ( )
return dest_confirmed
2024-03-26 02:34:00 +00:00
def dest_path ( dest : Optional [ str | Path ] = None ) - > Path | None :
2023-01-08 08:09:04 +00:00
"""
Prompt the user for the destination path and create the path
2023-02-01 04:13:37 +00:00
: param dest : a filesystem path , defaults to None
: type dest : str , optional
2023-01-08 08:09:04 +00:00
: return : absolute path to the created installation directory
: rtype : Path
"""
2023-01-10 21:55:57 +00:00
if dest is not None :
2023-02-01 04:13:37 +00:00
dest = Path ( dest ) . expanduser ( ) . resolve ( )
2023-01-30 08:15:05 +00:00
else :
2023-02-01 04:13:37 +00:00
dest = Path . cwd ( ) . expanduser ( ) . resolve ( )
2023-08-17 22:45:25 +00:00
prev_dest = init_path = dest
2024-02-06 01:28:12 +00:00
dest_confirmed = False
2023-01-14 16:24:18 +00:00
2023-01-30 08:15:05 +00:00
while not dest_confirmed :
2024-02-06 01:28:12 +00:00
browse_start = ( dest or Path . cwd ( ) ) . expanduser ( ) . resolve ( )
2023-01-30 08:15:05 +00:00
path_completer = PathCompleter (
only_directories = True ,
expanduser = True ,
2024-01-29 04:49:22 +00:00
get_paths = lambda : [ str ( browse_start ) ] , # noqa: B023
2023-01-30 08:15:05 +00:00
# get_paths=lambda: [".."].extend(list(browse_start.iterdir()))
)
2023-01-10 21:55:57 +00:00
2023-01-30 08:15:05 +00:00
console . line ( )
2024-02-06 01:28:12 +00:00
console . print ( f " :grey_question: [orange3]Please select the install destination:[/] \\ [ { browse_start } ]: " )
2023-01-30 08:15:05 +00:00
selected = prompt (
2023-08-17 22:45:25 +00:00
" >>> " ,
2023-01-30 08:15:05 +00:00
complete_in_thread = True ,
completer = path_completer ,
default = str ( browse_start ) + os . sep ,
vi_mode = True ,
2023-11-10 23:55:06 +00:00
complete_while_typing = True ,
2023-01-30 08:15:05 +00:00
# Test that this is not needed on Windows
# complete_style=CompleteStyle.READLINE_LIKE,
)
prev_dest = dest
dest = Path ( selected )
2024-02-06 01:28:12 +00:00
2023-01-30 08:15:05 +00:00
console . line ( )
2023-01-10 21:55:57 +00:00
2023-02-01 04:13:37 +00:00
dest_confirmed = confirm_install ( dest . expanduser ( ) . resolve ( ) )
2023-01-30 08:15:05 +00:00
if not dest_confirmed :
dest = prev_dest
dest = dest . expanduser ( ) . resolve ( )
2023-01-08 08:09:04 +00:00
try :
dest . mkdir ( exist_ok = True , parents = True )
return dest
2023-08-17 22:45:25 +00:00
except PermissionError :
console . print (
2023-01-08 08:09:04 +00:00
f " Failed to create directory { dest } due to insufficient permissions " ,
style = Style ( color = " red " ) ,
highlight = True ,
)
2023-08-17 22:45:25 +00:00
except OSError :
console . print_exception ( )
2023-01-08 08:09:04 +00:00
if Confirm . ask ( " Would you like to try again? " ) :
dest_path ( init_path )
else :
console . rule ( " Goodbye! " )
2023-01-13 06:57:06 +00:00
2024-02-05 16:51:01 +00:00
class GpuType ( Enum ) :
CUDA = " cuda "
CUDA_AND_DML = " cuda_and_dml "
ROCM = " rocm "
CPU = " cpu "
AUTODETECT = " autodetect "
def select_gpu ( ) - > GpuType :
2023-01-13 06:57:06 +00:00
"""
2024-02-05 16:51:01 +00:00
Prompt the user to select the GPU driver
2023-01-13 06:57:06 +00:00
"""
if ARCH == " arm64 " and OS != " Darwin " :
print ( f " Only CPU acceleration is available on { ARCH } architecture. Proceeding with that. " )
2024-02-05 16:51:01 +00:00
return GpuType . CPU
2023-01-13 06:57:06 +00:00
2023-01-30 08:15:05 +00:00
nvidia = (
" an [gold1 b]NVIDIA[/] GPU (using CUDA™) " ,
2024-02-05 16:51:01 +00:00
GpuType . CUDA ,
2023-01-30 08:15:05 +00:00
)
2023-07-29 01:02:48 +00:00
nvidia_with_dml = (
2023-07-31 20:47:48 +00:00
" an [gold1 b]NVIDIA[/] GPU (using CUDA™, and DirectML™ for ONNX) -- ALPHA " ,
2024-02-05 16:51:01 +00:00
GpuType . CUDA_AND_DML ,
2023-07-29 01:02:48 +00:00
)
2023-01-30 08:15:05 +00:00
amd = (
" an [gold1 b]AMD[/] GPU (using ROCm™) " ,
2024-02-05 16:51:01 +00:00
GpuType . ROCM ,
2023-01-30 08:15:05 +00:00
)
cpu = (
2024-02-05 16:51:01 +00:00
" Do not install any GPU support, use CPU for generation (slow) " ,
GpuType . CPU ,
2023-01-30 08:15:05 +00:00
)
2024-02-05 16:51:01 +00:00
autodetect = (
2023-09-25 23:18:58 +00:00
" I ' m not sure what to choose " ,
2024-02-05 16:51:01 +00:00
GpuType . AUTODETECT ,
2023-01-30 08:15:05 +00:00
)
2023-01-13 06:57:06 +00:00
2024-01-29 04:49:22 +00:00
options = [ ]
2023-01-13 06:57:06 +00:00
if OS == " Windows " :
2023-07-29 01:02:48 +00:00
options = [ nvidia , nvidia_with_dml , cpu ]
2023-01-13 06:57:06 +00:00
if OS == " Linux " :
options = [ nvidia , amd , cpu ]
elif OS == " Darwin " :
options = [ cpu ]
# future CoreML?
if len ( options ) == 1 :
2023-01-30 08:15:05 +00:00
print ( f ' Your platform [gold1] { OS } - { ARCH } [/] only supports the " { options [ 0 ] [ 1 ] } " driver. Proceeding with that. ' )
2023-01-13 06:57:06 +00:00
return options [ 0 ] [ 1 ]
# "I don't know" is always added the last option
2024-02-05 16:51:01 +00:00
options . append ( autodetect ) # type: ignore
2023-01-13 06:57:06 +00:00
options = { str ( i ) : opt for i , opt in enumerate ( options , 1 ) }
2023-01-13 09:10:34 +00:00
console . rule ( " :space_invader: GPU (Graphics Card) selection :space_invader: " )
2023-01-30 08:15:05 +00:00
console . print (
Panel (
Group (
" \n " . join (
[
f " Detected the [gold1] { OS } - { ARCH } [/] platform " ,
" " ,
2023-02-06 05:57:21 +00:00
" See [deep_sky_blue1]https://invoke-ai.github.io/InvokeAI/#system[/] to ensure your system meets the minimum requirements. " ,
2023-01-30 08:15:05 +00:00
" " ,
" [red3]🠶[/] [b]Your GPU drivers must be correctly installed before using InvokeAI![/] [red3]🠴[/] " ,
]
) ,
2023-01-13 06:57:06 +00:00
" " ,
2023-01-13 09:10:34 +00:00
" Please select the type of GPU installed in your computer. " ,
2023-01-30 08:15:05 +00:00
Panel (
" \n " . join ( [ f " [dark_goldenrod b i] { i } [/] [dark_red]🢒[/] { opt [ 0 ] } " for ( i , opt ) in options . items ( ) ] ) ,
box = box . MINIMAL ,
) ,
2023-01-13 06:57:06 +00:00
) ,
box = box . MINIMAL ,
2023-01-13 09:10:34 +00:00
padding = ( 1 , 1 ) ,
2023-01-13 06:57:06 +00:00
)
)
2023-01-30 08:15:05 +00:00
choice = prompt (
" Please make your selection: " ,
validator = Validator . from_callable (
lambda n : n in options . keys ( ) , error_message = " Please select one the above options "
) ,
)
2023-01-13 06:57:06 +00:00
2024-02-05 16:51:01 +00:00
if options [ choice ] [ 1 ] is GpuType . AUTODETECT :
2023-01-30 08:15:05 +00:00
console . print (
2024-02-05 16:51:01 +00:00
" No problem. We will install CUDA support first :crossed_fingers: If Invoke does not detect a GPU, please re-run the installer and select one of the other GPU types. "
2023-01-30 08:15:05 +00:00
)
2023-01-13 09:10:34 +00:00
2023-01-13 06:57:06 +00:00
return options [ choice ] [ 1 ]
2023-01-13 09:10:34 +00:00
def simple_banner ( message : str ) - > None :
"""
A simple banner with a message , defined here for styling consistency
: param message : The message to display
: type message : str
"""
console . rule ( message )
2023-01-30 08:15:05 +00:00
2023-01-14 16:24:18 +00:00
# TODO this does not yet work correctly
def windows_long_paths_registry ( ) - > None :
"""
Display a message about applying the Windows long paths registry fix
"""
2023-01-30 08:15:05 +00:00
with open ( str ( Path ( __file__ ) . parent / " WinLongPathsEnabled.reg " ) , " r " , encoding = " utf-16le " ) as code :
2024-01-29 04:49:22 +00:00
syntax = Syntax ( code . read ( ) , line_numbers = True , lexer = " regedit " )
2023-01-14 16:24:18 +00:00
2023-01-30 08:15:05 +00:00
console . print (
Panel (
Group (
" \n " . join (
[
" We will now apply a registry fix to enable long paths on Windows. InvokeAI needs this to function correctly. We are asking your permission to modify the Windows Registry on your behalf. " ,
" " ,
" This is the change that will be applied: " ,
2024-01-29 04:49:22 +00:00
str ( syntax ) ,
2023-01-30 08:15:05 +00:00
]
)
) ,
title = " Windows Long Paths registry fix " ,
box = box . HORIZONTALS ,
padding = ( 1 , 1 ) ,
)
)
2023-01-14 16:24:18 +00:00
2023-01-13 09:10:34 +00:00
2024-02-05 20:23:29 +00:00
def _platform_specific_help ( ) - > Text | None :
2023-02-06 05:57:21 +00:00
if OS == " Darwin " :
text = Text . from_markup (
""" [b wheat1]macOS Users![/] \n \n Please be sure you have the [b wheat1]Xcode command-line tools[/] installed before continuing. \n If not, cancel with [i]Control-C[/] and follow the Xcode install instructions at [deep_sky_blue1]https://www.freecodecamp.org/news/install-xcode-command-line-tools/[/]. """
)
elif OS == " Windows " :
text = Text . from_markup (
""" [b wheat1]Windows Users![/] \n \n Before you start, please do the following:
1. Double - click on the file [ b wheat1 ] WinLongPathsEnabled . reg [ / ] in order to
2023-02-05 17:43:13 +00:00
enable long path support on your system .
2023-02-06 05:57:21 +00:00
2. Make sure you have the [ b wheat1 ] Visual C + + core libraries [ / ] installed . If not , install from
[ deep_sky_blue1 ] https : / / learn . microsoft . com / en - US / cpp / windows / latest - supported - vc - redist ? view = msvc - 170 [ / ] """
)
else :
2024-02-05 20:23:29 +00:00
return
2023-02-05 17:43:13 +00:00
return text