mirror of
https://github.com/open-webui/open-webui
synced 2024-11-24 21:13:59 +00:00
feat: upload pipeline
This commit is contained in:
parent
bc4aeeeec8
commit
e52568e7cb
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-2">
|
||||
<div class=" mb-2 text-sm font-medium">
|
||||
{$i18n.t('Upload Pipeline')}
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<input
|
||||
id="pipelines-upload-input"
|
||||
bind:files={pipelineFiles}
|
||||
type="file"
|
||||
accept=".py"
|
||||
hidden
|
||||
/>
|
||||
|
||||
<button
|
||||
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
document.getElementById('pipelines-upload-input')?.click();
|
||||
}}
|
||||
>
|
||||
{#if pipelineFiles}
|
||||
{pipelineFiles.length > 0 ? `${pipelineFiles.length}` : ''} pipeline(s) selected.
|
||||
{:else}
|
||||
{$i18n.t('Click here to select a py file.')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
uploadPipelineHandler();
|
||||
}}
|
||||
disabled={uploading}
|
||||
type="button"
|
||||
>
|
||||
{#if uploading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/>
|
||||
<path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-2">
|
||||
<div class=" mb-2 text-sm font-medium">
|
||||
{$i18n.t('Install from Github URL')}
|
||||
|
Loading…
Reference in New Issue
Block a user