forked from open-webui/open-webui
		
	Merge branch 'dev' into dockerfile-optimisation
This commit is contained in:
		
						commit
						3b3d0cce1e
					
				
					 65 changed files with 1956 additions and 533 deletions
				
			
		|  | @ -10,3 +10,7 @@ OPENAI_API_KEY='' | ||||||
| # DO NOT TRACK | # DO NOT TRACK | ||||||
| SCARF_NO_ANALYTICS=true | SCARF_NO_ANALYTICS=true | ||||||
| DO_NOT_TRACK=true | DO_NOT_TRACK=true | ||||||
|  | 
 | ||||||
|  | # Use locally bundled version of the LiteLLM cost map json | ||||||
|  | # to avoid repetitive startup connections | ||||||
|  | LITELLM_LOCAL_MODEL_COST_MAP="True" | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								.github/workflows/build-release.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/build-release.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -57,3 +57,14 @@ jobs: | ||||||
|         path: . |         path: . | ||||||
|       env: |       env: | ||||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  | 
 | ||||||
|  |     - name: Trigger Docker build workflow | ||||||
|  |       uses: actions/github-script@v7 | ||||||
|  |       with: | ||||||
|  |         script: | | ||||||
|  |           github.rest.actions.createWorkflowDispatch({ | ||||||
|  |             owner: context.repo.owner, | ||||||
|  |             repo: context.repo.repo, | ||||||
|  |             workflow_id: 'docker-build.yaml', | ||||||
|  |             ref: 'v${{ steps.get_version.outputs.version }}', | ||||||
|  |           }) | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								.github/workflows/docker-build.yaml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/docker-build.yaml
									
										
									
									
										vendored
									
									
								
							|  | @ -2,6 +2,7 @@ name: Create and publish Docker images with specific build args | ||||||
| 
 | 
 | ||||||
| # Configures this workflow to run every time a change is pushed to the branch called `release`. | # Configures this workflow to run every time a change is pushed to the branch called `release`. | ||||||
| on: | on: | ||||||
|  |   workflow_dispatch: | ||||||
|   push: |   push: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGELOG.md
									
										
									
									
									
								
							|  | @ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. | ||||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||||
| 
 | 
 | ||||||
|  | ## [0.1.117] - 2024-04-03 | ||||||
|  | 
 | ||||||
|  | ### Added | ||||||
|  | 
 | ||||||
|  | - 🗨️ **Local Chat Sharing**: Share chat links seamlessly between users. | ||||||
|  | - 🔑 **API Key Generation Support**: Generate secret keys to leverage Open WebUI with OpenAI libraries. | ||||||
|  | - 📄 **Chat Download as PDF**: Easily download chats in PDF format. | ||||||
|  | - 📝 **Improved Logging**: Enhancements to logging functionality. | ||||||
|  | - 📧 **Trusted Email Authentication**: Authenticate using a trusted email header. | ||||||
|  | 
 | ||||||
|  | ### Fixed | ||||||
|  | 
 | ||||||
|  | - 🌷 **Enhanced Dutch Translation**: Improved translation for Dutch users. | ||||||
|  | - ⚪ **White Theme Styling**: Resolved styling issue with the white theme. | ||||||
|  | - 📜 **LaTeX Chat Screen Overflow**: Fixed screen overflow issue with LaTeX rendering. | ||||||
|  | - 🔒 **Security Patches**: Applied necessary security patches. | ||||||
|  | 
 | ||||||
| ## [0.1.116] - 2024-03-31 | ## [0.1.116] - 2024-03-31 | ||||||
| 
 | 
 | ||||||
| ### Added | ### Added | ||||||
|  |  | ||||||
|  | @ -215,7 +215,8 @@ async def get_ollama_versions(url_idx: Optional[int] = None): | ||||||
| 
 | 
 | ||||||
|         if len(responses) > 0: |         if len(responses) > 0: | ||||||
|             lowest_version = min( |             lowest_version = min( | ||||||
|                 responses, key=lambda x: tuple(map(int, x["version"].split("."))) |                 responses, | ||||||
|  |                 key=lambda x: tuple(map(int, x["version"].split("-")[0].split("."))), | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|             return {"version": lowest_version["version"]} |             return {"version": lowest_version["version"]} | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ from fastapi import ( | ||||||
|     Form, |     Form, | ||||||
| ) | ) | ||||||
| from fastapi.middleware.cors import CORSMiddleware | from fastapi.middleware.cors import CORSMiddleware | ||||||
| import os, shutil, logging | import os, shutil, logging, re | ||||||
| 
 | 
 | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import List | from typing import List | ||||||
|  | @ -438,25 +438,11 @@ def store_doc( | ||||||
| 
 | 
 | ||||||
|     log.info(f"file.content_type: {file.content_type}") |     log.info(f"file.content_type: {file.content_type}") | ||||||
|     try: |     try: | ||||||
|         is_valid_filename = True |  | ||||||
|         unsanitized_filename = file.filename |         unsanitized_filename = file.filename | ||||||
|         if not unsanitized_filename.isascii(): |         filename = os.path.basename(unsanitized_filename) | ||||||
|             is_valid_filename = False |  | ||||||
| 
 | 
 | ||||||
|         unvalidated_file_path = f"{UPLOAD_DIR}/{unsanitized_filename}" |         file_path = f"{UPLOAD_DIR}/{filename}" | ||||||
|         dereferenced_file_path = str(Path(unvalidated_file_path).resolve(strict=False)) |  | ||||||
|         if not dereferenced_file_path.startswith(UPLOAD_DIR): |  | ||||||
|             is_valid_filename = False |  | ||||||
| 
 | 
 | ||||||
|         if is_valid_filename: |  | ||||||
|             file_path = dereferenced_file_path |  | ||||||
|         else: |  | ||||||
|             raise HTTPException( |  | ||||||
|                 status_code=status.HTTP_400_BAD_REQUEST, |  | ||||||
|                 detail=ERROR_MESSAGES.DEFAULT(), |  | ||||||
|             ) |  | ||||||
| 
 |  | ||||||
|         filename = file.filename |  | ||||||
|         contents = file.file.read() |         contents = file.file.read() | ||||||
|         with open(file_path, "wb") as f: |         with open(file_path, "wb") as f: | ||||||
|             f.write(contents) |             f.write(contents) | ||||||
|  | @ -467,7 +453,7 @@ def store_doc( | ||||||
|             collection_name = calculate_sha256(f)[:63] |             collection_name = calculate_sha256(f)[:63] | ||||||
|         f.close() |         f.close() | ||||||
| 
 | 
 | ||||||
|         loader, known_type = get_loader(file.filename, file.content_type, file_path) |         loader, known_type = get_loader(filename, file.content_type, file_path) | ||||||
|         data = loader.load() |         data = loader.load() | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|  |  | ||||||
|  | @ -86,6 +86,7 @@ class SignupForm(BaseModel): | ||||||
|     name: str |     name: str | ||||||
|     email: str |     email: str | ||||||
|     password: str |     password: str | ||||||
|  |     profile_image_url: Optional[str] = "/user.png" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AuthsTable: | class AuthsTable: | ||||||
|  | @ -94,7 +95,12 @@ class AuthsTable: | ||||||
|         self.db.create_tables([Auth]) |         self.db.create_tables([Auth]) | ||||||
| 
 | 
 | ||||||
|     def insert_new_auth( |     def insert_new_auth( | ||||||
|         self, email: str, password: str, name: str, role: str = "pending" |         self, | ||||||
|  |         email: str, | ||||||
|  |         password: str, | ||||||
|  |         name: str, | ||||||
|  |         profile_image_url: str = "/user.png", | ||||||
|  |         role: str = "pending", | ||||||
|     ) -> Optional[UserModel]: |     ) -> Optional[UserModel]: | ||||||
|         log.info("insert_new_auth") |         log.info("insert_new_auth") | ||||||
| 
 | 
 | ||||||
|  | @ -105,7 +111,7 @@ class AuthsTable: | ||||||
|         ) |         ) | ||||||
|         result = Auth.create(**auth.model_dump()) |         result = Auth.create(**auth.model_dump()) | ||||||
| 
 | 
 | ||||||
|         user = Users.insert_new_user(id, name, email, role) |         user = Users.insert_new_user(id, name, email, profile_image_url, role) | ||||||
| 
 | 
 | ||||||
|         if result and user: |         if result and user: | ||||||
|             return user |             return user | ||||||
|  |  | ||||||
|  | @ -206,6 +206,18 @@ class ChatTable: | ||||||
|         except: |         except: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|  |     def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]: | ||||||
|  |         try: | ||||||
|  |             chat = Chat.get(Chat.share_id == id) | ||||||
|  | 
 | ||||||
|  |             if chat: | ||||||
|  |                 chat = Chat.get(Chat.id == id) | ||||||
|  |                 return ChatModel(**model_to_dict(chat)) | ||||||
|  |             else: | ||||||
|  |                 return None | ||||||
|  |         except: | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|     def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: |     def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: | ||||||
|         try: |         try: | ||||||
|             chat = Chat.get(Chat.id == id, Chat.user_id == user_id) |             chat = Chat.get(Chat.id == id, Chat.user_id == user_id) | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ class UserModel(BaseModel): | ||||||
|     name: str |     name: str | ||||||
|     email: str |     email: str | ||||||
|     role: str = "pending" |     role: str = "pending" | ||||||
|     profile_image_url: str = "/user.png" |     profile_image_url: str | ||||||
|     timestamp: int  # timestamp in epoch |     timestamp: int  # timestamp in epoch | ||||||
|     api_key: Optional[str] = None |     api_key: Optional[str] = None | ||||||
| 
 | 
 | ||||||
|  | @ -59,7 +59,12 @@ class UsersTable: | ||||||
|         self.db.create_tables([User]) |         self.db.create_tables([User]) | ||||||
| 
 | 
 | ||||||
|     def insert_new_user( |     def insert_new_user( | ||||||
|         self, id: str, name: str, email: str, role: str = "pending" |         self, | ||||||
|  |         id: str, | ||||||
|  |         name: str, | ||||||
|  |         email: str, | ||||||
|  |         profile_image_url: str = "/user.png", | ||||||
|  |         role: str = "pending", | ||||||
|     ) -> Optional[UserModel]: |     ) -> Optional[UserModel]: | ||||||
|         user = UserModel( |         user = UserModel( | ||||||
|             **{ |             **{ | ||||||
|  | @ -67,7 +72,7 @@ class UsersTable: | ||||||
|                 "name": name, |                 "name": name, | ||||||
|                 "email": email, |                 "email": email, | ||||||
|                 "role": role, |                 "role": role, | ||||||
|                 "profile_image_url": "/user.png", |                 "profile_image_url": profile_image_url, | ||||||
|                 "timestamp": int(time.time()), |                 "timestamp": int(time.time()), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -163,7 +163,11 @@ async def signup(request: Request, form_data: SignupForm): | ||||||
|         ) |         ) | ||||||
|         hashed = get_password_hash(form_data.password) |         hashed = get_password_hash(form_data.password) | ||||||
|         user = Auths.insert_new_auth( |         user = Auths.insert_new_auth( | ||||||
|             form_data.email.lower(), hashed, form_data.name, role |             form_data.email.lower(), | ||||||
|  |             hashed, | ||||||
|  |             form_data.name, | ||||||
|  |             form_data.profile_image_url, | ||||||
|  |             role, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if user: |         if user: | ||||||
|  |  | ||||||
|  | @ -251,7 +251,15 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)): | ||||||
| 
 | 
 | ||||||
| @router.get("/share/{share_id}", response_model=Optional[ChatResponse]) | @router.get("/share/{share_id}", response_model=Optional[ChatResponse]) | ||||||
| async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)): | async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)): | ||||||
|     chat = Chats.get_chat_by_id(share_id) |     if user.role == "pending": | ||||||
|  |         raise HTTPException( | ||||||
|  |             status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     if user.role == "user": | ||||||
|  |         chat = Chats.get_chat_by_share_id(share_id) | ||||||
|  |     elif user.role == "admin": | ||||||
|  |         chat = Chats.get_chat_by_id(share_id) | ||||||
| 
 | 
 | ||||||
|     if chat: |     if chat: | ||||||
|         return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) |         return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) | ||||||
|  |  | ||||||
|  | @ -1,14 +1,11 @@ | ||||||
| from fastapi import APIRouter, UploadFile, File, BackgroundTasks | from fastapi import APIRouter, UploadFile, File, Response | ||||||
| from fastapi import Depends, HTTPException, status | from fastapi import Depends, HTTPException, status | ||||||
| from starlette.responses import StreamingResponse, FileResponse | from starlette.responses import StreamingResponse, FileResponse | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel | ||||||
| 
 | 
 | ||||||
| import requests | 
 | ||||||
| import os | from fpdf import FPDF | ||||||
| import aiohttp | import markdown | ||||||
| import json |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| from utils.utils import get_admin_user | from utils.utils import get_admin_user | ||||||
|  | @ -16,7 +13,7 @@ from utils.misc import calculate_sha256, get_gravatar_url | ||||||
| 
 | 
 | ||||||
| from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR | from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR | ||||||
| from constants import ERROR_MESSAGES | from constants import ERROR_MESSAGES | ||||||
| 
 | from typing import List | ||||||
| 
 | 
 | ||||||
| router = APIRouter() | router = APIRouter() | ||||||
| 
 | 
 | ||||||
|  | @ -28,6 +25,70 @@ async def get_gravatar( | ||||||
|     return get_gravatar_url(email) |     return get_gravatar_url(email) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class MarkdownForm(BaseModel): | ||||||
|  |     md: str | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @router.post("/markdown") | ||||||
|  | async def get_html_from_markdown( | ||||||
|  |     form_data: MarkdownForm, | ||||||
|  | ): | ||||||
|  |     return {"html": markdown.markdown(form_data.md)} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ChatForm(BaseModel): | ||||||
|  |     title: str | ||||||
|  |     messages: List[dict] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @router.post("/pdf") | ||||||
|  | async def download_chat_as_pdf( | ||||||
|  |     form_data: ChatForm, | ||||||
|  | ): | ||||||
|  |     pdf = FPDF() | ||||||
|  |     pdf.add_page() | ||||||
|  | 
 | ||||||
|  |     STATIC_DIR = "./static" | ||||||
|  |     FONTS_DIR = f"{STATIC_DIR}/fonts" | ||||||
|  | 
 | ||||||
|  |     pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf") | ||||||
|  |     pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf") | ||||||
|  |     pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf") | ||||||
|  |     pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf") | ||||||
|  |     pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf") | ||||||
|  | 
 | ||||||
|  |     pdf.set_font("NotoSans", size=12) | ||||||
|  |     pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP"]) | ||||||
|  | 
 | ||||||
|  |     pdf.set_auto_page_break(auto=True, margin=15) | ||||||
|  | 
 | ||||||
|  |     # Adjust the effective page width for multi_cell | ||||||
|  |     effective_page_width = ( | ||||||
|  |         pdf.w - 2 * pdf.l_margin - 10 | ||||||
|  |     )  # Subtracted an additional 10 for extra padding | ||||||
|  | 
 | ||||||
|  |     # Add chat messages | ||||||
|  |     for message in form_data.messages: | ||||||
|  |         role = message["role"] | ||||||
|  |         content = message["content"] | ||||||
|  |         pdf.set_font("NotoSans", "B", size=14)  # Bold for the role | ||||||
|  |         pdf.multi_cell(effective_page_width, 10, f"{role.upper()}", 0, "L") | ||||||
|  |         pdf.ln(1)  # Extra space between messages | ||||||
|  | 
 | ||||||
|  |         pdf.set_font("NotoSans", size=10)  # Regular for content | ||||||
|  |         pdf.multi_cell(effective_page_width, 6, content, 0, "L") | ||||||
|  |         pdf.ln(1.5)  # Extra space between messages | ||||||
|  | 
 | ||||||
|  |     # Save the pdf with name .pdf | ||||||
|  |     pdf_bytes = pdf.output() | ||||||
|  | 
 | ||||||
|  |     return Response( | ||||||
|  |         content=bytes(pdf_bytes), | ||||||
|  |         media_type="application/pdf", | ||||||
|  |         headers={"Content-Disposition": f"attachment;filename=chat.pdf"}, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @router.get("/db/download") | @router.get("/db/download") | ||||||
| async def download_db(user=Depends(get_admin_user)): | async def download_db(user=Depends(get_admin_user)): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -25,8 +25,9 @@ try: | ||||||
| except ImportError: | except ImportError: | ||||||
|     log.warning("dotenv not installed, skipping...") |     log.warning("dotenv not installed, skipping...") | ||||||
| 
 | 
 | ||||||
| WEBUI_NAME = "Open WebUI" | WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI") | ||||||
| WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png" | WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png" | ||||||
|  | 
 | ||||||
| shutil.copyfile("../build/favicon.png", "./static/favicon.png") | shutil.copyfile("../build/favicon.png", "./static/favicon.png") | ||||||
| 
 | 
 | ||||||
| #################################### | #################################### | ||||||
|  | @ -149,6 +150,7 @@ log.setLevel(SRC_LOG_LEVELS["CONFIG"]) | ||||||
| #################################### | #################################### | ||||||
| 
 | 
 | ||||||
| CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "") | CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "") | ||||||
|  | 
 | ||||||
| if CUSTOM_NAME: | if CUSTOM_NAME: | ||||||
|     try: |     try: | ||||||
|         r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}") |         r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}") | ||||||
|  | @ -171,7 +173,9 @@ if CUSTOM_NAME: | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         log.exception(e) |         log.exception(e) | ||||||
|         pass |         pass | ||||||
| 
 | else: | ||||||
|  |     if WEBUI_NAME != "Open WebUI": | ||||||
|  |         WEBUI_NAME += " (Open WebUI)" | ||||||
| 
 | 
 | ||||||
| #################################### | #################################### | ||||||
| # DATA/FRONTEND BUILD DIR | # DATA/FRONTEND BUILD DIR | ||||||
|  |  | ||||||
|  | @ -84,7 +84,6 @@ app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST | ||||||
| 
 | 
 | ||||||
| app.state.WEBHOOK_URL = WEBHOOK_URL | app.state.WEBHOOK_URL = WEBHOOK_URL | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| origins = ["*"] | origins = ["*"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -284,6 +283,20 @@ async def get_app_latest_release_version(): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @app.get("/manifest.json") | ||||||
|  | async def get_manifest_json(): | ||||||
|  |     return { | ||||||
|  |         "name": WEBUI_NAME, | ||||||
|  |         "short_name": WEBUI_NAME, | ||||||
|  |         "start_url": "/", | ||||||
|  |         "display": "standalone", | ||||||
|  |         "background_color": "#343541", | ||||||
|  |         "theme_color": "#343541", | ||||||
|  |         "orientation": "portrait-primary", | ||||||
|  |         "icons": [{"src": "/favicon.png", "type": "image/png", "sizes": "844x884"}], | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| app.mount("/static", StaticFiles(directory="static"), name="static") | app.mount("/static", StaticFiles(directory="static"), name="static") | ||||||
| app.mount("/cache", StaticFiles(directory="data/cache"), name="cache") | app.mount("/cache", StaticFiles(directory="data/cache"), name="cache") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,6 +18,8 @@ peewee-migrate | ||||||
| bcrypt | bcrypt | ||||||
| 
 | 
 | ||||||
| litellm==1.30.7 | litellm==1.30.7 | ||||||
|  | boto3 | ||||||
|  | 
 | ||||||
| argon2-cffi | argon2-cffi | ||||||
| apscheduler | apscheduler | ||||||
| google-generativeai | google-generativeai | ||||||
|  | @ -40,6 +42,8 @@ xlrd | ||||||
| opencv-python-headless | opencv-python-headless | ||||||
| rapidocr-onnxruntime | rapidocr-onnxruntime | ||||||
| 
 | 
 | ||||||
|  | fpdf2 | ||||||
|  | 
 | ||||||
| faster-whisper | faster-whisper | ||||||
| 
 | 
 | ||||||
| PyJWT | PyJWT | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Bold.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Bold.ttf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Italic.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Italic.ttf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSans-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSansJP-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSansJP-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSansKR-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/static/fonts/NotoSansKR-Regular.ttf
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										8
									
								
								docker-compose.amdgpu.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docker-compose.amdgpu.yaml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | services: | ||||||
|  |   ollama: | ||||||
|  |     devices: | ||||||
|  |       - /dev/kfd:/dev/kfd | ||||||
|  |       - /dev/dri:/dev/dri | ||||||
|  |     image: ollama/ollama:${OLLAMA_DOCKER_TAG-rocm} | ||||||
|  |     environment: | ||||||
|  |       - 'HSA_OVERRIDE_GFX_VERSION=${HSA_OVERRIDE_GFX_VERSION-11.0.0}' | ||||||
|  | @ -8,7 +8,7 @@ services: | ||||||
|     pull_policy: always |     pull_policy: always | ||||||
|     tty: true |     tty: true | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     image: ollama/ollama:latest |     image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest} | ||||||
| 
 | 
 | ||||||
|   open-webui: |   open-webui: | ||||||
|     build: |     build: | ||||||
|  | @ -16,7 +16,7 @@ services: | ||||||
|       args: |       args: | ||||||
|         OLLAMA_BASE_URL: '/ollama' |         OLLAMA_BASE_URL: '/ollama' | ||||||
|       dockerfile: Dockerfile |       dockerfile: Dockerfile | ||||||
|     image: ghcr.io/open-webui/open-webui:main |     image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main} | ||||||
|     container_name: open-webui |     container_name: open-webui | ||||||
|     volumes: |     volumes: | ||||||
|       - open-webui:/app/backend/data |       - open-webui:/app/backend/data | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ ollama | ||||||
| {{- end -}} | {{- end -}} | ||||||
| 
 | 
 | ||||||
| {{- define "ollama.url" -}} | {{- define "ollama.url" -}} | ||||||
| {{- printf "http://%s.%s.svc.cluster.local:%d/api" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }} | {{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }} | ||||||
| {{- end }} | {{- end }} | ||||||
| 
 | 
 | ||||||
