InvokeAI icon indicating copy to clipboard operation
InvokeAI copied to clipboard

[enhancement]: Multi-select delete for models and loras

Open rking2981 opened this issue 1 year ago • 5 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Contact Details

Discord: @kingsman

What should this feature add?

When in the Model Manager, I decided to delete a bunch of loras that I no longer wanted by going to the loras folder and deleting them as I thought it would clear them out of InvokeAI Model Manager since there wasn't an option to select multiple and then delete. The one's I deleted are still in the Model Manager.

This feature should allow the user to select a checkbox next to each model/lora they would like to delete and when done selecting, click the Delete button which then will remove them from the Model Manager.

Alternatives

No response

Additional Content

No response

rking2981 avatar Apr 18 '24 21:04 rking2981

+1 I also need this (takes way too much time to delete each one by one) I think the best approach would be to have a button on the models page next to + Add Models something like Resolve Missing Models, which would automatically remove entries from the DB by missing files.


Besides the select-option variant OP mentioned, I think both are needed.

kenny-kvibe avatar Jun 06 '24 17:06 kenny-kvibe

Model management is tedious when you need to prune models. When you boot up Invoke it does a scan of the model library so it knows which models are missing it should reflect it in the ui, we could dim the missing models and add a multi selection option. IMHO it would be a huge usability upgrade with data that is already available.

freshlesh3 avatar Jul 14 '24 11:07 freshlesh3

Here's a simple Python code I wrote to do this directly via CLI:

import sqlite3
import json
import os
import shutil as sh

from typing import Any


ROOT_PATH = os.path.dirname(__file__)
DB_FILE_PATH = os.path.join(ROOT_PATH, 'databases', 'invokeai.db')
MODELS_DIR_PREFIX = os.path.join(ROOT_PATH, 'autoimport').replace('\\', '/')


def run_query(connection:sqlite3.Connection, query:str, params:list[Any]|tuple[Any, ...]|None = None, commit:bool = False) -> list[Any]:
	results = []
	if not query.endswith(';'):
		query = f'{query};'
	try:
		cursor = connection.cursor()
		if params:
			cursor.execute(query, params)
		else:
			cursor.execute(query)
		results = cursor.fetchall()
		if commit is True:
			connection.commit()
	except sqlite3.Error as exc:
		print(f'> ERROR {exc.__class__.__name__}: {exc}\n', flush=True, end='')
		results = []
	return results


def create_backup() -> bool:
	created = False
	try:
		db_backup = DB_FILE_PATH.replace('.db', '_BACKUP.db')
		sh.copy(DB_FILE_PATH, db_backup)
		print(f'> Created database backup: {db_backup}\n', flush=True, end='')
		created = True
	except IOError as exc:
		print(f'> ERROR {exc.__class__.__name__}: {exc}\n', flush=True, end='')
	return created


def clean_database(connection:sqlite3.Connection):
	for path in (DB_FILE_PATH, MODELS_DIR_PREFIX):
		if not os.path.exists(path):
			return print(f'> ERROR: Path "{path}" not found\n', flush=True, end='')
	table_name = 'models'
	results = run_query(connection, 'SELECT id, config FROM `{0}`;'.format(table_name))
	if not results:
		print(f'> OK - database table `{table_name}` has no entries (nothing to operate on)\n', flush=True, end='')
	else:
		missing_model_ids = []
		for row in results:
			id = row[0]
			config = json.loads(row[1])
			path = config.get('path', '')
			if path.startswith(MODELS_DIR_PREFIX) and not os.path.exists(path):
				missing_model_ids.append(id)
		missing_length = len(missing_model_ids)
		if missing_length > 0:
			if create_backup() is False:
				return
			run_query(connection, 'DELETE FROM `{0}` WHERE id IN ({1});'.format(table_name, ','.join('?'*missing_length)), missing_model_ids, True)
			print(f'> OK - database table `{table_name}` cleaned (deleted {missing_length} entries with missing model-file)\n', flush=True, end='')
		else:
			print(f'> OK - database table `{table_name}` already cleaned (missing model-file entries not found)\n', flush=True, end='')


def main() -> int:
	conn = None
	try:
		conn = sqlite3.connect(DB_FILE_PATH)
		clean_database(conn)
	except sqlite3.Error as exc:
		print(f'> ERROR {exc.__class__.__name__}: {exc}\n', flush=True, end='')
	finally:
		if conn:
			conn.close()
	print('Done.')
	return 0


if __name__ == '__main__':
	raise SystemExit(main())

It cleans the database of missing model-file entries, you must put it in the InvokeAI installation directory for the script to work.

Also change the MODELS_DIR_PREFIX to you preferred directory path from where you import models, I use the default autoimport directory for everything and this script currently checks only <InvokeAI-directory>/autoimport missing model-file entries.

And to expand on my previous comment the Resolve Missing Models button could just call something alike this code and refresh the list in-browser, but I don't know the implications, just sharing.

kenny-kvibe avatar Sep 08 '24 04:09 kenny-kvibe