feat: refactor model import to a single backend endpoint

This refactors the model import functionality to improve performance and user experience by centralizing the logic on the backend.

Previously, the frontend would parse an imported JSON file and send an individual API request for each model, which was slow and inefficient.

This change introduces a new backend endpoint, `/api/v1/models/import`, that accepts a list of model objects. The frontend now reads the selected JSON file, parses it, and sends the entire payload to the backend in a single request. The backend then processes this list, creating or updating models as necessary.

This commit also includes the following fixes:
- Handles cases where the imported JSON contains models without `meta` or `params` fields by providing default empty values.
This commit is contained in:
silentoplayz 2025-09-28 18:49:42 -04:00
parent 231d182c35
commit fe28097817
3 changed files with 29 additions and 19 deletions

View file

@ -22,8 +22,6 @@ from fastapi import (
Request, Request,
status, status,
Response, Response,
UploadFile,
File,
) )
from fastapi.responses import FileResponse, StreamingResponse from fastapi.responses import FileResponse, StreamingResponse
@ -112,12 +110,16 @@ async def export_models(user=Depends(get_admin_user)):
############################ ############################
class ModelsImportForm(BaseModel):
models: list[dict]
@router.post("/import", response_model=bool) @router.post("/import", response_model=bool)
async def import_models( async def import_models(
user: str = Depends(get_admin_user), file: UploadFile = File(...) user: str = Depends(get_admin_user), form_data: ModelsImportForm = (...)
): ):
try: try:
data = json.loads(await file.read()) data = form_data.models
if isinstance(data, list): if isinstance(data, list):
for model_data in data: for model_data in data:
# Here, you can add logic to validate model_data if needed # Here, you can add logic to validate model_data if needed

View file

@ -31,18 +31,16 @@ export const getModels = async (token: string = '') => {
return res; return res;
}; };
export const importModels = async (token: string, file: File) => { export const importModels = async (token: string, models: object[]) => {
let error = null; let error = null;
const formData = new FormData();
formData.append('file', file);
const res = await fetch(`${WEBUI_API_BASE_URL}/models/import`, { const res = await fetch(`${WEBUI_API_BASE_URL}/models/import`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${token}` authorization: `Bearer ${token}`
}, },
body: formData body: JSON.stringify({ models: models })
}) })
.then(async (res) => { .then(async (res) => {
if (!res.ok) throw await res.json(); if (!res.ok) throw await res.json();

View file

@ -465,10 +465,14 @@ let modelsImportInProgress = false;
type="file" type="file"
accept=".json" accept=".json"
hidden hidden
on:change={async () => { on:change={() => {
if (importFiles.length > 0) { if (importFiles.length > 0) {
const reader = new FileReader();
reader.onload = async (event) => {
try {
const models = JSON.parse(String(event.target.result));
modelsImportInProgress = true; modelsImportInProgress = true;
const res = await importModels(localStorage.token, importFiles[0]); const res = await importModels(localStorage.token, models);
modelsImportInProgress = false; modelsImportInProgress = false;
if (res) { if (res) {
@ -477,6 +481,12 @@ let modelsImportInProgress = false;
} else { } else {
toast.error($i18n.t('Failed to import models')); toast.error($i18n.t('Failed to import models'));
} }
} catch (e) {
toast.error($i18n.t('Invalid JSON file'));
console.error(e);
}
};
reader.readAsText(importFiles[0]);
} }
}} }}
/> />