forked from open-webui/open-webui
		
	main #2
					 27 changed files with 802 additions and 493 deletions
				
			
		|  | @ -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)) | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  |  | ||||||
|  | @ -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 |         return query_doc( | ||||||
|         collection = CHROMA_CLIENT.get_collection( |             collection_name=form_data.collection_name, | ||||||
|             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 = [] |     return query_collection( | ||||||
| 
 |         collection_names=form_data.collection_names, | ||||||
|     for collection_name in form_data.collection_names: |         query=form_data.query, | ||||||
|         try: |         k=form_data.k if form_data.k else app.state.TOP_K, | ||||||
|             # if you use docker use the model from the environment variable |         embedding_function=app.state.sentence_transformer_ef, | ||||||
|             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 |  | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										97
									
								
								backend/apps/rag/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								backend/apps/rag/utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | @ -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 | ||||||
| #################################### | #################################### | ||||||
|  |  | ||||||
							
								
								
									
										173
									
								
								backend/main.py
									
										
									
									
									
								
							
							
						
						
									
										173
									
								
								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(): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,7 +16,8 @@ aiohttp | ||||||
| peewee | peewee | ||||||
| bcrypt | bcrypt | ||||||
| 
 | 
 | ||||||
| litellm | litellm==1.30.7 | ||||||
|  | argon2-cffi | ||||||
| apscheduler | apscheduler | ||||||
| google-generativeai | google-generativeai | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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"> | ||||||
|  |  | ||||||
|  | @ -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 /> | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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')} | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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')} | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  |  | ||||||
|  | @ -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')} | ||||||
|  |  | ||||||
|  | @ -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 class="mb-2"> | ||||||
| 
 |  | ||||||
| 					<div> |  | ||||||
| 						<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; | 									showLiteLLM = !showLiteLLM; | ||||||
| 								}} | 								}}>{showLiteLLM ? $i18n.t('Hide') : $i18n.t('Show')}</button | ||||||
| 								>{showLiteLLMParams |  | ||||||
| 									? $i18n.t('Hide Additional Params') |  | ||||||
| 									: $i18n.t('Show Additional Params')}</button |  | ||||||
| 							> | 							> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 
 | 
 | ||||||
| 					<div class="my-2 space-y-2"> | 					{#if showLiteLLM} | ||||||
| 						<div class="flex w-full mb-1.5"> | 						<div> | ||||||
| 							<div class="flex-1 mr-2"> | 							<div class="flex justify-between items-center text-xs"> | ||||||
| 								<input | 								<div class=" text-sm font-medium">Add a model</div> | ||||||
| 									class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | 								<button | ||||||
| 									placeholder="Enter LiteLLM Model (litellm_params.model)" | 									class=" text-xs font-medium text-gray-500" | ||||||
| 									bind:value={liteLLMModel} | 									type="button" | ||||||
| 									autocomplete="off" | 									on:click={() => { | ||||||
| 								/> | 										showLiteLLMParams = !showLiteLLMParams; | ||||||
|  | 									}} | ||||||
|  | 									>{showLiteLLMParams ? $i18n.t('Hide Additional Params') : $i18n.t('Show Additional Params')}</button | ||||||
|  | 								> | ||||||
|  | 							</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" | ||||||
|  | 									/> | ||||||
|  | 								</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" | ||||||
|  | 										/> | ||||||
|  | 									</svg> | ||||||
|  | 								</button> | ||||||
| 							</div> | 							</div> | ||||||
| 
 | 
 | ||||||
| 							<button | 							{#if showLiteLLMParams} | ||||||
| 								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" | 								<div> | ||||||
| 								on:click={() => { | 									<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div> | ||||||
| 									addLiteLLMModelHandler(); | 									<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 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 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 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 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> | ||||||
|  | 							{/if} | ||||||
|  | 						</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" | ||||||
| 							> | 							> | ||||||
| 								<svg | 								Click here for help. | ||||||
| 									xmlns="http://www.w3.org/2000/svg" | 							</a> | ||||||
| 									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> |  | ||||||
| 						</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" |  | ||||||
| 										/> |  | ||||||
| 									</div> |  | ||||||
| 								</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" |  | ||||||
| 										/> |  | ||||||
| 									</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> |  | ||||||
| 							</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> |  | ||||||
| 							</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> |  | ||||||
| 							</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> |  | ||||||
| 						</div> | 						</div> | ||||||
| 
 | 
 | ||||||
| 						<div> | 						<div> | ||||||
| 							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API Base URL')}</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"> | 								<div class="flex-1 mr-2"> | ||||||
| 									<input | 									<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" | ||||||
| 										placeholder="Enter LiteLLM API Base URL" | 										bind:value={deleteLiteLLMModelId} | ||||||
| 										bind:value={liteLLMAPIBase} | 										placeholder="Select a model" | ||||||
| 										autocomplete="off" | 									> | ||||||
| 									/> | 										{#if !deleteLiteLLMModelId} | ||||||
| 								</div> | 											<option value="" disabled selected>Select a model</option> | ||||||
| 							</div> | 										{/if} | ||||||
| 						</div> | 										{#each liteLLMModelInfo as model} | ||||||
| 
 | 											<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700" | ||||||
| 						<div> | 												>{model.model_name}</option | ||||||
| 							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API RPM')}</div> | 											> | ||||||
| 							<div class="flex w-full"> | 										{/each} | ||||||
| 								<div class="flex-1"> | 									</select> | ||||||
| 									<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> | 								</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> | ||||||
|  | @ -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'} | ||||||
|  |  | ||||||
|  | @ -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) => ({ | 			.map((message, idx, arr) => { | ||||||
| 				role: message.role, | 				// Prepare the base message object | ||||||
| 				content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, | 				const baseMessage = { | ||||||
| 				...(message.files && { | 					role: message.role, | ||||||
| 					images: message.files | 					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content | ||||||
| 						.filter((file) => file.type === 'image') | 				}; | ||||||
| 						.map((file) => file.url.slice(file.url.indexOf(',') + 1)) | 
 | ||||||
| 				}) | 				// 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); | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) => ({ | 			.map((message, idx, arr) => { | ||||||
| 				role: message.role, | 				// Prepare the base message object | ||||||
| 				content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, | 				const baseMessage = { | ||||||
| 				...(message.files && { | 					role: message.role, | ||||||
| 					images: message.files | 					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content | ||||||
| 						.filter((file) => file.type === 'image') | 				}; | ||||||
| 						.map((file) => file.url.slice(file.url.indexOf(',') + 1)) | 
 | ||||||
| 				}) | 				// 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'); | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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"> | ||||||
|  |  | ||||||
|  | @ -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> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue