Quellcode durchsuchen

Merge branch 'dev' into feat/add-i18n

Ased Mammad vor 1 Jahr
Ursprung
Commit
b8902072fd

+ 5 - 5
backend/apps/images/main.py

@@ -250,7 +250,7 @@ class GenerateImageForm(BaseModel):
     model: Optional[str] = None
     model: Optional[str] = None
     prompt: str
     prompt: str
     n: int = 1
     n: int = 1
-    size: str = "512x512"
+    size: Optional[str] = None
     negative_prompt: Optional[str] = None
     negative_prompt: Optional[str] = None
 
 
 
 
@@ -278,8 +278,7 @@ def generate_image(
     user=Depends(get_current_user),
     user=Depends(get_current_user),
 ):
 ):
 
 
-    print(form_data)
-
+    r = None
     try:
     try:
         if app.state.ENGINE == "openai":
         if app.state.ENGINE == "openai":
 
 
@@ -291,10 +290,9 @@ def generate_image(
                 "model": app.state.MODEL if app.state.MODEL != "" else "dall-e-2",
                 "model": app.state.MODEL if app.state.MODEL != "" else "dall-e-2",
                 "prompt": form_data.prompt,
                 "prompt": form_data.prompt,
                 "n": form_data.n,
                 "n": form_data.n,
-                "size": form_data.size,
+                "size": form_data.size if form_data.size else app.state.IMAGE_SIZE,
                 "response_format": "b64_json",
                 "response_format": "b64_json",
             }
             }
-
             r = requests.post(
             r = requests.post(
                 url=f"https://api.openai.com/v1/images/generations",
                 url=f"https://api.openai.com/v1/images/generations",
                 json=data,
                 json=data,
@@ -359,4 +357,6 @@ def generate_image(
 
 
     except Exception as e:
     except Exception as e:
         print(e)
         print(e)
+        if r:
+            print(r.json())
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
         raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))

+ 17 - 3
backend/apps/ollama/main.py

@@ -15,7 +15,7 @@ import asyncio
 from apps.web.models.users import Users
 from apps.web.models.users import Users
 from constants import ERROR_MESSAGES
 from constants import ERROR_MESSAGES
 from utils.utils import decode_token, get_current_user, get_admin_user
 from utils.utils import decode_token, get_current_user, get_admin_user
-from config import OLLAMA_BASE_URLS
+from config import OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST
 
 
 from typing import Optional, List, Union
 from typing import Optional, List, Union
 
 
@@ -29,6 +29,10 @@ app.add_middleware(
     allow_headers=["*"],
     allow_headers=["*"],
 )
 )
 
 
+
+app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
+app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
 app.state.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
 app.state.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
 app.state.MODELS = {}
 app.state.MODELS = {}
 
 
@@ -129,9 +133,19 @@ async def get_all_models():
 async def get_ollama_tags(
 async def get_ollama_tags(
     url_idx: Optional[int] = None, user=Depends(get_current_user)
     url_idx: Optional[int] = None, user=Depends(get_current_user)
 ):
 ):
-
     if url_idx == None:
     if url_idx == None:
-        return await get_all_models()
+        models = await get_all_models()
+
+        if app.state.MODEL_FILTER_ENABLED:
+            if user.role == "user":
+                models["models"] = list(
+                    filter(
+                        lambda model: model["name"] in app.state.MODEL_FILTER_LIST,
+                        models["models"],
+                    )
+                )
+                return models
+        return models
     else:
     else:
         url = app.state.OLLAMA_BASE_URLS[url_idx]
         url = app.state.OLLAMA_BASE_URLS[url_idx]
         try:
         try:

+ 22 - 4
backend/apps/openai/main.py

@@ -18,7 +18,13 @@ from utils.utils import (
     get_verified_user,
     get_verified_user,
     get_admin_user,
     get_admin_user,
 )
 )
-from config import OPENAI_API_BASE_URLS, OPENAI_API_KEYS, CACHE_DIR
+from config import (
+    OPENAI_API_BASE_URLS,
+    OPENAI_API_KEYS,
+    CACHE_DIR,
+    MODEL_FILTER_ENABLED,
+    MODEL_FILTER_LIST,
+)
 from typing import List, Optional
 from typing import List, Optional
 
 
 
 
@@ -34,6 +40,9 @@ app.add_middleware(
     allow_headers=["*"],
     allow_headers=["*"],
 )
 )
 
 
+app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
+app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
 app.state.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
 app.state.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
 app.state.OPENAI_API_KEYS = OPENAI_API_KEYS
 app.state.OPENAI_API_KEYS = OPENAI_API_KEYS
 
 
@@ -186,12 +195,21 @@ async def get_all_models():
     return models
     return models
 
 
 
 
-# , user=Depends(get_current_user)
 @app.get("/models")
 @app.get("/models")
 @app.get("/models/{url_idx}")
 @app.get("/models/{url_idx}")
-async def get_models(url_idx: Optional[int] = None):
+async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_user)):
     if url_idx == None:
     if url_idx == None:
-        return await get_all_models()
+        models = await get_all_models()
+        if app.state.MODEL_FILTER_ENABLED:
+            if user.role == "user":
+                models["data"] = list(
+                    filter(
+                        lambda model: model["id"] in app.state.MODEL_FILTER_LIST,
+                        models["data"],
+                    )
+                )
+                return models
+        return models
     else:
     else:
         url = app.state.OPENAI_API_BASE_URLS[url_idx]
         url = app.state.OPENAI_API_BASE_URLS[url_idx]
         try:
         try:

+ 14 - 75
backend/apps/rag/main.py

@@ -44,6 +44,8 @@ from apps.web.models.documents import (
     DocumentResponse,
     DocumentResponse,
 )
 )
 
 
