added route to install huggingface models from model marketplace (#6515)

## Summary
added route to install huggingface models from model marketplace
<!--A description of the changes in this PR. Include the kind of change
(fix, feature, docs, etc), the "why" and the "how". Screenshots or
videos are useful for frontend changes.-->

## Related Issues / Discussions

<!--WHEN APPLICABLE: List any related issues or discussions on github or
discord. If this PR closes an issue, please use the "Closes #1234"
format, so that the issue will be automatically closed when the PR
merges.-->

## QA Instructions
test by going to
http://localhost:5173/api/v2/models/install/huggingface?source=${hfRepo}
<!--WHEN APPLICABLE: Describe how we can test the changes in this PR.-->

## Merge Plan

<!--WHEN APPLICABLE: Large PRs, or PRs that touch sensitive things like
DB schemas, may need some care when merging. For example, a careful
rebase by the change author, timing to not interfere with a pending
release, or a message to contributors on discord after merging.-->

## Checklist

- [ ] _The PR has a short but descriptive title, suitable for a
changelog_
- [ ] _Tests added / updated (if applicable)_
- [ ] _Documentation added / updated (if applicable)_
This commit is contained in:
chainchompa 2024-06-16 21:13:58 -04:00 committed by GitHub
commit 70e40fa6c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 602 additions and 224 deletions

View File

@ -9,7 +9,7 @@ from copy import deepcopy
from typing import Any, Dict, List, Optional, Type
from fastapi import Body, Path, Query, Response, UploadFile
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.routing import APIRouter
from PIL import Image
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field
@ -502,6 +502,133 @@ async def install_model(
return result
@model_manager_router.get(
"/install/huggingface",
operation_id="install_hugging_face_model",
responses={
201: {"description": "The model is being installed"},
400: {"description": "Bad request"},
409: {"description": "There is already a model corresponding to this path or repo_id"},
},
status_code=201,
response_class=HTMLResponse,
)
async def install_hugging_face_model(
source: str = Query(description="HuggingFace repo_id to install"),
) -> HTMLResponse:
"""Install a Hugging Face model using a string identifier."""
def generate_html(title: str, heading: str, repo_id: str, is_error: bool, message: str | None = "") -> str:
if message:
message = f"<p>{message}</p>"
title_class = "error" if is_error else "success"
return f"""
<html>
<head>
<title>{title}</title>
<style>
body {{
text-align: center;
background-color: hsl(220 12% 10% / 1);
font-family: Helvetica, sans-serif;
color: hsl(220 12% 86% / 1);
}}
.repo-id {{
color: hsl(220 12% 68% / 1);
}}
.error {{
color: hsl(0 42% 68% / 1)
}}
.message-box {{
display: inline-block;
border-radius: 5px;
background-color: hsl(220 12% 20% / 1);
padding-inline-end: 30px;
padding: 20px;
padding-inline-start: 30px;
padding-inline-end: 30px;
}}
.container {{
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}}
a {{
color: inherit
}}
a:visited {{
color: inherit
}}
a:active {{
color: inherit
}}
</style>
</head>
<body style="background-color: hsl(220 12% 10% / 1);">
<div class="container">
<div class="message-box">
<h2 class="{title_class}">{heading}</h2>
{message}
<p class="repo-id">Repo ID: {repo_id}</p>
</div>
</div>
</body>
</html>
"""
try:
metadata = HuggingFaceMetadataFetch().from_id(source)
assert isinstance(metadata, ModelMetadataWithFiles)
except UnknownMetadataException:
title = "Unable to Install Model"
heading = "No HuggingFace repository found with that repo ID."
message = "Ensure the repo ID is correct and try again."
return HTMLResponse(content=generate_html(title, heading, source, True, message), status_code=400)
logger = ApiDependencies.invoker.services.logger
try:
installer = ApiDependencies.invoker.services.model_manager.install
if metadata.is_diffusers:
installer.heuristic_import(
source=source,
inplace=False,
)
elif metadata.ckpt_urls is not None and len(metadata.ckpt_urls) == 1:
installer.heuristic_import(
source=str(metadata.ckpt_urls[0]),
inplace=False,
)
else:
title = "Unable to Install Model"
heading = "This HuggingFace repo has multiple models."
message = "Please use the Model Manager to install this model."
return HTMLResponse(content=generate_html(title, heading, source, True, message), status_code=200)
title = "Model Install Started"
heading = "Your HuggingFace model is installing now."
message = "You can close this tab and check the Model Manager for installation progress."
return HTMLResponse(content=generate_html(title, heading, source, False, message), status_code=201)
except Exception as e:
logger.error(str(e))
title = "Unable to Install Model"
heading = "There was an problem installing this model."
message = 'Please use the Model Manager directly to install this model. If the issue persists, ask for help on <a href="https://discord.gg/ZmtBAhwWhy">discord</a>.'
return HTMLResponse(content=generate_html(title, heading, source, True, message), status_code=500)
@model_manager_router.get(
"/install",
operation_id="list_model_installs",

View File

@ -22,6 +22,7 @@ from invokeai.app.services.events.events_common import (
ModelInstallCompleteEvent,
ModelInstallDownloadProgressEvent,
ModelInstallDownloadsCompleteEvent,
ModelInstallDownloadStartedEvent,
ModelInstallErrorEvent,
ModelInstallStartedEvent,
ModelLoadCompleteEvent,
@ -144,6 +145,10 @@ class EventServiceBase:
# region Model install
def emit_model_install_download_started(self, job: "ModelInstallJob") -> None:
"""Emitted at intervals while the install job is started (remote models only)."""
self.dispatch(ModelInstallDownloadStartedEvent.build(job))
def emit_model_install_download_progress(self, job: "ModelInstallJob") -> None:
"""Emitted at intervals while the install job is in progress (remote models only)."""
self.dispatch(ModelInstallDownloadProgressEvent.build(job))

View File

@ -417,6 +417,42 @@ class ModelLoadCompleteEvent(ModelEventBase):
return cls(config=config, submodel_type=submodel_type)
@payload_schema.register
class ModelInstallDownloadStartedEvent(ModelEventBase):
"""Event model for model_install_download_started"""
__event_name__ = "model_install_download_started"
id: int = Field(description="The ID of the install job")
source: str = Field(description="Source of the model; local path, repo_id or url")
local_path: str = Field(description="Where model is downloading to")
bytes: int = Field(description="Number of bytes downloaded so far")
total_bytes: int = Field(description="Total size of download, including all files")
parts: list[dict[str, int | str]] = Field(
description="Progress of downloading URLs that comprise the model, if any"
)
@classmethod
def build(cls, job: "ModelInstallJob") -> "ModelInstallDownloadStartedEvent":
parts: list[dict[str, str | int]] = [
{
"url": str(x.source),
"local_path": str(x.download_path),
"bytes": x.bytes,
"total_bytes": x.total_bytes,
}
for x in job.download_parts
]
return cls(
id=job.id,
source=str(job.source),
local_path=job.local_path.as_posix(),
parts=parts,
bytes=job.bytes,
total_bytes=job.total_bytes,
)
@payload_schema.register
class ModelInstallDownloadProgressEvent(ModelEventBase):
"""Event model for model_install_download_progress"""

View File

@ -822,7 +822,7 @@ class ModelInstallService(ModelInstallServiceBase):
install_job.download_parts = download_job.download_parts
install_job.bytes = sum(x.bytes for x in download_job.download_parts)
install_job.total_bytes = download_job.total_bytes
self._signal_job_downloading(install_job)
self._signal_job_download_started(install_job)
def _download_progress_callback(self, download_job: MultiFileDownloadJob) -> None:
with self._lock:
@ -874,6 +874,13 @@ class ModelInstallService(ModelInstallServiceBase):
if self._event_bus:
self._event_bus.emit_model_install_started(job)
def _signal_job_download_started(self, job: ModelInstallJob) -> None:
if self._event_bus:
assert job._multifile_job is not None
assert job.bytes is not None
assert job.total_bytes is not None
self._event_bus.emit_model_install_download_started(job)
def _signal_job_downloading(self, job: ModelInstallJob) -> None:
if self._event_bus:
assert job._multifile_job is not None

View File

@ -5,43 +5,122 @@ import {
socketModelInstallCancelled,
socketModelInstallComplete,
socketModelInstallDownloadProgress,
socketModelInstallDownloadsComplete,
socketModelInstallDownloadStarted,
socketModelInstallError,
socketModelInstallStarted,
} from 'services/events/actions';
/**
* A model install has two main stages - downloading and installing. All these events are namespaced under `model_install_`
* which is a bit misleading. For example, a `model_install_started` event is actually fired _after_ the model has fully
* downloaded and is being "physically" installed.
*
* Note: the download events are only fired for remote model installs, not local.
*
* Here's the expected flow:
* - API receives install request, model manager preps the install
* - `model_install_download_started` fired when the download starts
* - `model_install_download_progress` fired continually until the download is complete
* - `model_install_download_complete` fired when the download is complete
* - `model_install_started` fired when the "physical" installation starts
* - `model_install_complete` fired when the installation is complete
* - `model_install_cancelled` fired if the installation is cancelled
* - `model_install_error` fired if the installation has an error
*/
const selectModelInstalls = modelsApi.endpoints.listModelInstalls.select();
export const addModelInstallEventListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: socketModelInstallDownloadProgress,
effect: async (action, { dispatch }) => {
const { bytes, total_bytes, id } = action.payload.data;
actionCreator: socketModelInstallDownloadStarted,
effect: async (action, { dispatch, getState }) => {
const { id } = action.payload.data;
const { data } = selectModelInstalls(getState());
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.bytes = bytes;
modelImport.total_bytes = total_bytes;
modelImport.status = 'downloading';
}
return draft;
})
);
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'downloading';
}
return draft;
})
);
}
},
});
startAppListening({
actionCreator: socketModelInstallStarted,
effect: async (action, { dispatch, getState }) => {
const { id } = action.payload.data;
const { data } = selectModelInstalls(getState());
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'running';
}
return draft;
})
);
}
},
});
startAppListening({
actionCreator: socketModelInstallDownloadProgress,
effect: async (action, { dispatch, getState }) => {
const { bytes, total_bytes, id } = action.payload.data;
const { data } = selectModelInstalls(getState());
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.bytes = bytes;
modelImport.total_bytes = total_bytes;
modelImport.status = 'downloading';
}
return draft;
})
);
}
},
});
startAppListening({
actionCreator: socketModelInstallComplete,
effect: (action, { dispatch }) => {
effect: (action, { dispatch, getState }) => {
const { id } = action.payload.data;
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'completed';
}
return draft;
})
);
const { data } = selectModelInstalls(getState());
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'completed';
}
return draft;
})
);
}
dispatch(api.util.invalidateTags([{ type: 'ModelConfig', id: LIST_TAG }]));
dispatch(api.util.invalidateTags([{ type: 'ModelScanFolderResults', id: LIST_TAG }]));
},
@ -49,37 +128,69 @@ export const addModelInstallEventListener = (startAppListening: AppStartListenin
startAppListening({
actionCreator: socketModelInstallError,
effect: (action, { dispatch }) => {
effect: (action, { dispatch, getState }) => {
const { id, error, error_type } = action.payload.data;
const { data } = selectModelInstalls(getState());
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'error';
modelImport.error_reason = error_type;
modelImport.error = error;
}
return draft;
})
);
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'error';
modelImport.error_reason = error_type;
modelImport.error = error;
}
return draft;
})
);
}
},
});
startAppListening({
actionCreator: socketModelInstallCancelled,
effect: (action, { dispatch }) => {
effect: (action, { dispatch, getState }) => {
const { id } = action.payload.data;
const { data } = selectModelInstalls(getState());
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'cancelled';
}
return draft;
})
);
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'cancelled';
}
return draft;
})
);
}
},
});
startAppListening({
actionCreator: socketModelInstallDownloadsComplete,
effect: (action, { dispatch, getState }) => {
const { id } = action.payload.data;
const { data } = selectModelInstalls(getState());
if (!data || !data.find((m) => m.id === id)) {
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
} else {
dispatch(
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
const modelImport = draft.find((m) => m.id === id);
if (modelImport) {
modelImport.status = 'downloads_done';
}
return draft;
})
);
}
},
});
};