| {{- define "chart.name" -}} | {{- define "chart.name" -}} | ||||||
|  |  | ||||||
							
								
								
									
										200
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										200
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -1,12 +1,12 @@ | ||||||
| { | { | ||||||
| 	"name": "open-webui", | 	"name": "open-webui", | ||||||
| 	"version": "0.1.116", | 	"version": "0.1.117", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "open-webui", | 			"name": "open-webui", | ||||||
| 			"version": "0.1.116", | 			"version": "0.1.117", | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"@sveltejs/adapter-node": "^1.3.1", | 				"@sveltejs/adapter-node": "^1.3.1", | ||||||
| 				"async": "^3.2.5", | 				"async": "^3.2.5", | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| 				"i18next-resources-to-backend": "^1.2.0", | 				"i18next-resources-to-backend": "^1.2.0", | ||||||
| 				"idb": "^7.1.1", | 				"idb": "^7.1.1", | ||||||
| 				"js-sha256": "^0.10.1", | 				"js-sha256": "^0.10.1", | ||||||
|  | 				"jspdf": "^2.5.1", | ||||||
| 				"katex": "^0.16.9", | 				"katex": "^0.16.9", | ||||||
| 				"marked": "^9.1.0", | 				"marked": "^9.1.0", | ||||||
| 				"svelte-sonner": "^0.3.19", | 				"svelte-sonner": "^0.3.19", | ||||||
|  | @ -1067,6 +1068,12 @@ | ||||||
| 			"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", | 			"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", | ||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/@types/raf": { | ||||||
|  | 			"version": "3.4.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", | ||||||
|  | 			"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", | ||||||
|  | 			"optional": true | ||||||
|  | 		}, | ||||||
| 		"node_modules/@types/resolve": { | 		"node_modules/@types/resolve": { | ||||||
| 			"version": "1.20.2", | 			"version": "1.20.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", | 			"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", | ||||||
|  | @ -1402,6 +1409,17 @@ | ||||||
| 			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", | 			"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", | ||||||
| 			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" | 			"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/atob": { | ||||||
|  | 			"version": "2.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", | ||||||
|  | 			"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", | ||||||
|  | 			"bin": { | ||||||
|  | 				"atob": "bin/atob.js" | ||||||
|  | 			}, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">= 4.5.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/autoprefixer": { | 		"node_modules/autoprefixer": { | ||||||
| 			"version": "10.4.19", | 			"version": "10.4.19", | ||||||
| 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", | 			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", | ||||||
|  | @ -1459,6 +1477,15 @@ | ||||||
| 			"dev": true, | 			"dev": true, | ||||||
| 			"optional": true | 			"optional": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/base64-arraybuffer": { | ||||||
|  | 			"version": "1.0.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", | ||||||
|  | 			"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">= 0.6.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/base64-js": { | 		"node_modules/base64-js": { | ||||||
| 			"version": "1.5.1", | 			"version": "1.5.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | 			"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | ||||||
|  | @ -1666,6 +1693,17 @@ | ||||||
| 				"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" | 				"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/btoa": { | ||||||
|  | 			"version": "1.2.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", | ||||||
|  | 			"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", | ||||||
|  | 			"bin": { | ||||||
|  | 				"btoa": "bin/btoa.js" | ||||||
|  | 			}, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">= 0.4.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/buffer": { | 		"node_modules/buffer": { | ||||||
| 			"version": "6.0.3", | 			"version": "6.0.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", | 			"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", | ||||||
|  | @ -1758,6 +1796,31 @@ | ||||||
| 				} | 				} | ||||||
| 			] | 			] | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/canvg": { | ||||||
|  | 			"version": "3.0.10", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", | ||||||
|  | 			"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"@babel/runtime": "^7.12.5", | ||||||
|  | 				"@types/raf": "^3.4.0", | ||||||
|  | 				"core-js": "^3.8.3", | ||||||
|  | 				"raf": "^3.4.1", | ||||||
|  | 				"regenerator-runtime": "^0.13.7", | ||||||
|  | 				"rgbcolor": "^1.0.1", | ||||||
|  | 				"stackblur-canvas": "^2.0.0", | ||||||
|  | 				"svg-pathdata": "^6.0.3" | ||||||
|  | 			}, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">=10.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"node_modules/canvg/node_modules/regenerator-runtime": { | ||||||
|  | 			"version": "0.13.11", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", | ||||||
|  | 			"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", | ||||||
|  | 			"optional": true | ||||||
|  | 		}, | ||||||
| 		"node_modules/chalk": { | 		"node_modules/chalk": { | ||||||
| 			"version": "4.1.2", | 			"version": "4.1.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", | 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", | ||||||
|  | @ -1944,6 +2007,17 @@ | ||||||
| 				"node": ">= 0.6" | 				"node": ">= 0.6" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/core-js": { | ||||||
|  | 			"version": "3.36.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz", | ||||||
|  | 			"integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==", | ||||||
|  | 			"hasInstallScript": true, | ||||||
|  | 			"optional": true, | ||||||
|  | 			"funding": { | ||||||
|  | 				"type": "opencollective", | ||||||
|  | 				"url": "https://opencollective.com/core-js" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/core-util-is": { | 		"node_modules/core-util-is": { | ||||||
| 			"version": "1.0.3", | 			"version": "1.0.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", | 			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", | ||||||
|  | @ -1964,6 +2038,15 @@ | ||||||
| 				"node": ">= 8" | 				"node": ">= 8" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/css-line-break": { | ||||||
|  | 			"version": "2.1.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", | ||||||
|  | 			"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"utrie": "^1.0.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/css-select": { | 		"node_modules/css-select": { | ||||||
| 			"version": "5.1.0", | 			"version": "5.1.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", | 			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", | ||||||
|  | @ -2156,6 +2239,12 @@ | ||||||
| 				"url": "https://github.com/fb55/domhandler?sponsor=1" | 				"url": "https://github.com/fb55/domhandler?sponsor=1" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/dompurify": { | ||||||
|  | 			"version": "2.4.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.9.tgz", | ||||||
|  | 			"integrity": "sha512-iHtnxYMotKgOTvxIqq677JsKHvCOkAFqj9x8Mek2zdeHW1XjuFKwjpmZeMaXQRQ8AbJZDbcRz/+r1QhwvFtmQg==", | ||||||
|  | 			"optional": true | ||||||
|  | 		}, | ||||||
| 		"node_modules/domutils": { | 		"node_modules/domutils": { | ||||||
| 			"version": "3.1.0", | 			"version": "3.1.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", | 			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", | ||||||
|  | @ -2584,6 +2673,11 @@ | ||||||
| 				"reusify": "^1.0.4" | 				"reusify": "^1.0.4" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/fflate": { | ||||||
|  | 			"version": "0.4.8", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", | ||||||
|  | 			"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" | ||||||
|  | 		}, | ||||||
| 		"node_modules/file-entry-cache": { | 		"node_modules/file-entry-cache": { | ||||||
| 			"version": "6.0.1", | 			"version": "6.0.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | 			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | ||||||
|  | @ -3003,6 +3097,19 @@ | ||||||
| 				"node": ">=12.0.0" | 				"node": ">=12.0.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/html2canvas": { | ||||||
|  | 			"version": "1.4.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", | ||||||
|  | 			"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"css-line-break": "^2.1.0", | ||||||
|  | 				"text-segmentation": "^1.0.3" | ||||||
|  | 			}, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">=8.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/htmlparser2": { | 		"node_modules/htmlparser2": { | ||||||
| 			"version": "8.0.2", | 			"version": "8.0.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", | ||||||
|  | @ -3403,10 +3510,27 @@ | ||||||
| 				"graceful-fs": "^4.1.6" | 				"graceful-fs": "^4.1.6" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/jspdf": { | ||||||
|  | 			"version": "2.5.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", | ||||||
|  | 			"integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"@babel/runtime": "^7.14.0", | ||||||
|  | 				"atob": "^2.1.2", | ||||||
|  | 				"btoa": "^1.2.1", | ||||||
|  | 				"fflate": "^0.4.8" | ||||||
|  | 			}, | ||||||
|  | 			"optionalDependencies": { | ||||||
|  | 				"canvg": "^3.0.6", | ||||||
|  | 				"core-js": "^3.6.0", | ||||||
|  | 				"dompurify": "^2.2.0", | ||||||
|  | 				"html2canvas": "^1.0.0-rc.5" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/katex": { | 		"node_modules/katex": { | ||||||
| 			"version": "0.16.9", | 			"version": "0.16.10", | ||||||
| 			"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz", | 			"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", | ||||||
| 			"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==", | 			"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", | ||||||
| 			"funding": [ | 			"funding": [ | ||||||
| 				"https://opencollective.com/katex", | 				"https://opencollective.com/katex", | ||||||
| 				"https://github.com/sponsors/katex" | 				"https://github.com/sponsors/katex" | ||||||
|  | @ -3971,6 +4095,12 @@ | ||||||
| 				"node": ">=8" | 				"node": ">=8" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/performance-now": { | ||||||
|  | 			"version": "2.1.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", | ||||||
|  | 			"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", | ||||||
|  | 			"optional": true | ||||||
|  | 		}, | ||||||
| 		"node_modules/periscopic": { | 		"node_modules/periscopic": { | ||||||
| 			"version": "3.1.0", | 			"version": "3.1.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", | 			"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", | ||||||
|  | @ -4391,6 +4521,15 @@ | ||||||
| 				"rimraf": "bin.js" | 				"rimraf": "bin.js" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/raf": { | ||||||
|  | 			"version": "3.4.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", | ||||||
|  | 			"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"performance-now": "^2.1.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/read-cache": { | 		"node_modules/read-cache": { | ||||||
| 			"version": "1.0.0", | 			"version": "1.0.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", | 			"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", | ||||||
|  | @ -4494,6 +4633,15 @@ | ||||||
| 				"node": ">=0.10.0" | 				"node": ">=0.10.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/rgbcolor": { | ||||||
|  | 			"version": "1.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", | ||||||
|  | 			"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">= 0.8.15" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/rimraf": { | 		"node_modules/rimraf": { | ||||||
| 			"version": "3.0.2", | 			"version": "3.0.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", | ||||||
|  | @ -4814,6 +4962,15 @@ | ||||||
| 			"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", | 			"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", | ||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/stackblur-canvas": { | ||||||
|  | 			"version": "2.7.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", | ||||||
|  | 			"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">=0.1.14" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/stream-composer": { | 		"node_modules/stream-composer": { | ||||||
| 			"version": "1.0.2", | 			"version": "1.0.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", | ||||||
|  | @ -5215,6 +5372,15 @@ | ||||||
| 				"@types/estree": "*" | 				"@types/estree": "*" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/svg-pathdata": { | ||||||
|  | 			"version": "6.0.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", | ||||||
|  | 			"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"engines": { | ||||||
|  | 				"node": ">=12.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/symlink-or-copy": { | 		"node_modules/symlink-or-copy": { | ||||||
| 			"version": "1.3.1", | 			"version": "1.3.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", | 			"resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", | ||||||
|  | @ -5353,6 +5519,15 @@ | ||||||
| 				"streamx": "^2.12.5" | 				"streamx": "^2.12.5" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/text-segmentation": { | ||||||
|  | 			"version": "1.0.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", | ||||||
|  | 			"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"utrie": "^1.0.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/text-table": { | 		"node_modules/text-table": { | ||||||
| 			"version": "0.2.0", | 			"version": "0.2.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", | 			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", | ||||||
|  | @ -5583,6 +5758,15 @@ | ||||||
| 			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", | 			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", | ||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/utrie": { | ||||||
|  | 			"version": "1.0.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", | ||||||
|  | 			"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", | ||||||
|  | 			"optional": true, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"base64-arraybuffer": "^1.0.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/uuid": { | 		"node_modules/uuid": { | ||||||
| 			"version": "9.0.1", | 			"version": "9.0.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", | 			"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", | ||||||
|  | @ -5676,9 +5860,9 @@ | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/vite": { | 		"node_modules/vite": { | ||||||
| 			"version": "4.5.2", | 			"version": "4.5.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", | 			"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", | ||||||
| 			"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", | 			"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"esbuild": "^0.18.10", | 				"esbuild": "^0.18.10", | ||||||
| 				"postcss": "^8.4.27", | 				"postcss": "^8.4.27", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
| 	"name": "open-webui", | 	"name": "open-webui", | ||||||
| 	"version": "0.1.116", | 	"version": "0.1.117", | ||||||
| 	"private": true, | 	"private": true, | ||||||
| 	"scripts": { | 	"scripts": { | ||||||
| 		"dev": "vite dev --host", | 		"dev": "vite dev --host", | ||||||
|  | @ -53,6 +53,7 @@ | ||||||
| 		"i18next-resources-to-backend": "^1.2.0", | 		"i18next-resources-to-backend": "^1.2.0", | ||||||
| 		"idb": "^7.1.1", | 		"idb": "^7.1.1", | ||||||
| 		"js-sha256": "^0.10.1", | 		"js-sha256": "^0.10.1", | ||||||
|  | 		"jspdf": "^2.5.1", | ||||||
| 		"katex": "^0.16.9", | 		"katex": "^0.16.9", | ||||||
| 		"marked": "^9.1.0", | 		"marked": "^9.1.0", | ||||||
| 		"svelte-sonner": "^0.3.19", | 		"svelte-sonner": "^0.3.19", | ||||||
|  |  | ||||||
|  | @ -58,7 +58,12 @@ export const userSignIn = async (email: string, password: string) => { | ||||||
| 	return res; | 	return res; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const userSignUp = async (name: string, email: string, password: string) => { | export const userSignUp = async ( | ||||||
|  | 	name: string, | ||||||
|  | 	email: string, | ||||||
|  | 	password: string, | ||||||
|  | 	profile_image_url: string | ||||||
|  | ) => { | ||||||
| 	let error = null; | 	let error = null; | ||||||
| 
 | 
 | ||||||
| 	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup`, { | 	const res = await fetch(`${WEBUI_API_BASE_URL}/auths/signup`, { | ||||||
|  | @ -69,7 +74,8 @@ export const userSignUp = async (name: string, email: string, password: string) | ||||||
| 		body: JSON.stringify({ | 		body: JSON.stringify({ | ||||||
| 			name: name, | 			name: name, | ||||||
| 			email: email, | 			email: email, | ||||||
| 			password: password | 			password: password, | ||||||
|  | 			profile_image_url: profile_image_url | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 		.then(async (res) => { | 		.then(async (res) => { | ||||||
|  |  | ||||||
|  | @ -22,6 +22,57 @@ export const getGravatarUrl = async (email: string) => { | ||||||
| 	return res; | 	return res; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export const downloadChatAsPDF = async (chat: object) => { | ||||||
|  | 	let error = null; | ||||||
|  | 
 | ||||||
|  | 	const blob = await fetch(`${WEBUI_API_BASE_URL}/utils/pdf`, { | ||||||
|  | 		method: 'POST', | ||||||
|  | 		headers: { | ||||||
|  | 			'Content-Type': 'application/json' | ||||||
|  | 		}, | ||||||
|  | 		body: JSON.stringify({ | ||||||
|  | 			title: chat.title, | ||||||
|  | 			messages: chat.messages | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 		.then(async (res) => { | ||||||
|  | 			if (!res.ok) throw await res.json(); | ||||||
|  | 			return res.blob(); | ||||||
|  | 		}) | ||||||
|  | 		.catch((err) => { | ||||||
|  | 			console.log(err); | ||||||
|  | 			error = err; | ||||||
|  | 			return null; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 	return blob; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getHTMLFromMarkdown = async (md: string) => { | ||||||
|  | 	let error = null; | ||||||
|  | 
 | ||||||
|  | 	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/markdown`, { | ||||||
|  | 		method: 'POST', | ||||||
|  | 		headers: { | ||||||
|  | 			'Content-Type': 'application/json' | ||||||
|  | 		}, | ||||||
|  | 		body: JSON.stringify({ | ||||||
|  | 			md: md | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 		.then(async (res) => { | ||||||
|  | 			if (!res.ok) throw await res.json(); | ||||||
|  | 			return res.json(); | ||||||
|  | 		}) | ||||||
|  | 		.catch((err) => { | ||||||
|  | 			console.log(err); | ||||||
|  | 			error = err; | ||||||
|  | 			return null; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 	return res.html; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export const downloadDatabase = async (token: string) => { | export const downloadDatabase = async (token: string) => { | ||||||
| 	let error = null; | 	let error = null; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -295,6 +295,13 @@ | ||||||
| 
 | 
 | ||||||
| 		const dropZone = document.querySelector('body'); | 		const dropZone = document.querySelector('body'); | ||||||
| 
 | 
 | ||||||
|  | 		const handleKeyDown = (event: KeyboardEvent) => { | ||||||
|  | 			if (event.key === 'Escape') { | ||||||
|  | 				console.log('Escape'); | ||||||
|  | 				dragged = false; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
| 		const onDragOver = (e) => { | 		const onDragOver = (e) => { | ||||||
| 			e.preventDefault(); | 			e.preventDefault(); | ||||||
| 			dragged = true; | 			dragged = true; | ||||||
|  | @ -350,11 +357,15 @@ | ||||||
| 			dragged = false; | 			dragged = false; | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
|  | 		window.addEventListener('keydown', handleKeyDown); | ||||||
|  | 
 | ||||||
| 		dropZone?.addEventListener('dragover', onDragOver); | 		dropZone?.addEventListener('dragover', onDragOver); | ||||||
| 		dropZone?.addEventListener('drop', onDrop); | 		dropZone?.addEventListener('drop', onDrop); | ||||||
| 		dropZone?.addEventListener('dragleave', onDragLeave); | 		dropZone?.addEventListener('dragleave', onDragLeave); | ||||||
| 
 | 
 | ||||||
| 		return () => { | 		return () => { | ||||||
|  | 			window.removeEventListener('keydown', handleKeyDown); | ||||||
|  | 
 | ||||||
| 			dropZone?.removeEventListener('dragover', onDragOver); | 			dropZone?.removeEventListener('dragover', onDragOver); | ||||||
| 			dropZone?.removeEventListener('drop', onDrop); | 			dropZone?.removeEventListener('drop', onDrop); | ||||||
| 			dropZone?.removeEventListener('dragleave', onDragLeave); | 			dropZone?.removeEventListener('dragleave', onDragLeave); | ||||||
|  |  | ||||||
|  | @ -17,7 +17,11 @@ | ||||||
| 	import { config, settings } from '$lib/stores'; | 	import { config, settings } from '$lib/stores'; | ||||||
| 	import { synthesizeOpenAISpeech } from '$lib/apis/openai'; | 	import { synthesizeOpenAISpeech } from '$lib/apis/openai'; | ||||||
| 	import { imageGenerations } from '$lib/apis/images'; | 	import { imageGenerations } from '$lib/apis/images'; | ||||||
| 	import { extractSentences } from '$lib/utils'; | 	import { | ||||||
|  | 		extractSentences, | ||||||
|  | 		revertSanitizedResponseContent, | ||||||
|  | 		sanitizeResponseContent | ||||||
|  | 	} from '$lib/utils'; | ||||||
| 
 | 
 | ||||||
| 	import Name from './Name.svelte'; | 	import Name from './Name.svelte'; | ||||||
| 	import ProfileImage from './ProfileImage.svelte'; | 	import ProfileImage from './ProfileImage.svelte'; | ||||||
|  | @ -56,7 +60,7 @@ | ||||||
| 	let loadingSpeech = false; | 	let loadingSpeech = false; | ||||||
| 	let generatingImage = false; | 	let generatingImage = false; | ||||||
| 
 | 
 | ||||||
| 	$: tokens = marked.lexer(message.content); | 	$: tokens = marked.lexer(sanitizeResponseContent(message.content)); | ||||||
| 
 | 
 | ||||||
| 	const renderer = new marked.Renderer(); | 	const renderer = new marked.Renderer(); | ||||||
| 
 | 
 | ||||||
|  | @ -405,8 +409,10 @@ | ||||||
| 								{:else} | 								{:else} | ||||||
| 									{#each tokens as token} | 									{#each tokens as token} | ||||||
| 										{#if token.type === 'code'} | 										{#if token.type === 'code'} | ||||||
| 											<!-- {token.text} --> | 											<CodeBlock | ||||||
| 											<CodeBlock lang={token.lang} code={token.text} /> | 												lang={token.lang} | ||||||
|  | 												code={revertSanitizedResponseContent(token.text)} | ||||||
|  | 											/> | ||||||
| 										{:else} | 										{:else} | ||||||
| 											{@html marked.parse(token.raw, { | 											{@html marked.parse(token.raw, { | ||||||
| 												...defaults, | 												...defaults, | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ | ||||||
| 	import { compareVersion } from '$lib/utils'; | 	import { compareVersion } from '$lib/utils'; | ||||||
| 	import { onMount, getContext } from 'svelte'; | 	import { onMount, getContext } from 'svelte'; | ||||||
| 
 | 
 | ||||||
|  | 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||||
|  | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
| 	let ollamaVersion = ''; | 	let ollamaVersion = ''; | ||||||
|  | @ -51,8 +53,10 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="flex w-full justify-between items-center"> | 			<div class="flex w-full justify-between items-center"> | ||||||
| 				<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200"> | 				<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200"> | ||||||
| 					<div> | 					<div class="flex gap-1"> | ||||||
| 						v{WEBUI_VERSION} | 						<Tooltip content={WEBUI_VERSION === '0.1.117' ? "🪖 We're just getting started." : ''}> | ||||||
|  | 							v{WEBUI_VERSION} | ||||||
|  | 						</Tooltip> | ||||||
| 
 | 
 | ||||||
| 						<a | 						<a | ||||||
| 							href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}" | 							href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}" | ||||||
|  | @ -126,7 +130,9 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> | 		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> | ||||||
| 			{$i18n.t('Created by')} | 			{#if !$WEBUI_NAME.includes('Open WebUI')} | ||||||
|  | 				<span class=" text-gray-500 dark:text-gray-300 font-medium">{$WEBUI_NAME}</span> - | ||||||
|  | 			{/if}{$i18n.t('Created by')} | ||||||
| 			<a | 			<a | ||||||
| 				class=" text-gray-500 dark:text-gray-300 font-medium" | 				class=" text-gray-500 dark:text-gray-300 font-medium" | ||||||
| 				href="https://github.com/tjbck" | 				href="https://github.com/tjbck" | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| 
 | 
 | ||||||
| 	import UpdatePassword from './Account/UpdatePassword.svelte'; | 	import UpdatePassword from './Account/UpdatePassword.svelte'; | ||||||
| 	import { getGravatarUrl } from '$lib/apis/utils'; | 	import { getGravatarUrl } from '$lib/apis/utils'; | ||||||
|  | 	import { generateInitialsImage, canvasPixelTest } from '$lib/utils'; | ||||||
| 	import { copyToClipboard } from '$lib/utils'; | 	import { copyToClipboard } from '$lib/utils'; | ||||||
| 	import Plus from '$lib/components/icons/Plus.svelte'; | 	import Plus from '$lib/components/icons/Plus.svelte'; | ||||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | 	import Tooltip from '$lib/components/common/Tooltip.svelte'; | ||||||
|  | @ -18,6 +19,8 @@ | ||||||
| 	let profileImageUrl = ''; | 	let profileImageUrl = ''; | ||||||
| 	let name = ''; | 	let name = ''; | ||||||
| 
 | 
 | ||||||
|  | 	let showAPIKeys = false; | ||||||
|  | 
 | ||||||
| 	let showJWTToken = false; | 	let showJWTToken = false; | ||||||
| 	let JWTTokenCopied = false; | 	let JWTTokenCopied = false; | ||||||
| 
 | 
 | ||||||
|  | @ -28,6 +31,12 @@ | ||||||
| 	let profileImageInputElement: HTMLInputElement; | 	let profileImageInputElement: HTMLInputElement; | ||||||
| 
 | 
 | ||||||
| 	const submitHandler = async () => { | 	const submitHandler = async () => { | ||||||
|  | 		if (name !== $user.name) { | ||||||
|  | 			if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') { | ||||||
|  | 				profileImageUrl = generateInitialsImage(name); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch( | 		const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch( | ||||||
| 			(error) => { | 			(error) => { | ||||||
| 				toast.error(error); | 				toast.error(error); | ||||||
|  | @ -125,59 +134,93 @@ | ||||||
| 			}} | 			}} | ||||||
| 		/> | 		/> | ||||||
| 
 | 
 | ||||||
| 		<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div> | 		<div class="space-y-1"> | ||||||
|  | 			<!-- <div class=" text-sm font-medium">{$i18n.t('Account')}</div> --> | ||||||
| 
 | 
 | ||||||
| 		<div class="flex space-x-5"> | 			<div class="flex space-x-5"> | ||||||
| 			<div class="flex flex-col"> | 				<div class="flex flex-col"> | ||||||
| 				<div class="self-center"> | 					<div class="self-center mt-2"> | ||||||
| 					<button | 						<button | ||||||
| 						class="relative rounded-full dark:bg-gray-700" | 							class="relative rounded-full dark:bg-gray-700" | ||||||
| 						type="button" | 							type="button" | ||||||
| 						on:click={() => { | 							on:click={() => { | ||||||
| 							profileImageInputElement.click(); | 								profileImageInputElement.click(); | ||||||
| 						}} | 							}} | ||||||
| 					> |  | ||||||
| 						<img |  | ||||||
| 							src={profileImageUrl !== '' ? profileImageUrl : '/user.png'} |  | ||||||
| 							alt="profile" |  | ||||||
| 							class=" rounded-full w-16 h-16 object-cover" |  | ||||||
| 						/> |  | ||||||
| 
 |  | ||||||
| 						<div |  | ||||||
| 							class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50" |  | ||||||
| 						> | 						> | ||||||
| 							<div class="my-auto text-gray-100"> | 							<img | ||||||
| 								<svg | 								src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(name)} | ||||||
| 									xmlns="http://www.w3.org/2000/svg" | 								alt="profile" | ||||||
| 									viewBox="0 0 20 20" | 								class=" rounded-full size-16 object-cover" | ||||||
| 									fill="currentColor" | 							/> | ||||||
| 									class="w-5 h-5" |  | ||||||
| 								> |  | ||||||
| 									<path |  | ||||||
| 										d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z" |  | ||||||
| 									/> |  | ||||||
| 								</svg> |  | ||||||
| 							</div> |  | ||||||
| 						</div> |  | ||||||
| 					</button> |  | ||||||
| 				</div> |  | ||||||
| 				<button |  | ||||||
| 					class=" text-xs text-gray-600" |  | ||||||
| 					on:click={async () => { |  | ||||||
| 						const url = await getGravatarUrl($user.email); |  | ||||||
| 
 | 
 | ||||||
| 						profileImageUrl = url; | 							<div | ||||||
| 					}}>{$i18n.t('Use Gravatar')}</button | 								class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50" | ||||||
| 				> | 							> | ||||||
|  | 								<div class="my-auto text-gray-100"> | ||||||
|  | 									<svg | ||||||
|  | 										xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 										viewBox="0 0 20 20" | ||||||
|  | 										fill="currentColor" | ||||||
|  | 										class="w-5 h-5" | ||||||
|  | 									> | ||||||
|  | 										<path | ||||||
|  | 											d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z" | ||||||
|  | 										/> | ||||||
|  | 									</svg> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
|  | 						</button> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div class="flex-1 flex flex-col self-center gap-0.5"> | ||||||
|  | 					<div class=" mb-0.5 text-sm font-medium">{$i18n.t('Profile Image')}</div> | ||||||
|  | 
 | ||||||
|  | 					<div> | ||||||
|  | 						<button | ||||||
|  | 							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850" | ||||||
|  | 							on:click={async () => { | ||||||
|  | 								if (canvasPixelTest()) { | ||||||
|  | 									profileImageUrl = generateInitialsImage(name); | ||||||
|  | 								} else { | ||||||
|  | 									toast.info( | ||||||
|  | 										$i18n.t( | ||||||
|  | 											'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.' | ||||||
|  | 										), | ||||||
|  | 										{ | ||||||
|  | 											duration: 1000 * 10 | ||||||
|  | 										} | ||||||
|  | 									); | ||||||
|  | 								} | ||||||
|  | 							}}>{$i18n.t('Use Initials')}</button | ||||||
|  | 						> | ||||||
|  | 
 | ||||||
|  | 						<button | ||||||
|  | 							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850" | ||||||
|  | 							on:click={async () => { | ||||||
|  | 								const url = await getGravatarUrl($user.email); | ||||||
|  | 
 | ||||||
|  | 								profileImageUrl = url; | ||||||
|  | 							}}>{$i18n.t('Use Gravatar')}</button | ||||||
|  | 						> | ||||||
|  | 
 | ||||||
|  | 						<button | ||||||
|  | 							class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg px-2 py-1" | ||||||
|  | 							on:click={async () => { | ||||||
|  | 								profileImageUrl = '/user.png'; | ||||||
|  | 							}}>{$i18n.t('Remove')}</button | ||||||
|  | 						> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 
 | 
 | ||||||
| 			<div class="flex-1"> | 			<div class="pt-0.5"> | ||||||
| 				<div class="flex flex-col w-full"> | 				<div class="flex flex-col w-full"> | ||||||
| 					<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div> | 					<div class=" mb-1 text-xs font-medium">{$i18n.t('Name')}</div> | ||||||
| 
 | 
 | ||||||
| 					<div class="flex-1"> | 					<div class="flex-1"> | ||||||
| 						<input | 						<input | ||||||
| 							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" | ||||||
| 							type="text" | 							type="text" | ||||||
| 							bind:value={name} | 							bind:value={name} | ||||||
| 							required | 							required | ||||||
|  | @ -187,133 +230,46 @@ | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<hr class=" dark:border-gray-700 my-4" /> | 		<div class="py-0.5"> | ||||||
| 		<UpdatePassword /> | 			<UpdatePassword /> | ||||||
|  | 		</div> | ||||||
| 
 | 
 | ||||||
| 		<hr class=" dark:border-gray-700 my-4" /> | 		<hr class=" dark:border-gray-700 my-4" /> | ||||||
| 
 | 
 | ||||||
| 		<div class="flex flex-col gap-4"> | 		<div class="flex justify-between items-center text-sm"> | ||||||
| 			<div class="justify-between w-full"> | 			<div class="  font-medium">{$i18n.t('API keys')}</div> | ||||||
| 				<div class="flex justify-between w-full"> | 			<button | ||||||
| 					<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div> | 				class=" text-xs font-medium text-gray-500" | ||||||
| 				</div> | 				type="button" | ||||||
|  | 				on:click={() => { | ||||||
|  | 					showAPIKeys = !showAPIKeys; | ||||||
|  | 				}}>{showAPIKeys ? $i18n.t('Hide') : $i18n.t('Show')}</button | ||||||
|  | 			> | ||||||
|  | 		</div> | ||||||
| 
 | 
 | ||||||
| 				<div class="flex mt-2"> | 		{#if showAPIKeys} | ||||||
| 					<div class="flex w-full"> | 			<div class="flex flex-col gap-4"> | ||||||
| 						<input | 				<div class="justify-between w-full"> | ||||||
| 							class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none" | 					<div class="flex justify-between w-full"> | ||||||
| 							type={showJWTToken ? 'text' : 'password'} | 						<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div> | ||||||
| 							value={localStorage.token} |  | ||||||
| 							disabled |  | ||||||
| 						/> |  | ||||||
| 
 |  | ||||||
| 						<button |  | ||||||
| 							class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800" |  | ||||||
| 							on:click={() => { |  | ||||||
| 								showJWTToken = !showJWTToken; |  | ||||||
| 							}} |  | ||||||
| 						> |  | ||||||
| 							{#if showJWTToken} |  | ||||||
| 								<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="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z" |  | ||||||
| 										clip-rule="evenodd" |  | ||||||
| 									/> |  | ||||||
| 									<path |  | ||||||
| 										d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z" |  | ||||||
| 									/> |  | ||||||
| 								</svg> |  | ||||||
| 							{:else} |  | ||||||
| 								<svg |  | ||||||
| 									xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 									viewBox="0 0 16 16" |  | ||||||
| 									fill="currentColor" |  | ||||||
| 									class="w-4 h-4" |  | ||||||
| 								> |  | ||||||
| 									<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" /> |  | ||||||
| 									<path |  | ||||||
| 										fill-rule="evenodd" |  | ||||||
| 										d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" |  | ||||||
| 										clip-rule="evenodd" |  | ||||||
| 									/> |  | ||||||
| 								</svg> |  | ||||||
| 							{/if} |  | ||||||
| 						</button> |  | ||||||
| 					</div> | 					</div> | ||||||
| 
 | 
 | ||||||
| 					<button | 					<div class="flex mt-2"> | ||||||
| 						class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg" |  | ||||||
| 						on:click={() => { |  | ||||||
| 							copyToClipboard(localStorage.token); |  | ||||||
| 							JWTTokenCopied = true; |  | ||||||
| 							setTimeout(() => { |  | ||||||
| 								JWTTokenCopied = false; |  | ||||||
| 							}, 2000); |  | ||||||
| 						}} |  | ||||||
| 					> |  | ||||||
| 						{#if JWTTokenCopied} |  | ||||||
| 							<svg |  | ||||||
| 								xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 								viewBox="0 0 20 20" |  | ||||||
| 								fill="currentColor" |  | ||||||
| 								class="w-4 h-4" |  | ||||||
| 							> |  | ||||||
| 								<path |  | ||||||
| 									fill-rule="evenodd" |  | ||||||
| 									d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" |  | ||||||
| 									clip-rule="evenodd" |  | ||||||
| 								/> |  | ||||||
| 							</svg> |  | ||||||
| 						{:else} |  | ||||||
| 							<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="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z" |  | ||||||
| 									clip-rule="evenodd" |  | ||||||
| 								/> |  | ||||||
| 								<path |  | ||||||
| 									fill-rule="evenodd" |  | ||||||
| 									d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z" |  | ||||||
| 									clip-rule="evenodd" |  | ||||||
| 								/> |  | ||||||
| 							</svg> |  | ||||||
| 						{/if} |  | ||||||
| 					</button> |  | ||||||
| 				</div> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="justify-between w-full"> |  | ||||||
| 				<div class="flex justify-between w-full"> |  | ||||||
| 					<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div> |  | ||||||
| 				</div> |  | ||||||
| 
 |  | ||||||
| 				<div class="flex mt-2"> |  | ||||||
| 					{#if APIKey} |  | ||||||
| 						<div class="flex w-full"> | 						<div class="flex w-full"> | ||||||
| 							<input | 							<input | ||||||
| 								class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none" | 								class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||||
| 								type={showAPIKey ? 'text' : 'password'} | 								type={showJWTToken ? 'text' : 'password'} | ||||||
| 								value={APIKey} | 								value={localStorage.token} | ||||||
| 								disabled | 								disabled | ||||||
| 							/> | 							/> | ||||||
| 
 | 
 | ||||||
| 							<button | 							<button | ||||||
| 								class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800" | 								class="px-2 transition rounded-r-lg bg-white dark:bg-gray-850" | ||||||
| 								on:click={() => { | 								on:click={() => { | ||||||
| 									showAPIKey = !showAPIKey; | 									showJWTToken = !showJWTToken; | ||||||
| 								}} | 								}} | ||||||
| 							> | 							> | ||||||
| 								{#if showAPIKey} | 								{#if showJWTToken} | ||||||
| 									<svg | 									<svg | ||||||
| 										xmlns="http://www.w3.org/2000/svg" | 										xmlns="http://www.w3.org/2000/svg" | ||||||
| 										viewBox="0 0 16 16" | 										viewBox="0 0 16 16" | ||||||
|  | @ -348,16 +304,16 @@ | ||||||
| 						</div> | 						</div> | ||||||
| 
 | 
 | ||||||
| 						<button | 						<button | ||||||
| 							class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg" | 							class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg" | ||||||
| 							on:click={() => { | 							on:click={() => { | ||||||
| 								copyToClipboard(APIKey); | 								copyToClipboard(localStorage.token); | ||||||
| 								APIKeyCopied = true; | 								JWTTokenCopied = true; | ||||||
| 								setTimeout(() => { | 								setTimeout(() => { | ||||||
| 									APIKeyCopied = false; | 									JWTTokenCopied = false; | ||||||
| 								}, 2000); | 								}, 2000); | ||||||
| 							}} | 							}} | ||||||
| 						> | 						> | ||||||
| 							{#if APIKeyCopied} | 							{#if JWTTokenCopied} | ||||||
| 								<svg | 								<svg | ||||||
| 									xmlns="http://www.w3.org/2000/svg" | 									xmlns="http://www.w3.org/2000/svg" | ||||||
| 									viewBox="0 0 20 20" | 									viewBox="0 0 20 20" | ||||||
|  | @ -390,45 +346,146 @@ | ||||||
| 								</svg> | 								</svg> | ||||||
| 							{/if} | 							{/if} | ||||||
| 						</button> | 						</button> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="justify-between w-full"> | ||||||
|  | 					<div class="flex justify-between w-full"> | ||||||
|  | 						<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div> | ||||||
|  | 					</div> | ||||||
|  | 
 | ||||||
|  | 					<div class="flex mt-2"> | ||||||
|  | 						{#if APIKey} | ||||||
|  | 							<div class="flex w-full"> | ||||||
|  | 								<input | ||||||
|  | 									class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||||
|  | 									type={showAPIKey ? 'text' : 'password'} | ||||||
|  | 									value={APIKey} | ||||||
|  | 									disabled | ||||||
|  | 								/> | ||||||
|  | 
 | ||||||
|  | 								<button | ||||||
|  | 									class="px-2 transition rounded-r-lg bg-white dark:bg-gray-850" | ||||||
|  | 									on:click={() => { | ||||||
|  | 										showAPIKey = !showAPIKey; | ||||||
|  | 									}} | ||||||
|  | 								> | ||||||
|  | 									{#if showAPIKey} | ||||||
|  | 										<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="M3.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z" | ||||||
|  | 												clip-rule="evenodd" | ||||||
|  | 											/> | ||||||
|  | 											<path | ||||||
|  | 												d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z" | ||||||
|  | 											/> | ||||||
|  | 										</svg> | ||||||
|  | 									{:else} | ||||||
|  | 										<svg | ||||||
|  | 											xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 											viewBox="0 0 16 16" | ||||||
|  | 											fill="currentColor" | ||||||
|  | 											class="w-4 h-4" | ||||||
|  | 										> | ||||||
|  | 											<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" /> | ||||||
|  | 											<path | ||||||
|  | 												fill-rule="evenodd" | ||||||
|  | 												d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" | ||||||
|  | 												clip-rule="evenodd" | ||||||
|  | 											/> | ||||||
|  | 										</svg> | ||||||
|  | 									{/if} | ||||||
|  | 								</button> | ||||||
|  | 							</div> | ||||||
| 
 | 
 | ||||||
| 						<Tooltip content="Create new key"> |  | ||||||
| 							<button | 							<button | ||||||
| 								class=" px-1.5 py-1 dark:hover:bg-gray-800transition rounded-lg" | 								class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-850 transition rounded-lg" | ||||||
|  | 								on:click={() => { | ||||||
|  | 									copyToClipboard(APIKey); | ||||||
|  | 									APIKeyCopied = true; | ||||||
|  | 									setTimeout(() => { | ||||||
|  | 										APIKeyCopied = false; | ||||||
|  | 									}, 2000); | ||||||
|  | 								}} | ||||||
|  | 							> | ||||||
|  | 								{#if APIKeyCopied} | ||||||
|  | 									<svg | ||||||
|  | 										xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 										viewBox="0 0 20 20" | ||||||
|  | 										fill="currentColor" | ||||||
|  | 										class="w-4 h-4" | ||||||
|  | 									> | ||||||
|  | 										<path | ||||||
|  | 											fill-rule="evenodd" | ||||||
|  | 											d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" | ||||||
|  | 											clip-rule="evenodd" | ||||||
|  | 										/> | ||||||
|  | 									</svg> | ||||||
|  | 								{:else} | ||||||
|  | 									<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="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z" | ||||||
|  | 											clip-rule="evenodd" | ||||||
|  | 										/> | ||||||
|  | 										<path | ||||||
|  | 											fill-rule="evenodd" | ||||||
|  | 											d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z" | ||||||
|  | 											clip-rule="evenodd" | ||||||
|  | 										/> | ||||||
|  | 									</svg> | ||||||
|  | 								{/if} | ||||||
|  | 							</button> | ||||||
|  | 
 | ||||||
|  | 							<Tooltip content="Create new key"> | ||||||
|  | 								<button | ||||||
|  | 									class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg" | ||||||
|  | 									on:click={() => { | ||||||
|  | 										createAPIKeyHandler(); | ||||||
|  | 									}} | ||||||
|  | 								> | ||||||
|  | 									<svg | ||||||
|  | 										xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 										fill="none" | ||||||
|  | 										viewBox="0 0 24 24" | ||||||
|  | 										stroke-width="2" | ||||||
|  | 										stroke="currentColor" | ||||||
|  | 										class="size-4" | ||||||
|  | 									> | ||||||
|  | 										<path | ||||||
|  | 											stroke-linecap="round" | ||||||
|  | 											stroke-linejoin="round" | ||||||
|  | 											d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" | ||||||
|  | 										/> | ||||||
|  | 									</svg> | ||||||
|  | 								</button> | ||||||
|  | 							</Tooltip> | ||||||
|  | 						{:else} | ||||||
|  | 							<button | ||||||
|  | 								class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition" | ||||||
| 								on:click={() => { | 								on:click={() => { | ||||||
| 									createAPIKeyHandler(); | 									createAPIKeyHandler(); | ||||||
| 								}} | 								}} | ||||||
| 							> | 							> | ||||||
| 								<svg | 								<Plus strokeWidth="2" className=" size-3.5" /> | ||||||
| 									xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 									fill="none" |  | ||||||
| 									viewBox="0 0 24 24" |  | ||||||
| 									stroke-width="2" |  | ||||||
| 									stroke="currentColor" |  | ||||||
| 									class="size-4" |  | ||||||
| 								> |  | ||||||
| 									<path |  | ||||||
| 										stroke-linecap="round" |  | ||||||
| 										stroke-linejoin="round" |  | ||||||
| 										d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" |  | ||||||
| 									/> |  | ||||||
| 								</svg> |  | ||||||
| 							</button> |  | ||||||
| 						</Tooltip> |  | ||||||
| 					{:else} |  | ||||||
| 						<button |  | ||||||
| 							class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition" |  | ||||||
| 							on:click={() => { |  | ||||||
| 								createAPIKeyHandler(); |  | ||||||
| 							}} |  | ||||||
| 						> |  | ||||||
| 							<Plus strokeWidth="2" className=" size-3.5" /> |  | ||||||
| 
 | 
 | ||||||
| 							Create new secret key</button | 								Create new secret key</button | ||||||
| 						> | 							> | ||||||
| 					{/if} | 						{/if} | ||||||
|  | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		{/if} | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<div class="flex justify-end pt-3 text-sm font-medium"> | 	<div class="flex justify-end pt-3 text-sm font-medium"> | ||||||
|  |  | ||||||
|  | @ -185,7 +185,7 @@ | ||||||
| 
 | 
 | ||||||
| 			<div> | 			<div> | ||||||
| 				<div class=" py-0.5 flex w-full justify-between"> | 				<div class=" py-0.5 flex w-full justify-between"> | ||||||
| 					<div class=" self-center text-xs font-medium">{$i18n.t('Desktop Notifications')}</div> | 					<div class=" self-center text-xs font-medium">{$i18n.t('Notifications')}</div> | ||||||
| 
 | 
 | ||||||
| 					<button | 					<button | ||||||
| 						class="p-1 px-3 text-xs flex rounded transition" | 						class="p-1 px-3 text-xs flex rounded transition" | ||||||
|  |  | ||||||
|  | @ -1,9 +1,6 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { getContext, onMount } from 'svelte'; | 	import { getContext, onMount } from 'svelte'; | ||||||
| 
 | 
 | ||||||
| 	import fileSaver from 'file-saver'; |  | ||||||
| 	const { saveAs } = fileSaver; |  | ||||||
| 
 |  | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats'; | 	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats'; | ||||||
| 	import { chatId, modelfiles } from '$lib/stores'; | 	import { chatId, modelfiles } from '$lib/stores'; | ||||||
|  | @ -55,29 +52,18 @@ | ||||||
| 		); | 		); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const downloadChat = async () => { |  | ||||||
| 		const _chat = chat.chat; |  | ||||||
| 		console.log('download', chat); |  | ||||||
| 
 |  | ||||||
| 		const chatText = _chat.messages.reduce((a, message, i, arr) => { |  | ||||||
| 			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`; |  | ||||||
| 		}, ''); |  | ||||||
| 
 |  | ||||||
| 		let blob = new Blob([chatText], { |  | ||||||
| 			type: 'text/plain' |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		saveAs(blob, `chat-${_chat.title}.txt`); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	export let show = false; | 	export let show = false; | ||||||
| 
 | 
 | ||||||
| 	onMount(async () => { | 	$: if (show) { | ||||||
| 		chatId.subscribe(async (value) => { | 		(async () => { | ||||||
| 			chat = await getChatById(localStorage.token, value); | 			if ($chatId) { | ||||||
| 			console.log(chat); | 				chat = await getChatById(localStorage.token, $chatId); | ||||||
| 		}); | 			} else { | ||||||
| 	}); | 				chat = null; | ||||||
|  | 				console.log(chat); | ||||||
|  | 			} | ||||||
|  | 		})(); | ||||||
|  | 	} | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <Modal bind:show size="sm"> | <Modal bind:show size="sm"> | ||||||
|  | @ -159,19 +145,6 @@ | ||||||
| 								{/if} | 								{/if} | ||||||
| 							</button> | 							</button> | ||||||
| 						</div> | 						</div> | ||||||
| 						<div class="flex gap-1 mt-1.5"> |  | ||||||
| 							<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div> |  | ||||||
| 							<button |  | ||||||
| 								class=" text-right rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline" |  | ||||||
| 								type="button" |  | ||||||
| 								on:click={() => { |  | ||||||
| 									downloadChat(); |  | ||||||
| 									show = false; |  | ||||||
| 								}} |  | ||||||
| 							> |  | ||||||
| 								{$i18n.t('Download as a File')} |  | ||||||
| 							</button> |  | ||||||
| 						</div> |  | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| 	export let show = true; | 	export let show = true; | ||||||
| 	export let size = 'md'; | 	export let size = 'md'; | ||||||
| 
 | 
 | ||||||
|  | 	let modalElement = null; | ||||||
| 	let mounted = false; | 	let mounted = false; | ||||||
| 
 | 
 | ||||||
| 	const sizeToWidth = (size) => { | 	const sizeToWidth = (size) => { | ||||||
|  | @ -19,14 +20,23 @@ | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|  | 	const handleKeyDown = (event: KeyboardEvent) => { | ||||||
|  | 		if (event.key === 'Escape') { | ||||||
|  | 			console.log('Escape'); | ||||||
|  | 			show = false; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
| 	onMount(() => { | 	onMount(() => { | ||||||
| 		mounted = true; | 		mounted = true; | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	$: if (mounted) { | 	$: if (mounted) { | ||||||
| 		if (show) { | 		if (show) { | ||||||
|  | 			window.addEventListener('keydown', handleKeyDown); | ||||||
| 			document.body.style.overflow = 'hidden'; | 			document.body.style.overflow = 'hidden'; | ||||||
| 		} else { | 		} else { | ||||||
|  | 			window.removeEventListener('keydown', handleKeyDown); | ||||||
| 			document.body.style.overflow = 'unset'; | 			document.body.style.overflow = 'unset'; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -36,6 +46,7 @@ | ||||||
| 	<!-- svelte-ignore a11y-click-events-have-key-events --> | 	<!-- svelte-ignore a11y-click-events-have-key-events --> | ||||||
| 	<!-- svelte-ignore a11y-no-static-element-interactions --> | 	<!-- svelte-ignore a11y-no-static-element-interactions --> | ||||||
| 	<div | 	<div | ||||||
|  | 		bind:this={modalElement} | ||||||
| 		class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain" | 		class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain" | ||||||
| 		in:fade={{ duration: 10 }} | 		in:fade={{ duration: 10 }} | ||||||
| 		on:click={() => { | 		on:click={() => { | ||||||
|  |  | ||||||
|  | @ -2,21 +2,13 @@ | ||||||
| 	import { getContext } from 'svelte'; | 	import { getContext } from 'svelte'; | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 
 | 
 | ||||||
| 	import { Separator } from 'bits-ui'; |  | ||||||
| 	import { getChatById, shareChatById } from '$lib/apis/chats'; |  | ||||||
| 	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores'; | 	import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import { slide } from 'svelte/transition'; | 	import { slide } from 'svelte/transition'; | ||||||
| 	import ShareChatModal from '../chat/ShareChatModal.svelte'; | 	import ShareChatModal from '../chat/ShareChatModal.svelte'; | ||||||
| 	import TagInput from '../common/Tags/TagInput.svelte'; |  | ||||||
| 	import ModelSelector from '../chat/ModelSelector.svelte'; | 	import ModelSelector from '../chat/ModelSelector.svelte'; | ||||||
| 	import Tooltip from '../common/Tooltip.svelte'; | 	import Tooltip from '../common/Tooltip.svelte'; | ||||||
| 
 |  | ||||||
| 	import EllipsisVertical from '../icons/EllipsisVertical.svelte'; |  | ||||||
| 	import ChevronDown from '../icons/ChevronDown.svelte'; |  | ||||||
| 	import ChevronUpDown from '../icons/ChevronUpDown.svelte'; |  | ||||||
| 	import Menu from './Navbar/Menu.svelte'; | 	import Menu from './Navbar/Menu.svelte'; | ||||||
| 	import TagChatModal from '../chat/TagChatModal.svelte'; |  | ||||||
| 
 | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
|  | @ -24,6 +16,7 @@ | ||||||
| 	export let title: string = $WEBUI_NAME; | 	export let title: string = $WEBUI_NAME; | ||||||
| 	export let shareEnabled: boolean = false; | 	export let shareEnabled: boolean = false; | ||||||
| 
 | 
 | ||||||
|  | 	export let chat; | ||||||
| 	export let selectedModels; | 	export let selectedModels; | ||||||
| 
 | 
 | ||||||
| 	export let tags = []; | 	export let tags = []; | ||||||
|  | @ -33,63 +26,15 @@ | ||||||
| 	export let showModelSelector = true; | 	export let showModelSelector = true; | ||||||
| 
 | 
 | ||||||
| 	let showShareChatModal = false; | 	let showShareChatModal = false; | ||||||
| 	let showTagChatModal = false; | 	let showDownloadChatModal = false; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ShareChatModal bind:show={showShareChatModal} /> | <ShareChatModal bind:show={showShareChatModal} /> | ||||||
| <!-- <TagChatModal bind:show={showTagChatModal} {tags} {deleteTag} {addTag} /> --> |  | ||||||
| <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30"> | <nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30"> | ||||||
| 	<div | 	<div | ||||||
| 		class=" flex {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'}  | 		class=" flex {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'}  | ||||||
| 		 w-full mx-auto px-3" | 		 w-full mx-auto px-3" | ||||||
| 	> | 	> | ||||||
| 		<!-- {#if shareEnabled} |  | ||||||
| 			<div class="flex items-center w-full max-w-full"> |  | ||||||
| 				<div class=" flex-1 self-center font-medium line-clamp-1"> |  | ||||||
| 					<div> |  | ||||||
| 						{title != '' ? title : $WEBUI_NAME} |  | ||||||
| 					</div> |  | ||||||
| 				</div> |  | ||||||
| 				<div class="pl-2 self-center flex items-center"> |  | ||||||
| 					<div class=" mr-1"> |  | ||||||
| 						<Tags {tags} {deleteTag} {addTag} /> |  | ||||||
| 					</div> |  | ||||||
| 
 |  | ||||||
| 					<Tooltip content="Share"> |  | ||||||
| 						<button |  | ||||||
| 							class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition" |  | ||||||
| 							on:click={async () => { |  | ||||||
| 								showShareChatModal = !showShareChatModal; |  | ||||||
| 
 |  | ||||||
| 								// console.log(showShareChatModal); |  | ||||||
| 							}} |  | ||||||
| 						> |  | ||||||
| 							<div class=" m-auto self-center"> |  | ||||||
| 								<svg |  | ||||||
| 									xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 									viewBox="0 0 24 24" |  | ||||||
| 									fill="currentColor" |  | ||||||
| 									class="w-4 h-4" |  | ||||||
| 								> |  | ||||||
| 									<path |  | ||||||
| 										fill-rule="evenodd" |  | ||||||
| 										d="M15.75 4.5a3 3 0 1 1 .825 2.066l-8.421 4.679a3.002 3.002 0 0 1 0 1.51l8.421 4.679a3 3 0 1 1-.729 1.31l-8.421-4.678a3 3 0 1 1 0-4.132l8.421-4.679a3 3 0 0 1-.096-.755Z" |  | ||||||
| 										clip-rule="evenodd" |  | ||||||
| 									/> |  | ||||||
| 								</svg> |  | ||||||
| 							</div> |  | ||||||
| 						</button> |  | ||||||
| 					</Tooltip> |  | ||||||
| 				</div> |  | ||||||
| 			</div> |  | ||||||
| 		{/if} --> |  | ||||||
| 
 |  | ||||||
| 		<!-- <div class=" flex-1 self-center font-medium line-clamp-1"> |  | ||||||
| 			<div> |  | ||||||
| 				{title != '' ? title : $WEBUI_NAME} |  | ||||||
| 			</div> |  | ||||||
| 		</div> --> |  | ||||||
| 
 |  | ||||||
| 		<div class="flex items-center w-full max-w-full"> | 		<div class="flex items-center w-full max-w-full"> | ||||||
| 			<div class="flex-1 overflow-hidden max-w-full"> | 			<div class="flex-1 overflow-hidden max-w-full"> | ||||||
| 				{#if showModelSelector} | 				{#if showModelSelector} | ||||||
|  | @ -132,10 +77,14 @@ | ||||||
| 					</Tooltip> | 					</Tooltip> | ||||||
| 				{:else} | 				{:else} | ||||||
| 					<Menu | 					<Menu | ||||||
|  | 						{chat} | ||||||
| 						{shareEnabled} | 						{shareEnabled} | ||||||
| 						shareHandler={() => { | 						shareHandler={() => { | ||||||
| 							showShareChatModal = !showShareChatModal; | 							showShareChatModal = !showShareChatModal; | ||||||
| 						}} | 						}} | ||||||
|  | 						downloadHandler={() => { | ||||||
|  | 							showDownloadChatModal = !showDownloadChatModal; | ||||||
|  | 						}} | ||||||
| 						{tags} | 						{tags} | ||||||
| 						{deleteTag} | 						{deleteTag} | ||||||
| 						{addTag} | 						{addTag} | ||||||
|  |  | ||||||
|  | @ -1,23 +1,71 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { DropdownMenu } from 'bits-ui'; | 	import { DropdownMenu } from 'bits-ui'; | ||||||
|  | 
 | ||||||
|  | 	import fileSaver from 'file-saver'; | ||||||
|  | 	const { saveAs } = fileSaver; | ||||||
|  | 
 | ||||||
|  | 	import { jsPDF } from 'jspdf'; | ||||||
|  | 
 | ||||||
|  | 	import { showSettings } from '$lib/stores'; | ||||||
| 	import { flyAndScale } from '$lib/utils/transitions'; | 	import { flyAndScale } from '$lib/utils/transitions'; | ||||||
| 
 | 
 | ||||||
| 	import Dropdown from '$lib/components/common/Dropdown.svelte'; | 	import Dropdown from '$lib/components/common/Dropdown.svelte'; | ||||||
| 	import GarbageBin from '$lib/components/icons/GarbageBin.svelte'; |  | ||||||
| 	import Pencil from '$lib/components/icons/Pencil.svelte'; |  | ||||||
| 	import Tooltip from '$lib/components/common/Tooltip.svelte'; |  | ||||||
| 	import { showSettings } from '$lib/stores'; |  | ||||||
| 	import Tags from '$lib/components/common/Tags.svelte'; | 	import Tags from '$lib/components/common/Tags.svelte'; | ||||||
|  | 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||||
|  | 	import { downloadChatAsPDF } from '$lib/apis/utils'; | ||||||
| 
 | 
 | ||||||
| 	export let shareEnabled: boolean = false; | 	export let shareEnabled: boolean = false; | ||||||
| 	export let shareHandler: Function; | 	export let shareHandler: Function; | ||||||
|  | 	export let downloadHandler: Function; | ||||||
|  | 
 | ||||||
| 	// export let tagHandler: Function; | 	// export let tagHandler: Function; | ||||||
| 
 | 
 | ||||||
|  | 	export let chat; | ||||||
| 	export let tags; | 	export let tags; | ||||||
| 	export let deleteTag: Function; | 	export let deleteTag: Function; | ||||||
| 	export let addTag: Function; | 	export let addTag: Function; | ||||||
| 
 | 
 | ||||||
| 	export let onClose: Function = () => {}; | 	export let onClose: Function = () => {}; | ||||||
|  | 
 | ||||||
|  | 	const downloadTxt = async () => { | ||||||
|  | 		const _chat = chat.chat; | ||||||
|  | 		console.log('download', chat); | ||||||
|  | 
 | ||||||
|  | 		const chatText = _chat.messages.reduce((a, message, i, arr) => { | ||||||
|  | 			return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`; | ||||||
|  | 		}, ''); | ||||||
|  | 
 | ||||||
|  | 		let blob = new Blob([chatText], { | ||||||
|  | 			type: 'text/plain' | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		saveAs(blob, `chat-${_chat.title}.txt`); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const downloadPdf = async () => { | ||||||
|  | 		const _chat = chat.chat; | ||||||
|  | 		console.log('download', chat); | ||||||
|  | 
 | ||||||
|  | 		const blob = await downloadChatAsPDF(_chat); | ||||||
|  | 
 | ||||||
|  | 		// Create a URL for the blob | ||||||
|  | 		const url = window.URL.createObjectURL(blob); | ||||||
|  | 
 | ||||||
|  | 		// Create a link element to trigger the download | ||||||
|  | 		const a = document.createElement('a'); | ||||||
|  | 		a.href = url; | ||||||
|  | 		a.download = `chat-${_chat.title}.pdf`; | ||||||
|  | 
 | ||||||
|  | 		// Append the link to the body and click it programmatically | ||||||
|  | 		document.body.appendChild(a); | ||||||
|  | 		a.click(); | ||||||
|  | 
 | ||||||
|  | 		// Remove the link from the body | ||||||
|  | 		document.body.removeChild(a); | ||||||
|  | 
 | ||||||
|  | 		// Revoke the URL to release memory | ||||||
|  | 		window.URL.revokeObjectURL(url); | ||||||
|  | 	}; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <Dropdown | <Dropdown | ||||||
|  | @ -31,14 +79,14 @@ | ||||||
| 
 | 
 | ||||||
| 	<div slot="content"> | 	<div slot="content"> | ||||||
| 		<DropdownMenu.Content | 		<DropdownMenu.Content | ||||||
| 			class="w-full max-w-[150px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow" | 			class="w-full max-w-[200px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg" | ||||||
| 			sideOffset={8} | 			sideOffset={8} | ||||||
| 			side="bottom" | 			side="bottom" | ||||||
| 			align="end" | 			align="end" | ||||||
| 			transition={flyAndScale} | 			transition={flyAndScale} | ||||||
| 		> | 		> | ||||||
| 			<DropdownMenu.Item | 			<DropdownMenu.Item | ||||||
| 				class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer" | 				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||||
| 				on:click={async () => { | 				on:click={async () => { | ||||||
| 					await showSettings.set(!$showSettings); | 					await showSettings.set(!$showSettings); | ||||||
| 				}} | 				}} | ||||||
|  | @ -49,7 +97,7 @@ | ||||||
| 					viewBox="0 0 24 24" | 					viewBox="0 0 24 24" | ||||||
| 					stroke-width="1.5" | 					stroke-width="1.5" | ||||||
| 					stroke="currentColor" | 					stroke="currentColor" | ||||||
| 					class="w-5 h-5" | 					class="size-4" | ||||||
| 				> | 				> | ||||||
| 					<path | 					<path | ||||||
| 						stroke-linecap="round" | 						stroke-linecap="round" | ||||||
|  | @ -67,7 +115,7 @@ | ||||||
| 
 | 
 | ||||||
| 			{#if shareEnabled} | 			{#if shareEnabled} | ||||||
| 				<DropdownMenu.Item | 				<DropdownMenu.Item | ||||||
| 					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer" | 					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||||
| 					on:click={() => { | 					on:click={() => { | ||||||
| 						shareHandler(); | 						shareHandler(); | ||||||
| 					}} | 					}} | ||||||
|  | @ -87,7 +135,59 @@ | ||||||
| 					<div class="flex items-center">Share</div> | 					<div class="flex items-center">Share</div> | ||||||
| 				</DropdownMenu.Item> | 				</DropdownMenu.Item> | ||||||
| 
 | 
 | ||||||
| 				<hr class="border-gray-100 dark:border-gray-800 my-1" /> | 				<!-- <DropdownMenu.Item | ||||||
|  | 					class="flex gap-2 items-center px-3 py-2 text-sm  font-medium cursor-pointer" | ||||||
|  | 					on:click={() => { | ||||||
|  | 						downloadHandler(); | ||||||
|  | 					}} | ||||||
|  | 				/> --> | ||||||
|  | 				<DropdownMenu.Sub> | ||||||
|  | 					<DropdownMenu.SubTrigger | ||||||
|  | 						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||||
|  | 					> | ||||||
|  | 						<svg | ||||||
|  | 							xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 							fill="none" | ||||||
|  | 							viewBox="0 0 24 24" | ||||||
|  | 							stroke-width="1.5" | ||||||
|  | 							stroke="currentColor" | ||||||
|  | 							class="size-4" | ||||||
|  | 						> | ||||||
|  | 							<path | ||||||
|  | 								stroke-linecap="round" | ||||||
|  | 								stroke-linejoin="round" | ||||||
|  | 								d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" | ||||||
|  | 							/> | ||||||
|  | 						</svg> | ||||||
|  | 
 | ||||||
|  | 						<div class="flex items-center">Download</div> | ||||||
|  | 					</DropdownMenu.SubTrigger> | ||||||
|  | 					<DropdownMenu.SubContent | ||||||
|  | 						class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg" | ||||||
|  | 						transition={flyAndScale} | ||||||
|  | 						sideOffset={8} | ||||||
|  | 					> | ||||||
|  | 						<DropdownMenu.Item | ||||||
|  | 							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||||
|  | 							on:click={() => { | ||||||
|  | 								downloadTxt(); | ||||||
|  | 							}} | ||||||
|  | 						> | ||||||
|  | 							<div class="flex items-center line-clamp-1">Plain text (.txt)</div> | ||||||
|  | 						</DropdownMenu.Item> | ||||||
|  | 
 | ||||||
|  | 						<DropdownMenu.Item | ||||||
|  | 							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer dark:hover:bg-gray-850 rounded-md" | ||||||
|  | 							on:click={() => { | ||||||
|  | 								downloadPdf(); | ||||||
|  | 							}} | ||||||
|  | 						> | ||||||
|  | 							<div class="flex items-center line-clamp-1">PDF document (.pdf)</div> | ||||||
|  | 						</DropdownMenu.Item> | ||||||
|  | 					</DropdownMenu.SubContent> | ||||||
|  | 				</DropdownMenu.Sub> | ||||||
|  | 
 | ||||||
|  | 				<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" /> | ||||||
| 
 | 
 | ||||||
| 				<div class="flex p-1"> | 				<div class="flex p-1"> | ||||||
| 					<Tags {tags} {deleteTag} {addTag} /> | 					<Tags {tags} {deleteTag} {addTag} /> | ||||||
|  |  | ||||||
|  | @ -581,7 +581,7 @@ | ||||||
| 							<div class="py-2 w-full"> | 							<div class="py-2 w-full"> | ||||||
| 								{#if $user.role === 'admin'} | 								{#if $user.role === 'admin'} | ||||||
| 									<button | 									<button | ||||||
| 										class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition" | 										class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition" | ||||||
| 										on:click={() => { | 										on:click={() => { | ||||||
| 											goto('/admin'); | 											goto('/admin'); | ||||||
| 											showDropdown = false; | 											showDropdown = false; | ||||||
|  | @ -607,7 +607,7 @@ | ||||||
| 									</button> | 									</button> | ||||||
| 
 | 
 | ||||||
| 									<button | 									<button | ||||||
| 										class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition" | 										class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition" | ||||||
| 										on:click={() => { | 										on:click={() => { | ||||||
| 											goto('/playground'); | 											goto('/playground'); | ||||||
| 											showDropdown = false; | 											showDropdown = false; | ||||||
|  | @ -634,7 +634,7 @@ | ||||||
| 								{/if} | 								{/if} | ||||||
| 
 | 
 | ||||||
| 								<button | 								<button | ||||||
| 									class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition" | 									class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition" | ||||||
| 									on:click={async () => { | 									on:click={async () => { | ||||||
| 										await showSettings.set(true); | 										await showSettings.set(true); | ||||||
| 										showDropdown = false; | 										showDropdown = false; | ||||||
|  | @ -669,7 +669,7 @@ | ||||||
| 
 | 
 | ||||||
| 							<div class="py-2 w-full"> | 							<div class="py-2 w-full"> | ||||||
| 								<button | 								<button | ||||||
| 									class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition" | 									class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition" | ||||||
| 									on:click={() => { | 									on:click={() => { | ||||||
| 										localStorage.removeItem('token'); | 										localStorage.removeItem('token'); | ||||||
| 										location.href = '/auth'; | 										location.href = '/auth'; | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Изтрито {tagName}", | 	"Deleted {tagName}": "Изтрито {tagName}", | ||||||
| 	"Description": "Описание", | 	"Description": "Описание", | ||||||
| 	"Desktop Notifications": "Десктоп Известия", | 	"Notifications": "Десктоп Известия", | ||||||
| 	"Disabled": "Деактивиран", | 	"Disabled": "Деактивиран", | ||||||
| 	"Discover a modelfile": "Откриване на модфайл", | 	"Discover a modelfile": "Откриване на модфайл", | ||||||
| 	"Discover a prompt": "Откриване на промпт", | 	"Discover a prompt": "Откриване на промпт", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Esborrat {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Esborrat {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Esborrat {tagName}", | 	"Deleted {tagName}": "Esborrat {tagName}", | ||||||
| 	"Description": "Descripció", | 	"Description": "Descripció", | ||||||
| 	"Desktop Notifications": "Notificacions d'Escriptori", | 	"Notifications": "Notificacions d'Escriptori", | ||||||
| 	"Disabled": "Desactivat", | 	"Disabled": "Desactivat", | ||||||
| 	"Discover a modelfile": "Descobreix un fitxer de model", | 	"Discover a modelfile": "Descobreix un fitxer de model", | ||||||
| 	"Discover a prompt": "Descobreix un prompt", | 	"Discover a prompt": "Descobreix un prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht", | ||||||
| 	"Deleted {tagName}": "{tagName} gelöscht", | 	"Deleted {tagName}": "{tagName} gelöscht", | ||||||
| 	"Description": "Beschreibung", | 	"Description": "Beschreibung", | ||||||
| 	"Desktop Notifications": "Desktop-Benachrichtigungen", | 	"Notifications": "Desktop-Benachrichtigungen", | ||||||
| 	"Disabled": "Deaktiviert", | 	"Disabled": "Deaktiviert", | ||||||
| 	"Discover a modelfile": "Eine Modelfiles entdecken", | 	"Discover a modelfile": "Eine Modelfiles entdecken", | ||||||
| 	"Discover a prompt": "Einen Prompt entdecken", | 	"Discover a prompt": "Einen Prompt entdecken", | ||||||
|  |  | ||||||
							
								
								
									
										64
									
								
								src/lib/i18n/locales/en-GB/translation.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/lib/i18n/locales/en-GB/translation.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | ||||||
|  | { | ||||||
|  | 	"analyze": "analyse", | ||||||
|  | 	"analyzed": "analysed", | ||||||
|  | 	"analyzes": "analyses", | ||||||
|  | 	"apologize": "apologise", | ||||||
|  | 	"apologized": "apologised", | ||||||
|  | 	"apologizes": "apologises", | ||||||
|  | 	"apologizing": "apologising", | ||||||
|  | 	"canceled": "cancelled", | ||||||
|  | 	"canceling": "cancelling", | ||||||
|  | 	"capitalize": "capitalise", | ||||||
|  | 	"capitalized": "capitalised", | ||||||
|  | 	"capitalizes": "capitalises", | ||||||
|  | 	"center": "centre", | ||||||
|  | 	"centered": "centred", | ||||||
|  | 	"color": "colour", | ||||||
|  | 	"colorize": "colourise", | ||||||
|  | 	"customize": "customise", | ||||||
|  | 	"customizes": "customises", | ||||||
|  | 	"defense": "defence", | ||||||
|  | 	"dialog": "dialogue", | ||||||
|  | 	"emphasize": "emphasise", | ||||||
|  | 	"emphasized": "emphasised", | ||||||
|  | 	"emphasizes": "emphasises", | ||||||
|  | 	"favor": "favour", | ||||||
|  | 	"favorable": "favourable", | ||||||
|  | 	"favorite": "favourite", | ||||||
|  | 	"favoritism": "favouritism", | ||||||
|  | 	"labor": "labour", | ||||||
|  | 	"labored": "laboured", | ||||||
|  | 	"laboring": "labouring", | ||||||
|  | 	"maximize": "maximise", | ||||||
|  | 	"maximizes": "maximises", | ||||||
|  | 	"minimize": "minimise", | ||||||
|  | 	"minimizes": "minimises", | ||||||
|  | 	"neighbor": "neighbour", | ||||||
|  | 	"neighborhood": "neighbourhood", | ||||||
|  | 	"offense": "offence", | ||||||
|  | 	"organize": "organise", | ||||||
|  | 	"organizes": "organises", | ||||||
|  | 	"personalize": "personalise", | ||||||
|  | 	"personalizes": "personalises", | ||||||
|  | 	"program": "programme", | ||||||
|  | 	"programmed": "programmed", | ||||||
|  | 	"programs": "programmes", | ||||||
|  | 	"quantization": "quantisation", | ||||||
|  | 	"quantize": "quantise", | ||||||
|  | 	"randomize": "randomise", | ||||||
|  | 	"randomizes": "randomises", | ||||||
|  | 	"realize": "realise", | ||||||
|  | 	"realizes": "realises", | ||||||
|  | 	"recognize": "recognise", | ||||||
|  | 	"recognizes": "recognises", | ||||||
|  | 	"summarize": "summarise", | ||||||
|  | 	"summarizes": "summarises", | ||||||
|  | 	"theater": "theatre", | ||||||
|  | 	"theaters": "theatres", | ||||||
|  | 	"toward": "towards", | ||||||
|  | 	"traveled": "travelled", | ||||||
|  | 	"traveler": "traveller", | ||||||
|  | 	"traveling": "travelling", | ||||||
|  | 	"utilize": "utilise", | ||||||
|  | 	"utilizes": "utilises" | ||||||
|  | } | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "", | 	"Deleted {{deleteModelTag}}": "", | ||||||
| 	"Deleted {tagName}": "", | 	"Deleted {tagName}": "", | ||||||
| 	"Description": "", | 	"Description": "", | ||||||
| 	"Desktop Notifications": "", | 	"Notifications": "", | ||||||
| 	"Disabled": "", | 	"Disabled": "", | ||||||
| 	"Discover a modelfile": "", | 	"Discover a modelfile": "", | ||||||
| 	"Discover a prompt": "", | 	"Discover a prompt": "", | ||||||
|  | @ -150,6 +150,7 @@ | ||||||
| 	"Failed to read clipboard contents": "", | 	"Failed to read clipboard contents": "", | ||||||
| 	"File Mode": "", | 	"File Mode": "", | ||||||
| 	"File not found.": "", | 	"File not found.": "", | ||||||
|  | 	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "", | ||||||
| 	"Focus chat input": "", | 	"Focus chat input": "", | ||||||
| 	"Format your variables using square brackets like this:": "", | 	"Format your variables using square brackets like this:": "", | ||||||
| 	"From (Base Model)": "", | 	"From (Base Model)": "", | ||||||
|  | @ -340,6 +341,7 @@ | ||||||
| 	"URL Mode": "", | 	"URL Mode": "", | ||||||
| 	"Use '#' in the prompt input to load and select your documents.": "", | 	"Use '#' in the prompt input to load and select your documents.": "", | ||||||
| 	"Use Gravatar": "", | 	"Use Gravatar": "", | ||||||
|  | 	"Use Initials": "", | ||||||
| 	"user": "", | 	"user": "", | ||||||
| 	"User Permissions": "", | 	"User Permissions": "", | ||||||
| 	"Users": "", | 	"Users": "", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Se borró {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Se borró {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Se borró {tagName}", | 	"Deleted {tagName}": "Se borró {tagName}", | ||||||
| 	"Description": "Descripción", | 	"Description": "Descripción", | ||||||
| 	"Desktop Notifications": "Notificaciones", | 	"Notifications": "Notificaciones", | ||||||
| 	"Disabled": "Desactivado", | 	"Disabled": "Desactivado", | ||||||
| 	"Discover a modelfile": "Descubre un modelfile", | 	"Discover a modelfile": "Descubre un modelfile", | ||||||
| 	"Discover a prompt": "Descubre un Prompt", | 	"Discover a prompt": "Descubre un Prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد", | ||||||
| 	"Deleted {tagName}": "{tagName} حذف شد", | 	"Deleted {tagName}": "{tagName} حذف شد", | ||||||
| 	"Description": "توضیحات", | 	"Description": "توضیحات", | ||||||
| 	"Desktop Notifications": "اعلان", | 	"Notifications": "اعلان", | ||||||
| 	"Disabled": "غیرفعال", | 	"Disabled": "غیرفعال", | ||||||
| 	"Discover a modelfile": "فایل مدل را کشف کنید", | 	"Discover a modelfile": "فایل مدل را کشف کنید", | ||||||
| 	"Discover a prompt": "یک اعلان را کشف کنید", | 	"Discover a prompt": "یک اعلان را کشف کنید", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé", | ||||||
| 	"Deleted {tagName}": "{tagName} supprimé", | 	"Deleted {tagName}": "{tagName} supprimé", | ||||||
| 	"Description": "Description", | 	"Description": "Description", | ||||||
| 	"Desktop Notifications": "Notifications de bureau", | 	"Notifications": "Notifications de bureau", | ||||||
| 	"Disabled": "Désactivé", | 	"Disabled": "Désactivé", | ||||||
| 	"Discover a modelfile": "Découvrir un fichier de modèle", | 	"Discover a modelfile": "Découvrir un fichier de modèle", | ||||||
| 	"Discover a prompt": "Découvrir un prompt", | 	"Discover a prompt": "Découvrir un prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé", | ||||||
| 	"Deleted {tagName}": "{tagName} supprimé", | 	"Deleted {tagName}": "{tagName} supprimé", | ||||||
| 	"Description": "Description", | 	"Description": "Description", | ||||||
| 	"Desktop Notifications": "Notifications de bureau", | 	"Notifications": "Notifications de bureau", | ||||||
| 	"Disabled": "Désactivé", | 	"Disabled": "Désactivé", | ||||||
| 	"Discover a modelfile": "Découvrir un fichier de modèle", | 	"Discover a modelfile": "Découvrir un fichier de modèle", | ||||||
| 	"Discover a prompt": "Découvrir un prompt", | 	"Discover a prompt": "Découvrir un prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Eliminato {tagName}", | 	"Deleted {tagName}": "Eliminato {tagName}", | ||||||
| 	"Description": "Descrizione", | 	"Description": "Descrizione", | ||||||
| 	"Desktop Notifications": "Notifiche desktop", | 	"Notifications": "Notifiche desktop", | ||||||
| 	"Disabled": "Disabilitato", | 	"Disabled": "Disabilitato", | ||||||
| 	"Discover a modelfile": "Scopri un file modello", | 	"Discover a modelfile": "Scopri un file modello", | ||||||
| 	"Discover a prompt": "Scopri un prompt", | 	"Discover a prompt": "Scopri un prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました", | ||||||
| 	"Deleted {tagName}": "{tagName} を削除しました", | 	"Deleted {tagName}": "{tagName} を削除しました", | ||||||
| 	"Description": "説明", | 	"Description": "説明", | ||||||
| 	"Desktop Notifications": "デスクトップ通知", | 	"Notifications": "デスクトップ通知", | ||||||
| 	"Disabled": "無効", | 	"Disabled": "無効", | ||||||
| 	"Discover a modelfile": "モデルファイルを見つける", | 	"Discover a modelfile": "モデルファイルを見つける", | ||||||
| 	"Discover a prompt": "プロンプトを見つける", | 	"Discover a prompt": "プロンプトを見つける", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨", | ||||||
| 	"Deleted {tagName}": "{tagName} 삭제됨", | 	"Deleted {tagName}": "{tagName} 삭제됨", | ||||||
| 	"Description": "설명", | 	"Description": "설명", | ||||||
| 	"Desktop Notifications": "알림", | 	"Notifications": "알림", | ||||||
| 	"Disabled": "비활성화", | 	"Disabled": "비활성화", | ||||||
| 	"Discover a modelfile": "모델파일 검색", | 	"Discover a modelfile": "모델파일 검색", | ||||||
| 	"Discover a prompt": "프롬프트 검색", | 	"Discover a prompt": "프롬프트 검색", | ||||||
|  |  | ||||||
|  | @ -15,6 +15,10 @@ | ||||||
| 		"code": "de-DE", | 		"code": "de-DE", | ||||||
| 		"title": "Deutsch" | 		"title": "Deutsch" | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"code": "en-GB", | ||||||
|  | 		"title": "English (GB)" | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"code": "es-ES", | 		"code": "es-ES", | ||||||
| 		"title": "Spanish" | 		"title": "Spanish" | ||||||
|  | @ -51,10 +55,18 @@ | ||||||
| 		"code": "pt-PT", | 		"code": "pt-PT", | ||||||
| 		"title": "Portuguese (Portugal)" | 		"title": "Portuguese (Portugal)" | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"code": "pt-BR", | ||||||
|  | 		"title": "Portuguese (Brazil)" | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"code": "ru-RU", | 		"code": "ru-RU", | ||||||
| 		"title": "Russian (Russia)" | 		"title": "Russian (Russia)" | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		"code": "tr-TR", | ||||||
|  | 		"title": "Turkish" | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		"code": "uk-UA", | 		"code": "uk-UA", | ||||||
| 		"title": "Ukrainian" | 		"title": "Ukrainian" | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} is verwijderd", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} is verwijderd", | ||||||
| 	"Deleted {tagName}": "{tagName} is verwijderd", | 	"Deleted {tagName}": "{tagName} is verwijderd", | ||||||
| 	"Description": "Beschrijving", | 	"Description": "Beschrijving", | ||||||
| 	"Desktop Notifications": "Desktop Notificaties", | 	"Notifications": "Desktop Notificaties", | ||||||
| 	"Disabled": "Uitgeschakeld", | 	"Disabled": "Uitgeschakeld", | ||||||
| 	"Discover a modelfile": "Ontdek een modelfile", | 	"Discover a modelfile": "Ontdek een modelfile", | ||||||
| 	"Discover a prompt": "Ontdek een prompt", | 	"Discover a prompt": "Ontdek een prompt", | ||||||
|  |  | ||||||
							
								
								
									
										363
									
								
								src/lib/i18n/locales/pt-BR/translation.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								src/lib/i18n/locales/pt-BR/translation.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,363 @@ | ||||||
|  | { | ||||||
|  | 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para não expirar.", | ||||||
|  | 	"(Beta)": "(Beta)", | ||||||
|  | 	"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)", | ||||||
|  | 	"(latest)": "(mais recente)", | ||||||
|  | 	"{{modelName}} is thinking...": "{{modelName}} está pensando...", | ||||||
|  | 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário", | ||||||
|  | 	"a user": "um usuário", | ||||||
|  | 	"About": "Sobre", | ||||||
|  | 	"Account": "Conta", | ||||||
|  | 	"Action": "Ação", | ||||||
|  | 	"Add a model": "Adicionar um modelo", | ||||||
|  | 	"Add a model tag name": "Adicionar um nome de tag de modelo", | ||||||
|  | 	"Add a short description about what this modelfile does": "Adicione uma breve descrição sobre o que este arquivo de modelo faz", | ||||||
|  | 	"Add a short title for this prompt": "Adicione um título curto para este prompt", | ||||||
|  | 	"Add a tag": "Adicionar uma tag", | ||||||
|  | 	"Add Docs": "Adicionar Documentos", | ||||||
|  | 	"Add Files": "Adicionar Arquivos", | ||||||
|  | 	"Add message": "Adicionar mensagem", | ||||||
|  | 	"add tags": "adicionar tags", | ||||||
|  | 	"Adjusting these settings will apply changes universally to all users.": "Ajustar essas configurações aplicará alterações universalmente a todos os usuários.", | ||||||
|  | 	"admin": "administrador", | ||||||
|  | 	"Admin Panel": "Painel do Administrador", | ||||||
|  | 	"Admin Settings": "Configurações do Administrador", | ||||||
|  | 	"Advanced Parameters": "Parâmetros Avançados", | ||||||
|  | 	"all": "todos", | ||||||
|  | 	"All Users": "Todos os Usuários", | ||||||
|  | 	"Allow": "Permitir", | ||||||
|  | 	"Allow Chat Deletion": "Permitir Exclusão de Bate-papo", | ||||||
|  | 	"alphanumeric characters and hyphens": "caracteres alfanuméricos e hífens", | ||||||
|  | 	"Already have an account?": "Já tem uma conta?", | ||||||
|  | 	"an assistant": "um assistente", | ||||||
|  | 	"and": "e", | ||||||
|  | 	"API Base URL": "URL Base da API", | ||||||
|  | 	"API Key": "Chave da API", | ||||||
|  | 	"API RPM": "API RPM", | ||||||
|  | 	"are allowed - Activate this command by typing": "são permitidos - Ative este comando digitando", | ||||||
|  | 	"Are you sure?": "Tem certeza?", | ||||||
|  | 	"Audio": "Áudio", | ||||||
|  | 	"Auto-playback response": "Reprodução automática da resposta", | ||||||
|  | 	"Auto-send input after 3 sec.": "Enviar entrada automaticamente após 3 segundos.", | ||||||
|  | 	"AUTOMATIC1111 Base URL": "URL Base do AUTOMATIC1111", | ||||||
|  | 	"AUTOMATIC1111 Base URL is required.": "A URL Base do AUTOMATIC1111 é obrigatória.", | ||||||
|  | 	"available!": "disponível!", | ||||||
|  | 	"Back": "Voltar", | ||||||
|  | 	"Builder Mode": "Modo de Construtor", | ||||||
|  | 	"Cancel": "Cancelar", | ||||||
|  | 	"Categories": "Categorias", | ||||||
|  | 	"Change Password": "Alterar Senha", | ||||||
|  | 	"Chat": "Bate-papo", | ||||||
|  | 	"Chat History": "Histórico de Bate-papo", | ||||||
|  | 	"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.", | ||||||
|  | 	"Chats": "Bate-papos", | ||||||
|  | 	"Check Again": "Verifique novamente", | ||||||
|  | 	"Check for updates": "Verificar atualizações", | ||||||
|  | 	"Checking for updates...": "Verificando atualizações...", | ||||||
|  | 	"Choose a model before saving...": "Escolha um modelo antes de salvar...", | ||||||
|  | 	"Chunk Overlap": "Sobreposição de Fragmento", | ||||||
|  | 	"Chunk Params": "Parâmetros de Fragmento", | ||||||
|  | 	"Chunk Size": "Tamanho do Fragmento", | ||||||
|  | 	"Click here for help.": "Clique aqui para obter ajuda.", | ||||||
|  | 	"Click here to check other modelfiles.": "Clique aqui para verificar outros arquivos de modelo.", | ||||||
|  | 	"Click here to select": "Clique aqui para selecionar", | ||||||
|  | 	"Click here to select documents.": "Clique aqui para selecionar documentos.", | ||||||
|  | 	"click here.": "clique aqui.", | ||||||
|  | 	"Click on the user role button to change a user's role.": "Clique no botão de função do usuário para alterar a função de um usuário.", | ||||||
|  | 	"Close": "Fechar", | ||||||
|  | 	"Collection": "Coleção", | ||||||
|  | 	"Command": "Comando", | ||||||
|  | 	"Confirm Password": "Confirmar Senha", | ||||||
|  | 	"Connections": "Conexões", | ||||||
|  | 	"Content": "Conteúdo", | ||||||
|  | 	"Context Length": "Comprimento do Contexto", | ||||||
|  | 	"Conversation Mode": "Modo de Conversa", | ||||||
|  | 	"Copy last code block": "Copiar último bloco de código", | ||||||
|  | 	"Copy last response": "Copiar última resposta", | ||||||
|  | 	"Copying to clipboard was successful!": "Cópia para a área de transferência bem-sucedida!", | ||||||
|  | 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crie uma frase concisa de 3 a 5 palavras como cabeçalho para a seguinte consulta, aderindo estritamente ao limite de 3 a 5 palavras e evitando o uso da palavra 'título':", | ||||||
|  | 	"Create a modelfile": "Criar um arquivo de modelo", | ||||||
|  | 	"Create Account": "Criar Conta", | ||||||
|  | 	"Created at": "Criado em", | ||||||
|  | 	"Created by": "Criado por", | ||||||
|  | 	"Current Model": "Modelo Atual", | ||||||
|  | 	"Current Password": "Senha Atual", | ||||||
|  | 	"Custom": "Personalizado", | ||||||
|  | 	"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico", | ||||||
|  | 	"Dark": "Escuro", | ||||||
|  | 	"Database": "Banco de dados", | ||||||
|  | 	"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm", | ||||||
|  | 	"Default": "Padrão", | ||||||
|  | 	"Default (Automatic1111)": "Padrão (Automatic1111)", | ||||||
|  | 	"Default (Web API)": "Padrão (API Web)", | ||||||
|  | 	"Default model updated": "Modelo padrão atualizado", | ||||||
|  | 	"Default Prompt Suggestions": "Sugestões de Prompt Padrão", | ||||||
|  | 	"Default User Role": "Função de Usuário Padrão", | ||||||
|  | 	"delete": "excluir", | ||||||
|  | 	"Delete a model": "Excluir um modelo", | ||||||
|  | 	"Delete chat": "Excluir bate-papo", | ||||||
|  | 	"Delete Chats": "Excluir Bate-papos", | ||||||
|  | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído", | ||||||
|  | 	"Deleted {tagName}": "{tagName} excluído", | ||||||
|  | 	"Description": "Descrição", | ||||||
|  | 	"Notifications": "Notificações da Área de Trabalho", | ||||||
|  | 	"Disabled": "Desativado", | ||||||
|  | 	"Discover a modelfile": "Descobrir um arquivo de modelo", | ||||||
|  | 	"Discover a prompt": "Descobrir um prompt", | ||||||
|  | 	"Discover, download, and explore custom prompts": "Descubra, baixe e explore prompts personalizados", | ||||||
|  | 	"Discover, download, and explore model presets": "Descubra, baixe e explore predefinições de modelo", | ||||||
|  | 	"Display the username instead of You in the Chat": "Exibir o nome de usuário em vez de Você no Bate-papo", | ||||||
|  | 	"Document": "Documento", | ||||||
|  | 	"Document Settings": "Configurações de Documento", | ||||||
|  | 	"Documents": "Documentos", | ||||||
|  | 	"does not make any external connections, and your data stays securely on your locally hosted server.": "não faz conexões externas e seus dados permanecem seguros em seu servidor hospedado localmente.", | ||||||
|  | 	"Don't Allow": "Não Permitir", | ||||||
|  | 	"Don't have an account?": "Não tem uma conta?", | ||||||
|  | 	"Download as a File": "Baixar como Arquivo", | ||||||
|  | 	"Download Database": "Baixar Banco de Dados", | ||||||
|  | 	"Drop any files here to add to the conversation": "Solte os arquivos aqui para adicionar à conversa", | ||||||
|  | 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.", | ||||||
|  | 	"Edit Doc": "Editar Documento", | ||||||
|  | 	"Edit User": "Editar Usuário", | ||||||
|  | 	"Email": "E-mail", | ||||||
|  | 	"Enable Chat History": "Ativar Histórico de Bate-papo", | ||||||
|  | 	"Enable New Sign Ups": "Ativar Novas Inscrições", | ||||||
|  | 	"Enabled": "Ativado", | ||||||
|  | 	"Enter {{role}} message here": "Digite a mensagem de {{role}} aqui", | ||||||
|  | 	"Enter API Key": "Digite a Chave da API", | ||||||
|  | 	"Enter Chunk Overlap": "Digite a Sobreposição de Fragmento", | ||||||
|  | 	"Enter Chunk Size": "Digite o Tamanho do Fragmento", | ||||||
|  | 	"Enter Image Size (e.g. 512x512)": "Digite o Tamanho da Imagem (por exemplo, 512x512)", | ||||||
|  | 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "Digite a URL Base da API LiteLLM (litellm_params.api_base)", | ||||||
|  | 	"Enter LiteLLM API Key (litellm_params.api_key)": "Digite a Chave da API LiteLLM (litellm_params.api_key)", | ||||||
|  | 	"Enter LiteLLM API RPM (litellm_params.rpm)": "Digite o RPM da API LiteLLM (litellm_params.rpm)", | ||||||
|  | 	"Enter LiteLLM Model (litellm_params.model)": "Digite o Modelo LiteLLM (litellm_params.model)", | ||||||
|  | 	"Enter Max Tokens (litellm_params.max_tokens)": "Digite o Máximo de Tokens (litellm_params.max_tokens)", | ||||||
|  | 	"Enter model tag (e.g. {{modelTag}})": "Digite a tag do modelo (por exemplo, {{modelTag}})", | ||||||
|  | 	"Enter Number of Steps (e.g. 50)": "Digite o Número de Etapas (por exemplo, 50)", | ||||||
|  | 	"Enter stop sequence": "Digite a sequência de parada", | ||||||
|  | 	"Enter Top K": "Digite o Top K", | ||||||
|  | 	"Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)", | ||||||
|  | 	"Enter Your Email": "Digite seu E-mail", | ||||||
|  | 	"Enter Your Full Name": "Digite seu Nome Completo", | ||||||
|  | 	"Enter Your Password": "Digite sua Senha", | ||||||
|  | 	"Experimental": "Experimental", | ||||||
|  | 	"Export All Chats (All Users)": "Exportar Todos os Bate-papos (Todos os Usuários)", | ||||||
|  | 	"Export Chats": "Exportar Bate-papos", | ||||||
|  | 	"Export Documents Mapping": "Exportar Mapeamento de Documentos", | ||||||
|  | 	"Export Modelfiles": "Exportar Arquivos de Modelo", | ||||||
|  | 	"Export Prompts": "Exportar Prompts", | ||||||
|  | 	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência", | ||||||
|  | 	"File Mode": "Modo de Arquivo", | ||||||
|  | 	"File not found.": "Arquivo não encontrado.", | ||||||
|  | 	"Focus chat input": "Focar entrada de bate-papo", | ||||||
|  | 	"Format your variables using square brackets like this:": "Formate suas variáveis usando colchetes como este:", | ||||||
|  | 	"From (Base Model)": "De (Modelo Base)", | ||||||
|  | 	"Full Screen Mode": "Modo de Tela Cheia", | ||||||
|  | 	"General": "Geral", | ||||||
|  | 	"General Settings": "Configurações Gerais", | ||||||
|  | 	"Hello, {{name}}": "Olá, {{name}}", | ||||||
|  | 	"Hide": "Ocultar", | ||||||
|  | 	"Hide Additional Params": "Ocultar Parâmetros Adicionais", | ||||||
|  | 	"How can I help you today?": "Como posso ajudá-lo hoje?", | ||||||
|  | 	"Image Generation (Experimental)": "Geração de Imagens (Experimental)", | ||||||
|  | 	"Image Generation Engine": "Mecanismo de Geração de Imagens", | ||||||
|  | 	"Image Settings": "Configurações de Imagem", | ||||||
|  | 	"Images": "Imagens", | ||||||
|  | 	"Import Chats": "Importar Bate-papos", | ||||||
|  | 	"Import Documents Mapping": "Importar Mapeamento de Documentos", | ||||||
|  | 	"Import Modelfiles": "Importar Arquivos de Modelo", | ||||||
|  | 	"Import Prompts": "Importar Prompts", | ||||||
|  | 	"Include `--api` flag when running stable-diffusion-webui": "Inclua a flag `--api` ao executar stable-diffusion-webui", | ||||||
|  | 	"Interface": "Interface", | ||||||
|  | 	"join our Discord for help.": "junte-se ao nosso Discord para obter ajuda.", | ||||||
|  | 	"JSON": "JSON", | ||||||
|  | 	"JWT Expiration": "Expiração JWT", | ||||||
|  | 	"JWT Token": "Token JWT", | ||||||
|  | 	"Keep Alive": "Manter Vivo", | ||||||
|  | 	"Keyboard shortcuts": "Atalhos de teclado", | ||||||
|  | 	"Language": "Idioma", | ||||||
|  | 	"Light": "Claro", | ||||||
|  | 	"Listening...": "Ouvindo...", | ||||||
|  | 	"LLMs can make mistakes. Verify important information.": "LLMs podem cometer erros. Verifique informações importantes.", | ||||||
|  | 	"Made by OpenWebUI Community": "Feito pela Comunidade OpenWebUI", | ||||||
|  | 	"Make sure to enclose them with": "Certifique-se de colocá-los entre", | ||||||
|  | 	"Manage LiteLLM Models": "Gerenciar Modelos LiteLLM", | ||||||
|  | 	"Manage Models": "Gerenciar Modelos", | ||||||
|  | 	"Manage Ollama Models": "Gerenciar Modelos Ollama", | ||||||
|  | 	"Max Tokens": "Máximo de Tokens", | ||||||
|  | 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Tente novamente mais tarde.", | ||||||
|  | 	"Mirostat": "Mirostat", | ||||||
|  | 	"Mirostat Eta": "Mirostat Eta", | ||||||
|  | 	"Mirostat Tau": "Mirostat Tau", | ||||||
|  | 	"MMMM DD, YYYY": "MMMM DD, AAAA", | ||||||
|  | 	"Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi baixado com sucesso.", | ||||||
|  | 	"Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para download.", | ||||||
|  | 	"Model {{modelId}} not found": "Modelo {{modelId}} não encontrado", | ||||||
|  | 	"Model {{modelName}} already exists.": "O modelo {{modelName}} já existe.", | ||||||
|  | 	"Model Name": "Nome do Modelo", | ||||||
|  | 	"Model not selected": "Modelo não selecionado", | ||||||
|  | 	"Model Tag Name": "Nome da Tag do Modelo", | ||||||
|  | 	"Model Whitelisting": "Lista de Permissões de Modelo", | ||||||
|  | 	"Model(s) Whitelisted": "Modelo(s) na Lista de Permissões", | ||||||
|  | 	"Modelfile": "Arquivo de Modelo", | ||||||
|  | 	"Modelfile Advanced Settings": "Configurações Avançadas do Arquivo de Modelo", | ||||||
|  | 	"Modelfile Content": "Conteúdo do Arquivo de Modelo", | ||||||
|  | 	"Modelfiles": "Arquivos de Modelo", | ||||||
|  | 	"Models": "Modelos", | ||||||
|  | 	"My Documents": "Meus Documentos", | ||||||
|  | 	"My Modelfiles": "Meus Arquivos de Modelo", | ||||||
|  | 	"My Prompts": "Meus Prompts", | ||||||
|  | 	"Name": "Nome", | ||||||
|  | 	"Name Tag": "Nome da Tag", | ||||||
|  | 	"Name your modelfile": "Nomeie seu arquivo de modelo", | ||||||
|  | 	"New Chat": "Novo Bate-papo", | ||||||
|  | 	"New Password": "Nova Senha", | ||||||
|  | 	"Not sure what to add?": "Não tem certeza do que adicionar?", | ||||||
|  | 	"Not sure what to write? Switch to": "Não tem certeza do que escrever? Mude para", | ||||||
|  | 	"Off": "Desligado", | ||||||
|  | 	"Okay, Let's Go!": "Ok, Vamos Lá!", | ||||||
|  | 	"Ollama Base URL": "URL Base do Ollama", | ||||||
|  | 	"Ollama Version": "Versão do Ollama", | ||||||
|  | 	"On": "Ligado", | ||||||
|  | 	"Only": "Somente", | ||||||
|  | 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Somente caracteres alfanuméricos e hífens são permitidos na string de comando.", | ||||||
|  | 	"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Opa! Aguente firme! Seus arquivos ainda estão no forno de processamento. Estamos cozinhando-os com perfeição. Por favor, seja paciente e avisaremos quando estiverem prontos.", | ||||||
|  | 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Opa! Parece que a URL é inválida. Verifique novamente e tente outra vez.", | ||||||
|  | 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Opa! Você está usando um método não suportado (somente frontend). Por favor, sirva o WebUI a partir do backend.", | ||||||
|  | 	"Open": "Abrir", | ||||||
|  | 	"Open AI": "OpenAI", | ||||||
|  | 	"Open AI (Dall-E)": "OpenAI (Dall-E)", | ||||||
|  | 	"Open new chat": "Abrir novo bate-papo", | ||||||
|  | 	"OpenAI API": "API OpenAI", | ||||||
|  | 	"OpenAI API Key": "Chave da API OpenAI", | ||||||
|  | 	"OpenAI API Key is required.": "A Chave da API OpenAI é obrigatória.", | ||||||
|  | 	"or": "ou", | ||||||
|  | 	"Parameters": "Parâmetros", | ||||||
|  | 	"Password": "Senha", | ||||||
|  | 	"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)", | ||||||
|  | 	"pending": "pendente", | ||||||
|  | 	"Permission denied when accessing microphone: {{error}}": "Permissão negada ao acessar o microfone: {{error}}", | ||||||
|  | 	"Playground": "Playground", | ||||||
|  | 	"Profile": "Perfil", | ||||||
|  | 	"Prompt Content": "Conteúdo do Prompt", | ||||||
|  | 	"Prompt suggestions": "Sugestões de Prompt", | ||||||
|  | 	"Prompts": "Prompts", | ||||||
|  | 	"Pull a model from Ollama.com": "Extrair um modelo do Ollama.com", | ||||||
|  | 	"Pull Progress": "Progresso da Extração", | ||||||
|  | 	"Query Params": "Parâmetros de Consulta", | ||||||
|  | 	"RAG Template": "Modelo RAG", | ||||||
|  | 	"Raw Format": "Formato Bruto", | ||||||
|  | 	"Record voice": "Gravar voz", | ||||||
|  | 	"Redirecting you to OpenWebUI Community": "Redirecionando você para a Comunidade OpenWebUI", | ||||||
|  | 	"Release Notes": "Notas de Lançamento", | ||||||
|  | 	"Repeat Last N": "Repetir Últimos N", | ||||||
|  | 	"Repeat Penalty": "Penalidade de Repetição", | ||||||
|  | 	"Request Mode": "Modo de Solicitação", | ||||||
|  | 	"Reset Vector Storage": "Redefinir Armazenamento de Vetor", | ||||||
|  | 	"Response AutoCopy to Clipboard": "Cópia Automática da Resposta para a Área de Transferência", | ||||||
|  | 	"Role": "Função", | ||||||
|  | 	"Rosé Pine": "Rosé Pine", | ||||||
|  | 	"Rosé Pine Dawn": "Rosé Pine Dawn", | ||||||
|  | 	"Save": "Salvar", | ||||||
|  | 	"Save & Create": "Salvar e Criar", | ||||||
|  | 	"Save & Submit": "Salvar e Enviar", | ||||||
|  | 	"Save & Update": "Salvar e Atualizar", | ||||||
|  | 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar logs de bate-papo diretamente no armazenamento do seu navegador não é mais suportado. Reserve um momento para baixar e excluir seus logs de bate-papo clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus logs de bate-papo para o backend através de", | ||||||
|  | 	"Scan": "Digitalizar", | ||||||
|  | 	"Scan complete!": "Digitalização concluída!", | ||||||
|  | 	"Scan for documents from {{path}}": "Digitalizar documentos de {{path}}", | ||||||
|  | 	"Search": "Pesquisar", | ||||||
|  | 	"Search Documents": "Pesquisar Documentos", | ||||||
|  | 	"Search Prompts": "Pesquisar Prompts", | ||||||
|  | 	"See readme.md for instructions": "Consulte readme.md para obter instruções", | ||||||
|  | 	"See what's new": "Veja o que há de novo", | ||||||
|  | 	"Seed": "Semente", | ||||||
|  | 	"Select a mode": "Selecione um modo", | ||||||
|  | 	"Select a model": "Selecione um modelo", | ||||||
|  | 	"Select an Ollama instance": "Selecione uma instância Ollama", | ||||||
|  | 	"Send a Message": "Enviar uma Mensagem", | ||||||
|  | 	"Send message": "Enviar mensagem", | ||||||
|  | 	"Server connection verified": "Conexão com o servidor verificada", | ||||||
|  | 	"Set as default": "Definir como padrão", | ||||||
|  | 	"Set Default Model": "Definir Modelo Padrão", | ||||||
|  | 	"Set Image Size": "Definir Tamanho da Imagem", | ||||||
|  | 	"Set Steps": "Definir Etapas", | ||||||
|  | 	"Set Title Auto-Generation Model": "Definir Modelo de Geração Automática de Título", | ||||||
|  | 	"Set Voice": "Definir Voz", | ||||||
|  | 	"Settings": "Configurações", | ||||||
|  | 	"Settings saved successfully!": "Configurações salvas com sucesso!", | ||||||
|  | 	"Share to OpenWebUI Community": "Compartilhar com a Comunidade OpenWebUI", | ||||||
|  | 	"short-summary": "resumo-curto", | ||||||
|  | 	"Show": "Mostrar", | ||||||
|  | 	"Show Additional Params": "Mostrar Parâmetros Adicionais", | ||||||
|  | 	"Show shortcuts": "Mostrar", | ||||||
|  | 	"sidebar": "barra lateral", | ||||||
|  | 	"Sign in": "Entrar", | ||||||
|  | 	"Sign Out": "Sair", | ||||||
|  | 	"Sign up": "Inscrever-se", | ||||||
|  | 	"Speech recognition error: {{error}}": "Erro de reconhecimento de fala: {{error}}", | ||||||
|  | 	"Speech-to-Text Engine": "Mecanismo de Fala para Texto", | ||||||
|  | 	"SpeechRecognition API is not supported in this browser.": "A API SpeechRecognition não é suportada neste navegador.", | ||||||
|  | 	"Stop Sequence": "Sequência de Parada", | ||||||
|  | 	"STT Settings": "Configurações STT", | ||||||
|  | 	"Submit": "Enviar", | ||||||
|  | 	"Success": "Sucesso", | ||||||
|  | 	"Successfully updated.": "Atualizado com sucesso.", | ||||||
|  | 	"Sync All": "Sincronizar Tudo", | ||||||
|  | 	"System": "Sistema", | ||||||
|  | 	"System Prompt": "Prompt do Sistema", | ||||||
|  | 	"Tags": "Tags", | ||||||
|  | 	"Temperature": "Temperatura", | ||||||
|  | 	"Template": "Modelo", | ||||||
|  | 	"Text Completion": "Complemento de Texto", | ||||||
|  | 	"Text-to-Speech Engine": "Mecanismo de Texto para Fala", | ||||||
|  | 	"Tfs Z": "Tfs Z", | ||||||
|  | 	"Theme": "Tema", | ||||||
|  | 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Isso garante que suas conversas valiosas sejam salvas com segurança em seu banco de dados de backend. Obrigado!", | ||||||
|  | 	"This setting does not sync across browsers or devices.": "Esta configuração não sincroniza entre navegadores ou dispositivos.", | ||||||
|  | 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Dica: Atualize vários slots de variáveis consecutivamente pressionando a tecla Tab na entrada de bate-papo após cada substituição.", | ||||||
|  | 	"Title": "Título", | ||||||
|  | 	"Title Auto-Generation": "Geração Automática de Título", | ||||||
|  | 	"Title Generation Prompt": "Prompt de Geração de Título", | ||||||
|  | 	"to": "para", | ||||||
|  | 	"To access the available model names for downloading,": "Para acessar os nomes de modelo disponíveis para download,", | ||||||
|  | 	"To access the GGUF models available for downloading,": "Para acessar os modelos GGUF disponíveis para download,", | ||||||
|  | 	"to chat input.": "para a entrada de bate-papo.", | ||||||
|  | 	"Toggle settings": "Alternar configurações", | ||||||
|  | 	"Toggle sidebar": "Alternar barra lateral", | ||||||
|  | 	"Top K": "Top K", | ||||||
|  | 	"Top P": "Top P", | ||||||
|  | 	"Trouble accessing Ollama?": "Problemas para acessar o Ollama?", | ||||||
|  | 	"TTS Settings": "Configurações TTS", | ||||||
|  | 	"Type Hugging Face Resolve (Download) URL": "Digite a URL do Hugging Face Resolve (Download)", | ||||||
|  | 	"Uh-oh! There was an issue connecting to {{provider}}.": "Opa! Houve um problema ao conectar-se a {{provider}}.", | ||||||
|  | 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo de arquivo desconhecido '{{file_type}}', mas aceitando e tratando como texto simples", | ||||||
|  | 	"Update password": "Atualizar senha", | ||||||
|  | 	"Upload a GGUF model": "Carregar um modelo GGUF", | ||||||
|  | 	"Upload files": "Carregar arquivos", | ||||||
|  | 	"Upload Progress": "Progresso do Carregamento", | ||||||
|  | 	"URL Mode": "Modo de URL", | ||||||
|  | 	"Use '#' in the prompt input to load and select your documents.": "Use '#' na entrada do prompt para carregar e selecionar seus documentos.", | ||||||
|  | 	"Use Gravatar": "Usar Gravatar", | ||||||
|  | 	"user": "usuário", | ||||||
|  | 	"User Permissions": "Permissões do Usuário", | ||||||
|  | 	"Users": "Usuários", | ||||||
|  | 	"Utilize": "Utilizar", | ||||||
|  | 	"Valid time units:": "Unidades de tempo válidas:", | ||||||
|  | 	"variable": "variável", | ||||||
|  | 	"variable to have them replaced with clipboard content.": "variável para que sejam substituídos pelo conteúdo da área de transferência.", | ||||||
|  | 	"Version": "Versão", | ||||||
|  | 	"Web": "Web", | ||||||
|  | 	"WebUI Add-ons": "Complementos WebUI", | ||||||
|  | 	"WebUI Settings": "Configurações WebUI", | ||||||
|  | 	"WebUI will make requests to": "WebUI fará solicitações para", | ||||||
|  | 	"What’s New in": "O que há de novo em", | ||||||
|  | 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.", | ||||||
|  | 	"Whisper (Local)": "Whisper (Local)", | ||||||
|  | 	"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)", | ||||||
|  | 	"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].", | ||||||
|  | 	"You": "Você", | ||||||
|  | 	"You're a helpful assistant.": "Você é um assistente útil.", | ||||||
|  | 	"You're now logged in.": "Você está conectado agora." | ||||||
|  | } | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído", | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído", | ||||||
| 	"Deleted {tagName}": "{tagName} excluído", | 	"Deleted {tagName}": "{tagName} excluído", | ||||||
| 	"Description": "Descrição", | 	"Description": "Descrição", | ||||||
| 	"Desktop Notifications": "Notificações da Área de Trabalho", | 	"Notifications": "Notificações da Área de Trabalho", | ||||||
| 	"Disabled": "Desativado", | 	"Disabled": "Desativado", | ||||||
| 	"Discover a modelfile": "Descobrir um arquivo de modelo", | 	"Discover a modelfile": "Descobrir um arquivo de modelo", | ||||||
| 	"Discover a prompt": "Descobrir um prompt", | 	"Discover a prompt": "Descobrir um prompt", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Удалено {tagName}", | 	"Deleted {tagName}": "Удалено {tagName}", | ||||||
| 	"Description": "Описание", | 	"Description": "Описание", | ||||||
| 	"Desktop Notifications": "Уведомления на рабочем столе", | 	"Notifications": "Уведомления на рабочем столе", | ||||||
| 	"Disabled": "Отключено", | 	"Disabled": "Отключено", | ||||||
| 	"Discover a modelfile": "Найти файл модели", | 	"Discover a modelfile": "Найти файл модели", | ||||||
| 	"Discover a prompt": "Найти промт", | 	"Discover a prompt": "Найти промт", | ||||||
|  |  | ||||||
							
								
								
									
										363
									
								
								src/lib/i18n/locales/tr-TR/translation.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								src/lib/i18n/locales/tr-TR/translation.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,363 @@ | ||||||
|  | { | ||||||
|  | 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' veya süresiz için '-1'.", | ||||||
|  | 	"(Beta)": "(Beta)", | ||||||
|  | 	"(e.g. `sh webui.sh --api`)": "(örn. `sh webui.sh --api`)", | ||||||
|  | 	"(latest)": "(en son)", | ||||||
|  | 	"{{modelName}} is thinking...": "{{modelName}} düşünüyor...", | ||||||
|  | 	"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli", | ||||||
|  | 	"a user": "bir kullanıcı", | ||||||
|  | 	"About": "Hakkında", | ||||||
|  | 	"Account": "Hesap", | ||||||
|  | 	"Action": "Eylem", | ||||||
|  | 	"Add a model": "Bir model ekleyin", | ||||||
|  | 	"Add a model tag name": "Bir model etiket adı ekleyin", | ||||||
|  | 	"Add a short description about what this modelfile does": "Bu model dosyasının ne yaptığı hakkında kısa bir açıklama ekleyin", | ||||||
|  | 	"Add a short title for this prompt": "Bu prompt için kısa bir başlık ekleyin", | ||||||
|  | 	"Add a tag": "Bir etiket ekleyin", | ||||||
|  | 	"Add Docs": "Dökümanlar Ekle", | ||||||
|  | 	"Add Files": "Dosyalar Ekle", | ||||||
|  | 	"Add message": "Mesaj ekle", | ||||||
|  | 	"add tags": "etiketler ekle", | ||||||
|  | 	"Adjusting these settings will apply changes universally to all users.": "Bu ayarları ayarlamak değişiklikleri tüm kullanıcılara evrensel olarak uygular.", | ||||||
|  | 	"admin": "yönetici", | ||||||
|  | 	"Admin Panel": "Yönetici Paneli", | ||||||
|  | 	"Admin Settings": "Yönetici Ayarları", | ||||||
|  | 	"Advanced Parameters": "Gelişmiş Parametreler", | ||||||
|  | 	"all": "tümü", | ||||||
|  | 	"All Users": "Tüm Kullanıcılar", | ||||||
|  | 	"Allow": "İzin ver", | ||||||
|  | 	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver", | ||||||
|  | 	"alphanumeric characters and hyphens": "alfanumerik karakterler ve tireler", | ||||||
|  | 	"Already have an account?": "Zaten bir hesabınız mı var?", | ||||||
|  | 	"an assistant": "bir asistan", | ||||||
|  | 	"and": "ve", | ||||||
|  | 	"API Base URL": "API Temel URL", | ||||||
|  | 	"API Key": "API Anahtarı", | ||||||
|  | 	"API RPM": "API RPM", | ||||||
|  | 	"are allowed - Activate this command by typing": "izin verilir - Bu komutu yazarak etkinleştirin", | ||||||
|  | 	"Are you sure?": "Emin misiniz?", | ||||||
|  | 	"Audio": "Ses", | ||||||
|  | 	"Auto-playback response": "Yanıtı otomatik oynatma", | ||||||
|  | 	"Auto-send input after 3 sec.": "3 saniye sonra otomatik olarak gönder", | ||||||
|  | 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL", | ||||||
|  | 	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Temel URL gereklidir.", | ||||||
|  | 	"available!": "mevcut!", | ||||||
|  | 	"Back": "Geri", | ||||||
|  | 	"Builder Mode": "Oluşturucu Modu", | ||||||
|  | 	"Cancel": "İptal", | ||||||
|  | 	"Categories": "Kategoriler", | ||||||
|  | 	"Change Password": "Parola Değiştir", | ||||||
|  | 	"Chat": "Sohbet", | ||||||
|  | 	"Chat History": "Sohbet Geçmişi", | ||||||
|  | 	"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.", | ||||||
|  | 	"Chats": "Sohbetler", | ||||||
|  | 	"Check Again": "Tekrar Kontrol Et", | ||||||
|  | 	"Check for updates": "Güncellemeleri kontrol et", | ||||||
|  | 	"Checking for updates...": "Güncellemeler kontrol ediliyor...", | ||||||
|  | 	"Choose a model before saving...": "Kaydetmeden önce bir model seçin...", | ||||||
|  | 	"Chunk Overlap": "Chunk Çakışması", | ||||||
|  | 	"Chunk Params": "Chunk Parametreleri", | ||||||
|  | 	"Chunk Size": "Chunk Boyutu", | ||||||
|  | 	"Click here for help.": "Yardım için buraya tıklayın.", | ||||||
|  | 	"Click here to check other modelfiles.": "Diğer model dosyalarını kontrol etmek için buraya tıklayın.", | ||||||
|  | 	"Click here to select": "Seçmek için buraya tıklayın", | ||||||
|  | 	"Click here to select documents.": "Belgeleri seçmek için buraya tıklayın.", | ||||||
|  | 	"click here.": "buraya tıklayın.", | ||||||
|  | 	"Click on the user role button to change a user's role.": "Bir kullanıcının rolünü değiştirmek için kullanıcı rolü düğmesine tıklayın.", | ||||||
|  | 	"Close": "Kapat", | ||||||
|  | 	"Collection": "Koleksiyon", | ||||||
|  | 	"Command": "Komut", | ||||||
|  | 	"Confirm Password": "Parolayı Onayla", | ||||||
|  | 	"Connections": "Bağlantılar", | ||||||
|  | 	"Content": "İçerik", | ||||||
|  | 	"Context Length": "Bağlam Uzunluğu", | ||||||
|  | 	"Conversation Mode": "Sohbet Modu", | ||||||
|  | 	"Copy last code block": "Son kod bloğunu kopyala", | ||||||
|  | 	"Copy last response": "Son yanıtı kopyala", | ||||||
|  | 	"Copying to clipboard was successful!": "Panoya kopyalama başarılı!", | ||||||
|  | 	"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Aşağıdaki sorgu için başlık olarak 3-5 kelimelik kısa ve öz bir ifade oluşturun, 3-5 kelime sınırına kesinlikle uyun ve 'başlık' kelimesini kullanmaktan kaçının:", | ||||||
|  | 	"Create a modelfile": "Bir model dosyası oluştur", | ||||||
|  | 	"Create Account": "Hesap Oluştur", | ||||||
|  | 	"Created at": "Oluşturulma tarihi", | ||||||
|  | 	"Created by": "Oluşturan", | ||||||
|  | 	"Current Model": "Mevcut Model", | ||||||
|  | 	"Current Password": "Mevcut Parola", | ||||||
|  | 	"Custom": "Özel", | ||||||
|  | 	"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin", | ||||||
|  | 	"Dark": "Koyu", | ||||||
|  | 	"Database": "Veritabanı", | ||||||
|  | 	"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm", | ||||||
|  | 	"Default": "Varsayılan", | ||||||
|  | 	"Default (Automatic1111)": "Varsayılan (Automatic1111)", | ||||||
|  | 	"Default (Web API)": "Varsayılan (Web API)", | ||||||
|  | 	"Default model updated": "Varsayılan model güncellendi", | ||||||
|  | 	"Default Prompt Suggestions": "Varsayılan Prompt Önerileri", | ||||||
|  | 	"Default User Role": "Varsayılan Kullanıcı Rolü", | ||||||
|  | 	"delete": "sil", | ||||||
|  | 	"Delete a model": "Bir modeli sil", | ||||||
|  | 	"Delete chat": "Sohbeti sil", | ||||||
|  | 	"Delete Chats": "Sohbetleri Sil", | ||||||
|  | 	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} silindi", | ||||||
|  | 	"Deleted {tagName}": "{tagName} silindi", | ||||||
|  | 	"Description": "Açıklama", | ||||||
|  | 	"Notifications": "Masaüstü Bildirimleri", | ||||||
|  | 	"Disabled": "Devre Dışı", | ||||||
|  | 	"Discover a modelfile": "Bir model dosyası keşfedin", | ||||||
|  | 	"Discover a prompt": "Bir prompt keşfedin", | ||||||
|  | 	"Discover, download, and explore custom prompts": "Özel promptları keşfedin, indirin ve inceleyin", | ||||||
|  | 	"Discover, download, and explore model presets": "Model ön ayarlarını keşfedin, indirin ve inceleyin", | ||||||
|  | 	"Display the username instead of You in the Chat": "Sohbet'te Siz yerine kullanıcı adını göster", | ||||||
|  | 	"Document": "Belge", | ||||||
|  | 	"Document Settings": "Belge Ayarları", | ||||||
|  | 	"Documents": "Belgeler", | ||||||
|  | 	"does not make any external connections, and your data stays securely on your locally hosted server.": "herhangi bir harici bağlantı yapmaz ve verileriniz güvenli bir şekilde yerel olarak barındırılan sunucunuzda kalır.", | ||||||
|  | 	"Don't Allow": "İzin Verme", | ||||||
|  | 	"Don't have an account?": "Hesabınız yok mu?", | ||||||
|  | 	"Download as a File": "Dosya olarak indir", | ||||||
|  | 	"Download Database": "Veritabanını İndir", | ||||||
|  | 	"Drop any files here to add to the conversation": "Sohbete eklemek istediğiniz dosyaları buraya bırakın", | ||||||
|  | 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "örn. '30s', '10m'. Geçerli zaman birimleri 's', 'm', 'h'.", | ||||||
|  | 	"Edit Doc": "Belgeyi Düzenle", | ||||||
|  | 	"Edit User": "Kullanıcıyı Düzenle", | ||||||
|  | 	"Email": "E-posta", | ||||||
|  | 	"Enable Chat History": "Sohbet Geçmişini Etkinleştir", | ||||||
|  | 	"Enable New Sign Ups": "Yeni Kayıtları Etkinleştir", | ||||||
|  | 	"Enabled": "Etkin", | ||||||
|  | 	"Enter {{role}} message here": "Buraya {{role}} mesajını girin", | ||||||
|  | 	"Enter API Key": "API Anahtarını Girin", | ||||||
|  | 	"Enter Chunk Overlap": "Chunk Örtüşmesini Girin", | ||||||
|  | 	"Enter Chunk Size": "Chunk Boyutunu Girin", | ||||||
|  | 	"Enter Image Size (e.g. 512x512)": "Görüntü Boyutunu Girin (örn. 512x512)", | ||||||
|  | 	"Enter LiteLLM API Base URL (litellm_params.api_base)": "LiteLLM API Ana URL'sini Girin (litellm_params.api_base)", | ||||||
|  | 	"Enter LiteLLM API Key (litellm_params.api_key)": "LiteLLM API Anahtarını Girin (litellm_params.api_key)", | ||||||
|  | 	"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM'ini Girin (litellm_params.rpm)", | ||||||
|  | 	"Enter LiteLLM Model (litellm_params.model)": "LiteLLM Modelini Girin (litellm_params.model)", | ||||||
|  | 	"Enter Max Tokens (litellm_params.max_tokens)": "Maksimum Token Sayısını Girin (litellm_params.max_tokens)", | ||||||
|  | 	"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})", | ||||||
|  | 	"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)", | ||||||
|  | 	"Enter stop sequence": "Durdurma dizisini girin", | ||||||
|  | 	"Enter Top K": "Top K'yı girin", | ||||||
|  | 	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)", | ||||||
|  | 	"Enter Your Email": "E-postanızı Girin", | ||||||
|  | 	"Enter Your Full Name": "Tam Adınızı Girin", | ||||||
|  | 	"Enter Your Password": "Parolanızı Girin", | ||||||
|  | 	"Experimental": "Deneysel", | ||||||
|  | 	"Export All Chats (All Users)": "Tüm Sohbetleri Dışa Aktar (Tüm Kullanıcılar)", | ||||||
|  | 	"Export Chats": "Sohbetleri Dışa Aktar", | ||||||
|  | 	"Export Documents Mapping": "Belge Eşlemesini Dışa Aktar", | ||||||
|  | 	"Export Modelfiles": "Model Dosyalarını Dışa Aktar", | ||||||
|  | 	"Export Prompts": "Promptları Dışa Aktar", | ||||||
|  | 	"Failed to read clipboard contents": "Pano içeriği okunamadı", | ||||||
|  | 	"File Mode": "Dosya Modu", | ||||||
|  | 	"File not found.": "Dosya bulunamadı.", | ||||||
|  | 	"Focus chat input": "Sohbet girişine odaklan", | ||||||
|  | 	"Format your variables using square brackets like this:": "Değişkenlerinizi şu şekilde kare parantezlerle biçimlendirin:", | ||||||
|  | 	"From (Base Model)": "(Temel Model)'den", | ||||||
|  | 	"Full Screen Mode": "Tam Ekran Modu", | ||||||
|  | 	"General": "Genel", | ||||||
|  | 	"General Settings": "Genel Ayarlar", | ||||||
|  | 	"Hello, {{name}}": "Merhaba, {{name}}", | ||||||
|  | 	"Hide": "Gizle", | ||||||
|  | 	"Hide Additional Params": "Ek Parametreleri Gizle", | ||||||
|  | 	"How can I help you today?": "Bugün size nasıl yardımcı olabilirim?", | ||||||
|  | 	"Image Generation (Experimental)": "Görüntü Oluşturma (Deneysel)", | ||||||
|  | 	"Image Generation Engine": "Görüntü Oluşturma Motoru", | ||||||
|  | 	"Image Settings": "Görüntü Ayarları", | ||||||
|  | 	"Images": "Görüntüler", | ||||||
|  | 	"Import Chats": "Sohbetleri İçe Aktar", | ||||||
|  | 	"Import Documents Mapping": "Belge Eşlemesini İçe Aktar", | ||||||
|  | 	"Import Modelfiles": "Model Dosyalarını İçe Aktar", | ||||||
|  | 	"Import Prompts": "Promptları İçe Aktar", | ||||||
|  | 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api` bayrağını dahil edin", | ||||||
|  | 	"Interface": "Arayüz", | ||||||
|  | 	"join our Discord for help.": "yardım için Discord'umuza katılın.", | ||||||
|  | 	"JSON": "JSON", | ||||||
|  | 	"JWT Expiration": "JWT Bitişi", | ||||||
|  | 	"JWT Token": "JWT Token", | ||||||
|  | 	"Keep Alive": "Canlı Tut", | ||||||
|  | 	"Keyboard shortcuts": "Klavye kısayolları", | ||||||
|  | 	"Language": "Dil", | ||||||
|  | 	"Light": "Açık", | ||||||
|  | 	"Listening...": "Dinleniyor...", | ||||||
|  | 	"LLMs can make mistakes. Verify important information.": "LLM'ler hata yapabilir. Önemli bilgileri doğrulayın.", | ||||||
|  | 	"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır", | ||||||
|  | 	"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:", | ||||||
|  | 	"Manage LiteLLM Models": "LiteLLM Modellerini Yönet", | ||||||
|  | 	"Manage Models": "Modelleri Yönet", | ||||||
|  | 	"Manage Ollama Models": "Ollama Modellerini Yönet", | ||||||
|  | 	"Max Tokens": "Maksimum Token", | ||||||
|  | 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Aynı anda en fazla 3 model indirilebilir. Lütfen daha sonra tekrar deneyin.", | ||||||
|  | 	"Mirostat": "Mirostat", | ||||||
|  | 	"Mirostat Eta": "Mirostat Eta", | ||||||
|  | 	"Mirostat Tau": "Mirostat Tau", | ||||||
|  | 	"MMMM DD, YYYY": "DD MMMM YYYY", | ||||||
|  | 	"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' başarıyla indirildi.", | ||||||
|  | 	"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.", | ||||||
|  | 	"Model {{modelId}} not found": "{{modelId}} bulunamadı", | ||||||
|  | 	"Model {{modelName}} already exists.": "{{modelName}} zaten mevcut.", | ||||||
|  | 	"Model Name": "Model Adı", | ||||||
|  | 	"Model not selected": "Model seçilmedi", | ||||||
|  | 	"Model Tag Name": "Model Etiket Adı", | ||||||
|  | 	"Model Whitelisting": "Model Beyaz Listeye Alma", | ||||||
|  | 	"Model(s) Whitelisted": "Model(ler) Beyaz Listeye Alındı", | ||||||
|  | 	"Modelfile": "Model Dosyası", | ||||||
|  | 	"Modelfile Advanced Settings": "Model Dosyası Gelişmiş Ayarları", | ||||||
|  | 	"Modelfile Content": "Model Dosyası İçeriği", | ||||||
|  | 	"Modelfiles": "Model Dosyaları", | ||||||
|  | 	"Models": "Modeller", | ||||||
|  | 	"My Documents": "Belgelerim", | ||||||
|  | 	"My Modelfiles": "Model Dosyalarım", | ||||||
|  | 	"My Prompts": "Promptlarım", | ||||||
|  | 	"Name": "Ad", | ||||||
|  | 	"Name Tag": "Ad Etiketi", | ||||||
|  | 	"Name your modelfile": "Model dosyanıza ad verin", | ||||||
|  | 	"New Chat": "Yeni Sohbet", | ||||||
|  | 	"New Password": "Yeni Parola", | ||||||
|  | 	"Not sure what to add?": "Ne ekleyeceğinizden emin değil misiniz?", | ||||||
|  | 	"Not sure what to write? Switch to": "Ne yazacağınızdan emin değil misiniz? Şuraya geçin", | ||||||
|  | 	"Off": "Kapalı", | ||||||
|  | 	"Okay, Let's Go!": "Tamam, Hadi Başlayalım!", | ||||||
|  | 	"Ollama Base URL": "Ollama Temel URL", | ||||||
|  | 	"Ollama Version": "Ollama Sürümü", | ||||||
|  | 	"On": "Açık", | ||||||
|  | 	"Only": "Yalnızca", | ||||||
|  | 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Komut dizisinde yalnızca alfasayısal karakterler ve tireler kabul edilir.", | ||||||
|  | 	"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Hop! Biraz sabırlı ol! Dosyaların hala hazırlama fırınında. Onları ağzınıza layık olana kadar pişiriyoruz :) Lütfen sabırlı olun; hazır olduklarında size haber vereceğiz.", | ||||||
|  | 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hop! URL geçersiz gibi görünüyor. Lütfen tekrar kontrol edin ve yeniden deneyin.", | ||||||
|  | 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hop! Desteklenmeyen bir yöntem kullanıyorsunuz (yalnızca önyüz). Lütfen WebUI'yi arkayüzden sunun.", | ||||||
|  | 	"Open": "Aç", | ||||||
|  | 	"Open AI": "Open AI", | ||||||
|  | 	"Open AI (Dall-E)": "Open AI (Dall-E)", | ||||||
|  | 	"Open new chat": "Yeni sohbet aç", | ||||||
|  | 	"OpenAI API": "OpenAI API", | ||||||
|  | 	"OpenAI API Key": "OpenAI API Anahtarı", | ||||||
|  | 	"OpenAI API Key is required.": "OpenAI API Anahtarı gereklidir.", | ||||||
|  | 	"or": "veya", | ||||||
|  | 	"Parameters": "Parametreler", | ||||||
|  | 	"Password": "Parola", | ||||||
|  | 	"PDF Extract Images (OCR)": "PDF Görüntülerini Çıkart (OCR)", | ||||||
|  | 	"pending": "beklemede", | ||||||
|  | 	"Permission denied when accessing microphone: {{error}}": "Mikrofona erişim izni reddedildi: {{error}}", | ||||||
|  | 	"Playground": "Oyun Alanı", | ||||||
|  | 	"Profile": "Profil", | ||||||
|  | 	"Prompt Content": "Prompt İçeriği", | ||||||
|  | 	"Prompt suggestions": "Prompt önerileri", | ||||||
|  | 	"Prompts": "Promptlar", | ||||||
|  | 	"Pull a model from Ollama.com": "Ollama.com'dan bir model çekin", | ||||||
|  | 	"Pull Progress": "Çekme İlerlemesi", | ||||||
|  | 	"Query Params": "Sorgu Parametreleri", | ||||||
|  | 	"RAG Template": "RAG Şablonu", | ||||||
|  | 	"Raw Format": "Ham Format", | ||||||
|  | 	"Record voice": "Ses kaydı yap", | ||||||
|  | 	"Redirecting you to OpenWebUI Community": "OpenWebUI Topluluğuna yönlendiriliyorsunuz", | ||||||
|  | 	"Release Notes": "Sürüm Notları", | ||||||
|  | 	"Repeat Last N": "Son N'yi Tekrar Et", | ||||||
|  | 	"Repeat Penalty": "Tekrar Cezası", | ||||||
|  | 	"Request Mode": "İstek Modu", | ||||||
|  | 	"Reset Vector Storage": "Vektör Depolamayı Sıfırla", | ||||||
|  | 	"Response AutoCopy to Clipboard": "Yanıtı Panoya Otomatik Kopyala", | ||||||
|  | 	"Role": "Rol", | ||||||
|  | 	"Rosé Pine": "Rosé Pine", | ||||||
|  | 	"Rosé Pine Dawn": "Rosé Pine Dawn", | ||||||
|  | 	"Save": "Kaydet", | ||||||
|  | 	"Save & Create": "Kaydet ve Oluştur", | ||||||
|  | 	"Save & Submit": "Kaydet ve Gönder", | ||||||
|  | 	"Save & Update": "Kaydet ve Güncelle", | ||||||
|  | 	"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:", | ||||||
|  | 	"Scan": "Tarama", | ||||||
|  | 	"Scan complete!": "Tarama tamamlandı!", | ||||||
|  | 	"Scan for documents from {{path}}": "{{path}} dizininden belgeleri tarayın", | ||||||
|  | 	"Search": "Ara", | ||||||
|  | 	"Search Documents": "Belgeleri Ara", | ||||||
|  | 	"Search Prompts": "Prompt Ara", | ||||||
|  | 	"See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın", | ||||||
|  | 	"See what's new": "Yeniliklere göz atın", | ||||||
|  | 	"Seed": "Seed", | ||||||
|  | 	"Select a mode": "Bir mod seç", | ||||||
|  | 	"Select a model": "Bir model seç", | ||||||
|  | 	"Select an Ollama instance": "Bir Ollama örneği seçin", | ||||||
|  | 	"Send a Message": "Bir Mesaj Gönder", | ||||||
|  | 	"Send message": "Mesaj gönder", | ||||||
|  | 	"Server connection verified": "Sunucu bağlantısı doğrulandı", | ||||||
|  | 	"Set as default": "Varsayılan olarak ayarla", | ||||||
|  | 	"Set Default Model": "Varsayılan Modeli Ayarla", | ||||||
|  | 	"Set Image Size": "Görüntü Boyutunu Ayarla", | ||||||
|  | 	"Set Steps": "Adımları Ayarla", | ||||||
|  | 	"Set Title Auto-Generation Model": "Otomatik Başlık Oluşturma Modelini Ayarla", | ||||||
|  | 	"Set Voice": "Ses Ayarla", | ||||||
|  | 	"Settings": "Ayarlar", | ||||||
|  | 	"Settings saved successfully!": "Ayarlar başarıyla kaydedildi!", | ||||||
|  | 	"Share to OpenWebUI Community": "OpenWebUI Topluluğu ile Paylaş", | ||||||
|  | 	"short-summary": "kısa-özet", | ||||||
|  | 	"Show": "Göster", | ||||||
|  | 	"Show Additional Params": "Ek Parametreleri Göster", | ||||||
|  | 	"Show shortcuts": "Kısayolları göster", | ||||||
|  | 	"sidebar": "kenar çubuğu", | ||||||
|  | 	"Sign in": "Oturum aç", | ||||||
|  | 	"Sign Out": "Çıkış Yap", | ||||||
|  | 	"Sign up": "Kaydol", | ||||||
|  | 	"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}", | ||||||
|  | 	"Speech-to-Text Engine": "Konuşmadan Metne Motoru", | ||||||
|  | 	"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API bu tarayıcıda desteklenmiyor.", | ||||||
|  | 	"Stop Sequence": "Diziyi Durdur", | ||||||
|  | 	"STT Settings": "STT Ayarları", | ||||||
|  | 	"Submit": "Gönder", | ||||||
|  | 	"Success": "Başarılı", | ||||||
|  | 	"Successfully updated.": "Başarıyla güncellendi.", | ||||||
|  | 	"Sync All": "Tümünü Senkronize Et", | ||||||
|  | 	"System": "Sistem", | ||||||
|  | 	"System Prompt": "Sistem Promptu", | ||||||
|  | 	"Tags": "Etiketler", | ||||||
|  | 	"Temperature": "Temperature", | ||||||
|  | 	"Template": "Şablon", | ||||||
|  | 	"Text Completion": "Metin Tamamlama", | ||||||
|  | 	"Text-to-Speech Engine": "Metinden Sese Motoru", | ||||||
|  | 	"Tfs Z": "Tfs Z", | ||||||
|  | 	"Theme": "Tema", | ||||||
|  | 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Bu, önemli konuşmalarınızın güvenli bir şekilde arkayüz veritabanınıza kaydedildiğini garantiler. Teşekkür ederiz!", | ||||||
|  | 	"This setting does not sync across browsers or devices.": "Bu ayar tarayıcılar veya cihazlar arasında senkronize edilmez.", | ||||||
|  | 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "İpucu: Her değiştirmeden sonra sohbet girişinde tab tuşuna basarak birden fazla değişken yuvasını art arda güncelleyin.", | ||||||
|  | 	"Title": "Başlık", | ||||||
|  | 	"Title Auto-Generation": "Otomatik Başlık Oluşturma", | ||||||
|  | 	"Title Generation Prompt": "Başlık Oluşturma Promptu", | ||||||
|  | 	"to": "için", | ||||||
|  | 	"To access the available model names for downloading,": "İndirilebilir mevcut model adlarına erişmek için,", | ||||||
|  | 	"To access the GGUF models available for downloading,": "İndirilebilir mevcut GGUF modellerine erişmek için,", | ||||||
|  | 	"to chat input.": "sohbet girişine.", | ||||||
|  | 	"Toggle settings": "Ayarları Aç/Kapat", | ||||||
|  | 	"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat", | ||||||
|  | 	"Top K": "Top K", | ||||||
|  | 	"Top P": "Top P", | ||||||
|  | 	"Trouble accessing Ollama?": "Ollama'ya erişmede sorun mu yaşıyorsunuz?", | ||||||
|  | 	"TTS Settings": "TTS Ayarları", | ||||||
|  | 	"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Download) URL'sini Yazın", | ||||||
|  | 	"Uh-oh! There was an issue connecting to {{provider}}.": "Ah! {{provider}}'a bağlanırken bir sorun oluştu.", | ||||||
|  | 	"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Bilinmeyen Dosya Türü '{{file_type}}', ancak düz metin olarak kabul ediliyor ve işleniyor", | ||||||
|  | 	"Update password": "Parolayı Güncelle", | ||||||
|  | 	"Upload a GGUF model": "Bir GGUF modeli yükle", | ||||||
|  | 	"Upload files": "Dosyaları Yükle", | ||||||
|  | 	"Upload Progress": "Yükleme İlerlemesi", | ||||||
|  | 	"URL Mode": "URL Modu", | ||||||
|  | 	"Use '#' in the prompt input to load and select your documents.": "Belgelerinizi yüklemek ve seçmek için promptda '#' kullanın.", | ||||||
|  | 	"Use Gravatar": "Gravatar Kullan", | ||||||
|  | 	"user": "kullanıcı", | ||||||
|  | 	"User Permissions": "Kullanıcı İzinleri", | ||||||
|  | 	"Users": "Kullanıcılar", | ||||||
|  | 	"Utilize": "Kullan", | ||||||
|  | 	"Valid time units:": "Geçerli zaman birimleri:", | ||||||
|  | 	"variable": "değişken", | ||||||
|  | 	"variable to have them replaced with clipboard content.": "panodaki içerikle değiştirilmesi için değişken.", | ||||||
|  | 	"Version": "Sürüm", | ||||||
|  | 	"Web": "Web", | ||||||
|  | 	"WebUI Add-ons": "WebUI Eklentileri", | ||||||
|  | 	"WebUI Settings": "WebUI Ayarları", | ||||||
|  | 	"WebUI will make requests to": "WebUI, isteklerde bulunacak:", | ||||||
|  | 	"What’s New in": "Yenilikler:", | ||||||
|  | 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.", | ||||||
|  | 	"Whisper (Local)": "Whisper (Yerel)", | ||||||
|  | 	"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)", | ||||||
|  | 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.", | ||||||
|  | 	"You": "Siz", | ||||||
|  | 	"You're a helpful assistant.": "Sen yardımcı bir asistansın.", | ||||||
|  | 	"You're now logged in.": "Şimdi oturum açtınız." | ||||||
|  | } | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Видалено {tagName}", | 	"Deleted {tagName}": "Видалено {tagName}", | ||||||
| 	"Description": "Опис", | 	"Description": "Опис", | ||||||
| 	"Desktop Notifications": "Сповіщення", | 	"Notifications": "Сповіщення", | ||||||
| 	"Disabled": "Вимкнено", | 	"Disabled": "Вимкнено", | ||||||
| 	"Discover a modelfile": "Знайти файл моделі", | 	"Discover a modelfile": "Знайти файл моделі", | ||||||
| 	"Discover a prompt": "Знайти промт", | 	"Discover a prompt": "Знайти промт", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "Đã xóa {tagName}", | 	"Deleted {tagName}": "Đã xóa {tagName}", | ||||||
| 	"Description": "Mô tả", | 	"Description": "Mô tả", | ||||||
| 	"Desktop Notifications": "Thông báo trên máy tính (Notification)", | 	"Notifications": "Thông báo trên máy tính (Notification)", | ||||||
| 	"Disabled": "Đã vô hiệu hóa", | 	"Disabled": "Đã vô hiệu hóa", | ||||||
| 	"Discover a modelfile": "Khám phá thêm các mô hình mới", | 	"Discover a modelfile": "Khám phá thêm các mô hình mới", | ||||||
| 	"Discover a prompt": "Khám phá thêm prompt mới", | 	"Discover a prompt": "Khám phá thêm prompt mới", | ||||||
|  |  | ||||||
|  | @ -100,7 +100,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "已删除{tagName}", | 	"Deleted {tagName}": "已删除{tagName}", | ||||||
| 	"Description": "描述", | 	"Description": "描述", | ||||||
| 	"Desktop Notifications": "桌面通知", | 	"Notifications": "桌面通知", | ||||||
| 	"Disabled": "禁用", | 	"Disabled": "禁用", | ||||||
| 	"Discover a modelfile": "探索模型文件", | 	"Discover a modelfile": "探索模型文件", | ||||||
| 	"Discover a prompt": "探索提示词", | 	"Discover a prompt": "探索提示词", | ||||||
|  |  | ||||||
|  | @ -101,7 +101,7 @@ | ||||||
| 	"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}", | 	"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}", | ||||||
| 	"Deleted {tagName}": "已刪除 {tagName}", | 	"Deleted {tagName}": "已刪除 {tagName}", | ||||||
| 	"Description": "描述", | 	"Description": "描述", | ||||||
| 	"Desktop Notifications": "桌面通知", | 	"Notifications": "桌面通知", | ||||||
| 	"Disabled": "已停用", | 	"Disabled": "已停用", | ||||||
| 	"Discover a modelfile": "發現新 Modelfile", | 	"Discover a modelfile": "發現新 Modelfile", | ||||||
| 	"Discover a prompt": "發現新提示詞", | 	"Discover a prompt": "發現新提示詞", | ||||||
|  |  | ||||||
|  | @ -31,6 +31,21 @@ export const getModels = async (token: string) => { | ||||||
| // Helper functions
 | // Helper functions
 | ||||||
| //////////////////////////
 | //////////////////////////
 | ||||||
| 
 | 
 | ||||||
|  | export const sanitizeResponseContent = (content: string) => { | ||||||
|  | 	return content | ||||||
|  | 		.replace(/<\|[a-z]*$/, '') | ||||||
|  | 		.replace(/<\|[a-z]+\|$/, '') | ||||||
|  | 		.replace(/<$/, '') | ||||||
|  | 		.replaceAll(/<\|[a-z]+\|>/g, ' ') | ||||||
|  | 		.replaceAll(/<br\s?\/?>/gi, '\n') | ||||||
|  | 		.replaceAll('<', '<') | ||||||
|  | 		.trim(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const revertSanitizedResponseContent = (content: string) => { | ||||||
|  | 	return content.replaceAll('<', '<'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export const capitalizeFirstLetter = (string) => { | export const capitalizeFirstLetter = (string) => { | ||||||
| 	return string.charAt(0).toUpperCase() + string.slice(1); | 	return string.charAt(0).toUpperCase() + string.slice(1); | ||||||
| }; | }; | ||||||
|  | @ -96,6 +111,82 @@ export const getGravatarURL = (email) => { | ||||||
| 	return `https://www.gravatar.com/avatar/${hash}`; | 	return `https://www.gravatar.com/avatar/${hash}`; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export const canvasPixelTest = () => { | ||||||
|  | 	// Test a 1x1 pixel to potentially identify browser/plugin fingerprint blocking or spoofing
 | ||||||
|  | 	// Inspiration: https://github.com/kkapsner/CanvasBlocker/blob/master/test/detectionTest.js
 | ||||||
|  | 	const canvas = document.createElement('canvas'); | ||||||
|  | 	const ctx = canvas.getContext('2d'); | ||||||
|  | 	canvas.height = 1; | ||||||
|  | 	canvas.width = 1; | ||||||
|  | 	const imageData = new ImageData(canvas.width, canvas.height); | ||||||
|  | 	const pixelValues = imageData.data; | ||||||
|  | 
 | ||||||
|  | 	// Generate RGB test data
 | ||||||
|  | 	for (let i = 0; i < imageData.data.length; i += 1) { | ||||||
|  | 		if (i % 4 !== 3) { | ||||||
|  | 			pixelValues[i] = Math.floor(256 * Math.random()); | ||||||
|  | 		} else { | ||||||
|  | 			pixelValues[i] = 255; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.putImageData(imageData, 0, 0); | ||||||
|  | 	const p = ctx.getImageData(0, 0, canvas.width, canvas.height).data; | ||||||
|  | 
 | ||||||
|  | 	// Read RGB data and fail if unmatched
 | ||||||
|  | 	for (let i = 0; i < p.length; i += 1) { | ||||||
|  | 		if (p[i] !== pixelValues[i]) { | ||||||
|  | 			console.log( | ||||||
|  | 				'canvasPixelTest: Wrong canvas pixel RGB value detected:', | ||||||
|  | 				p[i], | ||||||
|  | 				'at:', | ||||||
|  | 				i, | ||||||
|  | 				'expected:', | ||||||
|  | 				pixelValues[i] | ||||||
|  | 			); | ||||||
|  | 			console.log('canvasPixelTest: Canvas blocking or spoofing is likely'); | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const generateInitialsImage = (name) => { | ||||||
|  | 	const canvas = document.createElement('canvas'); | ||||||
|  | 	const ctx = canvas.getContext('2d'); | ||||||
|  | 	canvas.width = 100; | ||||||
|  | 	canvas.height = 100; | ||||||
|  | 
 | ||||||
|  | 	if (!canvasPixelTest()) { | ||||||
|  | 		console.log( | ||||||
|  | 			'generateInitialsImage: failed pixel test, fingerprint evasion is likely. Using default image.' | ||||||
|  | 		); | ||||||
|  | 		return '/user.png'; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.fillStyle = '#F39C12'; | ||||||
|  | 	ctx.fillRect(0, 0, canvas.width, canvas.height); | ||||||
|  | 
 | ||||||
|  | 	ctx.fillStyle = '#FFFFFF'; | ||||||
|  | 	ctx.font = '40px Helvetica'; | ||||||
|  | 	ctx.textAlign = 'center'; | ||||||
|  | 	ctx.textBaseline = 'middle'; | ||||||
|  | 
 | ||||||
|  | 	const sanitizedName = name.trim(); | ||||||
|  | 	const initials = | ||||||
|  | 		sanitizedName.length > 0 | ||||||
|  | 			? sanitizedName[0] + | ||||||
|  | 			  (sanitizedName.split(' ').length > 1 | ||||||
|  | 					? sanitizedName[sanitizedName.lastIndexOf(' ') + 1] | ||||||
|  | 					: '') | ||||||
|  | 			: ''; | ||||||
|  | 
 | ||||||
|  | 	ctx.fillText(initials.toUpperCase(), canvas.width / 2, canvas.height / 2); | ||||||
|  | 
 | ||||||
|  | 	return canvas.toDataURL(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export const copyToClipboard = (text) => { | export const copyToClipboard = (text) => { | ||||||
| 	if (!navigator.clipboard) { | 	if (!navigator.clipboard) { | ||||||
| 		const textArea = document.createElement('textarea'); | 		const textArea = document.createElement('textarea'); | ||||||
|  |  | ||||||
|  | @ -847,6 +847,7 @@ | ||||||
| 		bind:selectedModels | 		bind:selectedModels | ||||||
| 		bind:showModelSelector | 		bind:showModelSelector | ||||||
| 		shareEnabled={messages.length > 0} | 		shareEnabled={messages.length > 0} | ||||||
|  | 		{chat} | ||||||
| 		{initNewChat} | 		{initNewChat} | ||||||
| 		{tags} | 		{tags} | ||||||
| 		{addTag} | 		{addTag} | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ | ||||||
| 	import { goto } from '$app/navigation'; | 	import { goto } from '$app/navigation'; | ||||||
| 	import { onMount, getContext } from 'svelte'; | 	import { onMount, getContext } from 'svelte'; | ||||||
| 
 | 
 | ||||||
|  | 	import dayjs from 'dayjs'; | ||||||
|  | 
 | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 
 | 
 | ||||||
| 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | 	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users'; | ||||||
|  | @ -16,6 +18,7 @@ | ||||||
| 	let loaded = false; | 	let loaded = false; | ||||||
| 	let users = []; | 	let users = []; | ||||||
| 
 | 
 | ||||||
|  | 	let search = ''; | ||||||
| 	let selectedUser = null; | 	let selectedUser = null; | ||||||
| 
 | 
 | ||||||
| 	let showSettingsModal = false; | 	let showSettingsModal = false; | ||||||
|  | @ -80,157 +83,193 @@ | ||||||
| 
 | 
 | ||||||
| <SettingsModal bind:show={showSettingsModal} /> | <SettingsModal bind:show={showSettingsModal} /> | ||||||
| 
 | 
 | ||||||
| <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white font-mona"> | <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white"> | ||||||
| 	{#if loaded} | 	{#if loaded} | ||||||
| 		<div class=" flex flex-col justify-between w-full overflow-y-auto"> | 		<div class=" flex flex-col justify-between w-full overflow-y-auto"> | ||||||
| 			<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10"> | 			<div class=" mx-auto w-full"> | ||||||
| 				<div class="w-full"> | 				<div class="w-full"> | ||||||
| 					<div class=" flex flex-col justify-center"> | 					<div class=" flex flex-col justify-center"> | ||||||
| 						<div class=" flex justify-between items-center"> | 						<div class=" px-5 pt-3"> | ||||||
| 							<div class="flex items-center text-2xl font-semibold"> | 							<div class=" flex justify-between items-center"> | ||||||
| 								{$i18n.t('All Users')} | 								<div class="flex items-center text-2xl font-semibold font-mona">Dashboard</div> | ||||||
| 								<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" /> | 								<div> | ||||||
| 								<span class="text-lg font-medium text-gray-500 dark:text-gray-300" | 									<button | ||||||
| 									>{users.length}</span | 										class="flex 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 transition" | ||||||
| 								> | 										type="button" | ||||||
| 							</div> | 										on:click={() => { | ||||||
| 							<div> | 											showSettingsModal = !showSettingsModal; | ||||||
| 								<button | 										}} | ||||||
| 									class="flex 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 transition" |  | ||||||
| 									type="button" |  | ||||||
| 									on:click={() => { |  | ||||||
| 										showSettingsModal = !showSettingsModal; |  | ||||||
| 									}} |  | ||||||
| 								> |  | ||||||
| 									<svg |  | ||||||
| 										xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 										viewBox="0 0 16 16" |  | ||||||
| 										fill="currentColor" |  | ||||||
| 										class="w-4 h-4" |  | ||||||
| 									> | 									> | ||||||
| 										<path | 										<svg | ||||||
| 											fill-rule="evenodd" | 											xmlns="http://www.w3.org/2000/svg" | ||||||
| 											d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z" | 											viewBox="0 0 16 16" | ||||||
| 											clip-rule="evenodd" | 											fill="currentColor" | ||||||
| 										/> | 											class="w-4 h-4" | ||||||
| 									</svg> | 										> | ||||||
|  | 											<path | ||||||
|  | 												fill-rule="evenodd" | ||||||
|  | 												d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z" | ||||||
|  | 												clip-rule="evenodd" | ||||||
|  | 											/> | ||||||
|  | 										</svg> | ||||||
| 
 | 
 | ||||||
| 									<div class=" text-xs">{$i18n.t('Admin Settings')}</div> | 										<div class=" text-xs">{$i18n.t('Admin Settings')}</div> | ||||||
| 								</button> | 									</button> | ||||||
|  | 								</div> | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 						<div class=" text-gray-500 text-xs mt-1"> | 
 | ||||||
| 							ⓘ {$i18n.t("Click on the user role button to change a user's role.")} | 						<div class="px-5 flex text-sm gap-2.5"> | ||||||
|  | 							<div class="py-3 border-b font-medium text-gray-100 cursor-pointer">Overview</div> | ||||||
|  | 							<!-- <div class="py-3 text-gray-300 cursor-pointer">Users</div> --> | ||||||
| 						</div> | 						</div> | ||||||
| 
 | 
 | ||||||
| 						<hr class=" my-3 dark:border-gray-600" /> | 						<hr class=" mb-3 dark:border-gray-800" /> | ||||||
| 
 | 
 | ||||||
| 						<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap"> | 						<div class="px-5"> | ||||||
| 							<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto"> | 							<div class="mt-0.5 mb-3 flex justify-between"> | ||||||
| 								<thead | 								<div class="flex text-lg font-medium px-0.5"> | ||||||
| 									class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400" | 									{$i18n.t('All Users')} | ||||||
| 								> | 									<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" /> | ||||||
| 									<tr> | 									<span class="text-lg font-medium text-gray-500 dark:text-gray-300" | ||||||
| 										<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th> | 										>{users.length}</span | ||||||
| 										<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th> | 									> | ||||||
| 										<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th> | 								</div> | ||||||
| 										<th scope="col" class="px-3 py-2"> {$i18n.t('Action')} </th> |  | ||||||
| 									</tr> |  | ||||||
| 								</thead> |  | ||||||
| 								<tbody> |  | ||||||
| 									{#each users as user} |  | ||||||
| 										<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs"> |  | ||||||
| 											<td class="px-3 py-2 min-w-[7rem] w-28"> |  | ||||||
| 												<button |  | ||||||
| 													class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role === |  | ||||||
| 														'admin' && |  | ||||||
| 														'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role === 'user' && |  | ||||||
| 														'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role === |  | ||||||
| 														'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}" |  | ||||||
| 													on:click={() => { |  | ||||||
| 														if (user.role === 'user') { |  | ||||||
| 															updateRoleHandler(user.id, 'admin'); |  | ||||||
| 														} else if (user.role === 'pending') { |  | ||||||
| 															updateRoleHandler(user.id, 'user'); |  | ||||||
| 														} else { |  | ||||||
| 															updateRoleHandler(user.id, 'pending'); |  | ||||||
| 														} |  | ||||||
| 													}} |  | ||||||
| 												> |  | ||||||
| 													<div |  | ||||||
| 														class="w-1 h-1 rounded-full {user.role === 'admin' && |  | ||||||
| 															'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' && |  | ||||||
| 															'bg-green-600 dark:bg-green-300'} {user.role === 'pending' && |  | ||||||
| 															'bg-gray-600 dark:bg-gray-300'}" |  | ||||||
| 													/> |  | ||||||
| 													{$i18n.t(user.role)}</button |  | ||||||
| 												> |  | ||||||
| 											</td> |  | ||||||
| 											<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max"> |  | ||||||
| 												<div class="flex flex-row w-max"> |  | ||||||
| 													<img |  | ||||||
| 														class=" rounded-full w-6 h-6 object-cover mr-2.5" |  | ||||||
| 														src={user.profile_image_url} |  | ||||||
| 														alt="user" |  | ||||||
| 													/> |  | ||||||
| 
 | 
 | ||||||
| 													<div class=" font-medium self-center">{user.name}</div> | 								<div class=""> | ||||||
| 												</div> | 									<input | ||||||
| 											</td> | 										class=" w-60 rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||||
| 											<td class=" px-3 py-2"> {user.email} </td> | 										placeholder={$i18n.t('Search')} | ||||||
|  | 										bind:value={search} | ||||||
|  | 									/> | ||||||
|  | 								</div> | ||||||
|  | 							</div> | ||||||
| 
 | 
 | ||||||
| 											<td class="px-3 py-2"> | 							<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap"> | ||||||
| 												<div class="flex justify-start w-full"> | 								<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto"> | ||||||
| 													<button | 									<thead | ||||||
| 														class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" | 										class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400" | ||||||
| 														on:click={async () => { | 									> | ||||||
| 															showEditUserModal = !showEditUserModal; | 										<tr> | ||||||
| 															selectedUser = user; | 											<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th> | ||||||
| 														}} | 											<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th> | ||||||
| 													> | 											<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th> | ||||||
| 														<svg | 											<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th> | ||||||
| 															xmlns="http://www.w3.org/2000/svg" | 											<th scope="col" class="px-3 py-2 text-right" /> | ||||||
| 															fill="none" |  | ||||||
| 															viewBox="0 0 24 24" |  | ||||||
| 															stroke-width="1.5" |  | ||||||
| 															stroke="currentColor" |  | ||||||
| 															class="w-4 h-4" |  | ||||||
| 														> |  | ||||||
| 															<path |  | ||||||
| 																stroke-linecap="round" |  | ||||||
| 																stroke-linejoin="round" |  | ||||||
| 																d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" |  | ||||||
| 															/> |  | ||||||
| 														</svg> |  | ||||||
| 													</button> |  | ||||||
| 
 |  | ||||||
| 													<button |  | ||||||
| 														class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" |  | ||||||
| 														on:click={async () => { |  | ||||||
| 															deleteUserHandler(user.id); |  | ||||||
| 														}} |  | ||||||
| 													> |  | ||||||
| 														<svg |  | ||||||
| 															xmlns="http://www.w3.org/2000/svg" |  | ||||||
| 															fill="none" |  | ||||||
| 															viewBox="0 0 24 24" |  | ||||||
| 															stroke-width="1.5" |  | ||||||
| 															stroke="currentColor" |  | ||||||
| 															class="w-4 h-4" |  | ||||||
| 														> |  | ||||||
| 															<path |  | ||||||
| 																stroke-linecap="round" |  | ||||||
| 																stroke-linejoin="round" |  | ||||||
| 																d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" |  | ||||||
| 															/> |  | ||||||
| 														</svg> |  | ||||||
| 													</button> |  | ||||||
| 												</div> |  | ||||||
| 											</td> |  | ||||||
| 										</tr> | 										</tr> | ||||||
| 									{/each} | 									</thead> | ||||||
| 								</tbody> | 									<tbody> | ||||||
| 							</table> | 										{#each users.filter((user) => { | ||||||
|  | 											if (search === '') { | ||||||
|  | 												return true; | ||||||
|  | 											} else { | ||||||
|  | 												let name = user.name.toLowerCase(); | ||||||
|  | 												const query = search.toLowerCase(); | ||||||
|  | 												return name.includes(query); | ||||||
|  | 											} | ||||||
|  | 										}) as user} | ||||||
|  | 											<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs"> | ||||||
|  | 												<td class="px-3 py-2 min-w-[7rem] w-28"> | ||||||
|  | 													<button | ||||||
|  | 														class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role === | ||||||
|  | 															'admin' && | ||||||
|  | 															'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role === | ||||||
|  | 															'user' && | ||||||
|  | 															'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role === | ||||||
|  | 															'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}" | ||||||
|  | 														on:click={() => { | ||||||
|  | 															if (user.role === 'user') { | ||||||
|  | 																updateRoleHandler(user.id, 'admin'); | ||||||
|  | 															} else if (user.role === 'pending') { | ||||||
|  | 																updateRoleHandler(user.id, 'user'); | ||||||
|  | 															} else { | ||||||
|  | 																updateRoleHandler(user.id, 'pending'); | ||||||
|  | 															} | ||||||
|  | 														}} | ||||||
|  | 													> | ||||||
|  | 														<div | ||||||
|  | 															class="w-1 h-1 rounded-full {user.role === 'admin' && | ||||||
|  | 																'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' && | ||||||
|  | 																'bg-green-600 dark:bg-green-300'} {user.role === 'pending' && | ||||||
|  | 																'bg-gray-600 dark:bg-gray-300'}" | ||||||
|  | 														/> | ||||||
|  | 														{$i18n.t(user.role)}</button | ||||||
|  | 													> | ||||||
|  | 												</td> | ||||||
|  | 												<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max"> | ||||||
|  | 													<div class="flex flex-row w-max"> | ||||||
|  | 														<img | ||||||
|  | 															class=" rounded-full w-6 h-6 object-cover mr-2.5" | ||||||
|  | 															src={user.profile_image_url} | ||||||
|  | 															alt="user" | ||||||
|  | 														/> | ||||||
|  | 
 | ||||||
|  | 														<div class=" font-medium self-center">{user.name}</div> | ||||||
|  | 													</div> | ||||||
|  | 												</td> | ||||||
|  | 												<td class=" px-3 py-2"> {user.email} </td> | ||||||
|  | 
 | ||||||
|  | 												<td class=" px-3 py-2"> | ||||||
|  | 													{dayjs(user.timestamp * 1000).format($i18n.t('MMMM DD, YYYY'))} | ||||||
|  | 												</td> | ||||||
|  | 
 | ||||||
|  | 												<td class="px-3 py-2 text-right"> | ||||||
|  | 													<div class="flex justify-end w-full"> | ||||||
|  | 														<button | ||||||
|  | 															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" | ||||||
|  | 															on:click={async () => { | ||||||
|  | 																showEditUserModal = !showEditUserModal; | ||||||
|  | 																selectedUser = user; | ||||||
|  | 															}} | ||||||
|  | 														> | ||||||
|  | 															<svg | ||||||
|  | 																xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 																fill="none" | ||||||
|  | 																viewBox="0 0 24 24" | ||||||
|  | 																stroke-width="1.5" | ||||||
|  | 																stroke="currentColor" | ||||||
|  | 																class="w-4 h-4" | ||||||
|  | 															> | ||||||
|  | 																<path | ||||||
|  | 																	stroke-linecap="round" | ||||||
|  | 																	stroke-linejoin="round" | ||||||
|  | 																	d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" | ||||||
|  | 																/> | ||||||
|  | 															</svg> | ||||||
|  | 														</button> | ||||||
|  | 
 | ||||||
|  | 														<button | ||||||
|  | 															class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl" | ||||||
|  | 															on:click={async () => { | ||||||
|  | 																deleteUserHandler(user.id); | ||||||
|  | 															}} | ||||||
|  | 														> | ||||||
|  | 															<svg | ||||||
|  | 																xmlns="http://www.w3.org/2000/svg" | ||||||
|  | 																fill="none" | ||||||
|  | 																viewBox="0 0 24 24" | ||||||
|  | 																stroke-width="1.5" | ||||||
|  | 																stroke="currentColor" | ||||||
|  | 																class="w-4 h-4" | ||||||
|  | 															> | ||||||
|  | 																<path | ||||||
|  | 																	stroke-linecap="round" | ||||||
|  | 																	stroke-linejoin="round" | ||||||
|  | 																	d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" | ||||||
|  | 																/> | ||||||
|  | 															</svg> | ||||||
|  | 														</button> | ||||||
|  | 													</div> | ||||||
|  | 												</td> | ||||||
|  | 											</tr> | ||||||
|  | 										{/each} | ||||||
|  | 									</tbody> | ||||||
|  | 								</table> | ||||||
|  | 							</div> | ||||||
|  | 
 | ||||||
|  | 							<div class=" text-gray-500 text-xs mt-2 text-right"> | ||||||
|  | 								ⓘ {$i18n.t("Click on the user role button to change a user's role.")} | ||||||
|  | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
|  | @ -865,6 +865,7 @@ | ||||||
| 	<div class="min-h-screen max-h-screen w-full flex flex-col"> | 	<div class="min-h-screen max-h-screen w-full flex flex-col"> | ||||||
| 		<Navbar | 		<Navbar | ||||||
| 			{title} | 			{title} | ||||||
|  | 			{chat} | ||||||
| 			bind:selectedModels | 			bind:selectedModels | ||||||
| 			bind:showModelSelector | 			bind:showModelSelector | ||||||
| 			shareEnabled={messages.length > 0} | 			shareEnabled={messages.length > 0} | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 	import { WEBUI_NAME, config, user } from '$lib/stores'; | 	import { WEBUI_NAME, config, user } from '$lib/stores'; | ||||||
| 	import { onMount, getContext } from 'svelte'; | 	import { onMount, getContext } from 'svelte'; | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
|  | 	import { generateInitialsImage, canvasPixelTest } from '$lib/utils'; | ||||||
| 
 | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
|  | @ -36,10 +37,12 @@ | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const signUpHandler = async () => { | 	const signUpHandler = async () => { | ||||||
| 		const sessionUser = await userSignUp(name, email, password).catch((error) => { | 		const sessionUser = await userSignUp(name, email, password, generateInitialsImage(name)).catch( | ||||||
| 			toast.error(error); | 			(error) => { | ||||||
| 			return null; | 				toast.error(error); | ||||||
| 		}); | 				return null; | ||||||
|  | 			} | ||||||
|  | 		); | ||||||
| 
 | 
 | ||||||
| 		await setSessionUser(sessionUser); | 		await setSessionUser(sessionUser); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | @ -1,16 +0,0 @@ | ||||||
| { |  | ||||||
| 	"name": "Open WebUI", |  | ||||||
| 	"short_name": "Open WebUI", |  | ||||||
| 	"start_url": "/", |  | ||||||
| 	"display": "standalone", |  | ||||||
| 	"background_color": "#343541", |  | ||||||
| 	"theme_color": "#343541", |  | ||||||
| 	"orientation": "portrait-primary", |  | ||||||
| 	"icons": [ |  | ||||||
| 		{ |  | ||||||
| 			"src": "/favicon.png", |  | ||||||
| 			"type": "image/png", |  | ||||||
| 			"sizes": "844x884" |  | ||||||
| 		} |  | ||||||
| 	] |  | ||||||
| } |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jannik S
						Jannik S