+from apps.rag.utils import query_doc, query_collection
+
 from utils.misc import (
 from utils.misc import (
     calculate_sha256,
     calculate_sha256,
     calculate_sha256_string,
     calculate_sha256_string,
@@ -248,21 +250,18 @@ class QueryDocForm(BaseModel):
 
 
 
 
 @app.post("/query/doc")
 @app.post("/query/doc")
-def query_doc(
+def query_doc_handler(
     form_data: QueryDocForm,
     form_data: QueryDocForm,
     user=Depends(get_current_user),
     user=Depends(get_current_user),
 ):
 ):
+
     try:
     try:
-        # if you use docker use the model from the environment variable
-        collection = CHROMA_CLIENT.get_collection(
-            name=form_data.collection_name,
+        return query_doc(
+            collection_name=form_data.collection_name,
+            query=form_data.query,
+            k=form_data.k if form_data.k else app.state.TOP_K,
             embedding_function=app.state.sentence_transformer_ef,
             embedding_function=app.state.sentence_transformer_ef,
         )
         )
-        result = collection.query(
-            query_texts=[form_data.query],
-            n_results=form_data.k if form_data.k else app.state.TOP_K,
-        )
-        return result
     except Exception as e:
     except Exception as e:
         print(e)
         print(e)
         raise HTTPException(
         raise HTTPException(
@@ -277,76 +276,16 @@ class QueryCollectionsForm(BaseModel):
     k: Optional[int] = None
     k: Optional[int] = None
 
 
 
 
-def merge_and_sort_query_results(query_results, k):
-    # Initialize lists to store combined data
-    combined_ids = []
-    combined_distances = []
-    combined_metadatas = []
-    combined_documents = []
-
-    # Combine data from each dictionary
-    for data in query_results:
-        combined_ids.extend(data["ids"][0])
-        combined_distances.extend(data["distances"][0])
-        combined_metadatas.extend(data["metadatas"][0])
-        combined_documents.extend(data["documents"][0])
-
-    # Create a list of tuples (distance, id, metadata, document)
-    combined = list(
-        zip(combined_distances, combined_ids, combined_metadatas, combined_documents)
-    )
-
-    # Sort the list based on distances
-    combined.sort(key=lambda x: x[0])
-
-    # Unzip the sorted list
-    sorted_distances, sorted_ids, sorted_metadatas, sorted_documents = zip(*combined)
-
-    # Slicing the lists to include only k elements
-    sorted_distances = list(sorted_distances)[:k]
-    sorted_ids = list(sorted_ids)[:k]
-    sorted_metadatas = list(sorted_metadatas)[:k]
-    sorted_documents = list(sorted_documents)[:k]
-
-    # Create the output dictionary
-    merged_query_results = {
-        "ids": [sorted_ids],
-        "distances": [sorted_distances],
-        "metadatas": [sorted_metadatas],
-        "documents": [sorted_documents],
-        "embeddings": None,
-        "uris": None,
-        "data": None,
-    }
-
-    return merged_query_results
-
-
 @app.post("/query/collection")
 @app.post("/query/collection")
-def query_collection(
+def query_collection_handler(
     form_data: QueryCollectionsForm,
     form_data: QueryCollectionsForm,
     user=Depends(get_current_user),
     user=Depends(get_current_user),
 ):
 ):
-    results = []
-
-    for collection_name in form_data.collection_names:
-        try:
-            # if you use docker use the model from the environment variable
-            collection = CHROMA_CLIENT.get_collection(
-                name=collection_name,
-                embedding_function=app.state.sentence_transformer_ef,
-            )
-
-            result = collection.query(
-                query_texts=[form_data.query],
-                n_results=form_data.k if form_data.k else app.state.TOP_K,
-            )
-            results.append(result)
-        except:
-            pass
-
-    return merge_and_sort_query_results(
-        results, form_data.k if form_data.k else app.state.TOP_K
+    return query_collection(
+        collection_names=form_data.collection_names,
+        query=form_data.query,
+        k=form_data.k if form_data.k else app.state.TOP_K,
+        embedding_function=app.state.sentence_transformer_ef,
     )
     )
 
 
 
 

+ 97 - 0
backend/apps/rag/utils.py

@@ -0,0 +1,97 @@
+import re
+from typing import List
+
+from config import CHROMA_CLIENT
+
+
+def query_doc(collection_name: str, query: str, k: int, embedding_function):
+    try:
+        # if you use docker use the model from the environment variable
+        collection = CHROMA_CLIENT.get_collection(
+            name=collection_name,
+            embedding_function=embedding_function,
+        )
+        result = collection.query(
+            query_texts=[query],
+            n_results=k,
+        )
+        return result
+    except Exception as e:
+        raise e
+
+
+def merge_and_sort_query_results(query_results, k):
+    # Initialize lists to store combined data
+    combined_ids = []
+    combined_distances = []
+    combined_metadatas = []
+    combined_documents = []
+
+    # Combine data from each dictionary
+    for data in query_results:
+        combined_ids.extend(data["ids"][0])
+        combined_distances.extend(data["distances"][0])
+        combined_metadatas.extend(data["metadatas"][0])
+        combined_documents.extend(data["documents"][0])
+
+    # Create a list of tuples (distance, id, metadata, document)
+    combined = list(
+        zip(combined_distances, combined_ids, combined_metadatas, combined_documents)
+    )
+
+    # Sort the list based on distances
+    combined.sort(key=lambda x: x[0])
+
+    # Unzip the sorted list
+    sorted_distances, sorted_ids, sorted_metadatas, sorted_documents = zip(*combined)
+
+    # Slicing the lists to include only k elements
+    sorted_distances = list(sorted_distances)[:k]
+    sorted_ids = list(sorted_ids)[:k]
+    sorted_metadatas = list(sorted_metadatas)[:k]
+    sorted_documents = list(sorted_documents)[:k]
+
+    # Create the output dictionary
+    merged_query_results = {
+        "ids": [sorted_ids],
+        "distances": [sorted_distances],
+        "metadatas": [sorted_metadatas],
+        "documents": [sorted_documents],
+        "embeddings": None,
+        "uris": None,
+        "data": None,
+    }
+
+    return merged_query_results
+
+
+def query_collection(
+    collection_names: List[str], query: str, k: int, embedding_function
+):
+
+    results = []
+
+    for collection_name in collection_names:
+        try:
+            # if you use docker use the model from the environment variable
+            collection = CHROMA_CLIENT.get_collection(
+                name=collection_name,
+                embedding_function=embedding_function,
+            )
+
+            result = collection.query(
+                query_texts=[query],
+                n_results=k,
+            )
+            results.append(result)
+        except:
+            pass
+
+    return merge_and_sort_query_results(results, k)
+
+
+def rag_template(template: str, context: str, query: str):
+    template = re.sub(r"\[context\]", context, template)
+    template = re.sub(r"\[query\]", query, template)
+
+    return template

+ 6 - 1
backend/config.py

@@ -251,7 +251,7 @@ OPENAI_API_BASE_URLS = (
     OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
     OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
 )
 )
 
 
-OPENAI_API_BASE_URLS = [url.strip() for url in OPENAI_API_BASE_URL.split(";")]
+OPENAI_API_BASE_URLS = [url.strip() for url in OPENAI_API_BASE_URLS.split(";")]
 
 
 
 
 ####################################
 ####################################
@@ -292,6 +292,11 @@ DEFAULT_USER_ROLE = os.getenv("DEFAULT_USER_ROLE", "pending")
 USER_PERMISSIONS = {"chat": {"deletion": True}}
 USER_PERMISSIONS = {"chat": {"deletion": True}}
 
 
 
 
+MODEL_FILTER_ENABLED = os.environ.get("MODEL_FILTER_ENABLED", False)
+MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
+MODEL_FILTER_LIST = [model.strip() for model in MODEL_FILTER_LIST.split(";")]
+
+
 ####################################
 ####################################
 # WEBUI_VERSION
 # WEBUI_VERSION
 ####################################
 ####################################

+ 172 - 1
backend/main.py

@@ -12,6 +12,7 @@ from fastapi import HTTPException
 from fastapi.middleware.wsgi import WSGIMiddleware
 from fastapi.middleware.wsgi import WSGIMiddleware
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.middleware.cors import CORSMiddleware
 from starlette.exceptions import HTTPException as StarletteHTTPException
 from starlette.exceptions import HTTPException as StarletteHTTPException
+from starlette.middleware.base import BaseHTTPMiddleware
 
 
 
 
 from apps.ollama.main import app as ollama_app
 from apps.ollama.main import app as ollama_app
@@ -22,8 +23,22 @@ from apps.images.main import app as images_app
 from apps.rag.main import app as rag_app
 from apps.rag.main import app as rag_app
 from apps.web.main import app as webui_app
 from apps.web.main import app as webui_app
 
 
+from pydantic import BaseModel
+from typing import List
 
 
-from config import WEBUI_NAME, ENV, VERSION, CHANGELOG, FRONTEND_BUILD_DIR
+
+from utils.utils import get_admin_user
+from apps.rag.utils import query_doc, query_collection, rag_template
+
+from config import (
+    WEBUI_NAME,
+    ENV,
+    VERSION,
+    CHANGELOG,
+    FRONTEND_BUILD_DIR,
+    MODEL_FILTER_ENABLED,
+    MODEL_FILTER_LIST,
+)
 from constants import ERROR_MESSAGES
 from constants import ERROR_MESSAGES
 
 
 
 
@@ -40,6 +55,9 @@ class SPAStaticFiles(StaticFiles):
 
 
 app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
 app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
 
 
+app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
+app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
+
 origins = ["*"]
 origins = ["*"]
 
 
 app.add_middleware(
 app.add_middleware(
@@ -56,6 +74,126 @@ async def on_startup():
     await litellm_app_startup()
     await litellm_app_startup()
 
 
 
 
+class RAGMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        if request.method == "POST" and (
+            "/api/chat" in request.url.path or "/chat/completions" in request.url.path
+        ):
+            print(request.url.path)
+
+            # Read the original request body
+            body = await request.body()
+            # Decode body to string
+            body_str = body.decode("utf-8")
+            # Parse string to JSON
+            data = json.loads(body_str) if body_str else {}
+
+            # Example: Add a new key-value pair or modify existing ones
+            # data["modified"] = True  # Example modification
+            if "docs" in data:
+                docs = data["docs"]
+                print(docs)
+
+                last_user_message_idx = None
+                for i in range(len(data["messages"]) - 1, -1, -1):
+                    if data["messages"][i]["role"] == "user":
+                        last_user_message_idx = i
+                        break
+
+                user_message = data["messages"][last_user_message_idx]
+
+                if isinstance(user_message["content"], list):
+                    # Handle list content input
+                    content_type = "list"
+                    query = ""
+                    for content_item in user_message["content"]:
+                        if content_item["type"] == "text":
+                            query = content_item["text"]
+                            break
+                elif isinstance(user_message["content"], str):
+                    # Handle text content input
+                    content_type = "text"
+                    query = user_message["content"]
+                else:
+                    # Fallback in case the input does not match expected types
+                    content_type = None
+                    query = ""
+
+                relevant_contexts = []
+
+                for doc in docs:
+                    context = None
+
+                    try:
+                        if doc["type"] == "collection":
+                            context = query_collection(
+                                collection_names=doc["collection_names"],
+                                query=query,
+                                k=rag_app.state.TOP_K,
+                                embedding_function=rag_app.state.sentence_transformer_ef,
+                            )
+                        else:
+                            context = query_doc(
+                                collection_name=doc["collection_name"],
+                                query=query,
+                                k=rag_app.state.TOP_K,
+                                embedding_function=rag_app.state.sentence_transformer_ef,
+                            )
+                    except Exception as e:
+                        print(e)
+                        context = None
+
+                    relevant_contexts.append(context)
+
+                context_string = ""
+                for context in relevant_contexts:
+                    if context:
+                        context_string += " ".join(context["documents"][0]) + "\n"
+
+                ra_content = rag_template(
+                    template=rag_app.state.RAG_TEMPLATE,
+                    context=context_string,
+                    query=query,
+                )
+
+                if content_type == "list":
+                    new_content = []
+                    for content_item in user_message["content"]:
+                        if content_item["type"] == "text":
+                            # Update the text item's content with ra_content
+                            new_content.append({"type": "text", "text": ra_content})
+                        else:
+                            # Keep other types of content as they are
+                            new_content.append(content_item)
+                    new_user_message = {**user_message, "content": new_content}
+                else:
+                    new_user_message = {
+                        **user_message,
+                        "content": ra_content,
+                    }
+
+                data["messages"][last_user_message_idx] = new_user_message
+                del data["docs"]
+
+                print(data["messages"])
+
+            modified_body_bytes = json.dumps(data).encode("utf-8")
+
+            # Create a new request with the modified body
+            scope = request.scope
+            scope["body"] = modified_body_bytes
+            request = Request(scope, receive=lambda: self._receive(modified_body_bytes))
+
+        response = await call_next(request)
+        return response
+
+    async def _receive(self, body: bytes):
+        return {"type": "http.request", "body": body, "more_body": False}
+
+
+app.add_middleware(RAGMiddleware)
+
+
 @app.middleware("http")
 @app.middleware("http")
 async def check_url(request: Request, call_next):
 async def check_url(request: Request, call_next):
     start_time = int(time.time())
     start_time = int(time.time())
@@ -90,6 +228,39 @@ async def get_app_config():
     }
     }
 
 
 
 
+@app.get("/api/config/model/filter")
+async def get_model_filter_config(user=Depends(get_admin_user)):
+    return {
+        "enabled": app.state.MODEL_FILTER_ENABLED,
+        "models": app.state.MODEL_FILTER_LIST,
+    }
+
+
+class ModelFilterConfigForm(BaseModel):
+    enabled: bool
+    models: List[str]
+
+
+@app.post("/api/config/model/filter")
+async def get_model_filter_config(
+    form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
+):
+
+    app.state.MODEL_FILTER_ENABLED = form_data.enabled
+    app.state.MODEL_FILTER_LIST = form_data.models
+
+    ollama_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED
+    ollama_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
+
+    openai_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED
+    openai_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
+
+    return {
+        "enabled": app.state.MODEL_FILTER_ENABLED,
+        "models": app.state.MODEL_FILTER_LIST,
+    }
+
+
 @app.get("/api/version")
 @app.get("/api/version")
 async def get_app_config():
 async def get_app_config():
 
 

+ 2 - 1
backend/requirements.txt

@@ -16,7 +16,8 @@ aiohttp
 peewee
 peewee
 bcrypt
 bcrypt
 
 
-litellm
+litellm==1.30.7
+argon2-cffi
 apscheduler
 apscheduler
 google-generativeai
 google-generativeai
 
 

+ 62 - 0
src/lib/apis/index.ts

@@ -77,3 +77,65 @@ export const getVersionUpdates = async () => {
 
 
 	return res;
 	return res;
 };
 };
+
+export const getModelFilterConfig = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/model/filter`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateModelFilterConfig = async (
+	token: string,
+	enabled: boolean,
+	models: string[]
+) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/config/model/filter`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			enabled: enabled,
+			models: models
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 1 - 1
src/lib/apis/rag/index.ts

@@ -252,7 +252,7 @@ export const queryCollection = async (
 	token: string,
 	token: string,
 	collection_names: string,
 	collection_names: string,
 	query: string,
 	query: string,
-	k: number
+	k: number | null = null
 ) => {
 ) => {
 	let error = null;
 	let error = null;
 
 

+ 114 - 0
src/lib/components/admin/Settings/Users.svelte

@@ -1,12 +1,17 @@
 <script lang="ts">
 <script lang="ts">
+	import { getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
 	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
+
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
+	import { models } from '$lib/stores';
 
 
 	const i18n = getContext('i18n');
 	const i18n = getContext('i18n');
 
 
 	export let saveHandler: Function;
 	export let saveHandler: Function;
 
 
+	let whitelistEnabled = false;
+	let whitelistModels = [''];
 	let permissions = {
 	let permissions = {
 		chat: {
 		chat: {
 			deletion: true
 			deletion: true
@@ -15,6 +20,13 @@
 
 
 	onMount(async () => {
 	onMount(async () => {
 		permissions = await getUserPermissions(localStorage.token);
 		permissions = await getUserPermissions(localStorage.token);
+
+		const res = await getModelFilterConfig(localStorage.token);
+		if (res) {
+			whitelistEnabled = res.enabled;
+
+			whitelistModels = res.models.length > 0 ? res.models : [''];
+		}
 	});
 	});
 </script>
 </script>
 
 
@@ -23,6 +35,8 @@
 	on:submit|preventDefault={async () => {
 	on:submit|preventDefault={async () => {
 		// console.log('submit');
 		// console.log('submit');
 		await updateUserPermissions(localStorage.token, permissions);
 		await updateUserPermissions(localStorage.token, permissions);
+
+		await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
 		saveHandler();
 		saveHandler();
 	}}
 	}}
 >
 >
@@ -71,6 +85,106 @@
 				</button>
 				</button>
 			</div>
 			</div>
 		</div>
 		</div>
+
+		<hr class=" dark:border-gray-700 my-2" />
+
+		<div class="mt-2 space-y-3 pr-1.5">
+			<div>
+				<div class="mb-2">
+					<div class="flex justify-between items-center text-xs">
+						<div class=" text-sm font-medium">Manage Models</div>
+					</div>
+				</div>
+
+				<div class=" space-y-3">
+					<div>
+						<div class="flex justify-between items-center text-xs">
+							<div class=" text-xs font-medium">Model Whitelisting</div>
+
+							<button
+								class=" text-xs font-medium text-gray-500"
+								type="button"
+								on:click={() => {
+									whitelistEnabled = !whitelistEnabled;
+								}}>{whitelistEnabled ? 'On' : 'Off'}</button
+							>
+						</div>
+					</div>
+
+					{#if whitelistEnabled}
+						<div>
+							<div class=" space-y-1.5">
+								{#each whitelistModels as modelId, modelIdx}
+									<div class="flex w-full">
+										<div class="flex-1 mr-2">
+											<select
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												bind:value={modelId}
+												placeholder="Select a model"
+											>
+												<option value="" disabled selected>Select a model</option>
+												{#each $models.filter((model) => model.id) as model}
+													<option value={model.id} class="bg-gray-100 dark:bg-gray-700"
+														>{model.name}</option
+													>
+												{/each}
+											</select>
+										</div>
+
+										{#if modelIdx === 0}
+											<button
+												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
+												type="button"
+												on:click={() => {
+													if (whitelistModels.at(-1) !== '') {
+														whitelistModels = [...whitelistModels, ''];
+													}
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+													/>
+												</svg>
+											</button>
+										{:else}
+											<button
+												class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
+												type="button"
+												on:click={() => {
+													whitelistModels.splice(modelIdx, 1);
+													whitelistModels = whitelistModels;
+												}}
+											>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													viewBox="0 0 16 16"
+													fill="currentColor"
+													class="w-4 h-4"
+												>
+													<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
+												</svg>
+											</button>
+										{/if}
+									</div>
+								{/each}
+							</div>
+
+							<div class="flex justify-end items-center text-xs mt-1.5 text-right">
+								<div class=" text-xs font-medium">
+									{whitelistModels.length} Model(s) Whitelisted
+								</div>
+							</div>
+						</div>
+					{/if}
+				</div>
+			</div>
+		</div>
 	</div>
 	</div>
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">

+ 2 - 2
src/lib/components/chat/MessageInput.svelte

@@ -364,12 +364,12 @@
 
 
 {#if dragged}
 {#if dragged}
 	<div
 	<div
-		class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
+		class="fixed lg:w-[calc(100%-260px)] w-full h-full flex z-50 touch-none pointer-events-none"
 		id="dropzone"
 		id="dropzone"
 		role="region"
 		role="region"
 		aria-label="Drag and Drop Container"
 		aria-label="Drag and Drop Container"
 	>
 	>
-		<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
+		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
 			<div class="m-auto pt-64 flex flex-col justify-center">
 			<div class="m-auto pt-64 flex flex-col justify-center">
 				<div class="max-w-md">
 				<div class="max-w-md">
 					<AddFilesPlaceholder />
 					<AddFilesPlaceholder />

+ 4 - 2
src/lib/components/chat/Settings/Account.svelte

@@ -111,7 +111,9 @@
 					<button
 					<button
 						class="relative rounded-full dark:bg-gray-700"
 						class="relative rounded-full dark:bg-gray-700"
 						type="button"
 						type="button"
-						on:click={profileImageInputElement.click}
+						on:click={() => {
+							profileImageInputElement.click();
+						}}
 					>
 					>
 						<img
 						<img
 							src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
 							src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
@@ -271,7 +273,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			on:click={async () => {
 			on:click={async () => {
 				const res = await submitHandler();
 				const res = await submitHandler();
 
 

+ 1 - 1
src/lib/components/chat/Settings/Audio.svelte

@@ -259,7 +259,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			type="submit"
 			type="submit"
 		>
 		>
 			{$i18n.t('Save')}
 			{$i18n.t('Save')}

+ 3 - 1
src/lib/components/chat/Settings/Chats.svelte

@@ -172,7 +172,9 @@
 			/>
 			/>
 			<button
 			<button
 				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
 				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
-				on:click={chatImportInputElement.click}
+				on:click={() => {
+					chatImportInputElement.click();
+				}}
 			>
 			>
 				<div class=" self-center mr-3">
 				<div class=" self-center mr-3">
 					<svg
 					<svg

+ 1 - 1
src/lib/components/chat/Settings/Connections.svelte

@@ -249,7 +249,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			type="submit"
 			type="submit"
 		>
 		>
 			{$i18n.t('Save')}
 			{$i18n.t('Save')}

+ 1 - 1
src/lib/components/chat/Settings/General.svelte

@@ -272,7 +272,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			on:click={() => {
 			on:click={() => {
 				saveSettings({
 				saveSettings({
 					system: system !== '' ? system : undefined,
 					system: system !== '' ? system : undefined,

+ 1 - 1
src/lib/components/chat/Settings/Images.svelte

@@ -301,7 +301,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded flex flex-row space-x-1 items-center {loading
+			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
 				? ' cursor-not-allowed'
 				? ' cursor-not-allowed'
 				: ''}"
 				: ''}"
 			type="submit"
 			type="submit"

+ 6 - 27
src/lib/components/chat/Settings/Interface.svelte

@@ -65,6 +65,7 @@
 		}
 		}
 
 
 		saveSettings({
 		saveSettings({
+			titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
 			titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
 			titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
 		});
 		});
 	};
 	};
@@ -192,7 +193,7 @@
 			<div class="flex w-full">
 			<div class="flex w-full">
 				<div class="flex-1 mr-2">
 				<div class="flex-1 mr-2">
 					<select
 					<select
-						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 						bind:value={titleAutoGenerateModel}
 						bind:value={titleAutoGenerateModel}
 						placeholder={$i18n.t('Select a model')}
 						placeholder={$i18n.t('Select a model')}
 					>
 					>
@@ -206,35 +207,13 @@
 						{/each}
 						{/each}
 					</select>
 					</select>
 				</div>
 				</div>
-				<button
-					class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
-					on:click={() => {
-						saveSettings({
-							titleAutoGenerateModel:
-								titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined
-						});
-					}}
-					type="button"
-				>
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 16 16"
-						fill="currentColor"
-						class="w-3.5 h-3.5"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
-							clip-rule="evenodd"
-						/>
-					</svg>
-				</button>
 			</div>
 			</div>
-			<div class="mt-3">
+
+			<div class="mt-3 mr-2">
 				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
 				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
 				<textarea
 				<textarea
 					bind:value={titleGenerationPrompt}
 					bind:value={titleGenerationPrompt}
-					class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
+					class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
 					rows="3"
 					rows="3"
 				/>
 				/>
 			</div>
 			</div>
@@ -329,7 +308,7 @@
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">
 		<button
 		<button
-			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
 			type="submit"
 			type="submit"
 		>
 		>
 			{$i18n.t('Save')}
 			{$i18n.t('Save')}

+ 151 - 220
src/lib/components/chat/Settings/Models.svelte

@@ -613,7 +613,9 @@
 											<button
 											<button
 												type="button"
 												type="button"
 												class="w-full rounded-lg text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
 												class="w-full rounded-lg text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
-												on:click={modelUploadInputElement.click}
+												on:click={() => {
+													modelUploadInputElement.click();
+												}}
 											>
 											>
 												{#if modelInputFile && modelInputFile.length > 0}
 												{#if modelInputFile && modelInputFile.length > 0}
 													{modelInputFile[0].name}
 													{modelInputFile[0].name}
@@ -737,264 +739,193 @@
 		<div class=" space-y-3">
 		<div class=" space-y-3">
 			<div class="mt-2 space-y-3 pr-1.5">
 			<div class="mt-2 space-y-3 pr-1.5">
 				<div>
 				<div>
-					<div class=" mb-2 text-sm font-medium">{$i18n.t('Manage LiteLLM Models')}</div>
-
-					<div>
+					<div class="mb-2">
 						<div class="flex justify-between items-center text-xs">
 						<div class="flex justify-between items-center text-xs">
-							<div class=" text-sm font-medium">{$i18n.t('Add a model')}</div>
+							<div class=" text-sm font-medium">{$i18n.t('Manage LiteLLM Models')}</div>
 							<button
 							<button
 								class=" text-xs font-medium text-gray-500"
 								class=" text-xs font-medium text-gray-500"
 								type="button"
 								type="button"
 								on:click={() => {
 								on:click={() => {
-									showLiteLLMParams = !showLiteLLMParams;
-								}}
-								>{showLiteLLMParams
-									? $i18n.t('Hide Additional Params')
-									: $i18n.t('Show Additional Params')}</button
+									showLiteLLM = !showLiteLLM;
+								}}>{showLiteLLM ? $i18n.t('Hide') : $i18n.t('Show')}</button
 							>
 							>
 						</div>
 						</div>
 					</div>
 					</div>
 
 
-					<div class="my-2 space-y-2">
-						<div class="flex w-full mb-1.5">
-							<div class="flex-1 mr-2">
-								<input
-									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-									placeholder="Enter LiteLLM Model (litellm_params.model)"
-									bind:value={liteLLMModel}
-									autocomplete="off"
-								/>
+					{#if showLiteLLM}
+						<div>
+							<div class="flex justify-between items-center text-xs">
+								<div class=" text-sm font-medium">Add a model</div>
+								<button
+									class=" text-xs font-medium text-gray-500"
+									type="button"
+									on:click={() => {
+										showLiteLLMParams = !showLiteLLMParams;
+									}}
+									>{showLiteLLMParams ? $i18n.t('Hide Additional Params') : $i18n.t('Show Additional Params')}</button
+								>
 							</div>
 							</div>
+						</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={() => {
-									addLiteLLMModelHandler();
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 16 16"
-									fill="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+						<div class="my-2 space-y-2">
+							<div class="flex w-full mb-1.5">
+								<div class="flex-1 mr-2">
+									<input
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+										placeholder="Enter LiteLLM Model (litellm_params.model)"
+										bind:value={liteLLMModel}
+										autocomplete="off"
 									/>
 									/>
-								</svg>
-							</button>
-						</div>
+								</div>
 
 
-						{#if showLiteLLMParams}
-							<div>
-								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div>
-								<div class="flex w-full">
-									<div class="flex-1">
-										<input
-											class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-											placeholder="Enter Model Name (model_name)"
-											bind:value={liteLLMModelName}
-											autocomplete="off"
+								<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={() => {
+										addLiteLLMModelHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
 										/>
 										/>
-									</div>
-								</div>
+									</svg>
+								</button>
 							</div>
 							</div>
 
 
-							<div>
-								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
-								<div class="flex w-full">
-									<div class="flex-1">
-										<input
-											class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-											placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
-											bind:value={liteLLMAPIBase}
-											autocomplete="off"
-										/>
+							{#if showLiteLLMParams}
+								<div>
+									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div>
+									<div class="flex w-full">
+										<div class="flex-1">
+											<input
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												placeholder="Enter Model Name (model_name)"
+												bind:value={liteLLMModelName}
+												autocomplete="off"
+											/>
+										</div>
 									</div>
 									</div>
 								</div>
 								</div>
-							</div>
 
 
-							<div>
-								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Key')}</div>
-								<div class="flex w-full">
-									<div class="flex-1">
-										<input
-											class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-											placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
-											bind:value={liteLLMAPIKey}
-											autocomplete="off"
-										/>
+								<div>
+									<div class=" mb-1.5 text-sm font-medium">API Base URL</div>
+									<div class="flex w-full">
+										<div class="flex-1">
+											<input
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
+												bind:value={liteLLMAPIBase}
+												autocomplete="off"
+											/>
+										</div>
 									</div>
 									</div>
 								</div>
 								</div>
-							</div>
 
 
-							<div>
-								<div class="mb-1.5 text-sm font-medium">{$i18n.t('API RPM')}</div>
-								<div class="flex w-full">
-									<div class="flex-1">
-										<input
-											class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-											placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
-											bind:value={liteLLMRPM}
-											autocomplete="off"
-										/>
+								<div>
+									<div class=" mb-1.5 text-sm font-medium">API Key</div>
+									<div class="flex w-full">
+										<div class="flex-1">
+											<input
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
+												bind:value={liteLLMAPIKey}
+												autocomplete="off"
+											/>
+										</div>
 									</div>
 									</div>
 								</div>
 								</div>
-							</div>
 
 
-							<div>
-								<div class="mb-1.5 text-sm font-medium">Max Tokens</div>
-								<div class="flex w-full">
-									<div class="flex-1">
-										<input
-											class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-											placeholder="Enter Max Tokens (litellm_params.max_tokens)"
-											bind:value={liteLLMMaxTokens}
-											type="number"
-											min="1"
-											autocomplete="off"
-										/>
+								<div>
+									<div class="mb-1.5 text-sm font-medium">API RPM</div>
+									<div class="flex w-full">
+										<div class="flex-1">
+											<input
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
+												bind:value={liteLLMRPM}
+												autocomplete="off"
+											/>
+										</div>
 									</div>
 									</div>
 								</div>
 								</div>
-							</div>
-						{/if}
-					</div>
-
-					<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('Not sure what to add?')}
-						<a
-							class=" text-gray-300 font-medium underline"
-							href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
-							target="_blank"
-						>
-							{$i18n.t('Click here for help')}
-						</a>
-					</div>
-
-					<div>
-						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Delete a model')}</div>
-						<div class="flex w-full">
-							<div class="flex-1 mr-2">
-								<select
-									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
-									bind:value={deleteLiteLLMModelId}
-									placeholder={$i18n.t('Select a model')}
-								>
-									{#if !deleteLiteLLMModelId}
-										<option value="" disabled selected>{$i18n.t('Select a model')}</option>
-									{/if}
-									{#each liteLLMModelInfo as model}
-										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
-											>{model.model_name}</option
-										>
-									{/each}
-								</select>
-							</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={() => {
-									deleteLiteLLMModelHandler();
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 16 16"
-									fill="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										fill-rule="evenodd"
-										d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
-										clip-rule="evenodd"
-									/>
-								</svg>
-							</button>
-						</div>
-					</div>
-				</div>
-			</div>
 
 
-			<!-- <div class="mt-2 space-y-3 pr-1.5">
-				<div>
-					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Add LiteLLM Model')}</div>
-					<div class="flex w-full mb-2">
-						<div class="flex-1">
-							<input
-								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
-								placeholder="Enter LiteLLM Model (e.g. ollama/mistral)"
-								bind:value={liteLLMModel}
-								autocomplete="off"
-							/>
-						</div>
-					</div>
-
-					<div class="flex justify-between items-center text-sm">
-						<div class="  font-medium">{$i18n.t('Advanced Model Params')}</div>
-						<button
-							class=" text-xs font-medium text-gray-500"
-							type="button"
-							on:click={() => {
-								showLiteLLMParams = !showLiteLLMParams;
-							}}>{showLiteLLMParams ? 'Hide' : 'Show'}</button
-						>
-					</div>
-
-					{#if showLiteLLMParams}
-						<div>
-							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API Key')}</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
-										placeholder="Enter LiteLLM API Key (e.g. os.environ/AZURE_API_KEY_CA)"
-										bind:value={liteLLMAPIKey}
-										autocomplete="off"
-									/>
+								<div>
+									<div class="mb-1.5 text-sm font-medium">Max Tokens</div>
+									<div class="flex w-full">
+										<div class="flex-1">
+											<input
+												class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+												placeholder="Enter Max Tokens (litellm_params.max_tokens)"
+												bind:value={liteLLMMaxTokens}
+												type="number"
+												min="1"
+												autocomplete="off"
+											/>
+										</div>
+									</div>
 								</div>
 								</div>
-							</div>
+							{/if}
 						</div>
 						</div>
 
 
-						<div>
-							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API Base URL')}</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
-										placeholder="Enter LiteLLM API Base URL"
-										bind:value={liteLLMAPIBase}
-										autocomplete="off"
-									/>
-								</div>
-							</div>
+						<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
+							Not sure what to add?
+							<a
+								class=" text-gray-300 font-medium underline"
+								href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
+								target="_blank"
+							>
+								Click here for help.
+							</a>
 						</div>
 						</div>
 
 
 						<div>
 						<div>
-							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API RPM')}</div>
+							<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
 							<div class="flex w-full">
 							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
-										placeholder="Enter LiteLLM API RPM"
-										bind:value={liteLLMRPM}
-										autocomplete="off"
-									/>
+								<div class="flex-1 mr-2">
+									<select
+										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+										bind:value={deleteLiteLLMModelId}
+										placeholder="Select a model"
+									>
+										{#if !deleteLiteLLMModelId}
+											<option value="" disabled selected>Select a model</option>
+										{/if}
+										{#each liteLLMModelInfo as model}
+											<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
+												>{model.model_name}</option
+											>
+										{/each}
+									</select>
 								</div>
 								</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={() => {
+										deleteLiteLLMModelHandler();
+									}}
+								>
+									<svg
+										xmlns="http://www.w3.org/2000/svg"
+										viewBox="0 0 16 16"
+										fill="currentColor"
+										class="w-4 h-4"
+									>
+										<path
+											fill-rule="evenodd"
+											d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
+											clip-rule="evenodd"
+										/>
+									</svg>
+								</button>
 							</div>
 							</div>
 						</div>
 						</div>
 					{/if}
 					{/if}
-
-					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
-						Not sure what to add?
-						<a
-							class=" text-gray-300 font-medium underline"
-							href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
-							target="_blank"
-						>
-							Click here for help.
-						</a>
-					</div>
 				</div>
 				</div>
-			</div> -->
+			</div>
 		</div>
 		</div>
 	</div>
 	</div>
-</div>
+</div>

+ 6 - 6
src/lib/components/chat/SettingsModal.svelte

@@ -329,7 +329,7 @@
 						{getModels}
 						{getModels}
 						{saveSettings}
 						{saveSettings}
 						on:save={() => {
 						on:save={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'models'}
 				{:else if selectedTab === 'models'}
@@ -338,28 +338,28 @@
 					<Connections
 					<Connections
 						{getModels}
 						{getModels}
 						on:save={() => {
 						on:save={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'interface'}
 				{:else if selectedTab === 'interface'}
 					<Interface
 					<Interface
 						{saveSettings}
 						{saveSettings}
 						on:save={() => {
 						on:save={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'audio'}
 				{:else if selectedTab === 'audio'}
 					<Audio
 					<Audio
 						{saveSettings}
 						{saveSettings}
 						on:save={() => {
 						on:save={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'images'}
 				{:else if selectedTab === 'images'}
 					<Images
 					<Images
 						{saveSettings}
 						{saveSettings}
 						on:save={() => {
 						on:save={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'chats'}
 				{:else if selectedTab === 'chats'}
@@ -367,7 +367,7 @@
 				{:else if selectedTab === 'account'}
 				{:else if selectedTab === 'account'}
 					<Account
 					<Account
 						saveHandler={() => {
 						saveHandler={() => {
-							show = false;
+							toast.success('Settings saved successfully!');
 						}}
 						}}
 					/>
 					/>
 				{:else if selectedTab === 'about'}
 				{:else if selectedTab === 'about'}

+ 45 - 59
src/routes/(app)/+page.svelte

@@ -237,53 +237,6 @@
 	const sendPrompt = async (prompt, parentId) => {
 	const sendPrompt = async (prompt, parentId) => {
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
 
-		const docs = messages
-			.filter((message) => message?.files ?? null)
-			.map((message) =>
-				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
-			)
-			.flat(1);
-
-		console.log(docs);
-		if (docs.length > 0) {
-			processing = 'Reading';
-			const query = history.messages[parentId].content;
-
-			let relevantContexts = await Promise.all(
-				docs.map(async (doc) => {
-					if (doc.type === 'collection') {
-						return await queryCollection(localStorage.token, doc.collection_names, query).catch(
-							(error) => {
-								console.log(error);
-								return null;
-							}
-						);
-					} else {
-						return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
-							console.log(error);
-							return null;
-						});
-					}
-				})
-			);
-			relevantContexts = relevantContexts.filter((context) => context);
-
-			const contextString = relevantContexts.reduce((a, context, i, arr) => {
-				return `${a}${context.documents.join(' ')}\n`;
-			}, '');
-
-			console.log(contextString);
-
-			history.messages[parentId].raContent = await RAGTemplate(
-				localStorage.token,
-				contextString,
-				query
-			);
-			history.messages[parentId].contexts = relevantContexts;
-			await tick();
-			processing = '';
-		}
-
 		await Promise.all(
 		await Promise.all(
 			selectedModels.map(async (modelId) => {
 			selectedModels.map(async (modelId) => {
 				const model = $models.filter((m) => m.id === modelId).at(0);
 				const model = $models.filter((m) => m.id === modelId).at(0);
@@ -347,15 +300,25 @@
 			...messages
 			...messages
 		]
 		]
 			.filter((message) => message)
 			.filter((message) => message)
-			.map((message, idx, arr) => ({
-				role: message.role,
-				content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content,
-				...(message.files && {
-					images: message.files
-						.filter((file) => file.type === 'image')
-						.map((file) => file.url.slice(file.url.indexOf(',') + 1))
-				})
-			}));
+			.map((message, idx, arr) => {
+				// Prepare the base message object
+				const baseMessage = {
+					role: message.role,
+					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
+				};
+
+				// Extract and format image URLs if any exist
+				const imageUrls = message.files
+					?.filter((file) => file.type === 'image')
+					.map((file) => file.url.slice(file.url.indexOf(',') + 1));
+
+				// Add images array only if it contains elements
+				if (imageUrls && imageUrls.length > 0) {
+					baseMessage.images = imageUrls;
+				}
+
+				return baseMessage;
+			});
 
 
 		let lastImageIndex = -1;
 		let lastImageIndex = -1;
 
 
@@ -373,6 +336,13 @@
 			}
 			}
 		});
 		});
 
 
+		const docs = messages
+			.filter((message) => message?.files ?? null)
+			.map((message) =>
+				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
+			)
+			.flat(1);
+
 		const [res, controller] = await generateChatCompletion(localStorage.token, {
 		const [res, controller] = await generateChatCompletion(localStorage.token, {
 			model: model,
 			model: model,
 			messages: messagesBody,
 			messages: messagesBody,
@@ -380,7 +350,8 @@
 				...($settings.options ?? {})
 				...($settings.options ?? {})
 			},
 			},
 			format: $settings.requestFormat ?? undefined,
 			format: $settings.requestFormat ?? undefined,
-			keep_alive: $settings.keepAlive ?? undefined
+			keep_alive: $settings.keepAlive ?? undefined,
+			docs: docs.length > 0 ? docs : undefined
 		});
 		});
 
 
 		if (res && res.ok) {
 		if (res && res.ok) {
@@ -546,6 +517,15 @@
 		const responseMessage = history.messages[responseMessageId];
 		const responseMessage = history.messages[responseMessageId];
 		scrollToBottom();
 		scrollToBottom();
 
 
+		const docs = messages
+			.filter((message) => message?.files ?? null)
+			.map((message) =>
+				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
+			)
+			.flat(1);
+
+		console.log(docs);
+
 		const res = await generateOpenAIChatCompletion(
 		const res = await generateOpenAIChatCompletion(
 			localStorage.token,
 			localStorage.token,
 			{
 			{
@@ -594,7 +574,8 @@
 				top_p: $settings?.options?.top_p ?? undefined,
 				top_p: $settings?.options?.top_p ?? undefined,
 				num_ctx: $settings?.options?.num_ctx ?? undefined,
 				num_ctx: $settings?.options?.num_ctx ?? undefined,
 				frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
 				frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
-				max_tokens: $settings?.options?.num_predict ?? undefined
+				max_tokens: $settings?.options?.num_predict ?? undefined,
+				docs: docs.length > 0 ? docs : undefined
 			},
 			},
 			model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
 			model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
 		);
 		);
@@ -711,7 +692,12 @@
 
 
 		if (messages.length == 2) {
 		if (messages.length == 2) {
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
-			await setChatTitle(_chatId, userPrompt);
+
+			if ($settings?.titleAutoGenerateModel) {
+				await generateChatTitle(_chatId, userPrompt);
+			} else {
+				await setChatTitle(_chatId, userPrompt);
+			}
 		}
 		}
 	};
 	};
 
 

+ 40 - 58
src/routes/(app)/c/[id]/+page.svelte

@@ -247,53 +247,6 @@
 	const sendPrompt = async (prompt, parentId) => {
 	const sendPrompt = async (prompt, parentId) => {
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
 
-		const docs = messages
-			.filter((message) => message?.files ?? null)
-			.map((message) =>
-				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
-			)
-			.flat(1);
-
-		console.log(docs);
-		if (docs.length > 0) {
-			processing = 'Reading';
-			const query = history.messages[parentId].content;
-
-			let relevantContexts = await Promise.all(
-				docs.map(async (doc) => {
-					if (doc.type === 'collection') {
-						return await queryCollection(localStorage.token, doc.collection_names, query).catch(
-							(error) => {
-								console.log(error);
-								return null;
-							}
-						);
-					} else {
-						return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
-							console.log(error);
-							return null;
-						});
-					}
-				})
-			);
-			relevantContexts = relevantContexts.filter((context) => context);
-
-			const contextString = relevantContexts.reduce((a, context, i, arr) => {
-				return `${a}${context.documents.join(' ')}\n`;
-			}, '');
-
-			console.log(contextString);
-
-			history.messages[parentId].raContent = await RAGTemplate(
-				localStorage.token,
-				contextString,
-				query
-			);
-			history.messages[parentId].contexts = relevantContexts;
-			await tick();
-			processing = '';
-		}
-
 		await Promise.all(
 		await Promise.all(
 			selectedModels.map(async (modelId) => {
 			selectedModels.map(async (modelId) => {
 				const model = $models.filter((m) => m.id === modelId).at(0);
 				const model = $models.filter((m) => m.id === modelId).at(0);
@@ -357,15 +310,25 @@
 			...messages
 			...messages
 		]
 		]
 			.filter((message) => message)
 			.filter((message) => message)
-			.map((message, idx, arr) => ({
-				role: message.role,
-				content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content,
-				...(message.files && {
-					images: message.files
-						.filter((file) => file.type === 'image')
-						.map((file) => file.url.slice(file.url.indexOf(',') + 1))
-				})
-			}));
+			.map((message, idx, arr) => {
+				// Prepare the base message object
+				const baseMessage = {
+					role: message.role,
+					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
+				};
+
+				// Extract and format image URLs if any exist
+				const imageUrls = message.files
+					?.filter((file) => file.type === 'image')
+					.map((file) => file.url.slice(file.url.indexOf(',') + 1));
+
+				// Add images array only if it contains elements
+				if (imageUrls && imageUrls.length > 0) {
+					baseMessage.images = imageUrls;
+				}
+
+				return baseMessage;
+			});
 
 
 		let lastImageIndex = -1;
 		let lastImageIndex = -1;
 
 
@@ -383,6 +346,13 @@
 			}
 			}
 		});
 		});
 
 
+		const docs = messages
+			.filter((message) => message?.files ?? null)
+			.map((message) =>
+				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
+			)
+			.flat(1);
+
 		const [res, controller] = await generateChatCompletion(localStorage.token, {
 		const [res, controller] = await generateChatCompletion(localStorage.token, {
 			model: model,
 			model: model,
 			messages: messagesBody,
 			messages: messagesBody,
@@ -390,7 +360,8 @@
 				...($settings.options ?? {})
 				...($settings.options ?? {})
 			},
 			},
 			format: $settings.requestFormat ?? undefined,
 			format: $settings.requestFormat ?? undefined,
-			keep_alive: $settings.keepAlive ?? undefined
+			keep_alive: $settings.keepAlive ?? undefined,
+			docs: docs.length > 0 ? docs : undefined
 		});
 		});
 
 
 		if (res && res.ok) {
 		if (res && res.ok) {
@@ -556,6 +527,15 @@
 		const responseMessage = history.messages[responseMessageId];
 		const responseMessage = history.messages[responseMessageId];
 		scrollToBottom();
 		scrollToBottom();
 
 
+		const docs = messages
+			.filter((message) => message?.files ?? null)
+			.map((message) =>
+				message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
+			)
+			.flat(1);
+
+		console.log(docs);
+
 		const res = await generateOpenAIChatCompletion(
 		const res = await generateOpenAIChatCompletion(
 			localStorage.token,
 			localStorage.token,
 			{
 			{
@@ -604,7 +584,8 @@
 				top_p: $settings?.options?.top_p ?? undefined,
 				top_p: $settings?.options?.top_p ?? undefined,
 				num_ctx: $settings?.options?.num_ctx ?? undefined,
 				num_ctx: $settings?.options?.num_ctx ?? undefined,
 				frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
 				frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
-				max_tokens: $settings?.options?.num_predict ?? undefined
+				max_tokens: $settings?.options?.num_predict ?? undefined,
+				docs: docs.length > 0 ? docs : undefined
 			},
 			},
 			model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
 			model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
 		);
 		);
@@ -724,6 +705,7 @@
 			await setChatTitle(_chatId, userPrompt);
 			await setChatTitle(_chatId, userPrompt);
 		}
 		}
 	};
 	};
+
 	const stopResponse = () => {
 	const stopResponse = () => {
 		stopResponseFlag = true;
 		stopResponseFlag = true;
 		console.log('stopResponse');
 		console.log('stopResponse');

+ 3 - 1
src/routes/(app)/documents/+page.svelte

@@ -562,7 +562,9 @@
 
 
 					<button
 					<button
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={documentsImportInputElement.click}
+						on:click={() => {
+							documentsImportInputElement.click();
+						}}
 					>
 					>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
 
 

+ 3 - 1
src/routes/(app)/modelfiles/+page.svelte

@@ -268,7 +268,9 @@
 
 
 					<button
 					<button
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={modelfilesImportInputElement.click}
+						on:click={() => {
+							modelfilesImportInputElement.click();
+						}}
 					>
 					>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
 
 

+ 1 - 1
src/routes/(app)/playground/+page.svelte

@@ -269,7 +269,7 @@
 
 
 <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
 <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
 	<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
 	<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
-		<div class="max-w-2xl mx-auto w-full px-3 p-3 md:px-0 h-full">
+		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
 			<div class=" flex flex-col h-full">
 			<div class=" flex flex-col h-full">
 				<div class="flex flex-col justify-between mb-2.5 gap-1">
 				<div class="flex flex-col justify-between mb-2.5 gap-1">
 					<div class="flex justify-between items-center gap-2">
 					<div class="flex justify-between items-center gap-2">

+ 3 - 1
src/routes/(app)/prompts/+page.svelte

@@ -244,7 +244,9 @@
 
 
 					<button
 					<button
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
 						class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
-						on:click={promptsImportInputElement.click}
+						on:click={() => {
+							promptsImportInputElement.click();
+						}}
 					>
 					>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>
 						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>