From e3503d66173bd6b9f89e3054a7e7ac4f2bde91df Mon Sep 17 00:00:00 2001 From: lucasew Date: Fri, 19 Jan 2024 17:12:14 -0300 Subject: [PATCH 01/30] backend: make dotenv optional Signed-off-by: lucasew --- backend/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/config.py b/backend/config.py index 2a96d018..f31ee16a 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,4 +1,3 @@ -from dotenv import load_dotenv, find_dotenv import os @@ -14,7 +13,11 @@ from constants import ERROR_MESSAGES from pathlib import Path -load_dotenv(find_dotenv("../.env")) +try: + from dotenv import load_dotenv, find_dotenv + load_dotenv(find_dotenv("../.env")) +except ImportError: + print("dotenv not installed, skipping...") #################################### From 5b26d2a686d47822d5959e90a1d0b64ebec88d67 Mon Sep 17 00:00:00 2001 From: lucasew Date: Fri, 19 Jan 2024 17:13:09 -0300 Subject: [PATCH 02/30] backend: make the data directory and the artifacts from the frontend customizable using environment variables Signed-off-by: lucasew --- backend/apps/web/internal/db.py | 4 +++- backend/apps/web/routers/utils.py | 12 +++++------- backend/config.py | 10 ++++++---- backend/main.py | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/backend/apps/web/internal/db.py b/backend/apps/web/internal/db.py index 3d639f3c..802d2a40 100644 --- a/backend/apps/web/internal/db.py +++ b/backend/apps/web/internal/db.py @@ -1,4 +1,6 @@ from peewee import * +from config import DATA_DIR -DB = SqliteDatabase("./data/ollama.db") + +DB = SqliteDatabase(str(DATA_DIR / "ollama.db")) DB.connect() diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index b2ca409a..cb316bbc 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -11,7 +11,7 @@ import json from utils.misc import calculate_sha256 -from config import OLLAMA_API_BASE_URL +from config import OLLAMA_API_BASE_URL, DATA_DIR, UPLOAD_DIR from constants import ERROR_MESSAGES @@ -96,8 +96,7 @@ async def download( file_name = parse_huggingface_url(url) if file_name: - os.makedirs("./uploads", exist_ok=True) - file_path = os.path.join("./uploads", f"{file_name}") + file_path = str(UPLOAD_DIR / file_name) return StreamingResponse( download_file_stream(url, file_path, file_name), @@ -109,16 +108,15 @@ async def download( @router.post("/upload") def upload(file: UploadFile = File(...)): - os.makedirs("./data/uploads", exist_ok=True) - file_path = os.path.join("./data/uploads", file.filename) + file_path = UPLOAD_DIR / file.filename # Save file in chunks - with open(file_path, "wb+") as f: + with file_path.open("wb+") as f: for chunk in file.file: f.write(chunk) def file_process_stream(): - total_size = os.path.getsize(file_path) + total_size = os.path.getsize(str(file_path)) chunk_size = 1024 * 1024 try: with open(file_path, "rb") as f: diff --git a/backend/config.py b/backend/config.py index f31ee16a..188dbbd7 100644 --- a/backend/config.py +++ b/backend/config.py @@ -24,10 +24,12 @@ except ImportError: # File Upload #################################### +DATA_DIR = Path(os.getenv("DATA_DIR", './data')).resolve() -UPLOAD_DIR = "./data/uploads" -Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True) +UPLOAD_DIR = DATA_DIR / "uploads" +UPLOAD_DIR.mkdir(parents=True, exist_ok=True) +WEB_DIR = Path(os.getenv("WEB_DIR", "../build")) #################################### # ENV (dev,test,prod) @@ -82,10 +84,10 @@ if WEBUI_AUTH and WEBUI_JWT_SECRET_KEY == "": # RAG #################################### -CHROMA_DATA_PATH = "./data/vector_db" +CHROMA_DATA_PATH = DATA_DIR / "vector_db" EMBED_MODEL = "all-MiniLM-L6-v2" CHROMA_CLIENT = chromadb.PersistentClient( - path=CHROMA_DATA_PATH, settings=Settings(allow_reset=True) + path=str(CHROMA_DATA_PATH), settings=Settings(allow_reset=True) ) CHUNK_SIZE = 1500 CHUNK_OVERLAP = 100 diff --git a/backend/main.py b/backend/main.py index e4d4bdb5..4b734da8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,7 +14,7 @@ from apps.openai.main import app as openai_app from apps.web.main import app as webui_app from apps.rag.main import app as rag_app -from config import ENV +from config import ENV, WEB_DIR class SPAStaticFiles(StaticFiles): @@ -58,4 +58,4 @@ app.mount("/openai/api", openai_app) app.mount("/rag/api/v1", rag_app) -app.mount("/", SPAStaticFiles(directory="../build", html=True), name="spa-static-files") +app.mount("/", SPAStaticFiles(directory=str(WEB_DIR), html=True), name="spa-static-files") From d2c5f3d59164137e047f2d93f207b6f8f7a44cd3 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 22 Jan 2024 01:47:07 -0800 Subject: [PATCH 03/30] refac: convert str var to f-string --- backend/apps/web/internal/db.py | 2 +- backend/apps/web/routers/utils.py | 4 ++-- backend/config.py | 9 +++++---- backend/main.py | 8 ++++++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/backend/apps/web/internal/db.py b/backend/apps/web/internal/db.py index 802d2a40..1f8c3bf7 100644 --- a/backend/apps/web/internal/db.py +++ b/backend/apps/web/internal/db.py @@ -2,5 +2,5 @@ from peewee import * from config import DATA_DIR -DB = SqliteDatabase(str(DATA_DIR / "ollama.db")) +DB = SqliteDatabase(f"{DATA_DIR}/ollama.db") DB.connect() diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index cb316bbc..0bb3ea3c 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -96,7 +96,7 @@ async def download( file_name = parse_huggingface_url(url) if file_name: - file_path = str(UPLOAD_DIR / file_name) + file_path = f"{UPLOAD_DIR}/{file_name}" return StreamingResponse( download_file_stream(url, file_path, file_name), @@ -108,7 +108,7 @@ async def download( @router.post("/upload") def upload(file: UploadFile = File(...)): - file_path = UPLOAD_DIR / file.filename + file_path = f"{UPLOAD_DIR}/{file.filename}" # Save file in chunks with file_path.open("wb+") as f: diff --git a/backend/config.py b/backend/config.py index 188dbbd7..59ef1947 100644 --- a/backend/config.py +++ b/backend/config.py @@ -15,6 +15,7 @@ from pathlib import Path try: from dotenv import load_dotenv, find_dotenv + load_dotenv(find_dotenv("../.env")) except ImportError: print("dotenv not installed, skipping...") @@ -24,12 +25,12 @@ except ImportError: # File Upload #################################### -DATA_DIR = Path(os.getenv("DATA_DIR", './data')).resolve() +DATA_DIR = Path(os.getenv("DATA_DIR", "./data")).resolve() -UPLOAD_DIR = DATA_DIR / "uploads" +UPLOAD_DIR = f"{DATA_DIR}/uploads" UPLOAD_DIR.mkdir(parents=True, exist_ok=True) -WEB_DIR = Path(os.getenv("WEB_DIR", "../build")) +FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", "../build")) #################################### # ENV (dev,test,prod) @@ -84,7 +85,7 @@ if WEBUI_AUTH and WEBUI_JWT_SECRET_KEY == "": # RAG #################################### -CHROMA_DATA_PATH = DATA_DIR / "vector_db" +CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db" EMBED_MODEL = "all-MiniLM-L6-v2" CHROMA_CLIENT = chromadb.PersistentClient( path=str(CHROMA_DATA_PATH), settings=Settings(allow_reset=True) diff --git a/backend/main.py b/backend/main.py index 4b734da8..a0ad73fd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,7 +14,7 @@ from apps.openai.main import app as openai_app from apps.web.main import app as webui_app from apps.rag.main import app as rag_app -from config import ENV, WEB_DIR +from config import ENV, FRONTEND_BUILD_DIR class SPAStaticFiles(StaticFiles): @@ -58,4 +58,8 @@ app.mount("/openai/api", openai_app) app.mount("/rag/api/v1", rag_app) -app.mount("/", SPAStaticFiles(directory=str(WEB_DIR), html=True), name="spa-static-files") +app.mount( + "/", + SPAStaticFiles(directory=str(FRONTEND_BUILD_DIR), html=True), + name="spa-static-files", +) From 8da06f5e743c33389c6d24748147fbd262f40733 Mon Sep 17 00:00:00 2001 From: lucasew Date: Tue, 23 Jan 2024 12:59:52 -0300 Subject: [PATCH 04/30] fixes after the refactor Signed-off-by: lucasew --- backend/apps/web/routers/utils.py | 4 ++-- backend/config.py | 9 +++++---- backend/main.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index 0bb3ea3c..9adf2801 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -111,12 +111,12 @@ def upload(file: UploadFile = File(...)): file_path = f"{UPLOAD_DIR}/{file.filename}" # Save file in chunks - with file_path.open("wb+") as f: + with open(file_path, "wb+") as f: for chunk in file.file: f.write(chunk) def file_process_stream(): - total_size = os.path.getsize(str(file_path)) + total_size = os.path.getsize(file_path) chunk_size = 1024 * 1024 try: with open(file_path, "rb") as f: diff --git a/backend/config.py b/backend/config.py index 59ef1947..169baa69 100644 --- a/backend/config.py +++ b/backend/config.py @@ -25,12 +25,13 @@ except ImportError: # File Upload #################################### -DATA_DIR = Path(os.getenv("DATA_DIR", "./data")).resolve() +DATA_DIR = str(Path(os.getenv("DATA_DIR", "./data")).resolve()) UPLOAD_DIR = f"{DATA_DIR}/uploads" -UPLOAD_DIR.mkdir(parents=True, exist_ok=True) -FRONTEND_BUILD_DIR = Path(os.getenv("FRONTEND_BUILD_DIR", "../build")) +Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True) + +FRONTEND_BUILD_DIR = str(Path(os.getenv("FRONTEND_BUILD_DIR", "../build"))) #################################### # ENV (dev,test,prod) @@ -88,7 +89,7 @@ if WEBUI_AUTH and WEBUI_JWT_SECRET_KEY == "": CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db" EMBED_MODEL = "all-MiniLM-L6-v2" CHROMA_CLIENT = chromadb.PersistentClient( - path=str(CHROMA_DATA_PATH), settings=Settings(allow_reset=True) + path=CHROMA_DATA_PATH, settings=Settings(allow_reset=True) ) CHUNK_SIZE = 1500 CHUNK_OVERLAP = 100 diff --git a/backend/main.py b/backend/main.py index a0ad73fd..f7a82b66 100644 --- a/backend/main.py +++ b/backend/main.py @@ -60,6 +60,6 @@ app.mount("/rag/api/v1", rag_app) app.mount( "/", - SPAStaticFiles(directory=str(FRONTEND_BUILD_DIR), html=True), + SPAStaticFiles(directory=FRONTEND_BUILD_DIR, html=True), name="spa-static-files", ) From 8bfda730d9dd99eae2147bcb8207efe09016d165 Mon Sep 17 00:00:00 2001 From: Marclass Date: Tue, 23 Jan 2024 14:03:22 -0700 Subject: [PATCH 05/30] add excel document support --- backend/apps/rag/main.py | 6 ++++++ backend/requirements.txt | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/apps/rag/main.py b/backend/apps/rag/main.py index 4ceae2a8..e6bb02a4 100644 --- a/backend/apps/rag/main.py +++ b/backend/apps/rag/main.py @@ -23,6 +23,7 @@ from langchain_community.document_loaders import ( UnstructuredMarkdownLoader, UnstructuredXMLLoader, UnstructuredRSTLoader, + UnstructuredExcelLoader, ) from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Chroma @@ -157,6 +158,9 @@ def store_doc( ] docx_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document" known_doc_ext=["doc","docx"] + excel_types=["application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] + known_excel_ext=["xls", "xlsx"] + file_ext=file.filename.split(".")[-1].lower() known_type=True @@ -179,6 +183,8 @@ def store_doc( loader = Docx2txtLoader(file_path) elif file_ext=="csv": loader = CSVLoader(file_path) + elif (file.content_type in excel_types or file_ext in known_excel_ext): + loader = UnstructuredExcelLoader(file_path) elif file_ext=="rst": loader = UnstructuredRSTLoader(file_path, mode="elements") elif file_ext in text_xml: diff --git a/backend/requirements.txt b/backend/requirements.txt index 76a20824..07ea0ea3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -28,4 +28,9 @@ markdown PyJWT pyjwt[crypto] -black \ No newline at end of file +black + +pandas +openpyxl +pyxlsb +xlrd \ No newline at end of file From 1e932d91cb527d00382362ee146007c8dff15ac5 Mon Sep 17 00:00:00 2001 From: Marclass Date: Tue, 23 Jan 2024 14:04:40 -0700 Subject: [PATCH 06/30] Update constants.ts add excel file ext --- src/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 5e736313..b00d6280 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -30,7 +30,7 @@ export const SUPPORTED_FILE_EXTENSIONS = [ 'pl', 'pm', 'r', 'dart', 'dockerfile', 'env', 'php', 'hs', 'hsc', 'lua', 'nginxconf', 'conf', 'm', 'mm', 'plsql', 'perl', 'rb', 'rs', 'db2', 'scala', 'bash', 'swift', 'vue', 'svelte', - 'doc','docx', 'pdf', 'csv', 'txt' + 'doc','docx', 'pdf', 'csv', 'txt', 'xls', 'xlsx' ]; // Source: https://kit.svelte.dev/docs/modules#$env-static-public From 55820b79917d725e120d4a57f4be43851f6b1143 Mon Sep 17 00:00:00 2001 From: Doug Winzell <142451442+Collected5353@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:08:49 -0800 Subject: [PATCH 07/30] Create SECURITY.md Hello Team, As the continued popularity of this project increases we should be taking our projects goals in mind with concerns to privacy and security. In the near future I would like to at least start testing the codebase with SAST(semgrep) and Synk.io (Software Composition Analysys) To highlight any potential weaknesses in the security of the product. I am happy to start contributing from the security side. --- SECURITY.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..6ceafb16 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy +Our primary goal is to ensure the protection and confidentiality of sensitive data stored by users on ollama-webui. +## Supported Versions + + +| Version | Supported | +| ------- | ------------------ | +| main | :white_check_mark: | +| others | :x: | + + +## Reporting a Vulnerability + +If you discover a security issue within our system, please notify us immediately via a pull request or contact us on discord. + +## Product Security +We regularly audit our internal processes and system's architecture for vulnerabilities using a combination of automated and manual testing techniques. + +We are planning on implementing SAST and SCA scans in our project soon. + From 1cbbfc7c66de6f12c190a24506f7d8225c66abca Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 24 Jan 2024 17:57:35 +0000 Subject: [PATCH 08/30] fix: use exec the uvicorn process to handle Docker's SIGTERM correctly Replace the shell with the uvicorn process, so it becomes PID 1 and receives the signals directly --- backend/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/start.sh b/backend/start.sh index e6d09364..09a791fc 100755 --- a/backend/start.sh +++ b/backend/start.sh @@ -4,4 +4,4 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "$SCRIPT_DIR" || exit PORT="${PORT:-8080}" -uvicorn main:app --host 0.0.0.0 --port "$PORT" --forwarded-allow-ips '*' +exec uvicorn main:app --host 0.0.0.0 --port "$PORT" --forwarded-allow-ips '*' From b297b432f7322350b26f7fa55b7308079cbad2f6 Mon Sep 17 00:00:00 2001 From: Xiaodong Ye Date: Thu, 25 Jan 2024 15:07:00 +0800 Subject: [PATCH 09/30] Add default voice setting and apply to chat --- .../chat/Messages/ResponseMessage.svelte | 3 + src/lib/components/chat/SettingsModal.svelte | 77 ++++++++++++++++++- src/lib/stores/index.ts | 1 + 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index ef88207f..915928b0 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -1,6 +1,7 @@ -{#if filteredDocs.length > 0} +{#if filteredDocs.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
@@ -55,6 +66,7 @@ : ''}" type="button" on:click={() => { + console.log(doc); confirmSelect(doc); }} on:mousemove={() => { @@ -71,6 +83,25 @@
{/each} + + {#if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')} + + {/if}
diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index b12bdedd..16bf1cd5 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -212,8 +212,12 @@ const convertOpenAIMessages = (convo) => { const message = mapping[message_id]; currentId = message_id; try { - if (messages.length == 0 && (message['message'] == null || - (message['message']['content']['parts']?.[0] == '' && message['message']['content']['text'] == null))) { + if ( + messages.length == 0 && + (message['message'] == null || + (message['message']['content']['parts']?.[0] == '' && + message['message']['content']['text'] == null)) + ) { // Skip chat messages with no content continue; } else { @@ -222,7 +226,10 @@ const convertOpenAIMessages = (convo) => { parentId: lastId, childrenIds: message['children'] || [], role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user', - content: message['message']?.['content']?.['parts']?.[0] || message['message']?.['content']?.['text'] || '', + content: + message['message']?.['content']?.['parts']?.[0] || + message['message']?.['content']?.['text'] || + '', model: 'gpt-3.5-turbo', done: true, context: null @@ -231,7 +238,7 @@ const convertOpenAIMessages = (convo) => { lastId = currentId; } } catch (error) { - console.log("Error with", message, "\nError:", error); + console.log('Error with', message, '\nError:', error); } } @@ -256,31 +263,31 @@ const validateChat = (chat) => { // Because ChatGPT sometimes has features we can't use like DALL-E or migh have corrupted messages, need to validate const messages = chat.messages; - // Check if messages array is empty - if (messages.length === 0) { - return false; - } + // Check if messages array is empty + if (messages.length === 0) { + return false; + } - // Last message's children should be an empty array - const lastMessage = messages[messages.length - 1]; - if (lastMessage.childrenIds.length !== 0) { - return false; - } + // Last message's children should be an empty array + const lastMessage = messages[messages.length - 1]; + if (lastMessage.childrenIds.length !== 0) { + return false; + } - // First message's parent should be null - const firstMessage = messages[0]; - if (firstMessage.parentId !== null) { - return false; - } + // First message's parent should be null + const firstMessage = messages[0]; + if (firstMessage.parentId !== null) { + return false; + } - // Every message's content should be a string - for (let message of messages) { - if (typeof message.content !== 'string') { - return false; - } - } + // Every message's content should be a string + for (let message of messages) { + if (typeof message.content !== 'string') { + return false; + } + } - return true; + return true; }; export const convertOpenAIChats = (_chats) => { @@ -298,8 +305,22 @@ export const convertOpenAIChats = (_chats) => { chat: chat, timestamp: convo['timestamp'] }); - } else { failed ++} + } else { + failed++; + } } - console.log(failed, "Conversations could not be imported"); + console.log(failed, 'Conversations could not be imported'); return chats; }; + +export const isValidHttpUrl = (string) => { + let url; + + try { + url = new URL(string); + } catch (_) { + return false; + } + + return url.protocol === 'http:' || url.protocol === 'https:'; +}; From 2f1f2b12b4a1dc75fe286afee1b0a8f3f6b67bef Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 26 Jan 2024 22:22:37 -0800 Subject: [PATCH 29/30] feat: invalid url error handling --- src/lib/components/chat/MessageInput/Documents.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/components/chat/MessageInput/Documents.svelte b/src/lib/components/chat/MessageInput/Documents.svelte index 8f4e46c0..5f252b3d 100644 --- a/src/lib/components/chat/MessageInput/Documents.svelte +++ b/src/lib/components/chat/MessageInput/Documents.svelte @@ -4,6 +4,7 @@ import { documents } from '$lib/stores'; import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils'; import { tick } from 'svelte'; + import toast from 'svelte-french-toast'; export let prompt = ''; @@ -92,6 +93,10 @@ const url = prompt.split(' ')?.at(0)?.substring(1); if (isValidHttpUrl(url)) { confirmSelectWeb(url); + } else { + toast.error( + 'Oops! Looks like the URL is invalid. Please double-check and try again.' + ); } }} > From 685d37418f741606c5ab9e8d664d06c6ada5d15a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 29 Jan 2024 15:38:56 -0800 Subject: [PATCH 30/30] fix: run-ollama-docker.sh --- run-ollama-docker.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/run-ollama-docker.sh b/run-ollama-docker.sh index c8ce166a..c2a025be 100644 --- a/run-ollama-docker.sh +++ b/run-ollama-docker.sh @@ -10,10 +10,10 @@ docker pull ollama/ollama:latest docker_args="-d -v ollama:/root/.ollama -p $host_port:$container_port --name ollama ollama/ollama" -if [ "$use_gpu" == "y" ]; then - docker_args+=" --gpus=all" +if [ "$use_gpu" = "y" ]; then + docker_args="--gpus=all $docker_args" fi -docker run "$docker_args" +docker run $docker_args -docker image prune -f +docker image prune -f \ No newline at end of file