Skip to content
Merged

V7 #2

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
155 commits
Select commit Hold shift + click to select a range
bdf5827
feat(v7): initial shell setup
joshistoast Jun 9, 2026
388d733
docs(webv2): document workbench phase contracts and oxc rules
joshistoast Jun 10, 2026
7e78034
chore(webv2): remove phase planning notes
joshistoast Jun 10, 2026
12b2af6
test(webv2): add vitest coverage for workbench state
joshistoast Jun 10, 2026
da5dd80
feat(webv2): expand workbench state for runtime features
joshistoast Jun 10, 2026
b3919f1
feat(webv2): add theme preferences and settings dialog
joshistoast Jun 10, 2026
e256794
feat(webv2): wire generate queue runtime to backend
joshistoast Jun 10, 2026
d0a59d1
feat(webv2): add diagnostics notifications and preview widgets
joshistoast Jun 10, 2026
9155437
feat(webv2): route generated images through gallery and canvas
joshistoast Jun 10, 2026
0a16801
refactor(webv2): move widget actions into manifest chrome
joshistoast Jun 10, 2026
7056733
feat(webv2): polish workbench shell layout
joshistoast Jun 10, 2026
1e40906
feat(webv2): add themed workbench control wrappers
joshistoast Jun 10, 2026
2535542
feat(webv2): expand gallery data model and settings
joshistoast Jun 10, 2026
1d6aafb
feat(webv2): add shared image actions and context menu
joshistoast Jun 10, 2026
4122f7d
feat(webv2): rebuild gallery widget with virtualized grid and board m…
joshistoast Jun 10, 2026
7adcf82
feat(webv2): add preview image comparison
joshistoast Jun 10, 2026
5c69672
fix(webv2): refine image menu scrolling and load Inter
joshistoast Jun 10, 2026
7317e9c
feat(webv2): use arrows on tooltips
joshistoast Jun 10, 2026
5152763
refactor(webv2): centralize backend API helpers
joshistoast Jun 10, 2026
4c09a3a
feat(webv2): add socket queue coordinator
joshistoast Jun 10, 2026
6bffa72
feat(webv2): reconcile queue runtime with backend
joshistoast Jun 10, 2026
7d27fb1
feat(webv2): polish workbench queue and preview UI
joshistoast Jun 10, 2026
12965e0
feat(webv2): shell ui and icon tweaks
joshistoast Jun 10, 2026
deb805b
feat(webv2): use react compiler, upgrade deps
joshistoast Jun 10, 2026
fc08c24
fix(db): rebuild model_relationships table with valid foreign keys
joshistoast Jun 11, 2026
00daa0a
feat(api): expose the models directory path
joshistoast Jun 11, 2026
de37b79
feat(webv2): move widget padding into views, add header chrome
joshistoast Jun 11, 2026
af839a0
feat(webv2): add shared shell tools for widgets
joshistoast Jun 11, 2026
502437a
feat(webv2): add model manager widget
joshistoast Jun 11, 2026
d56e5a8
feat(webv2): rework starter models UI with a bundle sidebar
joshistoast Jun 11, 2026
d28206a
feat(webv2): use rolldown babel compiler preset
joshistoast Jun 11, 2026
c31e51a
feat(webv2): initial multi-user implementation
joshistoast Jun 11, 2026
a3e1bf3
feat: add project records persistence
joshistoast Jun 11, 2026
d35fdc8
feat: sync workbench projects with backend
joshistoast Jun 11, 2026
8df6010
feat: add project home and management UI
joshistoast Jun 11, 2026
6d5f4c8
chore: allow custom dev backend URL
joshistoast Jun 11, 2026
510b539
feat: add shared workbench settings
joshistoast Jun 11, 2026
7531bc8
feat: refresh workbench theme system
joshistoast Jun 12, 2026
f72e116
feat(webv2): move user management to home screen
joshistoast Jun 12, 2026
6924259
Merge branch 'invoke-ai:main' into v7
joshistoast Jun 12, 2026
5dd8ef7
feat: add shared widget opener
joshistoast Jun 12, 2026
a930d6f
feat: show queue progress in top bar
joshistoast Jun 12, 2026
2e7c4ae
fix: allow notifications outside workbench
joshistoast Jun 12, 2026
59b47c7
chore: ignore generated InvokeAI artifacts
joshistoast Jun 12, 2026
f760340
feat: expand generate settings controls
joshistoast Jun 12, 2026
2f0ef6a
chore: polish queue menu borders
joshistoast Jun 12, 2026
6cc9399
feat: add workflow graph invocation
joshistoast Jun 14, 2026
d0a7f80
feat: add workflow editor UI
joshistoast Jun 14, 2026
b3bc3f8
feat: recall image generation metadata
joshistoast Jun 14, 2026
4460bb1
fix(webv2): send model install tokens in header
joshistoast Jun 14, 2026
2502aa1
fix(webv2): scope queue socket events to tracked runs
joshistoast Jun 14, 2026
b967f03
fix(webv2): ignore corrupt workbench cache
joshistoast Jun 14, 2026
85b6f05
fix(webv2): preserve runtime state across hydration
joshistoast Jun 14, 2026
7cdccd4
fix(webv2): stop replaying persisted notifications
joshistoast Jun 14, 2026
b8d6980
ci(webv2): add lint build and test gates
joshistoast Jun 14, 2026
2c51140
refactor(webv2): introduce workbench external store
joshistoast Jun 14, 2026
61e9bc9
refactor(webv2): migrate workbench UI to selectors
joshistoast Jun 14, 2026
69e26fd
fix(webv2): harden workbench autosave scheduling
joshistoast Jun 14, 2026
dc0aebf
fix(webv2): stabilize image context menu mounting
joshistoast Jun 14, 2026
f726448
fix(webv2): workflow form json preview scrolling
joshistoast Jun 14, 2026
85b1510
feat(webv2): add workflow connector support
joshistoast Jun 15, 2026
9dbdeff
feat(webv2): improve workflow editor interactions
joshistoast Jun 15, 2026
d37d77f
fix(webv2): refine graph preview layout
joshistoast Jun 15, 2026
b91f871
fix(webv2): clarify invoke tooltip
joshistoast Jun 15, 2026
119eedd
fix(webv2): simplify model filter scrolling
joshistoast Jun 15, 2026
ecc09d4
refactor(webv2): derive theme tokens from neutral ramps
joshistoast Jun 15, 2026
a3e7e03
style(webv2): align workbench surfaces with theme ladder
joshistoast Jun 15, 2026
b91ea44
style(webv2): polish workflow editor controls
joshistoast Jun 15, 2026
f370652
fix(webv2): improve starter model selection styling
joshistoast Jun 15, 2026
2c6e459
fix(webv2): use chakra package button in more places
joshistoast Jun 15, 2026
7d3bb4e
style(webv2): no relative parent imports
joshistoast Jun 15, 2026
7986ad2
style(webv2): add lint + formatting rules
joshistoast Jun 15, 2026
2bc83f8
feat(workbench): support batched workflow runs
joshistoast Jun 15, 2026
c7b8ba6
fix(workbench): harden image context menu targets
joshistoast Jun 15, 2026
de50c6a
feat(workflow): improve editor connections and validation
joshistoast Jun 15, 2026
27a3d84
chore(frontend): enforce exhaustive deps linting
joshistoast Jun 15, 2026
1913ddb
feat(workbench): improve queue lifecycle controls
joshistoast Jun 15, 2026
785c212
feat(workbench): show progress images in gallery and preview
joshistoast Jun 15, 2026
f77387f
refactor(workbench): simplify image context menu lifecycle
joshistoast Jun 15, 2026
e628fae
feat(webv2): organize workbench components
joshistoast Jun 15, 2026
4540593
feat(webv2): add model taxonomy helpers
joshistoast Jun 17, 2026
c25b337
feat(webv2): support expanded generation policies
joshistoast Jun 17, 2026
464c162
feat(webv2): add generate model component controls
joshistoast Jun 17, 2026
bfe2a70
feat(webv2): improve model picker interactions
joshistoast Jun 17, 2026
672ae7f
fix(webv2): validate invocations against loaded models
joshistoast Jun 17, 2026
f37f9d9
feat(webv2): improve gallery refresh and image recall
joshistoast Jun 17, 2026
ce83d78
feat(webv2): improve workflow validation and editor controls
joshistoast Jun 17, 2026
4440612
feat(webv2): improve model management views
joshistoast Jun 17, 2026
618d729
fix(webv2): improve API validation error messages
joshistoast Jun 17, 2026
5000102
style(webv2): standardize workbench chrome recipes
joshistoast Jun 17, 2026
1e4341a
feat(webv2): add workbench widget runtime api
joshistoast Jun 18, 2026
7d06547
feat(webv2): add workbench widget placement commands
joshistoast Jun 18, 2026
d1739c5
perf(webv2): narrow workbench shell widget subscriptions
joshistoast Jun 18, 2026
4dcf1bc
feat(webv2): normalize workbench widget manifests
joshistoast Jun 18, 2026
1978599
fix(api): sanitize stale queue image results
joshistoast Jun 19, 2026
389551e
fix(api): secure manager endpoints
joshistoast Jun 19, 2026
811c8d3
feat(webv2): add launchpad managers
joshistoast Jun 19, 2026
d3ec7f9
perf(webv2): virtualize workflow node picker
joshistoast Jun 20, 2026
837c3f1
style(webv2): unify workflow node chrome
joshistoast Jun 20, 2026
ec6ecf1
feat(webv2): add workflow field reset controls
joshistoast Jun 20, 2026
04f6c9e
fix(webv2): preserve model default state
joshistoast Jun 20, 2026
b294cab
fix(webv2): stabilize tooltip-wrapped menu triggers
joshistoast Jun 20, 2026
37aac1a
fix(webv2): constrain gallery image board drops
joshistoast Jun 20, 2026
c393b59
style(webv2): polish workbench controls
joshistoast Jun 20, 2026
48775eb
refactor(webv2): remove layout actions widget
joshistoast Jun 20, 2026
38e827e
style(webv2): refine status bar widgets
joshistoast Jun 20, 2026
49d571b
chore(webv2): update frontend dependencies
joshistoast Jun 20, 2026
2bcbd45
feat(webv2): add configurable workbench hotkeys
joshistoast Jun 20, 2026
9f04435
refactor(webv2): extract image recall helpers
joshistoast Jun 20, 2026
e87da4d
feat(webv2): mount singleton widget hosts
joshistoast Jun 20, 2026
60abc3b
refactor(webv2): remove history controls widget
joshistoast Jun 20, 2026
36fe681
fix(webv2): respect reduced motion during first paint
joshistoast Jun 20, 2026
c4cc9bc
style(webv2): polish menus and workflow controls
joshistoast Jun 20, 2026
5fa86c8
feat(webv2): add color compensation to generate widget
joshistoast Jun 20, 2026
036fca3
fix(webv2): generation policiies lint
joshistoast Jun 20, 2026
971950f
feat(webv2): add loading screen, organize brand mark component usage
joshistoast Jun 20, 2026
a0d639d
fix(webv2): generate settings collapsible height
joshistoast Jun 20, 2026
ff698a8
feat(webv2): add resizable textarea component
joshistoast Jun 20, 2026
e133a89
feat(webv2): toggle negative prompt, remember textarea height
joshistoast Jun 20, 2026
d701cbc
feat(webv2): add some prompt actions
joshistoast Jun 20, 2026
70b921f
feat(webv2): add prompt AST, keybind weighting and experimental syntax
joshistoast Jun 20, 2026
5fdbaf0
feat(webv2): update model picker and base identity palettes
joshistoast Jun 20, 2026
a667a13
feat(webv2): better layout presets
joshistoast Jun 21, 2026
784625d
feat(webv2): add prompt trigger to neg, trigger on '<'
joshistoast Jun 21, 2026
ea5c016
feat(webv2): state selection overhaul
joshistoast Jun 26, 2026
125c067
chore(webv2): upgrade deps, apply new linting and style formatting
joshistoast Jun 26, 2026
2c9f62d
feat(queue): retain denoise image preview while final processes
joshistoast Jun 26, 2026
bfefc0d
fix(gallery): prevent repeated total update dispatches
joshistoast Jun 26, 2026
693cbb3
feat(workbench): rename graph invocation source ids
joshistoast Jun 26, 2026
73b5a9e
feat(queue): show project queue progress in tabs
joshistoast Jun 26, 2026
15e147c
style(workbench): polish widget rail slot styling
joshistoast Jun 26, 2026
3660e0f
fix(generate): default missing batch count
joshistoast Jun 26, 2026
a103845
feat(model-cache): report clear results and current usage
joshistoast Jun 28, 2026
979f964
feat(queue): support project-scoped queue queries
joshistoast Jun 28, 2026
6ca1872
feat(workbench): tag queue submissions with project origin
joshistoast Jun 28, 2026
eccc112
fix(workbench): preserve live progress images while streaming
joshistoast Jun 28, 2026
9de16cc
feat(queue): add server-backed queue widget
joshistoast Jun 28, 2026
eeb0094
Merge remote-tracking branch 'v7/main' into v7
joshistoast Jun 28, 2026
611fee4
fix(api): formatting
joshistoast Jun 28, 2026
dc02852
fix(ci): sort service imports
joshistoast Jun 28, 2026
a885fc5
fix(ci): format project records tests
joshistoast Jun 28, 2026
39cb546
fix(ci): update generated api schema
joshistoast Jun 28, 2026
fa536d3
fix(ci): regenerate api types with locked deps
joshistoast Jun 28, 2026
83b7c70
fix(tests): update stale router and queue test doubles
joshistoast Jun 28, 2026
0b0793e
perf(workflows): index workflow graph lookups
joshistoast Jun 29, 2026
dc87fe9
perf(workflows): optimize large workflow rendering
joshistoast Jun 29, 2026
d84db2e
fix(workbench): harden persistence for large projects
joshistoast Jun 29, 2026
64f219c
feat(workbench): show fallback for lazy widgets
joshistoast Jun 29, 2026
6bd123e
feat(router): use workbnch splash screen
joshistoast Jun 29, 2026
c0a7419
feat(workbench): add project-scoped diagnostics logger
joshistoast Jun 29, 2026
7bab0c2
feat(workbench): expose diagnostics developer settings
joshistoast Jun 29, 2026
d9c6a72
feat(workbench): route errors through diagnostics
joshistoast Jun 29, 2026
d90b53c
feat(workbench): add diagnostic metadata to error producers
joshistoast Jun 29, 2026
25a3716
feat(workbench): record performance timings in diagnostics
joshistoast Jun 29, 2026
a6b35ea
feat(workbench): render diagnostics entries in diagnostics widget
joshistoast Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 6 additions & 1 deletion .github/actions/install-frontend-deps/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: install frontend dependencies
description: Installs frontend dependencies with pnpm, with caching
inputs:
working-directory:
description: Directory to install dependencies in
required: false
default: invokeai/frontend/web
runs:
using: 'composite'
steps:
Expand Down Expand Up @@ -30,4 +35,4 @@ runs:
- name: install frontend dependencies
run: pnpm install --prefer-frozen-lockfile
shell: bash
working-directory: invokeai/frontend/web
working-directory: ${{ inputs.working-directory }}
41 changes: 37 additions & 4 deletions .github/workflows/frontend-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ on:
type: boolean
default: true

