diff --git a/backend/main.py b/backend/main.py index 4ab13e98f..d72c91673 100644 --- a/backend/main.py +++ b/backend/main.py @@ -9,8 +9,11 @@ import logging import aiohttp import requests import mimetypes +import shutil +import os +import asyncio -from fastapi import FastAPI, Request, Depends, status +from fastapi import FastAPI, Request, Depends, status, UploadFile, File, Form from fastapi.staticfiles import StaticFiles from fastapi.responses import JSONResponse from fastapi import HTTPException @@ -30,7 +33,7 @@ from apps.images.main import app as images_app from apps.rag.main import app as rag_app from apps.webui.main import app as webui_app -import asyncio + from pydantic import BaseModel from typing import List, Optional @@ -574,6 +577,63 @@ async def get_pipelines_list(user=Depends(get_admin_user)): } +@app.post("/api/pipelines/upload") +async def upload_pipeline( + urlIdx: int = Form(...), file: UploadFile = File(...), user=Depends(get_admin_user) +): + print("upload_pipeline", urlIdx, file.filename) + # Check if the uploaded file is a python file + if not file.filename.endswith(".py"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Only Python (.py) files are allowed.", + ) + + upload_folder = f"{CACHE_DIR}/pipelines" + os.makedirs(upload_folder, exist_ok=True) + file_path = os.path.join(upload_folder, file.filename) + + try: + # Save the uploaded file + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx] + key = openai_app.state.config.OPENAI_API_KEYS[urlIdx] + + headers = {"Authorization": f"Bearer {key}"} + + with open(file_path, "rb") as f: + files = {"file": f} + r = requests.post(f"{url}/pipelines/upload", headers=headers, files=files) + + r.raise_for_status() + data = r.json() + + return {**data} + except Exception as e: + # Handle connection error here + print(f"Connection error: {e}") + + detail = "Pipeline not found" + if r is not None: + try: + res = r.json() + if "detail" in res: + detail = res["detail"] + except: + pass + + raise HTTPException( + status_code=(r.status_code if r is not None else status.HTTP_404_NOT_FOUND), + detail=detail, + ) + finally: + # Ensure the file is deleted after the upload is completed or on failure + if os.path.exists(file_path): + os.remove(file_path) + + class AddPipelineForm(BaseModel): url: str urlIdx: int diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index f6b2de4d0..70d8b8804 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -133,6 +133,43 @@ export const getPipelinesList = async (token: string = '') => { return pipelines; }; +export const uploadPipeline = async (token: string, file: File, urlIdx: string) => { + let error = null; + + // Create a new FormData object to handle the file upload + const formData = new FormData(); + formData.append('file', file); + formData.append('urlIdx', urlIdx); + + const res = await fetch(`${WEBUI_BASE_URL}/api/pipelines/upload`, { + method: 'POST', + headers: { + ...(token && { authorization: `Bearer ${token}` }) + // 'Content-Type': 'multipart/form-data' is not needed as Fetch API will set it automatically + }, + body: formData + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + if ('detail' in err) { + error = err.detail; + } else { + error = err; + } + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const downloadPipeline = async (token: string, url: string, urlIdx: string) => { let error = null; diff --git a/src/lib/components/admin/Settings/Pipelines.svelte b/src/lib/components/admin/Settings/Pipelines.svelte index 875e61051..be557bee1 100644 --- a/src/lib/components/admin/Settings/Pipelines.svelte +++ b/src/lib/components/admin/Settings/Pipelines.svelte @@ -14,7 +14,8 @@ getModels, getPipelinesList, downloadPipeline, - deletePipeline + deletePipeline, + uploadPipeline } from '$lib/apis'; import Spinner from '$lib/components/common/Spinner.svelte'; @@ -24,6 +25,9 @@ export let saveHandler: Function; let downloading = false; + let uploading = false; + + let pipelineFiles; let PIPELINES_LIST = null; let selectedPipelinesUrlIdx = ''; @@ -126,6 +130,41 @@ downloading = false; }; + const uploadPipelineHandler = async () => { + uploading = true; + + if (pipelineFiles && pipelineFiles.length !== 0) { + const file = pipelineFiles[0]; + + console.log(file); + + const res = await uploadPipeline(localStorage.token, file, selectedPipelinesUrlIdx).catch( + (error) => { + console.log(error); + toast.error('Something went wrong :/'); + return null; + } + ); + + if (res) { + toast.success('Pipeline downloaded successfully'); + setPipelines(); + models.set(await getModels(localStorage.token)); + } + } else { + toast.error('No file selected'); + } + + pipelineFiles = null; + const pipelineUploadInputElement = document.getElementById('pipeline-upload-input'); + + if (pipelineUploadInputElement) { + pipelineUploadInputElement.value = null; + } + + uploading = false; + }; + const deletePipelineHandler = async () => { const res = await deletePipeline( localStorage.token, @@ -196,6 +235,91 @@ +
+
+ {$i18n.t('Upload Pipeline')} +
+
+
+ + + +
+ +
+
+
{$i18n.t('Install from Github URL')}