mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	Merge branch 'dev' into dockerfile-optimisation
This commit is contained in:
		
						commit
						3b3d0cce1e
					
				@ -9,4 +9,8 @@ OPENAI_API_KEY=''
 | 
			
		||||
 | 
			
		||||
# DO NOT TRACK
 | 
			
		||||
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: .
 | 
			
		||||
      env:
 | 
			
		||||
        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`.
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - 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/),
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
@ -215,7 +215,8 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
 | 
			
		||||
 | 
			
		||||
        if len(responses) > 0:
 | 
			
		||||
            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"]}
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ from fastapi import (
 | 
			
		||||
    Form,
 | 
			
		||||
)
 | 
			
		||||
from fastapi.middleware.cors import CORSMiddleware
 | 
			
		||||
import os, shutil, logging
 | 
			
		||||
import os, shutil, logging, re
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import List
 | 
			
		||||
@ -438,25 +438,11 @@ def store_doc(
 | 
			
		||||
 | 
			
		||||
    log.info(f"file.content_type: {file.content_type}")
 | 
			
		||||
    try:
 | 
			
		||||
        is_valid_filename = True
 | 
			
		||||
        unsanitized_filename = file.filename
 | 
			
		||||
        if not unsanitized_filename.isascii():
 | 
			
		||||
            is_valid_filename = False
 | 
			
		||||
        filename = os.path.basename(unsanitized_filename)
 | 
			
		||||
 | 
			
		||||
        unvalidated_file_path = f"{UPLOAD_DIR}/{unsanitized_filename}"
 | 
			
		||||
        dereferenced_file_path = str(Path(unvalidated_file_path).resolve(strict=False))
 | 
			
		||||
        if not dereferenced_file_path.startswith(UPLOAD_DIR):
 | 
			
		||||
            is_valid_filename = False
 | 
			
		||||
        file_path = f"{UPLOAD_DIR}/{filename}"
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
        with open(file_path, "wb") as f:
 | 
			
		||||
            f.write(contents)
 | 
			
		||||
@ -467,7 +453,7 @@ def store_doc(
 | 
			
		||||
            collection_name = calculate_sha256(f)[:63]
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
 | 
			
		||||
@ -86,6 +86,7 @@ class SignupForm(BaseModel):
 | 
			
		||||
    name: str
 | 
			
		||||
    email: str
 | 
			
		||||
    password: str
 | 
			
		||||
    profile_image_url: Optional[str] = "/user.png"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthsTable:
 | 
			
		||||
@ -94,7 +95,12 @@ class AuthsTable:
 | 
			
		||||
        self.db.create_tables([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]:
 | 
			
		||||
        log.info("insert_new_auth")
 | 
			
		||||
 | 
			
		||||
@ -105,7 +111,7 @@ class AuthsTable:
 | 
			
		||||
        )
 | 
			
		||||
        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:
 | 
			
		||||
            return user
 | 
			
		||||
 | 
			
		||||
@ -206,6 +206,18 @@ class ChatTable:
 | 
			
		||||
        except:
 | 
			
		||||
            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]:
 | 
			
		||||
        try:
 | 
			
		||||
            chat = Chat.get(Chat.id == id, Chat.user_id == user_id)
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ class UserModel(BaseModel):
 | 
			
		||||
    name: str
 | 
			
		||||
    email: str
 | 
			
		||||
    role: str = "pending"
 | 
			
		||||
    profile_image_url: str = "/user.png"
 | 
			
		||||
    profile_image_url: str
 | 
			
		||||
    timestamp: int  # timestamp in epoch
 | 
			
		||||
    api_key: Optional[str] = None
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,12 @@ class UsersTable:
 | 
			
		||||
        self.db.create_tables([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]:
 | 
			
		||||
        user = UserModel(
 | 
			
		||||
            **{
 | 
			
		||||
@ -67,7 +72,7 @@ class UsersTable:
 | 
			
		||||
                "name": name,
 | 
			
		||||
                "email": email,
 | 
			
		||||
                "role": role,
 | 
			
		||||
                "profile_image_url": "/user.png",
 | 
			
		||||
                "profile_image_url": profile_image_url,
 | 
			
		||||
                "timestamp": int(time.time()),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -163,7 +163,11 @@ async def signup(request: Request, form_data: SignupForm):
 | 
			
		||||
        )
 | 
			
		||||
        hashed = get_password_hash(form_data.password)
 | 
			
		||||
        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:
 | 
			
		||||
 | 
			
		||||
@ -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])
 | 
			
		||||
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:
 | 
			
		||||
        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 starlette.responses import StreamingResponse, FileResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
import os
 | 
			
		||||
import aiohttp
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from fpdf import FPDF
 | 
			
		||||
import markdown
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 constants import ERROR_MESSAGES
 | 
			
		||||
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@ -28,6 +25,70 @@ async def get_gravatar(
 | 
			
		||||
    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")
 | 
			
		||||
async def download_db(user=Depends(get_admin_user)):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,9 @@ try:
 | 
			
		||||
except ImportError:
 | 
			
		||||
    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"
 | 
			
		||||
 | 
			
		||||
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", "")
 | 
			
		||||
 | 
			
		||||
if CUSTOM_NAME:
 | 
			
		||||
    try:
 | 
			
		||||
        r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
 | 
			
		||||
@ -171,7 +173,9 @@ if CUSTOM_NAME:
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        log.exception(e)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
else:
 | 
			
		||||
    if WEBUI_NAME != "Open WebUI":
 | 
			
		||||
        WEBUI_NAME += " (Open WebUI)"
 | 
			
		||||
 | 
			
		||||
####################################
 | 
			
		||||
# DATA/FRONTEND BUILD DIR
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,6 @@ app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
 | 
			
		||||
 | 
			
		||||
app.state.WEBHOOK_URL = WEBHOOK_URL
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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("/cache", StaticFiles(directory="data/cache"), name="cache")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,8 @@ peewee-migrate
 | 
			
		||||
bcrypt
 | 
			
		||||
 | 
			
		||||
litellm==1.30.7
 | 
			
		||||
boto3
 | 
			
		||||
 | 
			
		||||
argon2-cffi
 | 
			
		||||
apscheduler
 | 
			
		||||
google-generativeai
 | 
			
		||||
@ -40,6 +42,8 @@ xlrd
 | 
			
		||||
opencv-python-headless
 | 
			
		||||
rapidocr-onnxruntime
 | 
			
		||||
 | 
			
		||||
fpdf2
 | 
			
		||||
 | 
			
		||||
faster-whisper
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    tty: true
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    image: ollama/ollama:latest
 | 
			
		||||
    image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
 | 
			
		||||
 | 
			
		||||
  open-webui:
 | 
			
		||||
    build:
 | 
			
		||||
@ -16,7 +16,7 @@ services:
 | 
			
		||||
      args:
 | 
			
		||||
        OLLAMA_BASE_URL: '/ollama'
 | 
			
		||||
      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
 | 
			
		||||
    volumes:
 | 
			
		||||
      - open-webui:/app/backend/data
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ ollama
 | 
			
		||||
{{- end -}}
 | 
			
		||||
 | 
			
		||||
{{- 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 }}
 | 
			
		||||
 | 
			
		||||
{{- define "chart.name" -}}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										200
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										200
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,12 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "open-webui",
 | 
			
		||||
	"version": "0.1.116",
 | 
			
		||||
	"version": "0.1.117",
 | 
			
		||||
	"lockfileVersion": 3,
 | 
			
		||||
	"requires": true,
 | 
			
		||||
	"packages": {
 | 
			
		||||
		"": {
 | 
			
		||||
			"name": "open-webui",
 | 
			
		||||
			"version": "0.1.116",
 | 
			
		||||
			"version": "0.1.117",
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"@sveltejs/adapter-node": "^1.3.1",
 | 
			
		||||
				"async": "^3.2.5",
 | 
			
		||||
@ -19,6 +19,7 @@
 | 
			
		||||
				"i18next-resources-to-backend": "^1.2.0",
 | 
			
		||||
				"idb": "^7.1.1",
 | 
			
		||||
				"js-sha256": "^0.10.1",
 | 
			
		||||
				"jspdf": "^2.5.1",
 | 
			
		||||
				"katex": "^0.16.9",
 | 
			
		||||
				"marked": "^9.1.0",
 | 
			
		||||
				"svelte-sonner": "^0.3.19",
 | 
			
		||||
@ -1067,6 +1068,12 @@
 | 
			
		||||
			"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
 | 
			
		||||
			"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": {
 | 
			
		||||
			"version": "1.20.2",
 | 
			
		||||
			"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",
 | 
			
		||||
			"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": {
 | 
			
		||||
			"version": "10.4.19",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
 | 
			
		||||
@ -1459,6 +1477,15 @@
 | 
			
		||||
			"dev": 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": {
 | 
			
		||||
			"version": "1.5.1",
 | 
			
		||||
			"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_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": {
 | 
			
		||||
			"version": "6.0.3",
 | 
			
		||||
			"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": {
 | 
			
		||||
			"version": "4.1.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 | 
			
		||||
@ -1944,6 +2007,17 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "1.0.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
 | 
			
		||||
@ -1964,6 +2038,15 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "5.1.0",
 | 
			
		||||
			"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"
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"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": {
 | 
			
		||||
			"version": "3.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
 | 
			
		||||
@ -2584,6 +2673,11 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "6.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
 | 
			
		||||
@ -3003,6 +3097,19 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "8.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
 | 
			
		||||
@ -3403,10 +3510,27 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "0.16.9",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz",
 | 
			
		||||
			"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==",
 | 
			
		||||
			"version": "0.16.10",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
 | 
			
		||||
			"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
 | 
			
		||||
			"funding": [
 | 
			
		||||
				"https://opencollective.com/katex",
 | 
			
		||||
				"https://github.com/sponsors/katex"
 | 
			
		||||
@ -3971,6 +4095,12 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "3.1.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
 | 
			
		||||
@ -4391,6 +4521,15 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "1.0.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
			
		||||
@ -4494,6 +4633,15 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "3.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
 | 
			
		||||
@ -4814,6 +4962,15 @@
 | 
			
		||||
			"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
 | 
			
		||||
			"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": {
 | 
			
		||||
			"version": "1.0.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
 | 
			
		||||
@ -5215,6 +5372,15 @@
 | 
			
		||||
				"@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": {
 | 
			
		||||
			"version": "1.3.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz",
 | 
			
		||||
@ -5353,6 +5519,15 @@
 | 
			
		||||
				"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": {
 | 
			
		||||
			"version": "0.2.0",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
 | 
			
		||||
@ -5583,6 +5758,15 @@
 | 
			
		||||
			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
 | 
			
		||||
			"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": {
 | 
			
		||||
			"version": "9.0.1",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
 | 
			
		||||
@ -5676,9 +5860,9 @@
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"node_modules/vite": {
 | 
			
		||||
			"version": "4.5.2",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
 | 
			
		||||
			"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
 | 
			
		||||
			"version": "4.5.3",
 | 
			
		||||
			"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
 | 
			
		||||
			"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
 | 
			
		||||
			"dependencies": {
 | 
			
		||||
				"esbuild": "^0.18.10",
 | 
			
		||||
				"postcss": "^8.4.27",
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "open-webui",
 | 
			
		||||
	"version": "0.1.116",
 | 
			
		||||
	"version": "0.1.117",
 | 
			
		||||
	"private": true,
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite dev --host",
 | 
			
		||||
@ -53,6 +53,7 @@
 | 
			
		||||
		"i18next-resources-to-backend": "^1.2.0",
 | 
			
		||||
		"idb": "^7.1.1",
 | 
			
		||||
		"js-sha256": "^0.10.1",
 | 
			
		||||
		"jspdf": "^2.5.1",
 | 
			
		||||
		"katex": "^0.16.9",
 | 
			
		||||
		"marked": "^9.1.0",
 | 
			
		||||
		"svelte-sonner": "^0.3.19",
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,12 @@ export const userSignIn = async (email: string, password: string) => {
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	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({
 | 
			
		||||
			name: name,
 | 
			
		||||
			email: email,
 | 
			
		||||
			password: password
 | 
			
		||||
			password: password,
 | 
			
		||||
			profile_image_url: profile_image_url
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
		.then(async (res) => {
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,57 @@ export const getGravatarUrl = async (email: string) => {
 | 
			
		||||
	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) => {
 | 
			
		||||
	let error = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -295,6 +295,13 @@
 | 
			
		||||
 | 
			
		||||
		const dropZone = document.querySelector('body');
 | 
			
		||||
 | 
			
		||||
		const handleKeyDown = (event: KeyboardEvent) => {
 | 
			
		||||
			if (event.key === 'Escape') {
 | 
			
		||||
				console.log('Escape');
 | 
			
		||||
				dragged = false;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const onDragOver = (e) => {
 | 
			
		||||
			e.preventDefault();
 | 
			
		||||
			dragged = true;
 | 
			
		||||
@ -350,11 +357,15 @@
 | 
			
		||||
			dragged = false;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		window.addEventListener('keydown', handleKeyDown);
 | 
			
		||||
 | 
			
		||||
		dropZone?.addEventListener('dragover', onDragOver);
 | 
			
		||||
		dropZone?.addEventListener('drop', onDrop);
 | 
			
		||||
		dropZone?.addEventListener('dragleave', onDragLeave);
 | 
			
		||||
 | 
			
		||||
		return () => {
 | 
			
		||||
			window.removeEventListener('keydown', handleKeyDown);
 | 
			
		||||
 | 
			
		||||
			dropZone?.removeEventListener('dragover', onDragOver);
 | 
			
		||||
			dropZone?.removeEventListener('drop', onDrop);
 | 
			
		||||
			dropZone?.removeEventListener('dragleave', onDragLeave);
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,11 @@
 | 
			
		||||
	import { config, settings } from '$lib/stores';
 | 
			
		||||
	import { synthesizeOpenAISpeech } from '$lib/apis/openai';
 | 
			
		||||
	import { imageGenerations } from '$lib/apis/images';
 | 
			
		||||
	import { extractSentences } from '$lib/utils';
 | 
			
		||||
	import {
 | 
			
		||||
		extractSentences,
 | 
			
		||||
		revertSanitizedResponseContent,
 | 
			
		||||
		sanitizeResponseContent
 | 
			
		||||
	} from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
	import Name from './Name.svelte';
 | 
			
		||||
	import ProfileImage from './ProfileImage.svelte';
 | 
			
		||||
@ -56,7 +60,7 @@
 | 
			
		||||
	let loadingSpeech = false;
 | 
			
		||||
	let generatingImage = false;
 | 
			
		||||
 | 
			
		||||
	$: tokens = marked.lexer(message.content);
 | 
			
		||||
	$: tokens = marked.lexer(sanitizeResponseContent(message.content));
 | 
			
		||||
 | 
			
		||||
	const renderer = new marked.Renderer();
 | 
			
		||||
 | 
			
		||||
@ -405,8 +409,10 @@
 | 
			
		||||
								{:else}
 | 
			
		||||
									{#each tokens as token}
 | 
			
		||||
										{#if token.type === 'code'}
 | 
			
		||||
											<!-- {token.text} -->
 | 
			
		||||
											<CodeBlock lang={token.lang} code={token.text} />
 | 
			
		||||
											<CodeBlock
 | 
			
		||||
												lang={token.lang}
 | 
			
		||||
												code={revertSanitizedResponseContent(token.text)}
 | 
			
		||||
											/>
 | 
			
		||||
										{:else}
 | 
			
		||||
											{@html marked.parse(token.raw, {
 | 
			
		||||
												...defaults,
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@
 | 
			
		||||
	import { compareVersion } from '$lib/utils';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	let ollamaVersion = '';
 | 
			
		||||
@ -51,8 +53,10 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="flex w-full justify-between items-center">
 | 
			
		||||
				<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
 | 
			
		||||
					<div>
 | 
			
		||||
						v{WEBUI_VERSION}
 | 
			
		||||
					<div class="flex gap-1">
 | 
			
		||||
						<Tooltip content={WEBUI_VERSION === '0.1.117' ? "🪖 We're just getting started." : ''}>
 | 
			
		||||
							v{WEBUI_VERSION}
 | 
			
		||||
						</Tooltip>
 | 
			
		||||
 | 
			
		||||
						<a
 | 
			
		||||
							href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
 | 
			
		||||
@ -126,7 +130,9 @@
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<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
 | 
			
		||||
				class=" text-gray-500 dark:text-gray-300 font-medium"
 | 
			
		||||
				href="https://github.com/tjbck"
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
 | 
			
		||||
	import UpdatePassword from './Account/UpdatePassword.svelte';
 | 
			
		||||
	import { getGravatarUrl } from '$lib/apis/utils';
 | 
			
		||||
	import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
 | 
			
		||||
	import { copyToClipboard } from '$lib/utils';
 | 
			
		||||
	import Plus from '$lib/components/icons/Plus.svelte';
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
@ -18,6 +19,8 @@
 | 
			
		||||
	let profileImageUrl = '';
 | 
			
		||||
	let name = '';
 | 
			
		||||
 | 
			
		||||
	let showAPIKeys = false;
 | 
			
		||||
 | 
			
		||||
	let showJWTToken = false;
 | 
			
		||||
	let JWTTokenCopied = false;
 | 
			
		||||
 | 
			
		||||
@ -28,6 +31,12 @@
 | 
			
		||||
	let profileImageInputElement: HTMLInputElement;
 | 
			
		||||
 | 
			
		||||
	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(
 | 
			
		||||
			(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 flex-col">
 | 
			
		||||
				<div class="self-center">
 | 
			
		||||
					<button
 | 
			
		||||
						class="relative rounded-full dark:bg-gray-700"
 | 
			
		||||
						type="button"
 | 
			
		||||
						on: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="flex space-x-5">
 | 
			
		||||
				<div class="flex flex-col">
 | 
			
		||||
					<div class="self-center mt-2">
 | 
			
		||||
						<button
 | 
			
		||||
							class="relative rounded-full dark:bg-gray-700"
 | 
			
		||||
							type="button"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								profileImageInputElement.click();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<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>
 | 
			
		||||
				<button
 | 
			
		||||
					class=" text-xs text-gray-600"
 | 
			
		||||
					on:click={async () => {
 | 
			
		||||
						const url = await getGravatarUrl($user.email);
 | 
			
		||||
							<img
 | 
			
		||||
								src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(name)}
 | 
			
		||||
								alt="profile"
 | 
			
		||||
								class=" rounded-full size-16 object-cover"
 | 
			
		||||
							/>
 | 
			
		||||
 | 
			
		||||
						profileImageUrl = url;
 | 
			
		||||
					}}>{$i18n.t('Use Gravatar')}</button
 | 
			
		||||
				>
 | 
			
		||||
							<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">
 | 
			
		||||
									<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 class="flex-1">
 | 
			
		||||
			<div class="pt-0.5">
 | 
			
		||||
				<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">
 | 
			
		||||
						<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"
 | 
			
		||||
							bind:value={name}
 | 
			
		||||
							required
 | 
			
		||||
@ -187,133 +230,46 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700 my-4" />
 | 
			
		||||
		<UpdatePassword />
 | 
			
		||||
		<div class="py-0.5">
 | 
			
		||||
			<UpdatePassword />
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<hr class=" dark:border-gray-700 my-4" />
 | 
			
		||||
 | 
			
		||||
		<div class="flex flex-col gap-4">
 | 
			
		||||
			<div class="justify-between w-full">
 | 
			
		||||
				<div class="flex justify-between w-full">
 | 
			
		||||
					<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
 | 
			
		||||
				</div>
 | 
			
		||||
		<div class="flex justify-between items-center text-sm">
 | 
			
		||||
			<div class="  font-medium">{$i18n.t('API keys')}</div>
 | 
			
		||||
			<button
 | 
			
		||||
				class=" text-xs font-medium text-gray-500"
 | 
			
		||||
				type="button"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					showAPIKeys = !showAPIKeys;
 | 
			
		||||
				}}>{showAPIKeys ? $i18n.t('Hide') : $i18n.t('Show')}</button
 | 
			
		||||
			>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
				<div class="flex mt-2">
 | 
			
		||||
					<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-800 outline-none"
 | 
			
		||||
							type={showJWTToken ? 'text' : 'password'}
 | 
			
		||||
							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>
 | 
			
		||||
		{#if showAPIKeys}
 | 
			
		||||
			<div class="flex flex-col gap-4">
 | 
			
		||||
				<div class="justify-between w-full">
 | 
			
		||||
					<div class="flex justify-between w-full">
 | 
			
		||||
						<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<button
 | 
			
		||||
						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 mt-2">
 | 
			
		||||
						<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-800 outline-none"
 | 
			
		||||
								type={showAPIKey ? 'text' : 'password'}
 | 
			
		||||
								value={APIKey}
 | 
			
		||||
								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={showJWTToken ? 'text' : 'password'}
 | 
			
		||||
								value={localStorage.token}
 | 
			
		||||
								disabled
 | 
			
		||||
							/>
 | 
			
		||||
 | 
			
		||||
							<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={() => {
 | 
			
		||||
									showAPIKey = !showAPIKey;
 | 
			
		||||
									showJWTToken = !showJWTToken;
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								{#if showAPIKey}
 | 
			
		||||
								{#if showJWTToken}
 | 
			
		||||
									<svg
 | 
			
		||||
										xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
										viewBox="0 0 16 16"
 | 
			
		||||
@ -348,16 +304,16 @@
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<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={() => {
 | 
			
		||||
								copyToClipboard(APIKey);
 | 
			
		||||
								APIKeyCopied = true;
 | 
			
		||||
								copyToClipboard(localStorage.token);
 | 
			
		||||
								JWTTokenCopied = true;
 | 
			
		||||
								setTimeout(() => {
 | 
			
		||||
									APIKeyCopied = false;
 | 
			
		||||
									JWTTokenCopied = false;
 | 
			
		||||
								}, 2000);
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							{#if APIKeyCopied}
 | 
			
		||||
							{#if JWTTokenCopied}
 | 
			
		||||
								<svg
 | 
			
		||||
									xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
									viewBox="0 0 20 20"
 | 
			
		||||
@ -390,45 +346,146 @@
 | 
			
		||||
								</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">
 | 
			
		||||
								<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
 | 
			
		||||
								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={() => {
 | 
			
		||||
									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-800 transition"
 | 
			
		||||
							on:click={() => {
 | 
			
		||||
								createAPIKeyHandler();
 | 
			
		||||
							}}
 | 
			
		||||
						>
 | 
			
		||||
							<Plus strokeWidth="2" className=" size-3.5" />
 | 
			
		||||
								<Plus strokeWidth="2" className=" size-3.5" />
 | 
			
		||||
 | 
			
		||||
							Create new secret key</button
 | 
			
		||||
						>
 | 
			
		||||
					{/if}
 | 
			
		||||
								Create new secret key</button
 | 
			
		||||
							>
 | 
			
		||||
						{/if}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		{/if}
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<div class="flex justify-end pt-3 text-sm font-medium">
 | 
			
		||||
 | 
			
		||||
@ -185,7 +185,7 @@
 | 
			
		||||
 | 
			
		||||
			<div>
 | 
			
		||||
				<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
 | 
			
		||||
						class="p-1 px-3 text-xs flex rounded transition"
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import { getContext, onMount } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import fileSaver from 'file-saver';
 | 
			
		||||
	const { saveAs } = fileSaver;
 | 
			
		||||
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		chatId.subscribe(async (value) => {
 | 
			
		||||
			chat = await getChatById(localStorage.token, value);
 | 
			
		||||
			console.log(chat);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
	$: if (show) {
 | 
			
		||||
		(async () => {
 | 
			
		||||
			if ($chatId) {
 | 
			
		||||
				chat = await getChatById(localStorage.token, $chatId);
 | 
			
		||||
			} else {
 | 
			
		||||
				chat = null;
 | 
			
		||||
				console.log(chat);
 | 
			
		||||
			}
 | 
			
		||||
		})();
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<Modal bind:show size="sm">
 | 
			
		||||
@ -159,19 +145,6 @@
 | 
			
		||||
								{/if}
 | 
			
		||||
							</button>
 | 
			
		||||
						</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>
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
	export let show = true;
 | 
			
		||||
	export let size = 'md';
 | 
			
		||||
 | 
			
		||||
	let modalElement = null;
 | 
			
		||||
	let mounted = false;
 | 
			
		||||
 | 
			
		||||
	const sizeToWidth = (size) => {
 | 
			
		||||
@ -19,14 +20,23 @@
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const handleKeyDown = (event: KeyboardEvent) => {
 | 
			
		||||
		if (event.key === 'Escape') {
 | 
			
		||||
			console.log('Escape');
 | 
			
		||||
			show = false;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(() => {
 | 
			
		||||
		mounted = true;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	$: if (mounted) {
 | 
			
		||||
		if (show) {
 | 
			
		||||
			window.addEventListener('keydown', handleKeyDown);
 | 
			
		||||
			document.body.style.overflow = 'hidden';
 | 
			
		||||
		} else {
 | 
			
		||||
			window.removeEventListener('keydown', handleKeyDown);
 | 
			
		||||
			document.body.style.overflow = 'unset';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@ -36,6 +46,7 @@
 | 
			
		||||
	<!-- svelte-ignore a11y-click-events-have-key-events -->
 | 
			
		||||
	<!-- svelte-ignore a11y-no-static-element-interactions -->
 | 
			
		||||
	<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"
 | 
			
		||||
		in:fade={{ duration: 10 }}
 | 
			
		||||
		on:click={() => {
 | 
			
		||||
 | 
			
		||||
@ -2,21 +2,13 @@
 | 
			
		||||
	import { getContext } from 'svelte';
 | 
			
		||||
	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 { slide } from 'svelte/transition';
 | 
			
		||||
	import ShareChatModal from '../chat/ShareChatModal.svelte';
 | 
			
		||||
	import TagInput from '../common/Tags/TagInput.svelte';
 | 
			
		||||
	import ModelSelector from '../chat/ModelSelector.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 TagChatModal from '../chat/TagChatModal.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
@ -24,6 +16,7 @@
 | 
			
		||||
	export let title: string = $WEBUI_NAME;
 | 
			
		||||
	export let shareEnabled: boolean = false;
 | 
			
		||||
 | 
			
		||||
	export let chat;
 | 
			
		||||
	export let selectedModels;
 | 
			
		||||
 | 
			
		||||
	export let tags = [];
 | 
			
		||||
@ -33,63 +26,15 @@
 | 
			
		||||
	export let showModelSelector = true;
 | 
			
		||||
 | 
			
		||||
	let showShareChatModal = false;
 | 
			
		||||
	let showTagChatModal = false;
 | 
			
		||||
	let showDownloadChatModal = false;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<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">
 | 
			
		||||
	<div
 | 
			
		||||
		class=" flex {$settings?.fullScreenMode ?? null ? 'max-w-full' : 'max-w-3xl'} 
 | 
			
		||||
		 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-1 overflow-hidden max-w-full">
 | 
			
		||||
				{#if showModelSelector}
 | 
			
		||||
@ -132,10 +77,14 @@
 | 
			
		||||
					</Tooltip>
 | 
			
		||||
				{:else}
 | 
			
		||||
					<Menu
 | 
			
		||||
						{chat}
 | 
			
		||||
						{shareEnabled}
 | 
			
		||||
						shareHandler={() => {
 | 
			
		||||
							showShareChatModal = !showShareChatModal;
 | 
			
		||||
						}}
 | 
			
		||||
						downloadHandler={() => {
 | 
			
		||||
							showDownloadChatModal = !showDownloadChatModal;
 | 
			
		||||
						}}
 | 
			
		||||
						{tags}
 | 
			
		||||
						{deleteTag}
 | 
			
		||||
						{addTag}
 | 
			
		||||
 | 
			
		||||
@ -1,23 +1,71 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	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 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 { WEBUI_BASE_URL } from '$lib/constants';
 | 
			
		||||
	import { downloadChatAsPDF } from '$lib/apis/utils';
 | 
			
		||||
 | 
			
		||||
	export let shareEnabled: boolean = false;
 | 
			
		||||
	export let shareHandler: Function;
 | 
			
		||||
	export let downloadHandler: Function;
 | 
			
		||||
 | 
			
		||||
	// export let tagHandler: Function;
 | 
			
		||||
 | 
			
		||||
	export let chat;
 | 
			
		||||
	export let tags;
 | 
			
		||||
	export let deleteTag: Function;
 | 
			
		||||
	export let addTag: 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>
 | 
			
		||||
 | 
			
		||||
<Dropdown
 | 
			
		||||
@ -31,14 +79,14 @@
 | 
			
		||||
 | 
			
		||||
	<div slot="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}
 | 
			
		||||
			side="bottom"
 | 
			
		||||
			align="end"
 | 
			
		||||
			transition={flyAndScale}
 | 
			
		||||
		>
 | 
			
		||||
			<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 () => {
 | 
			
		||||
					await showSettings.set(!$showSettings);
 | 
			
		||||
				}}
 | 
			
		||||
@ -49,7 +97,7 @@
 | 
			
		||||
					viewBox="0 0 24 24"
 | 
			
		||||
					stroke-width="1.5"
 | 
			
		||||
					stroke="currentColor"
 | 
			
		||||
					class="w-5 h-5"
 | 
			
		||||
					class="size-4"
 | 
			
		||||
				>
 | 
			
		||||
					<path
 | 
			
		||||
						stroke-linecap="round"
 | 
			
		||||
@ -67,7 +115,7 @@
 | 
			
		||||
 | 
			
		||||
			{#if shareEnabled}
 | 
			
		||||
				<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={() => {
 | 
			
		||||
						shareHandler();
 | 
			
		||||
					}}
 | 
			
		||||
@ -87,7 +135,59 @@
 | 
			
		||||
					<div class="flex items-center">Share</div>
 | 
			
		||||
				</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">
 | 
			
		||||
					<Tags {tags} {deleteTag} {addTag} />
 | 
			
		||||
 | 
			
		||||
@ -581,7 +581,7 @@
 | 
			
		||||
							<div class="py-2 w-full">
 | 
			
		||||
								{#if $user.role === 'admin'}
 | 
			
		||||
									<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={() => {
 | 
			
		||||
											goto('/admin');
 | 
			
		||||
											showDropdown = false;
 | 
			
		||||
@ -607,7 +607,7 @@
 | 
			
		||||
									</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={() => {
 | 
			
		||||
											goto('/playground');
 | 
			
		||||
											showDropdown = false;
 | 
			
		||||
@ -634,7 +634,7 @@
 | 
			
		||||
								{/if}
 | 
			
		||||
 | 
			
		||||
								<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 () => {
 | 
			
		||||
										await showSettings.set(true);
 | 
			
		||||
										showDropdown = false;
 | 
			
		||||
@ -669,7 +669,7 @@
 | 
			
		||||
 | 
			
		||||
							<div class="py-2 w-full">
 | 
			
		||||
								<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={() => {
 | 
			
		||||
										localStorage.removeItem('token');
 | 
			
		||||
										location.href = '/auth';
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Изтрито {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Изтрито {tagName}",
 | 
			
		||||
	"Description": "Описание",
 | 
			
		||||
	"Desktop Notifications": "Десктоп Известия",
 | 
			
		||||
	"Notifications": "Десктоп Известия",
 | 
			
		||||
	"Disabled": "Деактивиран",
 | 
			
		||||
	"Discover a modelfile": "Откриване на модфайл",
 | 
			
		||||
	"Discover a prompt": "Откриване на промпт",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Esborrat {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Esborrat {tagName}",
 | 
			
		||||
	"Description": "Descripció",
 | 
			
		||||
	"Desktop Notifications": "Notificacions d'Escriptori",
 | 
			
		||||
	"Notifications": "Notificacions d'Escriptori",
 | 
			
		||||
	"Disabled": "Desactivat",
 | 
			
		||||
	"Discover a modelfile": "Descobreix un fitxer de model",
 | 
			
		||||
	"Discover a prompt": "Descobreix un prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} gelöscht",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} gelöscht",
 | 
			
		||||
	"Description": "Beschreibung",
 | 
			
		||||
	"Desktop Notifications": "Desktop-Benachrichtigungen",
 | 
			
		||||
	"Notifications": "Desktop-Benachrichtigungen",
 | 
			
		||||
	"Disabled": "Deaktiviert",
 | 
			
		||||
	"Discover a modelfile": "Eine Modelfiles 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 {tagName}": "",
 | 
			
		||||
	"Description": "",
 | 
			
		||||
	"Desktop Notifications": "",
 | 
			
		||||
	"Notifications": "",
 | 
			
		||||
	"Disabled": "",
 | 
			
		||||
	"Discover a modelfile": "",
 | 
			
		||||
	"Discover a prompt": "",
 | 
			
		||||
@ -150,6 +150,7 @@
 | 
			
		||||
	"Failed to read clipboard contents": "",
 | 
			
		||||
	"File Mode": "",
 | 
			
		||||
	"File not found.": "",
 | 
			
		||||
	"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
 | 
			
		||||
	"Focus chat input": "",
 | 
			
		||||
	"Format your variables using square brackets like this:": "",
 | 
			
		||||
	"From (Base Model)": "",
 | 
			
		||||
@ -340,6 +341,7 @@
 | 
			
		||||
	"URL Mode": "",
 | 
			
		||||
	"Use '#' in the prompt input to load and select your documents.": "",
 | 
			
		||||
	"Use Gravatar": "",
 | 
			
		||||
	"Use Initials": "",
 | 
			
		||||
	"user": "",
 | 
			
		||||
	"User Permissions": "",
 | 
			
		||||
	"Users": "",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Se borró {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Se borró {tagName}",
 | 
			
		||||
	"Description": "Descripción",
 | 
			
		||||
	"Desktop Notifications": "Notificaciones",
 | 
			
		||||
	"Notifications": "Notificaciones",
 | 
			
		||||
	"Disabled": "Desactivado",
 | 
			
		||||
	"Discover a modelfile": "Descubre un modelfile",
 | 
			
		||||
	"Discover a prompt": "Descubre un Prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} حذف شد",
 | 
			
		||||
	"Description": "توضیحات",
 | 
			
		||||
	"Desktop Notifications": "اعلان",
 | 
			
		||||
	"Notifications": "اعلان",
 | 
			
		||||
	"Disabled": "غیرفعال",
 | 
			
		||||
	"Discover a modelfile": "فایل مدل را کشف کنید",
 | 
			
		||||
	"Discover a prompt": "یک اعلان را کشف کنید",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} supprimé",
 | 
			
		||||
	"Description": "Description",
 | 
			
		||||
	"Desktop Notifications": "Notifications de bureau",
 | 
			
		||||
	"Notifications": "Notifications de bureau",
 | 
			
		||||
	"Disabled": "Désactivé",
 | 
			
		||||
	"Discover a modelfile": "Découvrir un fichier de modèle",
 | 
			
		||||
	"Discover a prompt": "Découvrir un prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} supprimé",
 | 
			
		||||
	"Description": "Description",
 | 
			
		||||
	"Desktop Notifications": "Notifications de bureau",
 | 
			
		||||
	"Notifications": "Notifications de bureau",
 | 
			
		||||
	"Disabled": "Désactivé",
 | 
			
		||||
	"Discover a modelfile": "Découvrir un fichier de modèle",
 | 
			
		||||
	"Discover a prompt": "Découvrir un prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Eliminato {tagName}",
 | 
			
		||||
	"Description": "Descrizione",
 | 
			
		||||
	"Desktop Notifications": "Notifiche desktop",
 | 
			
		||||
	"Notifications": "Notifiche desktop",
 | 
			
		||||
	"Disabled": "Disabilitato",
 | 
			
		||||
	"Discover a modelfile": "Scopri un file modello",
 | 
			
		||||
	"Discover a prompt": "Scopri un prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} を削除しました",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} を削除しました",
 | 
			
		||||
	"Description": "説明",
 | 
			
		||||
	"Desktop Notifications": "デスクトップ通知",
 | 
			
		||||
	"Notifications": "デスクトップ通知",
 | 
			
		||||
	"Disabled": "無効",
 | 
			
		||||
	"Discover a modelfile": "モデルファイルを見つける",
 | 
			
		||||
	"Discover a prompt": "プロンプトを見つける",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} 삭제됨",
 | 
			
		||||
	"Description": "설명",
 | 
			
		||||
	"Desktop Notifications": "알림",
 | 
			
		||||
	"Notifications": "알림",
 | 
			
		||||
	"Disabled": "비활성화",
 | 
			
		||||
	"Discover a modelfile": "모델파일 검색",
 | 
			
		||||
	"Discover a prompt": "프롬프트 검색",
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,10 @@
 | 
			
		||||
		"code": "de-DE",
 | 
			
		||||
		"title": "Deutsch"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "en-GB",
 | 
			
		||||
		"title": "English (GB)"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "es-ES",
 | 
			
		||||
		"title": "Spanish"
 | 
			
		||||
@ -51,10 +55,18 @@
 | 
			
		||||
		"code": "pt-PT",
 | 
			
		||||
		"title": "Portuguese (Portugal)"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "pt-BR",
 | 
			
		||||
		"title": "Portuguese (Brazil)"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "ru-RU",
 | 
			
		||||
		"title": "Russian (Russia)"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "tr-TR",
 | 
			
		||||
		"title": "Turkish"
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"code": "uk-UA",
 | 
			
		||||
		"title": "Ukrainian"
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "{{deleteModelTag}} is verwijderd",
 | 
			
		||||
	"Deleted {tagName}": "{tagName} is verwijderd",
 | 
			
		||||
	"Description": "Beschrijving",
 | 
			
		||||
	"Desktop Notifications": "Desktop Notificaties",
 | 
			
		||||
	"Notifications": "Desktop Notificaties",
 | 
			
		||||
	"Disabled": "Uitgeschakeld",
 | 
			
		||||
	"Discover a modelfile": "Ontdek een modelfile",
 | 
			
		||||
	"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 {tagName}": "{tagName} excluído",
 | 
			
		||||
	"Description": "Descrição",
 | 
			
		||||
	"Desktop Notifications": "Notificações da Área de Trabalho",
 | 
			
		||||
	"Notifications": "Notificações da Área de Trabalho",
 | 
			
		||||
	"Disabled": "Desativado",
 | 
			
		||||
	"Discover a modelfile": "Descobrir um arquivo de modelo",
 | 
			
		||||
	"Discover a prompt": "Descobrir um prompt",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Удалено {tagName}",
 | 
			
		||||
	"Description": "Описание",
 | 
			
		||||
	"Desktop Notifications": "Уведомления на рабочем столе",
 | 
			
		||||
	"Notifications": "Уведомления на рабочем столе",
 | 
			
		||||
	"Disabled": "Отключено",
 | 
			
		||||
	"Discover a modelfile": "Найти файл модели",
 | 
			
		||||
	"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 {tagName}": "Видалено {tagName}",
 | 
			
		||||
	"Description": "Опис",
 | 
			
		||||
	"Desktop Notifications": "Сповіщення",
 | 
			
		||||
	"Notifications": "Сповіщення",
 | 
			
		||||
	"Disabled": "Вимкнено",
 | 
			
		||||
	"Discover a modelfile": "Знайти файл моделі",
 | 
			
		||||
	"Discover a prompt": "Знайти промт",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "Đã xóa {tagName}",
 | 
			
		||||
	"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",
 | 
			
		||||
	"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",
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "已删除{tagName}",
 | 
			
		||||
	"Description": "描述",
 | 
			
		||||
	"Desktop Notifications": "桌面通知",
 | 
			
		||||
	"Notifications": "桌面通知",
 | 
			
		||||
	"Disabled": "禁用",
 | 
			
		||||
	"Discover a modelfile": "探索模型文件",
 | 
			
		||||
	"Discover a prompt": "探索提示词",
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@
 | 
			
		||||
	"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}",
 | 
			
		||||
	"Deleted {tagName}": "已刪除 {tagName}",
 | 
			
		||||
	"Description": "描述",
 | 
			
		||||
	"Desktop Notifications": "桌面通知",
 | 
			
		||||
	"Notifications": "桌面通知",
 | 
			
		||||
	"Disabled": "已停用",
 | 
			
		||||
	"Discover a modelfile": "發現新 Modelfile",
 | 
			
		||||
	"Discover a prompt": "發現新提示詞",
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,21 @@ export const getModels = async (token: string) => {
 | 
			
		||||
// 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) => {
 | 
			
		||||
	return string.charAt(0).toUpperCase() + string.slice(1);
 | 
			
		||||
};
 | 
			
		||||
@ -96,6 +111,82 @@ export const getGravatarURL = (email) => {
 | 
			
		||||
	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) => {
 | 
			
		||||
	if (!navigator.clipboard) {
 | 
			
		||||
		const textArea = document.createElement('textarea');
 | 
			
		||||
 | 
			
		||||
@ -847,6 +847,7 @@
 | 
			
		||||
		bind:selectedModels
 | 
			
		||||
		bind:showModelSelector
 | 
			
		||||
		shareEnabled={messages.length > 0}
 | 
			
		||||
		{chat}
 | 
			
		||||
		{initNewChat}
 | 
			
		||||
		{tags}
 | 
			
		||||
		{addTag}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
 | 
			
		||||
@ -16,6 +18,7 @@
 | 
			
		||||
	let loaded = false;
 | 
			
		||||
	let users = [];
 | 
			
		||||
 | 
			
		||||
	let search = '';
 | 
			
		||||
	let selectedUser = null;
 | 
			
		||||
 | 
			
		||||
	let showSettingsModal = false;
 | 
			
		||||
@ -80,157 +83,193 @@
 | 
			
		||||
 | 
			
		||||
<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}
 | 
			
		||||
		<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=" flex flex-col justify-center">
 | 
			
		||||
						<div class=" flex justify-between items-center">
 | 
			
		||||
							<div class="flex items-center text-2xl font-semibold">
 | 
			
		||||
								{$i18n.t('All Users')}
 | 
			
		||||
								<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
 | 
			
		||||
								<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
 | 
			
		||||
									>{users.length}</span
 | 
			
		||||
								>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div>
 | 
			
		||||
								<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"
 | 
			
		||||
						<div class=" px-5 pt-3">
 | 
			
		||||
							<div class=" flex justify-between items-center">
 | 
			
		||||
								<div class="flex items-center text-2xl font-semibold font-mona">Dashboard</div>
 | 
			
		||||
								<div>
 | 
			
		||||
									<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;
 | 
			
		||||
										}}
 | 
			
		||||
									>
 | 
			
		||||
										<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>
 | 
			
		||||
										<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="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>
 | 
			
		||||
								</button>
 | 
			
		||||
										<div class=" text-xs">{$i18n.t('Admin Settings')}</div>
 | 
			
		||||
									</button>
 | 
			
		||||
								</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>
 | 
			
		||||
 | 
			
		||||
						<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">
 | 
			
		||||
							<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
 | 
			
		||||
								<thead
 | 
			
		||||
									class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
 | 
			
		||||
								>
 | 
			
		||||
									<tr>
 | 
			
		||||
										<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>
 | 
			
		||||
										<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="px-5">
 | 
			
		||||
							<div class="mt-0.5 mb-3 flex justify-between">
 | 
			
		||||
								<div class="flex text-lg font-medium px-0.5">
 | 
			
		||||
									{$i18n.t('All Users')}
 | 
			
		||||
									<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
 | 
			
		||||
									<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
 | 
			
		||||
										>{users.length}</span
 | 
			
		||||
									>
 | 
			
		||||
								</div>
 | 
			
		||||
 | 
			
		||||
													<div class=" font-medium self-center">{user.name}</div>
 | 
			
		||||
												</div>
 | 
			
		||||
											</td>
 | 
			
		||||
											<td class=" px-3 py-2"> {user.email} </td>
 | 
			
		||||
								<div class="">
 | 
			
		||||
									<input
 | 
			
		||||
										class=" w-60 rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
 | 
			
		||||
										placeholder={$i18n.t('Search')}
 | 
			
		||||
										bind:value={search}
 | 
			
		||||
									/>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
											<td class="px-3 py-2">
 | 
			
		||||
												<div class="flex justify-start 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>
 | 
			
		||||
							<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap">
 | 
			
		||||
								<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
 | 
			
		||||
									<thead
 | 
			
		||||
										class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400"
 | 
			
		||||
									>
 | 
			
		||||
										<tr>
 | 
			
		||||
											<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>
 | 
			
		||||
											<th scope="col" class="px-3 py-2"> {$i18n.t('Created at')} </th>
 | 
			
		||||
											<th scope="col" class="px-3 py-2 text-right" />
 | 
			
		||||
										</tr>
 | 
			
		||||
									{/each}
 | 
			
		||||
								</tbody>
 | 
			
		||||
							</table>
 | 
			
		||||
									</thead>
 | 
			
		||||
									<tbody>
 | 
			
		||||
										{#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>
 | 
			
		||||
 | 
			
		||||
@ -865,6 +865,7 @@
 | 
			
		||||
	<div class="min-h-screen max-h-screen w-full flex flex-col">
 | 
			
		||||
		<Navbar
 | 
			
		||||
			{title}
 | 
			
		||||
			{chat}
 | 
			
		||||
			bind:selectedModels
 | 
			
		||||
			bind:showModelSelector
 | 
			
		||||
			shareEnabled={messages.length > 0}
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@
 | 
			
		||||
	import { WEBUI_NAME, config, user } from '$lib/stores';
 | 
			
		||||
	import { onMount, getContext } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
	import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
@ -36,10 +37,12 @@
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const signUpHandler = async () => {
 | 
			
		||||
		const sessionUser = await userSignUp(name, email, password).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
			return null;
 | 
			
		||||
		});
 | 
			
		||||
		const sessionUser = await userSignUp(name, email, password, generateInitialsImage(name)).catch(
 | 
			
		||||
			(error) => {
 | 
			
		||||
				toast.error(error);
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		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…
	
		Reference in New Issue
	
	Block a user