View File

@ -123,6 +123,13 @@ export type paths = {
*/
delete: operations["prune_model_install_jobs"];
};
"/api/v2/models/install/huggingface": {
/**
* Install Hugging Face Model
* @description Install a Hugging Face model using a string identifier.
*/
get: operations["install_hugging_face_model"];
};
"/api/v2/models/install/{id}": {
/**
* Get Model Install Job
@ -3788,23 +3795,6 @@ export type components = {
* @description Class to monitor and control a model download request.
*/
DownloadJob: {
/**
* Source
* Format: uri
* @description Where to download from. Specific types specified in child classes.
*/
source: string;
/**
* Dest
* Format: path
* @description Destination of downloaded model on local disk; a directory or file path
*/
dest: string;
/**
* Access Token
* @description authorization token for protected resources
*/
access_token?: string | null;
/**
* Id
* @description Numeric ID of this job
@ -3812,36 +3802,21 @@ export type components = {
*/
id?: number;
/**
* Priority
* @description Queue priority; lower values are higher priority
* @default 10
* Dest
* Format: path
* @description Initial destination of downloaded model on local disk; a directory or file path
*/
priority?: number;
dest: string;
/**
* Download Path
* @description Final location of downloaded file or directory
*/
download_path?: string | null;
/**
* @description Status of the download
* @default waiting
*/
status?: components["schemas"]["DownloadJobStatus"];
/**
* Download Path
* @description Final location of downloaded file
*/
download_path?: string | null;
/**
* Job Started
* @description Timestamp for when the download job started
*/
job_started?: string | null;
/**
* Job Ended
* @description Timestamp for when the download job ende1d (completed or errored)
*/
job_ended?: string | null;
/**
* Content Type
* @description Content type of downloaded file
*/
content_type?: string | null;
/**
* Bytes
* @description Bytes downloaded so far
@ -3864,6 +3839,38 @@ export type components = {
* @description Traceback of the exception that caused an error
*/
error?: string | null;
/**
* Source
* Format: uri
* @description Where to download from. Specific types specified in child classes.
*/
source: string;
/**
* Access Token
* @description authorization token for protected resources
*/
access_token?: string | null;
/**
* Priority
* @description Queue priority; lower values are higher priority
* @default 10
*/
priority?: number;
/**
* Job Started
* @description Timestamp for when the download job started
*/
job_started?: string | null;
/**
* Job Ended
* @description Timestamp for when the download job ende1d (completed or errored)
*/
job_ended?: string | null;
/**
* Content Type
* @description Content type of downloaded file
*/
content_type?: string | null;
};
/**
* DownloadJobStatus
@ -7276,144 +7283,144 @@ export type components = {
project_id: string | null;
};
InvocationOutputMap: {
pidi_image_processor: components["schemas"]["ImageOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
vae_loader: components["schemas"]["VAEOutput"];
collect: components["schemas"]["CollectInvocationOutput"];
string_join_three: components["schemas"]["StringOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
color_correct: components["schemas"]["ImageOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
img_channel_multiply: components["schemas"]["ImageOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
infill_lama: components["schemas"]["ImageOutput"];
mask_combine: components["schemas"]["ImageOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
segment_anything_processor: components["schemas"]["ImageOutput"];
merge_metadata: components["schemas"]["MetadataOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
div: components["schemas"]["IntegerOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
float_math: components["schemas"]["FloatOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
canny_image_processor: components["schemas"]["ImageOutput"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
freeu: components["schemas"]["UNetOutput"];
lresize: components["schemas"]["LatentsOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
rand_int: components["schemas"]["IntegerOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
string_split: components["schemas"]["String2Output"];
img_nsfw: components["schemas"]["ImageOutput"];
string: components["schemas"]["StringOutput"];
mask_edge: components["schemas"]["ImageOutput"];
i2l: components["schemas"]["LatentsOutput"];
face_identifier: components["schemas"]["ImageOutput"];
compel: components["schemas"]["ConditioningOutput"];
esrgan: components["schemas"]["ImageOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
invert_tensor_mask: components["schemas"]["MaskOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
string_collection: components["schemas"]["StringCollectionOutput"];
show_image: components["schemas"]["ImageOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
string_split_neg: components["schemas"]["StringPosNegOutput"];
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"];
img_conv: components["schemas"]["ImageOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
image: components["schemas"]["ImageOutput"];
image_collection: components["schemas"]["ImageCollectionOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
lblend: components["schemas"]["LatentsOutput"];
float: components["schemas"]["FloatOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
color: components["schemas"]["ColorOutput"];
midas_depth_image_processor: components["schemas"]["ImageOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
lscale: components["schemas"]["LatentsOutput"];
string_split: components["schemas"]["String2Output"];
mask_edge: components["schemas"]["ImageOutput"];
content_shuffle_image_processor: components["schemas"]["ImageOutput"];
color_correct: components["schemas"]["ImageOutput"];
save_image: components["schemas"]["ImageOutput"];
show_image: components["schemas"]["ImageOutput"];
segment_anything_processor: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
infill_lama: components["schemas"]["ImageOutput"];
infill_patchmatch: components["schemas"]["ImageOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
denoise_latents: components["schemas"]["LatentsOutput"];
metadata: components["schemas"]["MetadataOutput"];
compel: components["schemas"]["ConditioningOutput"];
img_blur: components["schemas"]["ImageOutput"];
img_crop: components["schemas"]["ImageOutput"];
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
img_ilerp: components["schemas"]["ImageOutput"];
img_paste: components["schemas"]["ImageOutput"];
core_metadata: components["schemas"]["MetadataOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
rectangle_mask: components["schemas"]["MaskOutput"];
noise: components["schemas"]["NoiseOutput"];
float_to_int: components["schemas"]["IntegerOutput"];
esrgan: components["schemas"]["ImageOutput"];
merge_tiles_to_image: components["schemas"]["ImageOutput"];
prompt_from_file: components["schemas"]["StringCollectionOutput"];
boolean: components["schemas"]["BooleanOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
rand_float: components["schemas"]["FloatOutput"];
img_mul: components["schemas"]["ImageOutput"];
controlnet: components["schemas"]["ControlOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
img_lerp: components["schemas"]["ImageOutput"];
noise: components["schemas"]["NoiseOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
lineart_image_processor: components["schemas"]["ImageOutput"];
tomask: components["schemas"]["ImageOutput"];
integer: components["schemas"]["IntegerOutput"];
create_denoise_mask: components["schemas"]["DenoiseMaskOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
denoise_latents: components["schemas"]["LatentsOutput"];
string_join: components["schemas"]["StringOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
face_off: components["schemas"]["FaceOffOutput"];
hed_image_processor: components["schemas"]["ImageOutput"];
img_paste: components["schemas"]["ImageOutput"];
img_chan: components["schemas"]["ImageOutput"];
img_watermark: components["schemas"]["ImageOutput"];
l2i: components["schemas"]["ImageOutput"];
string_replace: components["schemas"]["StringOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
crop_latents: components["schemas"]["LatentsOutput"];
sdxl_lora_collection_loader: components["schemas"]["SDXLLoRALoaderOutput"];
add: components["schemas"]["IntegerOutput"];
sub: components["schemas"]["IntegerOutput"];
img_scale: components["schemas"]["ImageOutput"];
range: components["schemas"]["IntegerCollectionOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
img_crop: components["schemas"]["ImageOutput"];
infill_tile: components["schemas"]["ImageOutput"];
img_resize: components["schemas"]["ImageOutput"];
mediapipe_face_processor: components["schemas"]["ImageOutput"];
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
lora_selector: components["schemas"]["LoRASelectorOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
latents: components["schemas"]["LatentsOutput"];
lora_collection_loader: components["schemas"]["LoRALoaderOutput"];
img_blur: components["schemas"]["ImageOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
float_collection: components["schemas"]["FloatCollectionOutput"];
blank_image: components["schemas"]["ImageOutput"];
integer_math: components["schemas"]["IntegerOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"];
metadata: components["schemas"]["MetadataOutput"];
infill_rgba: components["schemas"]["ImageOutput"];
sdxl_lora_loader: components["schemas"]["SDXLLoRALoaderOutput"];
round_float: components["schemas"]["FloatOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
mul: components["schemas"]["IntegerOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
lscale: components["schemas"]["LatentsOutput"];
save_image: components["schemas"]["ImageOutput"];
lora_loader: components["schemas"]["LoRALoaderOutput"];
iterate: components["schemas"]["IterateInvocationOutput"];
t2i_adapter: components["schemas"]["T2IAdapterOutput"];
color_map_image_processor: components["schemas"]["ImageOutput"];
blank_image: components["schemas"]["ImageOutput"];
normalbae_image_processor: components["schemas"]["ImageOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
string_split_neg: components["schemas"]["StringPosNegOutput"];
img_channel_offset: components["schemas"]["ImageOutput"];
face_mask_detection: components["schemas"]["FaceMaskOutput"];
cv_inpaint: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
invert_tensor_mask: components["schemas"]["MaskOutput"];
tomask: components["schemas"]["ImageOutput"];
main_model_loader: components["schemas"]["ModelLoaderOutput"];
img_watermark: components["schemas"]["ImageOutput"];
img_pad_crop: components["schemas"]["ImageOutput"];
random_range: components["schemas"]["IntegerCollectionOutput"];
mlsd_image_processor: components["schemas"]["ImageOutput"];
merge_metadata: components["schemas"]["MetadataOutput"];
string_join: components["schemas"]["StringOutput"];
vae_loader: components["schemas"]["VAEOutput"];
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
mask_from_id: components["schemas"]["ImageOutput"];
zoe_depth_image_processor: components["schemas"]["ImageOutput"];
img_resize: components["schemas"]["ImageOutput"];
string_replace: components["schemas"]["StringOutput"];
face_identifier: components["schemas"]["ImageOutput"];
canny_image_processor: components["schemas"]["ImageOutput"];
collect: components["schemas"]["CollectInvocationOutput"];
infill_tile: components["schemas"]["ImageOutput"];
integer_collection: components["schemas"]["IntegerCollectionOutput"];
img_lerp: components["schemas"]["ImageOutput"];
step_param_easing: components["schemas"]["FloatCollectionOutput"];
lresize: components["schemas"]["LatentsOutput"];
img_mul: components["schemas"]["ImageOutput"];
create_gradient_mask: components["schemas"]["GradientMaskOutput"];
img_scale: components["schemas"]["ImageOutput"];
rand_float: components["schemas"]["FloatOutput"];
tile_to_properties: components["schemas"]["TileToPropertiesOutput"];
calculate_image_tiles: components["schemas"]["CalculateImageTilesOutput"];
range_of_size: components["schemas"]["IntegerCollectionOutput"];
sdxl_refiner_model_loader: components["schemas"]["SDXLRefinerModelLoaderOutput"];
heuristic_resize: components["schemas"]["ImageOutput"];
controlnet: components["schemas"]["ControlOutput"];
string: components["schemas"]["StringOutput"];
tile_image_processor: components["schemas"]["ImageOutput"];
metadata_item: components["schemas"]["MetadataItemOutput"];
freeu: components["schemas"]["UNetOutput"];
round_float: components["schemas"]["FloatOutput"];
conditioning: components["schemas"]["ConditioningOutput"];
ideal_size: components["schemas"]["IdealSizeOutput"];
float: components["schemas"]["FloatOutput"];
conditioning_collection: components["schemas"]["ConditioningCollectionOutput"];
alpha_mask_to_tensor: components["schemas"]["MaskOutput"];
integer_math: components["schemas"]["IntegerOutput"];
string_collection: components["schemas"]["StringCollectionOutput"];
img_conv: components["schemas"]["ImageOutput"];
img_channel_multiply: components["schemas"]["ImageOutput"];
lblend: components["schemas"]["LatentsOutput"];
color: components["schemas"]["ColorOutput"];
image: components["schemas"]["ImageOutput"];
sdxl_model_loader: components["schemas"]["SDXLModelLoaderOutput"];
image_collection: components["schemas"]["ImageCollectionOutput"];
model_identifier: components["schemas"]["ModelIdentifierOutput"];
l2i: components["schemas"]["ImageOutput"];
seamless: components["schemas"]["SeamlessModeOutput"];
boolean_collection: components["schemas"]["BooleanCollectionOutput"];
string_join_three: components["schemas"]["StringOutput"];
ip_adapter: components["schemas"]["IPAdapterOutput"];
add: components["schemas"]["IntegerOutput"];
crop_latents: components["schemas"]["LatentsOutput"];
float_range: components["schemas"]["FloatCollectionOutput"];
mul: components["schemas"]["IntegerOutput"];
dw_openpose_image_processor: components["schemas"]["ImageOutput"];
boolean: components["schemas"]["BooleanOutput"];
dynamic_prompt: components["schemas"]["StringCollectionOutput"];
mediapipe_face_processor: components["schemas"]["ImageOutput"];
i2l: components["schemas"]["LatentsOutput"];
latents_collection: components["schemas"]["LatentsCollectionOutput"];
integer: components["schemas"]["IntegerOutput"];
img_chan: components["schemas"]["ImageOutput"];
pair_tile_image: components["schemas"]["PairTileImageOutput"];
unsharp_mask: components["schemas"]["ImageOutput"];
img_hue_adjust: components["schemas"]["ImageOutput"];
lineart_anime_image_processor: components["schemas"]["ImageOutput"];
face_off: components["schemas"]["FaceOffOutput"];
mask_combine: components["schemas"]["ImageOutput"];
leres_image_processor: components["schemas"]["ImageOutput"];
image_mask_to_tensor: components["schemas"]["MaskOutput"];
sdxl_refiner_compel_prompt: components["schemas"]["ConditioningOutput"];
scheduler: components["schemas"]["SchedulerOutput"];
sub: components["schemas"]["IntegerOutput"];
pidi_image_processor: components["schemas"]["ImageOutput"];
infill_cv2: components["schemas"]["ImageOutput"];
div: components["schemas"]["IntegerOutput"];
img_nsfw: components["schemas"]["ImageOutput"];
depth_anything_image_processor: components["schemas"]["ImageOutput"];
sdxl_compel_prompt: components["schemas"]["ConditioningOutput"];
range: components["schemas"]["IntegerCollectionOutput"];
rand_int: components["schemas"]["IntegerOutput"];
float_math: components["schemas"]["FloatOutput"];
};
/**
* InvocationStartedEvent
@ -9443,6 +9450,49 @@ export type components = {
[key: string]: number | string;
})[];
};
/**
* ModelInstallDownloadStartedEvent
* @description Event model for model_install_download_started
*/
ModelInstallDownloadStartedEvent: {
/**
* Timestamp
* @description The timestamp of the event
*/
timestamp: number;
/**
* Id
* @description The ID of the install job
*/
id: number;
/**
* Source
* @description Source of the model; local path, repo_id or url
*/
source: string;
/**
* Local Path
* @description Where model is downloading to
*/
local_path: string;
/**
* Bytes
* @description Number of bytes downloaded so far
*/
bytes: number;
/**
* Total Bytes
* @description Total size of download, including all files
*/
total_bytes: number;
/**
* Parts
* @description Progress of downloading URLs that comprise the model, if any
*/
parts: ({
[key: string]: number | string;
})[];
};
/**
* ModelInstallDownloadsCompleteEvent
* @description Emitted once when an install job becomes active.
@ -10671,8 +10721,9 @@ export type components = {
/**
* Size
* @description The size of this file, in bytes
* @default 0
*/
size: number;
size?: number | null;
/**
* Sha256
* @description SHA256 hash of this model (not always available)
@ -14050,6 +14101,40 @@ export type operations = {
};
};
};
/**
* Install Hugging Face Model
* @description Install a Hugging Face model using a string identifier.
*/
install_hugging_face_model: {
parameters: {
query: {
/** @description Hugging Face repo_id to install */
source: string;
};
};
responses: {
/** @description The model is being installed */
201: {
content: {
"text/html": string;
};
};
/** @description Bad request */
400: {
content: never;
};
/** @description There is already a model corresponding to this path or repo_id */
409: {
content: never;
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
/**
* Get Model Install Job
* @description Return model install job corresponding to the given source. See the documentation for 'List Model Install Jobs'

View File

@ -16,6 +16,7 @@ import type {
ModelInstallCompleteEvent,
ModelInstallDownloadProgressEvent,
ModelInstallDownloadsCompleteEvent,
ModelInstallDownloadStartedEvent,
ModelInstallErrorEvent,
ModelInstallStartedEvent,
ModelLoadCompleteEvent,
@ -45,6 +46,9 @@ export const socketModelInstallStarted = createSocketAction<ModelInstallStartedE
export const socketModelInstallDownloadProgress = createSocketAction<ModelInstallDownloadProgressEvent>(
'ModelInstallDownloadProgressEvent'
);
export const socketModelInstallDownloadStarted = createSocketAction<ModelInstallDownloadStartedEvent>(
'ModelInstallDownloadStartedEvent'
);
export const socketModelInstallDownloadsComplete = createSocketAction<ModelInstallDownloadsCompleteEvent>(
'ModelInstallDownloadsCompleteEvent'
);

View File

@ -9,6 +9,7 @@ export type InvocationCompleteEvent = S['InvocationCompleteEvent'];
export type InvocationErrorEvent = S['InvocationErrorEvent'];
export type ProgressImage = InvocationDenoiseProgressEvent['progress_image'];
export type ModelInstallDownloadStartedEvent = S['ModelInstallDownloadStartedEvent'];
export type ModelInstallDownloadProgressEvent = S['ModelInstallDownloadProgressEvent'];
export type ModelInstallDownloadsCompleteEvent = S['ModelInstallDownloadsCompleteEvent'];
export type ModelInstallCompleteEvent = S['ModelInstallCompleteEvent'];
@ -49,6 +50,7 @@ export type ServerToClientEvents = {
download_error: (payload: DownloadErrorEvent) => void;
model_load_started: (payload: ModelLoadStartedEvent) => void;
model_install_started: (payload: ModelInstallStartedEvent) => void;
model_install_download_started: (payload: ModelInstallDownloadStartedEvent) => void;
model_install_download_progress: (payload: ModelInstallDownloadProgressEvent) => void;
model_install_downloads_complete: (payload: ModelInstallDownloadsCompleteEvent) => void;
model_install_complete: (payload: ModelInstallCompleteEvent) => void;

View File

@ -17,6 +17,7 @@ from invokeai.app.services.events.events_common import (
ModelInstallCompleteEvent,
ModelInstallDownloadProgressEvent,
ModelInstallDownloadsCompleteEvent,
ModelInstallDownloadStartedEvent,
ModelInstallStartedEvent,
)
from invokeai.app.services.model_install import (
@ -252,7 +253,7 @@ def test_simple_download(mm2_installer: ModelInstallServiceBase, mm2_app_config:
assert (mm2_app_config.models_path / model_record.path).exists()
assert len(bus.events) == 5
assert isinstance(bus.events[0], ModelInstallDownloadProgressEvent) # download starts
assert isinstance(bus.events[0], ModelInstallDownloadStartedEvent) # download starts
assert isinstance(bus.events[1], ModelInstallDownloadProgressEvent) # download progresses
assert isinstance(bus.events[2], ModelInstallDownloadsCompleteEvent) # download completed
assert isinstance(bus.events[3], ModelInstallStartedEvent) # install started