defaults:
run:
working-directory: invokeai/frontend/web

jobs:
frontend-checks:
runs-on: ubuntu-latest
timeout-minutes: 10 # expected run time: <2 min
defaults:
run:
working-directory: invokeai/frontend/web
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -94,3 +93,37 @@ jobs:
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || inputs.always_run == true }}
run: 'pnpm lint:knip'
shell: bash

frontend-webv2-checks:
runs-on: ubuntu-latest
timeout-minutes: 10 # expected run time: <2 min
defaults:
run:
working-directory: invokeai/frontend/webv2
steps:
- uses: actions/checkout@v6

- name: check for changed webv2 files
if: ${{ inputs.always_run != true }}
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96
with:
files_yaml: |
webv2:
- 'invokeai/frontend/webv2/**'

- name: install dependencies
if: ${{ steps.changed-files.outputs.webv2_any_changed == 'true' || inputs.always_run == true }}
uses: ./.github/actions/install-frontend-deps
with:
working-directory: invokeai/frontend/webv2

- name: lint
if: ${{ steps.changed-files.outputs.webv2_any_changed == 'true' || inputs.always_run == true }}
run: 'pnpm run lint'
shell: bash

- name: build
if: ${{ steps.changed-files.outputs.webv2_any_changed == 'true' || inputs.always_run == true }}
run: 'pnpm run build'
shell: bash
36 changes: 32 additions & 4 deletions .github/workflows/frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ on:
type: boolean
default: true

defaults:
run:
working-directory: invokeai/frontend/web

jobs:
frontend-tests:
runs-on: ubuntu-latest
timeout-minutes: 10 # expected run time: <2 min
defaults:
run:
working-directory: invokeai/frontend/web
steps:
- uses: actions/checkout@v6

Expand All @@ -63,3 +62,32 @@ jobs:
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || inputs.always_run == true }}
run: 'pnpm test:no-watch'
shell: bash

frontend-webv2-tests:
runs-on: ubuntu-latest
timeout-minutes: 10 # expected run time: <2 min
defaults:
run:
working-directory: invokeai/frontend/webv2
steps:
- uses: actions/checkout@v6

- name: check for changed webv2 files
if: ${{ inputs.always_run != true }}
id: changed-files
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96
with:
files_yaml: |
webv2:
- 'invokeai/frontend/webv2/**'

- name: install dependencies
if: ${{ steps.changed-files.outputs.webv2_any_changed == 'true' || inputs.always_run == true }}
uses: ./.github/actions/install-frontend-deps
with:
working-directory: invokeai/frontend/webv2

- name: vitest
if: ${{ steps.changed-files.outputs.webv2_any_changed == 'true' || inputs.always_run == true }}
run: 'pnpm test'
shell: bash
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ env/
venv/
ENV/

# Generated stuff
invokeai.yaml
invokeai.example.yaml

/models/
/databases/
/configs/
/nodes/
/outputs/

# Spyder project settings
.spyderproject
.spyproject
Expand Down
3 changes: 3 additions & 0 deletions invokeai/app/api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from invokeai.app.services.names.names_default import SimpleNameService
from invokeai.app.services.object_serializer.object_serializer_disk import ObjectSerializerDisk
from invokeai.app.services.object_serializer.object_serializer_forward_cache import ObjectSerializerForwardCache
from invokeai.app.services.project_records.project_records_sqlite import ProjectRecordsSqlite
from invokeai.app.services.session_processor.session_processor_default import (
DefaultSessionProcessor,
DefaultSessionRunner,
Expand Down Expand Up @@ -187,6 +188,7 @@ def initialize(
style_preset_image_files = StylePresetImageFileStorageDisk(style_presets_folder / "images")
workflow_thumbnails = WorkflowThumbnailFileStorageDisk(workflow_thumbnails_folder)
client_state_persistence = ClientStatePersistenceSqlite(db=db)
project_records = ProjectRecordsSqlite(db=db)
users = UserService(db=db)

services = InvocationServices(
Expand Down Expand Up @@ -220,6 +222,7 @@ def initialize(
style_preset_image_files=style_preset_image_files,
workflow_thumbnails=workflow_thumbnails,
client_state_persistence=client_state_persistence,
project_records=project_records,
users=users,
)

Expand Down
4 changes: 4 additions & 0 deletions invokeai/app/api/routers/_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def assert_image_owner(image_name: str, current_user: CurrentUserOrDefault) -> N
"""
if current_user.is_admin:
return
if not ApiDependencies.invoker.services.image_records.exists(image_name):
raise HTTPException(status_code=404, detail="Image not found")
owner = ApiDependencies.invoker.services.image_records.get_user_id(image_name)
if owner is not None and owner == current_user.user_id:
return
Expand Down Expand Up @@ -50,6 +52,8 @@ def assert_image_read_access(image_name: str, current_user: CurrentUserOrDefault
"""
if current_user.is_admin:
return
if not ApiDependencies.invoker.services.image_records.exists(image_name):
raise HTTPException(status_code=404, detail="Image not found")

owner = ApiDependencies.invoker.services.image_records.get_user_id(image_name)
if owner is not None and owner == current_user.user_id:
Expand Down
23 changes: 21 additions & 2 deletions invokeai/app/api/routers/app_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
load_external_api_keys,
)
from invokeai.app.services.external_generation.external_generation_common import ExternalProviderStatus
from invokeai.app.services.external_generation.startup import sync_configured_external_starter_models
from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus
from invokeai.app.services.model_records.model_records_base import UnknownModelException
from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch
Expand Down Expand Up @@ -178,7 +179,7 @@ async def update_runtime_config(
status_code=200,
response_model=list[ExternalProviderStatusModel],
)
async def get_external_provider_statuses() -> list[ExternalProviderStatusModel]:
async def get_external_provider_statuses(_: AdminUserOrDefault) -> list[ExternalProviderStatusModel]:
statuses = ApiDependencies.invoker.services.external_generation.get_provider_statuses()
return [status_to_model(status) for status in statuses.values()]

Expand All @@ -189,7 +190,7 @@ async def get_external_provider_statuses() -> list[ExternalProviderStatusModel]:
status_code=200,
response_model=list[ExternalProviderConfigModel],
)
async def get_external_provider_configs() -> list[ExternalProviderConfigModel]:
async def get_external_provider_configs(_: AdminUserOrDefault) -> list[ExternalProviderConfigModel]:
config = get_config()
return [_build_external_provider_config(provider_id, config) for provider_id in EXTERNAL_PROVIDER_FIELDS]

Expand Down Expand Up @@ -219,9 +220,14 @@ async def set_external_provider_config(
raise HTTPException(status_code=400, detail="No external provider config fields provided")

api_key_removed = update.api_key is not None and updates.get(api_key_field) is None
api_key_set = update.api_key is not None and updates.get(api_key_field) is not None
_apply_external_provider_update(updates)
if api_key_removed:
_remove_external_models_for_provider(provider_id)
elif api_key_set:
# Configuring a key should make the provider's models usable without a
# restart; queue its external starter models the same way startup does.
_sync_external_starter_models_for_provider(provider_id)
return _build_external_provider_config(provider_id, get_config())


Expand Down Expand Up @@ -316,6 +322,19 @@ def _build_external_provider_config(provider_id: str, config: InvokeAIAppConfig)
)


def _sync_external_starter_models_for_provider(provider_id: str) -> None:
invoker = ApiDependencies.invoker
try:
sync_configured_external_starter_models(
configured_provider_ids={provider_id},
model_manager=invoker.services.model_manager,
logger=invoker.services.logger,
)
except Exception as error:
# Queuing installs must never fail the config save; surface and move on.
invoker.services.logger.warning(f"Failed queueing external starter models for '{provider_id}': {error}")


def _remove_external_models_for_provider(provider_id: str) -> None:
model_manager = ApiDependencies.invoker.services.model_manager
external_models = model_manager.store.search_by_attr(
Expand Down
36 changes: 26 additions & 10 deletions invokeai/app/api/routers/custom_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""FastAPI routes for custom node management."""

import asyncio
import json
import shutil
import subprocess
import sys
import traceback
from importlib.util import module_from_spec, spec_from_file_location
Expand All @@ -28,6 +28,7 @@
# were imported by that pack. Used on uninstall to delete only pack-imported workflows
# — deleting by tag alone is unsafe because users can edit tags on their own workflows.
PACK_MANIFEST_FILENAME = ".invokeai_pack_manifest.json"
GIT_CLONE_TIMEOUT_SECONDS = 120


class NodePackInfo(BaseModel):
Expand Down Expand Up @@ -126,6 +127,26 @@ def _get_installed_packs() -> list[NodePackInfo]:
return packs


async def _clone_node_pack(source: str, target_dir: Path) -> tuple[int, str]:
process = await asyncio.create_subprocess_exec(
"git",
"clone",
source,
str(target_dir),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)

try:
_stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=GIT_CLONE_TIMEOUT_SECONDS)
except asyncio.TimeoutError:
process.kill()
await process.communicate()
raise

return process.returncode or 0, stderr.decode(errors="replace").strip()


@custom_nodes_router.get(
"/",
operation_id="list_custom_node_packs",
Expand Down Expand Up @@ -172,21 +193,16 @@ async def install_custom_node_pack(

try:
# Clone the repository
result = subprocess.run(
["git", "clone", source, str(target_dir)],
capture_output=True,
text=True,
timeout=120,
)
returncode, stderr = await _clone_node_pack(source, target_dir)

if result.returncode != 0:
if returncode != 0:
# Clean up on failure
if target_dir.exists():
shutil.rmtree(target_dir)
return InstallNodePackResponse(
name=pack_name,
success=False,
message=f"Git clone failed: {result.stderr.strip()}",
message=f"Git clone failed: {stderr}",
)

# Detect dependency manifests but do NOT install them automatically.
Expand Down Expand Up @@ -232,7 +248,7 @@ async def install_custom_node_pack(
dependency_file=dependency_file,
)

except subprocess.TimeoutExpired:
except asyncio.TimeoutError:
if target_dir.exists():
shutil.rmtree(target_dir)
return InstallNodePackResponse(
Expand Down
Loading
Loading