Compare commits

..

8 commits

Author SHA1 Message Date
921a1cf978 Merge pull request 'main' (!3) from open-webui/open-webui:main into main
Some checks failed
Release / release (push) Failing after 6s
Create and publish a Docker image / build-and-push-image (push) Successful in 31m48s
Python CI / Format Backend (latest, 3.12.2) (push) Failing after 7s
Bun CI / Format & Build Frontend (push) Successful in 1m7s
Reviewed-on: #3
2024-03-26 12:22:22 +01:00
50a2f02906 Merge pull request 'main' (!2) from open-webui/open-webui:main into main
Some checks failed
Release / release (push) Failing after 6s
Python CI / Format Backend (latest, 3.12.2) (push) Failing after 7s
Bun CI / Format & Build Frontend (push) Successful in 1m9s
Create and publish a Docker image / build-and-push-image (push) Successful in 31m21s
Reviewed-on: #2
2024-03-20 21:29:11 +01:00
f7ec002fe6 Merge pull request 'dev' (!1) from dev into main
Some checks failed
Release / release (push) Failing after 9s
Create and publish a Docker image / build-and-push-image (push) Successful in 30m44s
Python CI / Format Backend (latest, 3.12.2) (push) Failing after 8s
Bun CI / Format & Build Frontend (push) Successful in 51s
Reviewed-on: #1
2024-03-19 12:35:03 +01:00
8524e28589 Merge branch 'main' into dev
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Successful in 30m39s
Python CI / Format Backend (latest, 3.12.2) (pull_request) Failing after 8s
Bun CI / Format & Build Frontend (pull_request) Successful in 50s
2024-03-19 12:34:39 +01:00
7144e07122 ci: Tweak python
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
Python CI / Format Backend (latest, 3.12.2) (pull_request) Failing after 7s
Bun CI / Format & Build Frontend (pull_request) Successful in 50s
2024-03-19 12:11:56 +01:00
336868c487 ci: Change login token
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Successful in 30m56s
Python CI / Format Backend (latest) (pull_request) Failing after 18s
Bun CI / Format & Build Frontend (pull_request) Successful in 50s
2024-03-19 09:09:04 +01:00
c7c81f538d ci: Change image
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Failing after 29m25s
2024-03-18 21:06:01 +01:00
af55e94c4f Change avatar and name
Some checks failed
Release / release (push) Failing after 16s
Create and publish a Docker image / build-and-push-image (push) Failing after 32s
Python CI / Format Backend (latest) (push) Failing after 48s
Bun CI / Format & Build Frontend (push) Successful in 1m7s
2024-03-15 21:56:19 +01:00
111 changed files with 2729 additions and 6484 deletions

View file

@ -10,7 +10,3 @@ OPENAI_API_KEY=''
# DO NOT TRACK
SCARF_NO_ANALYTICS=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"

View file

@ -12,13 +12,16 @@ on:
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
REGISTRY: git.depeuter.dev
IMAGE_NAME: ${{ github.repository }}
RUNNER_TOOL_CACHE: /toolcache
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
build-and-push-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
@ -39,7 +42,7 @@ jobs:
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
password: ${{ secrets.CI_TOKEN }}
- name: Extract metadata for Docker images
id: meta

View file

@ -1,39 +1,31 @@
name: Python CI
on:
push:
branches:
- main
- dev
branches: ['main']
pull_request:
branches:
- main
- dev
jobs:
build:
name: 'Format Backend'
env:
PUBLIC_API_BASE_URL: ''
RUNNER_TOOL_CACHE: /toolcache
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11]
node-version:
- latest
python-version: [ '3.12.2' ]
steps:
- uses: actions/checkout@v4
- name: Set up Python
- name: Use Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Use Bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
pip install yapf
- name: Format backend
run: npm run format:backend
- name: Check for changes after format
run: git diff --exit-code
run: bun run format:backend

View file

@ -1,36 +1,22 @@
name: Frontend Build
name: Bun CI
on:
push:
branches:
- main
- dev
branches: ['main']
pull_request:
branches:
- main
- dev
jobs:
build:
name: 'Format & Build Frontend'
env:
PUBLIC_API_BASE_URL: ''
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20' # Or specify any other version you want to use
- name: Install Dependencies
run: npm install
- name: Format Frontend
run: npm run format
- name: Check for Changes After Format
run: git diff --exit-code
- name: Build Frontend
run: npm run build
- uses: actions/checkout@v4
- name: Use Bun
uses: oven-sh/setup-bun@v1
- run: bun --version
- name: Install frontend dependencies
run: bun install
- name: Format frontend
run: bun run format
- name: Build frontend
run: bun run build

View file

@ -5,41 +5,6 @@ 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
- **🔄 Enhanced UI**: Model selector now conveniently located in the navbar, enabling seamless switching between multiple models during conversations.
- **🔍 Improved Model Selector**: Directly pull a model from the selector/Models now display detailed information for better understanding.
- **💬 Webhook Support**: Now compatible with Google Chat and Microsoft Teams.
- **🌐 Localization**: Korean translation (I18n) now available.
- **🌑 Dark Theme**: OLED dark theme introduced for reduced strain during prolonged usage.
- **🏷️ Tag Autocomplete**: Dropdown feature added for effortless chat tagging.
### Fixed
- **🔽 Auto-Scrolling**: Addressed OpenAI auto-scrolling issue.
- **🏷️ Tag Validation**: Implemented tag validation to prevent empty string tags.
- **🚫 Model Whitelisting**: Resolved LiteLLM model whitelisting issue.
- **✅ Spelling**: Corrected various spelling issues for improved readability.
## [0.1.115] - 2024-03-24
### Added

View file

@ -26,15 +26,10 @@ ENV OPENAI_API_BASE_URL ""
ENV OPENAI_API_KEY ""
ENV WEBUI_SECRET_KEY ""
ENV WEBUI_AUTH_TRUSTED_EMAIL_HEADER ""
ENV SCARF_NO_ANALYTICS true
ENV DO_NOT_TRACK true
# Use locally bundled version of the LiteLLM cost map json
# to avoid repetitive startup connections
ENV LITELLM_LOCAL_MODEL_COST_MAP="True"
######## Preloaded models ########
# whisper TTS Settings
ENV WHISPER_MODEL="base"

View file

@ -8,8 +8,6 @@ remove:
start:
@docker-compose start
startAndBuild:
docker-compose up -d --build
stop:
@docker-compose stop

View file

@ -22,13 +22,7 @@ from utils.utils import (
)
from utils.misc import calculate_sha256
from config import (
SRC_LOG_LEVELS,
CACHE_DIR,
UPLOAD_DIR,
WHISPER_MODEL,
WHISPER_MODEL_DIR,
)
from config import SRC_LOG_LEVELS, CACHE_DIR, UPLOAD_DIR, WHISPER_MODEL, WHISPER_MODEL_DIR
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["AUDIO"])

View file

@ -325,7 +325,7 @@ def save_url_image(url):
return image_id
except Exception as e:
log.exception(f"Error saving image: {e}")
print(f"Error saving image: {e}")
return None
@ -397,7 +397,7 @@ def generate_image(
user.id,
app.state.COMFYUI_BASE_URL,
)
log.debug(f"res: {res}")
print(res)
images = []
@ -409,7 +409,7 @@ def generate_image(
with open(file_body_path, "w") as f:
json.dump(data.model_dump(exclude_none=True), f)
log.debug(f"images: {images}")
print(images)
return images
else:
if form_data.model:

View file

@ -4,12 +4,6 @@ import json
import urllib.request
import urllib.parse
import random
import logging
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["COMFYUI"])
from pydantic import BaseModel
@ -127,7 +121,7 @@ COMFYUI_DEFAULT_PROMPT = """
def queue_prompt(prompt, client_id, base_url):
log.info("queue_prompt")
print("queue_prompt")
p = {"prompt": prompt, "client_id": client_id}
data = json.dumps(p).encode("utf-8")
req = urllib.request.Request(f"{base_url}/prompt", data=data)
@ -135,7 +129,7 @@ def queue_prompt(prompt, client_id, base_url):
def get_image(filename, subfolder, folder_type, base_url):
log.info("get_image")
print("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
with urllib.request.urlopen(f"{base_url}/view?{url_values}") as response:
@ -143,14 +137,14 @@ def get_image(filename, subfolder, folder_type, base_url):
def get_image_url(filename, subfolder, folder_type, base_url):
log.info("get_image")
print("get_image")
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
return f"{base_url}/view?{url_values}"
def get_history(prompt_id, base_url):
log.info("get_history")
print("get_history")
with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response:
return json.loads(response.read())
@ -218,15 +212,15 @@ def comfyui_generate_image(
try:
ws = websocket.WebSocket()
ws.connect(f"ws://{host}/ws?clientId={client_id}")
log.info("WebSocket connection established.")
print("WebSocket connection established.")
except Exception as e:
log.exception(f"Failed to connect to WebSocket server: {e}")
print(f"Failed to connect to WebSocket server: {e}")
return None
try:
images = get_images(ws, comfyui_prompt, client_id, base_url)
except Exception as e:
log.exception(f"Error while receiving images: {e}")
print(f"Error while receiving images: {e}")
images = None
ws.close()

View file

@ -33,13 +33,7 @@ from constants import ERROR_MESSAGES
from utils.utils import decode_token, get_current_user, get_admin_user
from config import (
SRC_LOG_LEVELS,
OLLAMA_BASE_URLS,
MODEL_FILTER_ENABLED,
MODEL_FILTER_LIST,
UPLOAD_DIR,
)
from config import SRC_LOG_LEVELS, OLLAMA_BASE_URLS, MODEL_FILTER_ENABLED, MODEL_FILTER_LIST, UPLOAD_DIR
from utils.misc import calculate_sha256
log = logging.getLogger(__name__)
@ -81,12 +75,6 @@ async def check_url(request: Request, call_next):
return response
@app.head("/")
@app.get("/")
async def get_status():
return {"status": True}
@app.get("/urls")
async def get_ollama_api_urls(user=Depends(get_admin_user)):
return {"OLLAMA_BASE_URLS": app.state.OLLAMA_BASE_URLS}
@ -278,7 +266,7 @@ async def pull_model(
if request_id in REQUEST_POOL:
yield chunk
else:
log.warning("User: canceled request")
print("User: canceled request")
break
finally:
if hasattr(r, "close"):
@ -676,7 +664,7 @@ async def generate_completion(
else:
raise HTTPException(
status_code=400,
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
detail="error_detail",
)
url = app.state.OLLAMA_BASE_URLS[url_idx]
@ -782,11 +770,7 @@ async def generate_chat_completion(
r = None
log.debug(
"form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(
form_data.model_dump_json(exclude_none=True).encode()
)
)
log.debug("form_data.model_dump_json(exclude_none=True).encode(): {0} ".format(form_data.model_dump_json(exclude_none=True).encode()))
def get_request():
nonlocal form_data
@ -1035,14 +1019,6 @@ async def download_model(
url_idx: Optional[int] = None,
):
allowed_hosts = ["https://huggingface.co/", "https://github.com/"]
if not any(form_data.url.startswith(host) for host in allowed_hosts):
raise HTTPException(
status_code=400,
detail="Invalid file_url. Only URLs from allowed hosts are permitted.",
)
if url_idx == None:
url_idx = 0
url = app.state.OLLAMA_BASE_URLS[url_idx]
@ -1051,7 +1027,6 @@ async def download_model(
if file_name:
file_path = f"{UPLOAD_DIR}/{file_name}"
return StreamingResponse(
download_file_stream(url, form_data.url, file_path, file_name),
)

View file

@ -8,7 +8,7 @@ from fastapi import (
Form,
)
from fastapi.middleware.cors import CORSMiddleware
import os, shutil, logging, re
import os, shutil, logging
from pathlib import Path
from typing import List
@ -21,7 +21,6 @@ from langchain_community.document_loaders import (
TextLoader,
PyPDFLoader,
CSVLoader,
BSHTMLLoader,
Docx2txtLoader,
UnstructuredEPubLoader,
UnstructuredWordDocumentLoader,
@ -115,7 +114,6 @@ class CollectionNameForm(BaseModel):
class StoreWebForm(CollectionNameForm):
url: str
@app.get("/")
async def get_status():
return {
@ -298,18 +296,13 @@ def store_web(form_data: StoreWebForm, user=Depends(get_current_user)):
def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> bool:
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=app.state.CHUNK_SIZE,
chunk_overlap=app.state.CHUNK_OVERLAP,
add_start_index=True,
)
docs = text_splitter.split_documents(data)
if len(docs) > 0:
return store_docs_in_vector_db(docs, collection_name, overwrite), None
else:
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
return store_docs_in_vector_db(docs, collection_name, overwrite)
def store_text_in_vector_db(
@ -325,7 +318,6 @@ def store_text_in_vector_db(
def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> bool:
texts = [doc.page_content for doc in docs]
metadatas = [doc.metadata for doc in docs]
@ -333,7 +325,7 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
if overwrite:
for collection in CHROMA_CLIENT.list_collections():
if collection_name == collection.name:
log.info(f"deleting existing collection {collection_name}")
print(f"deleting existing collection {collection_name}")
CHROMA_CLIENT.delete_collection(name=collection_name)
collection = CHROMA_CLIENT.create_collection(
@ -346,7 +338,7 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
)
return True
except Exception as e:
log.exception(e)
print(e)
if e.__class__.__name__ == "UniqueConstraintError":
return True
@ -410,8 +402,6 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
loader = UnstructuredRSTLoader(file_path, mode="elements")
elif file_ext == "xml":
loader = UnstructuredXMLLoader(file_path)
elif file_ext in ["htm", "html"]:
loader = BSHTMLLoader(file_path, open_encoding="unicode_escape")
elif file_ext == "md":
loader = UnstructuredMarkdownLoader(file_path)
elif file_content_type == "application/epub+zip":
@ -448,11 +438,8 @@ def store_doc(
log.info(f"file.content_type: {file.content_type}")
try:
unsanitized_filename = file.filename
filename = os.path.basename(unsanitized_filename)
filename = file.filename
file_path = f"{UPLOAD_DIR}/{filename}"
contents = file.file.read()
with open(file_path, "wb") as f:
f.write(contents)
@ -463,10 +450,8 @@ def store_doc(
collection_name = calculate_sha256(f)[:63]
f.close()
loader, known_type = get_loader(filename, file.content_type, file_path)
loader, known_type = get_loader(file.filename, file.content_type, file_path)
data = loader.load()
try:
result = store_data_in_vector_db(data, collection_name)
if result:
@ -476,10 +461,10 @@ def store_doc(
"filename": filename,
"known_type": known_type,
}
except Exception as e:
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=e,
detail=ERROR_MESSAGES.DEFAULT(),
)
except Exception as e:
log.exception(e)
@ -544,7 +529,6 @@ def scan_docs_dir(user=Depends(get_admin_user)):
)
data = loader.load()
try:
result = store_data_in_vector_db(data, collection_name)
if result:
@ -577,9 +561,6 @@ def scan_docs_dir(user=Depends(get_admin_user)):
}
),
)
except Exception as e:
log.exception(e)
pass
except Exception as e:
log.exception(e)

View file

@ -156,8 +156,6 @@ def rag_messages(docs, messages, template, k, embedding_function):
relevant_contexts.append(context)
log.debug(f"relevant_contexts: {relevant_contexts}")
context_string = ""
for context in relevant_contexts:
if context:

View file

@ -1,5 +1,4 @@
from peewee import *
from peewee_migrate import Router
from config import SRC_LOG_LEVELS, DATA_DIR
import os
import logging
@ -17,6 +16,4 @@ else:
DB = SqliteDatabase(f"{DATA_DIR}/webui.db")
router = Router(DB, migrate_dir="apps/web/internal/migrations", logger=log)
router.run()
DB.connect(reuse_if_open=True)
DB.connect()

View file

@ -1,149 +0,0 @@
"""Peewee migrations -- 001_initial_schema.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
@migrator.create_model
class Auth(pw.Model):
id = pw.CharField(max_length=255, unique=True)
email = pw.CharField(max_length=255)
password = pw.CharField(max_length=255)
active = pw.BooleanField()
class Meta:
table_name = "auth"
@migrator.create_model
class Chat(pw.Model):
id = pw.CharField(max_length=255, unique=True)
user_id = pw.CharField(max_length=255)
title = pw.CharField()
chat = pw.TextField()
timestamp = pw.DateField()
class Meta:
table_name = "chat"
@migrator.create_model
class ChatIdTag(pw.Model):
id = pw.CharField(max_length=255, unique=True)
tag_name = pw.CharField(max_length=255)
chat_id = pw.CharField(max_length=255)
user_id = pw.CharField(max_length=255)
timestamp = pw.DateField()
class Meta:
table_name = "chatidtag"
@migrator.create_model
class Document(pw.Model):
id = pw.AutoField()
collection_name = pw.CharField(max_length=255, unique=True)
name = pw.CharField(max_length=255, unique=True)
title = pw.CharField()
filename = pw.CharField()
content = pw.TextField(null=True)
user_id = pw.CharField(max_length=255)
timestamp = pw.DateField()
class Meta:
table_name = "document"
@migrator.create_model
class Modelfile(pw.Model):
id = pw.AutoField()
tag_name = pw.CharField(max_length=255, unique=True)
user_id = pw.CharField(max_length=255)
modelfile = pw.TextField()
timestamp = pw.DateField()
class Meta:
table_name = "modelfile"
@migrator.create_model
class Prompt(pw.Model):
id = pw.AutoField()
command = pw.CharField(max_length=255, unique=True)
user_id = pw.CharField(max_length=255)
title = pw.CharField()
content = pw.TextField()
timestamp = pw.DateField()
class Meta:
table_name = "prompt"
@migrator.create_model
class Tag(pw.Model):
id = pw.CharField(max_length=255, unique=True)
name = pw.CharField(max_length=255)
user_id = pw.CharField(max_length=255)
data = pw.TextField(null=True)
class Meta:
table_name = "tag"
@migrator.create_model
class User(pw.Model):
id = pw.CharField(max_length=255, unique=True)
name = pw.CharField(max_length=255)
email = pw.CharField(max_length=255)
role = pw.CharField(max_length=255)
profile_image_url = pw.CharField(max_length=255)
timestamp = pw.DateField()
class Meta:
table_name = "user"
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_model("user")
migrator.remove_model("tag")
migrator.remove_model("prompt")
migrator.remove_model("modelfile")
migrator.remove_model("document")
migrator.remove_model("chatidtag")
migrator.remove_model("chat")
migrator.remove_model("auth")

View file

@ -1,48 +0,0 @@
"""Peewee migrations -- 002_add_local_sharing.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields(
"chat", share_id=pw.CharField(max_length=255, null=True, unique=True)
)
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields("chat", "share_id")

View file

@ -1,48 +0,0 @@
"""Peewee migrations -- 002_add_local_sharing.py.
Some examples (model - class or model name)::
> Model = migrator.orm['table_name'] # Return model in current state by name
> Model = migrator.ModelClass # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.run(func, *args, **kwargs) # Run python function with the given args
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.add_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
> migrator.add_constraint(model, name, sql)
> migrator.drop_index(model, *col_names)
> migrator.drop_not_null(model, *field_names)
> migrator.drop_constraints(model, *constraints)
"""
from contextlib import suppress
import peewee as pw
from peewee_migrate import Migrator
with suppress(ImportError):
import playhouse.postgres_ext as pw_pext
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your migrations here."""
migrator.add_fields(
"user", api_key=pw.CharField(max_length=255, null=True, unique=True)
)
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
"""Write your rollback migrations here."""
migrator.remove_fields("user", "api_key")

View file

@ -1,21 +0,0 @@
# Database Migrations
This directory contains all the database migrations for the web app.
Migrations are done using the [`peewee-migrate`](https://github.com/klen/peewee_migrate) library.
Migrations are automatically ran at app startup.
## Creating a migration
Have you made a change to the schema of an existing model?
You will need to create a migration file to ensure that existing databases are updated for backwards compatibility.
1. Have a database file (`webui.db`) that has the old schema prior to any of your changes.
2. Make your changes to the models.
3. From the `backend` directory, run the following command:
```bash
pw_migrate create --auto --auto-source apps.web.models --database sqlite:///${SQLITE_DB} --directory apps/web/internal/migrations ${MIGRATION_NAME}
```
- `$SQLITE_DB` should be the path to the database file.
- `$MIGRATION_NAME` should be a descriptive name for the migration.
4. The migration file will be created in the `apps/web/internal/migrations` directory.

View file

@ -20,7 +20,6 @@ from config import (
ENABLE_SIGNUP,
USER_PERMISSIONS,
WEBHOOK_URL,
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
)
app = FastAPI()
@ -35,7 +34,7 @@ app.state.DEFAULT_PROMPT_SUGGESTIONS = DEFAULT_PROMPT_SUGGESTIONS
app.state.DEFAULT_USER_ROLE = DEFAULT_USER_ROLE
app.state.USER_PERMISSIONS = USER_PERMISSIONS
app.state.WEBHOOK_URL = WEBHOOK_URL
app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
app.add_middleware(
CORSMiddleware,

View file

@ -11,7 +11,6 @@ from utils.utils import verify_password
from apps.web.internal.db import DB
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@ -47,10 +46,6 @@ class Token(BaseModel):
token_type: str
class ApiKey(BaseModel):
api_key: Optional[str] = None
class UserResponse(BaseModel):
id: str
email: str
@ -127,28 +122,6 @@ class AuthsTable:
except:
return None
def authenticate_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
log.info(f"authenticate_user_by_api_key: {api_key}")
# if no api_key, return None
if not api_key:
return None
try:
user = Users.get_user_by_api_key(api_key)
return user if user else None
except:
return False
def authenticate_user_by_trusted_header(self, email: str) -> Optional[UserModel]:
log.info(f"authenticate_user_by_trusted_header: {email}")
try:
auth = Auth.get(Auth.email == email, Auth.active == True)
if auth:
user = Users.get_user_by_id(auth.id)
return user
except:
return None
def update_user_password_by_id(self, id: str, new_password: str) -> bool:
try:
query = Auth.update(password=new_password).where(Auth.id == id)

View file

@ -20,7 +20,6 @@ class Chat(Model):
title = CharField()
chat = TextField() # Save Chat JSON as Text
timestamp = DateField()
share_id = CharField(null=True, unique=True)
class Meta:
database = DB
@ -32,7 +31,6 @@ class ChatModel(BaseModel):
title: str
chat: str
timestamp: int # timestamp in epoch
share_id: Optional[str] = None
####################
@ -54,7 +52,6 @@ class ChatResponse(BaseModel):
title: str
chat: dict
timestamp: int # timestamp in epoch
share_id: Optional[str] = None # id of the chat to be shared
class ChatTitleIdResponse(BaseModel):
@ -98,71 +95,6 @@ class ChatTable:
except:
return None
def insert_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
# Get the existing chat to share
chat = Chat.get(Chat.id == chat_id)
# Check if the chat is already shared
if chat.share_id:
return self.get_chat_by_id_and_user_id(chat.share_id, "shared")
# Create a new chat with the same data, but with a new ID
shared_chat = ChatModel(
**{
"id": str(uuid.uuid4()),
"user_id": f"shared-{chat_id}",
"title": chat.title,
"chat": chat.chat,
"timestamp": int(time.time()),
}
)
shared_result = Chat.create(**shared_chat.model_dump())
# Update the original chat with the share_id
result = (
Chat.update(share_id=shared_chat.id).where(Chat.id == chat_id).execute()
)
return shared_chat if (shared_result and result) else None
def update_shared_chat_by_chat_id(self, chat_id: str) -> Optional[ChatModel]:
try:
print("update_shared_chat_by_id")
chat = Chat.get(Chat.id == chat_id)
print(chat)
query = Chat.update(
title=chat.title,
chat=chat.chat,
).where(Chat.id == chat.share_id)
query.execute()
chat = Chat.get(Chat.id == chat.share_id)
return ChatModel(**model_to_dict(chat))
except:
return None
def delete_shared_chat_by_chat_id(self, chat_id: str) -> bool:
try:
query = Chat.delete().where(Chat.user_id == f"shared-{chat_id}")
query.execute() # Remove the rows, return number of rows removed.
return True
except:
return False
def update_chat_share_id_by_id(
self, id: str, share_id: Optional[str]
) -> Optional[ChatModel]:
try:
query = Chat.update(
share_id=share_id,
).where(Chat.id == id)
query.execute()
chat = Chat.get(Chat.id == id)
return ChatModel(**model_to_dict(chat))
except:
return None
def get_chat_lists_by_user_id(
self, user_id: str, skip: int = 0, limit: int = 50
) -> List[ChatModel]:
@ -199,13 +131,6 @@ class ChatTable:
.order_by(Chat.timestamp.desc())
]
def get_chat_by_id(self, id: str) -> Optional[ChatModel]:
try:
chat = Chat.get(Chat.id == id)
return ChatModel(**model_to_dict(chat))
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)
@ -224,15 +149,12 @@ class ChatTable:
query = Chat.delete().where((Chat.id == id) & (Chat.user_id == user_id))
query.execute() # Remove the rows, return number of rows removed.
return True and self.delete_shared_chat_by_chat_id(id)
return True
except:
return False
def delete_chats_by_user_id(self, user_id: str) -> bool:
try:
self.delete_shared_chats_by_user_id(user_id)
query = Chat.delete().where(Chat.user_id == user_id)
query.execute() # Remove the rows, return number of rows removed.
@ -240,19 +162,5 @@ class ChatTable:
except:
return False
def delete_shared_chats_by_user_id(self, user_id: str) -> bool:
try:
shared_chat_ids = [
f"shared-{chat.id}"
for chat in Chat.select().where(Chat.user_id == user_id)
]
query = Chat.delete().where(Chat.user_id << shared_chat_ids)
query.execute() # Remove the rows, return number of rows removed.
return True
except:
return False
Chats = ChatTable(DB)

View file

@ -13,7 +13,6 @@ from apps.web.internal.db import DB
import json
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])

View file

@ -64,8 +64,8 @@ class ModelfilesTable:
self.db.create_tables([Modelfile])
def insert_new_modelfile(
self, user_id: str, form_data: ModelfileForm
) -> Optional[ModelfileModel]:
self, user_id: str,
form_data: ModelfileForm) -> Optional[ModelfileModel]:
if "tagName" in form_data.modelfile:
modelfile = ModelfileModel(
**{
@ -73,8 +73,7 @@ class ModelfilesTable:
"tag_name": form_data.modelfile["tagName"],
"modelfile": json.dumps(form_data.modelfile),
"timestamp": int(time.time()),
}
)
})
try:
result = Modelfile.create(**modelfile.model_dump())
@ -88,28 +87,29 @@ class ModelfilesTable:
else:
return None
def get_modelfile_by_tag_name(self, tag_name: str) -> Optional[ModelfileModel]:
def get_modelfile_by_tag_name(self,
tag_name: str) -> Optional[ModelfileModel]:
try:
modelfile = Modelfile.get(Modelfile.tag_name == tag_name)
return ModelfileModel(**model_to_dict(modelfile))
except:
return None
def get_modelfiles(self, skip: int = 0, limit: int = 50) -> List[ModelfileResponse]:
def get_modelfiles(self,
skip: int = 0,
limit: int = 50) -> List[ModelfileResponse]:
return [
ModelfileResponse(
**{
**model_to_dict(modelfile),
"modelfile": json.loads(modelfile.modelfile),
}
)
for modelfile in Modelfile.select()
"modelfile":
json.loads(modelfile.modelfile),
}) for modelfile in Modelfile.select()
# .limit(limit).offset(skip)
]
def update_modelfile_by_tag_name(
self, tag_name: str, modelfile: dict
) -> Optional[ModelfileModel]:
self, tag_name: str, modelfile: dict) -> Optional[ModelfileModel]:
try:
query = Modelfile.update(
modelfile=json.dumps(modelfile),

View file

@ -52,9 +52,8 @@ class PromptsTable:
self.db = db
self.db.create_tables([Prompt])
def insert_new_prompt(
self, user_id: str, form_data: PromptForm
) -> Optional[PromptModel]:
def insert_new_prompt(self, user_id: str,
form_data: PromptForm) -> Optional[PromptModel]:
prompt = PromptModel(
**{
"user_id": user_id,
@ -62,8 +61,7 @@ class PromptsTable:
"title": form_data.title,
"content": form_data.content,
"timestamp": int(time.time()),
}
)
})
try:
result = Prompt.create(**prompt.model_dump())
@ -83,14 +81,13 @@ class PromptsTable:
def get_prompts(self) -> List[PromptModel]:
return [
PromptModel(**model_to_dict(prompt))
for prompt in Prompt.select()
PromptModel(**model_to_dict(prompt)) for prompt in Prompt.select()
# .limit(limit).offset(skip)
]
def update_prompt_by_command(
self, command: str, form_data: PromptForm
) -> Optional[PromptModel]:
self, command: str,
form_data: PromptForm) -> Optional[PromptModel]:
try:
query = Prompt.update(
title=form_data.title,

View file

@ -11,7 +11,6 @@ import logging
from apps.web.internal.db import DB
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])

View file

@ -20,7 +20,6 @@ class User(Model):
role = CharField()
profile_image_url = CharField()
timestamp = DateField()
api_key = CharField(null=True, unique=True)
class Meta:
database = DB
@ -33,7 +32,6 @@ class UserModel(BaseModel):
role: str = "pending"
profile_image_url: str = "/user.png"
timestamp: int # timestamp in epoch
api_key: Optional[str] = None
####################
@ -84,13 +82,6 @@ class UsersTable:
except:
return None
def get_user_by_api_key(self, api_key: str) -> Optional[UserModel]:
try:
user = User.get(User.api_key == api_key)
return UserModel(**model_to_dict(user))
except:
return None
def get_user_by_email(self, email: str) -> Optional[UserModel]:
try:
user = User.get(User.email == email)
@ -158,21 +149,5 @@ class UsersTable:
except:
return False
def update_user_api_key_by_id(self, id: str, api_key: str) -> str:
try:
query = User.update(api_key=api_key).where(User.id == id)
result = query.execute()
return True if result == 1 else False
except:
return False
def get_user_api_key_by_id(self, id: str) -> Optional[str]:
try:
user = User.get(User.id == id)
return user.api_key
except:
return None
Users = UsersTable(DB)

View file

@ -1,10 +1,13 @@
from fastapi import Request
from fastapi import Depends, HTTPException, status
from fastapi import Response, Request
from fastapi import Depends, FastAPI, HTTPException, status
from datetime import datetime, timedelta
from typing import List, Union
from fastapi import APIRouter
from fastapi import APIRouter, status
from pydantic import BaseModel
import re
import time
import uuid
import re
from apps.web.models.auths import (
SigninForm,
@ -14,7 +17,6 @@ from apps.web.models.auths import (
UserResponse,
SigninResponse,
Auths,
ApiKey,
)
from apps.web.models.users import Users
@ -23,12 +25,10 @@ from utils.utils import (
get_current_user,
get_admin_user,
create_token,
create_api_key,
)
from utils.misc import parse_duration, validate_email_format
from utils.webhook import post_webhook
from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from config import WEBUI_AUTH_TRUSTED_EMAIL_HEADER
router = APIRouter()
@ -79,8 +79,6 @@ async def update_profile(
async def update_password(
form_data: UpdatePasswordForm, session_user=Depends(get_current_user)
):
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
if session_user:
user = Auths.authenticate_user(session_user.email, form_data.password)
@ -100,22 +98,7 @@ async def update_password(
@router.post("/signin", response_model=SigninResponse)
async def signin(request: Request, form_data: SigninForm):
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER not in request.headers:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_TRUSTED_HEADER)
trusted_email = request.headers[WEBUI_AUTH_TRUSTED_EMAIL_HEADER].lower()
if not Users.get_user_by_email(trusted_email.lower()):
await signup(
request,
SignupForm(
email=trusted_email, password=str(uuid.uuid4()), name=trusted_email
),
)
user = Auths.authenticate_user_by_trusted_header(trusted_email)
else:
user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
if user:
token = create_token(
data={"id": user.id},
@ -266,40 +249,3 @@ async def update_token_expires_duration(
return request.app.state.JWT_EXPIRES_IN
else:
return request.app.state.JWT_EXPIRES_IN
############################
# API Key
############################
# create api key
@router.post("/api_key", response_model=ApiKey)
async def create_api_key_(user=Depends(get_current_user)):
api_key = create_api_key()
success = Users.update_user_api_key_by_id(user.id, api_key)
if success:
return {
"api_key": api_key,
}
else:
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_API_KEY_ERROR)
# delete api key
@router.delete("/api_key", response_model=bool)
async def delete_api_key(user=Depends(get_current_user)):
success = Users.update_user_api_key_by_id(user.id, None)
return success
# get api key
@router.get("/api_key", response_model=ApiKey)
async def get_api_key(user=Depends(get_current_user)):
api_key = Users.get_user_api_key_by_id(user.id)
if api_key:
return {
"api_key": api_key,
}
else:
raise HTTPException(404, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)

View file

@ -29,7 +29,6 @@ from apps.web.models.tags import (
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])
@ -189,78 +188,6 @@ async def delete_chat_by_id(request: Request, id: str, user=Depends(get_current_
return result
############################
# ShareChatById
############################
@router.post("/{id}/share", response_model=Optional[ChatResponse])
async def share_chat_by_id(id: str, user=Depends(get_current_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
if chat.share_id:
shared_chat = Chats.update_shared_chat_by_chat_id(chat.id)
return ChatResponse(
**{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)}
)
shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id)
if not shared_chat:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=ERROR_MESSAGES.DEFAULT(),
)
return ChatResponse(
**{**shared_chat.model_dump(), "chat": json.loads(shared_chat.chat)}
)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
############################
# DeletedSharedChatById
############################
@router.delete("/{id}/share", response_model=Optional[bool])
async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)):
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
if chat:
if not chat.share_id:
return False
result = Chats.delete_shared_chat_by_chat_id(id)
update_result = Chats.update_chat_share_id_by_id(id, None)
return result and update_result != None
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
############################
# GetSharedChatById
############################
@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 chat:
return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)})
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
)
############################
# GetChatTagsById
############################

View file

@ -10,12 +10,7 @@ import uuid
from apps.web.models.users import Users
from utils.utils import (
get_password_hash,
get_current_user,
get_admin_user,
create_token,
)
from utils.utils import get_password_hash, get_current_user, get_admin_user, create_token
from utils.misc import get_gravatar_url, validate_email_format
from constants import ERROR_MESSAGES
@ -48,6 +43,7 @@ async def set_global_default_models(
return request.app.state.DEFAULT_MODELS
@router.post("/default/suggestions", response_model=List[PromptSuggestion])
async def set_global_default_suggestions(
request: Request,

View file

@ -24,9 +24,9 @@ router = APIRouter()
@router.get("/", response_model=List[ModelfileResponse])
async def get_modelfiles(
skip: int = 0, limit: int = 50, user=Depends(get_current_user)
):
async def get_modelfiles(skip: int = 0,
limit: int = 50,
user=Depends(get_current_user)):
return Modelfiles.get_modelfiles(skip, limit)
@ -36,16 +36,17 @@ async def get_modelfiles(
@router.post("/create", response_model=Optional[ModelfileResponse])
async def create_new_modelfile(form_data: ModelfileForm, user=Depends(get_admin_user)):
async def create_new_modelfile(form_data: ModelfileForm,
user=Depends(get_admin_user)):
modelfile = Modelfiles.insert_new_modelfile(user.id, form_data)
if modelfile:
return ModelfileResponse(
**{
**modelfile.model_dump(),
"modelfile": json.loads(modelfile.modelfile),
}
)
"modelfile":
json.loads(modelfile.modelfile),
})
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -59,18 +60,17 @@ async def create_new_modelfile(form_data: ModelfileForm, user=Depends(get_admin_
@router.post("/", response_model=Optional[ModelfileResponse])
async def get_modelfile_by_tag_name(
form_data: ModelfileTagNameForm, user=Depends(get_current_user)
):
async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm,
user=Depends(get_current_user)):
modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name)
if modelfile:
return ModelfileResponse(
**{
**modelfile.model_dump(),
"modelfile": json.loads(modelfile.modelfile),
}
)
"modelfile":
json.loads(modelfile.modelfile),
})
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -84,9 +84,8 @@ async def get_modelfile_by_tag_name(
@router.post("/update", response_model=Optional[ModelfileResponse])
async def update_modelfile_by_tag_name(
form_data: ModelfileUpdateForm, user=Depends(get_admin_user)
):
async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm,
user=Depends(get_admin_user)):
modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name)
if modelfile:
updated_modelfile = {
@ -95,15 +94,14 @@ async def update_modelfile_by_tag_name(
}
modelfile = Modelfiles.update_modelfile_by_tag_name(
form_data.tag_name, updated_modelfile
)
form_data.tag_name, updated_modelfile)
return ModelfileResponse(
**{
**modelfile.model_dump(),
"modelfile": json.loads(modelfile.modelfile),
}
)
"modelfile":
json.loads(modelfile.modelfile),
})
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -117,8 +115,7 @@ async def update_modelfile_by_tag_name(
@router.delete("/delete", response_model=bool)
async def delete_modelfile_by_tag_name(
form_data: ModelfileTagNameForm, user=Depends(get_admin_user)
):
async def delete_modelfile_by_tag_name(form_data: ModelfileTagNameForm,
user=Depends(get_admin_user)):
result = Modelfiles.delete_modelfile_by_tag_name(form_data.tag_name)
return result

View file

@ -16,7 +16,6 @@ from utils.utils import get_current_user, get_password_hash, get_admin_user
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MODELS"])

View file

@ -1,11 +1,14 @@
from fastapi import APIRouter, UploadFile, File, Response
from fastapi import APIRouter, UploadFile, File, BackgroundTasks
from fastapi import Depends, HTTPException, status
from starlette.responses import StreamingResponse, FileResponse
from pydantic import BaseModel
from fpdf import FPDF
import markdown
import requests
import os
import aiohttp
import json
from utils.utils import get_admin_user
@ -13,7 +16,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()
@ -25,70 +28,6 @@ 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)):

View file

@ -25,9 +25,7 @@ try:
except ImportError:
log.warning("dotenv not installed, skipping...")
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
WEBUI_NAME = "Aura"
shutil.copyfile("../build/favicon.png", "./static/favicon.png")
####################################
@ -118,20 +116,7 @@ else:
log = logging.getLogger(__name__)
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
log_sources = [
"AUDIO",
"COMFYUI",
"CONFIG",
"DB",
"IMAGES",
"LITELLM",
"MAIN",
"MODELS",
"OLLAMA",
"OPENAI",
"RAG",
"WEBHOOK",
]
log_sources = ["AUDIO", "CONFIG", "DB", "IMAGES", "LITELLM", "MAIN", "MODELS", "OLLAMA", "OPENAI", "RAG"]
SRC_LOG_LEVELS = {}
@ -150,14 +135,13 @@ 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}")
data = r.json()
if r.ok:
if "logo" in data:
WEBUI_FAVICON_URL = url = (
url = (
f"https://api.openwebui.com{data['logo']}"
if data["logo"][0] == "/"
else data["logo"]
@ -173,9 +157,7 @@ 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
@ -256,7 +238,7 @@ OLLAMA_API_BASE_URL = os.environ.get(
)
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
K8S_FLAG = os.environ.get("K8S_FLAG", "")
if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
OLLAMA_BASE_URL = (
@ -269,9 +251,6 @@ if ENV == "prod":
if OLLAMA_BASE_URL == "/ollama":
OLLAMA_BASE_URL = "http://host.docker.internal:11434"
elif K8S_FLAG:
OLLAMA_BASE_URL = "http://ollama-service.open-webui.svc.cluster.local:11434"
OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
@ -366,9 +345,6 @@ WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100")
####################################
WEBUI_AUTH = True
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
####################################
# WEBUI_SECRET_KEY

View file

@ -20,7 +20,6 @@ class ERROR_MESSAGES(str, Enum):
ENV_VAR_NOT_FOUND = "Required environment variable not found. Terminating now."
CREATE_USER_ERROR = "Oops! Something went wrong while creating your account. Please try again later. If the issue persists, contact support for assistance."
DELETE_USER_ERROR = "Oops! Something went wrong. We encountered an issue while trying to delete the user. Please give it another shot."
EMAIL_MISMATCH = "Uh-oh! This email does not match the email your provider is registered with. Please check your email and try again."
EMAIL_TAKEN = "Uh-oh! This email is already registered. Sign in with your existing account or choose another email to start anew."
USERNAME_TAKEN = (
"Uh-oh! This username is already registered. Please choose another username."
@ -37,7 +36,6 @@ class ERROR_MESSAGES(str, Enum):
INVALID_PASSWORD = (
"The password provided is incorrect. Please check for typos and try again."
)
INVALID_TRUSTED_HEADER = "Your provider has not provided a trusted header. Please contact your administrator for assistance."
UNAUTHORIZED = "401 Unauthorized"
ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance."
ACTION_PROHIBITED = (
@ -60,8 +58,5 @@ class ERROR_MESSAGES(str, Enum):
RATE_LIMIT_EXCEEDED = "API rate limit exceeded"
MODEL_NOT_FOUND = lambda name="": f"Model '{name}' was not found"
OPENAI_NOT_FOUND = lambda name="": "OpenAI API was not found"
OPENAI_NOT_FOUND = lambda name="": f"OpenAI API was not found"
OLLAMA_NOT_FOUND = "WebUI could not connect to Ollama"
CREATE_API_KEY_ERROR = "Oops! Something went wrong while creating your API key. Please try again later. If the issue persists, contact support for assistance."
EMPTY_CONTENT = "The content provided is empty. Please ensure that there is text or data present before proceeding."

View file

@ -1,22 +1,33 @@
{
"version": 0,
"ui": {
"default_locale": "en-US",
"prompt_suggestions": [
{
"title": ["Help me study", "vocabulary for a college entrance exam"],
"title": [
"Help me study",
"vocabulary for a college entrance exam"
],
"content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option."
},
{
"title": ["Give me ideas", "for what to do with my kids' art"],
"title": [
"Give me ideas",
"for what to do with my kids' art"
],
"content": "What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter."
},
{
"title": ["Tell me a fun fact", "about the Roman Empire"],
"title": [
"Tell me a fun fact",
"about the Roman Empire"
],
"content": "Tell me a random fun fact about the Roman Empire"
},
{
"title": ["Show me a code snippet", "of a website's sticky header"],
"title": [
"Show me a code snippet",
"of a website's sticky header"
],
"content": "Show me a code snippet of a website's sticky header in CSS and JavaScript."
}
]

View file

@ -32,7 +32,6 @@ from utils.utils import get_admin_user
from apps.rag.utils import rag_messages
from config import (
CONFIG_DATA,
WEBUI_NAME,
ENV,
VERSION,
@ -50,7 +49,6 @@ logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["MAIN"])
class SPAStaticFiles(StaticFiles):
async def get_response(self, path: str, scope):
try:
@ -62,21 +60,6 @@ class SPAStaticFiles(StaticFiles):
raise ex
print(
f"""
___ __ __ _ _ _ ___
/ _ \ _ __ ___ _ __ \ \ / /__| |__ | | | |_ _|
| | | | '_ \ / _ \ '_ \ \ \ /\ / / _ \ '_ \| | | || |
| |_| | |_) | __/ | | | \ V V / __/ |_) | |_| || |
\___/| .__/ \___|_| |_| \_/\_/ \___|_.__/ \___/|___|
|_|
v{VERSION} - building the best open-source AI user interface.
https://github.com/open-webui/open-webui
"""
)
app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
app.state.MODEL_FILTER_ENABLED = MODEL_FILTER_ENABLED
@ -84,6 +67,7 @@ app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.WEBHOOK_URL = WEBHOOK_URL
origins = ["*"]
@ -104,6 +88,7 @@ class RAGMiddleware(BaseHTTPMiddleware):
# Example: Add a new key-value pair or modify existing ones
# data["modified"] = True # Example modification
if "docs" in data:
data = {**data}
data["messages"] = rag_messages(
data["docs"],
@ -178,22 +163,14 @@ app.mount("/rag/api/v1", rag_app)
@app.get("/api/config")
async def get_app_config():
# Checking and Handling the Absence of 'ui' in CONFIG_DATA
default_locale = "en-US"
if "ui" in CONFIG_DATA:
default_locale = CONFIG_DATA["ui"].get("default_locale", "en-US")
# The Rest of the Function Now Uses the Variables Defined Above
return {
"status": True,
"name": WEBUI_NAME,
"version": VERSION,
"default_locale": default_locale,
"images": images_app.state.ENABLED,
"default_models": webui_app.state.DEFAULT_MODELS,
"default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS,
"trusted_header_auth": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
}
@ -214,6 +191,7 @@ class ModelFilterConfigForm(BaseModel):
async def update_model_filter_config(
form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
):
app.state.MODEL_FILTER_ENABLED = form_data.enabled
app.state.MODEL_FILTER_LIST = form_data.models
@ -223,9 +201,6 @@ async def update_model_filter_config(
openai_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED
openai_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
litellm_app.state.MODEL_FILTER_ENABLED = app.state.MODEL_FILTER_ENABLED
litellm_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST
return {
"enabled": app.state.MODEL_FILTER_ENABLED,
"models": app.state.MODEL_FILTER_LIST,
@ -256,6 +231,7 @@ async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
@app.get("/api/version")
async def get_app_config():
return {
"version": VERSION,
}
@ -263,7 +239,7 @@ async def get_app_config():
@app.get("/api/changelog")
async def get_app_changelog():
return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
return CHANGELOG
@app.get("/api/version/updates")
@ -283,20 +259,6 @@ 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")

View file

@ -14,12 +14,9 @@ uuid
requests
aiohttp
peewee
peewee-migrate
bcrypt
litellm==1.30.7
boto3
argon2-cffi
apscheduler
google-generativeai
@ -42,8 +39,6 @@ xlrd
opencv-python-headless
rapidocr-onnxruntime
fpdf2
faster-whisper
PyJWT

View file

@ -1,8 +1,6 @@
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi import HTTPException, status, Depends
from apps.web.models.users import Users
from pydantic import BaseModel
from typing import Union, Optional
from constants import ERROR_MESSAGES
@ -10,7 +8,6 @@ from passlib.context import CryptContext
from datetime import datetime, timedelta
import requests
import jwt
import uuid
import logging
import config
@ -61,11 +58,6 @@ def extract_token_from_auth_header(auth_header: str):
return auth_header[len("Bearer ") :]
def create_api_key():
key = str(uuid.uuid4()).replace("-", "")
return f"sk-{key}"
def get_http_authorization_cred(auth_header: str):
try:
scheme, credentials = auth_header.split(" ")
@ -77,10 +69,6 @@ def get_http_authorization_cred(auth_header: str):
def get_current_user(
auth_token: HTTPAuthorizationCredentials = Depends(bearer_security),
):
# auth by api key
if auth_token.credentials.startswith("sk-"):
return get_current_user_by_api_key(auth_token.credentials)
# auth by jwt token
data = decode_token(auth_token.credentials)
if data != None and "id" in data:
user = Users.get_user_by_id(data["id"])
@ -97,16 +85,6 @@ def get_current_user(
)
def get_current_user_by_api_key(api_key: str):
user = Users.get_user_by_api_key(api_key)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.INVALID_TOKEN,
)
return user
def get_verified_user(user=Depends(get_current_user)):
if user.role not in {"user", "admin"}:
raise HTTPException(

View file

@ -1,54 +1,20 @@
import json
import requests
import logging
from config import SRC_LOG_LEVELS, VERSION, WEBUI_FAVICON_URL, WEBUI_NAME
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["WEBHOOK"])
def post_webhook(url: str, message: str, event_data: dict) -> bool:
try:
payload = {}
# Slack and Google Chat Webhooks
if "https://hooks.slack.com" in url or "https://chat.googleapis.com" in url:
if "https://hooks.slack.com" in url:
payload["text"] = message
# Discord Webhooks
elif "https://discord.com/api/webhooks" in url:
payload["content"] = message
# Microsoft Teams Webhooks
elif "webhook.office.com" in url:
action = event_data.get("action", "undefined")
facts = [
{"name": name, "value": value}
for name, value in json.loads(event_data.get("user", {})).items()
]
payload = {
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": message,
"sections": [
{
"activityTitle": message,
"activitySubtitle": f"{WEBUI_NAME} ({VERSION}) - {action}",
"activityImage": WEBUI_FAVICON_URL,
"facts": facts,
"markdown": True,
}
],
}
# Default Payload
else:
payload = {**event_data}
log.debug(f"payload: {payload}")
r = requests.post(url, json=payload)
r.raise_for_status()
log.debug(f"r.text: {r.text}")
return True
except Exception as e:
log.exception(e)
print(e)
return False

View file

@ -7,7 +7,7 @@ ollama
{{- end -}}
{{- define "ollama.url" -}}
{{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
{{- printf "http://%s.%s.svc.cluster.local:%d/api" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
{{- end }}
{{- define "chart.name" -}}

View file

@ -88,7 +88,7 @@ spec:
resources:
requests:
storage: {{ .Values.ollama.persistence.size | quote }}
storageClassName: {{ .Values.ollama.persistence.storageClass }}
storageClass: {{ .Values.ollama.persistence.storageClass }}
{{- with .Values.ollama.persistence.selector }}
selector:
{{- toYaml . | nindent 8 }}

View file

@ -17,7 +17,7 @@ spec:
resources:
requests:
storage: {{ .Values.webui.persistence.size }}
storageClassName: {{ .Values.webui.persistence.storageClass }}
storageClass: {{ .Values.webui.persistence.storageClass }}
{{- with .Values.webui.persistence.selector }}
selector:
{{- toYaml . | nindent 4 }}

View file

@ -35,4 +35,4 @@ spec:
volumes:
- name: webui-volume
persistentVolumeClaim:
claimName: open-webui-pvc
claimName: ollama-webui-pvc

View file

@ -2,8 +2,8 @@ apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: open-webui
name: open-webui-pvc
app: ollama-webui
name: ollama-webui-pvc
namespace: open-webui
spec:
accessModes: ["ReadWriteOnce"]

200
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.1.117",
"version": "0.1.114",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.1.117",
"version": "0.1.114",
"dependencies": {
"@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5",
@ -19,7 +19,6 @@
"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",
@ -1068,12 +1067,6 @@
"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",
@ -1409,17 +1402,6 @@
"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",
@ -1477,15 +1459,6 @@
"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",
@ -1693,17 +1666,6 @@
"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",
@ -1796,31 +1758,6 @@
}
]
},
"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",
@ -2007,17 +1944,6 @@
"node": ">= 0.6"
}
},
"node_modules/core-js": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
"integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
"hasInstallScript": true,
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@ -2038,15 +1964,6 @@
"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",
@ -2239,12 +2156,6 @@
"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",
@ -2673,11 +2584,6 @@
"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",
@ -3097,19 +3003,6 @@
"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",
@ -3510,27 +3403,10 @@
"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.10",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
"version": "0.16.9",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz",
"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
@ -4095,12 +3971,6 @@
"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",
@ -4521,15 +4391,6 @@
"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",
@ -4633,15 +4494,6 @@
"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",
@ -4962,15 +4814,6 @@
"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",
@ -5372,15 +5215,6 @@
"@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",
@ -5519,15 +5353,6 @@
"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",
@ -5758,15 +5583,6 @@
"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",
@ -5860,9 +5676,9 @@
}
},
"node_modules/vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",

View file

@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.1.117",
"version": "0.1.115",
"private": true,
"scripts": {
"dev": "vite dev --host",
@ -13,7 +13,7 @@
"lint:types": "npm run check",
"lint:backend": "pylint backend/",
"format": "prettier --plugin-search-dir --write '**/*.{js,ts,svelte,css,md,html,json}'",
"format:backend": "black . --exclude \"/venv/\"",
"format:backend": "yapf --recursive backend -p -i",
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write 'src/lib/i18n/**/*.{js,json}'"
},
"devDependencies": {
@ -53,7 +53,6 @@
"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",

View file

@ -78,7 +78,3 @@ select {
/* for Chrome */
-webkit-appearance: none;
}
.katex-mathml {
display: none;
}

View file

@ -9,11 +9,7 @@
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
(() => {
if (localStorage?.theme && localStorage?.theme.includes('oled')) {
document.documentElement.style.setProperty('--color-gray-900', '#000000');
document.documentElement.style.setProperty('--color-gray-950', '#000000');
document.documentElement.classList.add('dark');
} else if (
if (
localStorage.theme === 'light' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: light)').matches)
) {

View file

@ -318,78 +318,3 @@ export const updateJWTExpiresDuration = async (token: string, duration: string)
return res;
};
export const createAPIKey = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res.api_key;
};
export const getAPIKey = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res.api_key;
};
export const deleteAPIKey = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/auths/api_key`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};

View file

@ -218,102 +218,6 @@ export const getChatById = async (token: string, id: string) => {
return res;
};
export const getChatByShareId = async (token: string, share_id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/share/${share_id}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const shareChatById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteSharedChatById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/share`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateChatById = async (token: string, id: string, chat: object) => {
let error = null;

View file

@ -33,7 +33,7 @@ export const getLiteLLMModels = async (token: string = '') => {
id: model.id,
name: model.name ?? model.id,
external: true,
source: 'LiteLLM'
source: 'litellm'
}))
.sort((a, b) => {
return a.name.localeCompare(b.name);

View file

@ -263,53 +263,3 @@ export const synthesizeOpenAISpeech = async (
return res;
};
export const generateTitle = async (
token: string = '',
template: string,
model: string,
prompt: string,
url: string = OPENAI_API_BASE_URL
) => {
let error = null;
template = template.replace(/{{prompt}}/g, prompt);
console.log(template);
const res = await fetch(`${url}/chat/completions`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
model: model,
messages: [
{
role: 'user',
content: template
}
],
stream: false
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
}
return null;
});
if (error) {
throw error;
}
return res?.choices[0]?.message?.content ?? 'New Chat';
};

View file

@ -22,57 +22,6 @@ 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;

View file

@ -16,7 +16,6 @@
const i18n = getContext('i18n');
export let chatId = '';
export let readOnly = false;
export let sendPrompt: Function;
export let continueGeneration: Function;
export let regenerateResponse: Function;
@ -318,7 +317,6 @@
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
user={$user}
{readOnly}
{message}
isFirstMessage={messageIdx === 0}
siblings={message.parentId !== null
@ -337,7 +335,6 @@
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{readOnly}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}

View file

@ -17,11 +17,7 @@
import { config, settings } from '$lib/stores';
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
import { imageGenerations } from '$lib/apis/images';
import {
extractSentences,
revertSanitizedResponseContent,
sanitizeResponseContent
} from '$lib/utils';
import { extractSentences } from '$lib/utils';
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
@ -37,8 +33,6 @@
export let isLastMessage = true;
export let readOnly = false;
export let confirmEditResponseMessage: Function;
export let showPreviousMessage: Function;
export let showNextMessage: Function;
@ -60,7 +54,7 @@
let loadingSpeech = false;
let generatingImage = false;
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
$: tokens = marked.lexer(message.content);
const renderer = new marked.Renderer();
@ -409,10 +403,8 @@
{:else}
{#each tokens as token}
{#if token.type === 'code'}
<CodeBlock
lang={token.lang}
code={revertSanitizedResponseContent(token.text)}
/>
<!-- {token.text} -->
<CodeBlock lang={token.lang} code={token.text} />
{:else}
{@html marked.parse(token.raw, {
...defaults,
@ -430,7 +422,7 @@
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
>
{#if siblings.length > 1}
<div class="flex self-center min-w-fit">
<div class="flex self-center min-w-fit -mt-1">
<button
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
@ -477,7 +469,6 @@
</div>
{/if}
{#if !readOnly}
<Tooltip content="Edit" placement="bottom">
<button
class="{isLastMessage
@ -503,7 +494,6 @@
</svg>
</button>
</Tooltip>
{/if}
<Tooltip content="Copy" placement="bottom">
<button
@ -531,7 +521,6 @@
</button>
</Tooltip>
{#if !readOnly}
<Tooltip content="Good Response" placement="bottom">
<button
class="{isLastMessage
@ -585,7 +574,6 @@
>
</button>
</Tooltip>
{/if}
<Tooltip content="Read Aloud" placement="bottom">
<button
@ -668,7 +656,7 @@
</button>
</Tooltip>
{#if $config.images && !readOnly}
{#if $config.images}
<Tooltip content="Generate Image" placement="bottom">
<button
class="{isLastMessage
@ -764,7 +752,7 @@
</Tooltip>
{/if}
{#if isLastMessage && !readOnly}
{#if isLastMessage}
<Tooltip content="Continue Response" placement="bottom">
<button
type="button"

View file

@ -15,7 +15,6 @@
export let message;
export let siblings;
export let isFirstMessage: boolean;
export let readOnly: boolean;
export let confirmEditMessage: Function;
export let showPreviousMessage: Function;
@ -204,7 +203,7 @@
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
{#if siblings.length > 1}
<div class="flex self-center">
<div class="flex self-center -mt-1">
<button
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
@ -251,7 +250,6 @@
</div>
{/if}
{#if !readOnly}
<Tooltip content="Edit" placement="bottom">
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
@ -275,7 +273,6 @@
</svg>
</button>
</Tooltip>
{/if}
<Tooltip content="Copy" placement="bottom">
<button
@ -301,7 +298,7 @@
</button>
</Tooltip>
{#if !isFirstMessage && !readOnly}
{#if !isFirstMessage}
<Tooltip content="Delete" placement="bottom">
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"

View file

@ -1,12 +1,9 @@
<script lang="ts">
import { Collapsible } from 'bits-ui';
import { setDefaultModels } from '$lib/apis/configs';
import { models, showSettings, settings, user } from '$lib/stores';
import { onMount, tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import Selector from './ModelSelector/Selector.svelte';
import Tooltip from '../common/Tooltip.svelte';
import Select from '../common/Select.svelte';
const i18n = getContext('i18n');
@ -36,19 +33,19 @@
}
</script>
<div class="flex flex-col mt-0.5 w-full">
<div class="flex flex-col my-2 w-full">
{#each selectedModels as selectedModel, selectedModelIdx}
<div class="flex w-full">
<div class="overflow-hidden w-full">
<div class="mr-0.5 max-w-full">
<Selector
<div class="mr-2 max-w-full">
<Select
placeholder={$i18n.t('Select a model')}
items={$models
.filter((model) => model.name !== 'hr')
.map((model) => ({
value: model.id,
label: model.name,
info: model
label:
model.name + `${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`
}))}
bind:value={selectedModel}
/>
@ -56,10 +53,10 @@
</div>
{#if selectedModelIdx === 0}
<div class=" self-center mr-2 disabled:text-gray-600 disabled:hover:text-gray-600">
<Tooltip content="Add Model">
<button
class=" "
class=" self-center {selectedModelIdx === 0
? 'mr-3'
: 'mr-7'} disabled:text-gray-600 disabled:hover:text-gray-600"
{disabled}
on:click={() => {
selectedModels = [...selectedModels, ''];
@ -76,12 +73,12 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
</button>
</Tooltip>
</div>
{:else}
<div class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2">
<Tooltip content="Remove Model">
<button
class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 {selectedModelIdx ===
0
? 'mr-3'
: 'mr-7'}"
{disabled}
on:click={() => {
selectedModels.splice(selectedModelIdx, 1);
@ -99,13 +96,41 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
</button>
</Tooltip>
</div>
{/if}
{#if selectedModelIdx === 0}
<button
class=" self-center dark:hover:text-gray-300"
id="open-settings-button"
on:click={async () => {
await showSettings.set(!$showSettings);
}}
>
<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="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button>
{/if}
</div>
{/each}
</div>
<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
<div class="text-left mt-1.5 ml-1 text-xs text-gray-500">
<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
</div>

View file

@ -1,389 +0,0 @@
<script lang="ts">
import { Select } from 'bits-ui';
import { flyAndScale } from '$lib/utils/transitions';
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import Check from '$lib/components/icons/Check.svelte';
import Search from '$lib/components/icons/Search.svelte';
import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
import { toast } from 'svelte-sonner';
import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let value = '';
export let placeholder = 'Select a model';
export let searchEnabled = true;
export let searchPlaceholder = 'Search a model';
export let items = [{ value: 'mango', label: 'Mango' }];
let searchValue = '';
let ollamaVersion = null;
$: filteredItems = searchValue
? items.filter((item) => item.value.includes(searchValue.toLowerCase()))
: items;
const pullModelHandler = async () => {
const sanitizedModelTag = searchValue.trim();
console.log($MODEL_DOWNLOAD_POOL);
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
toast.error(
$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
modelTag: sanitizedModelTag
})
);
return;
}
if (Object.keys($MODEL_DOWNLOAD_POOL).length === 3) {
toast.error(
$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
);
return;
}
const res = await pullModel(localStorage.token, sanitizedModelTag, '0').catch((error) => {
toast.error(error);
return null;
});
if (res) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitStream('\n'))
.getReader();
while (true) {
try {
const { value, done } = await reader.read();
if (done) break;
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
let data = JSON.parse(line);
console.log(data);
if (data.error) {
throw data.error;
}
if (data.detail) {
throw data.detail;
}
if (data.id) {
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
requestId: data.id,
reader,
done: false
}
});
console.log(data);
}
if (data.status) {
if (data.digest) {
let downloadProgress = 0;
if (data.completed) {
downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
} else {
downloadProgress = 100;
}
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
pullProgress: downloadProgress,
digest: data.digest
}
});
} else {
toast.success(data.status);
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
done: data.status === 'success'
}
});
}
}
}
}
} catch (error) {
console.log(error);
if (typeof error !== 'string') {
error = error.message;
}
toast.error(error);
// opts.callback({ success: false, error, modelName: opts.modelName });
}
}
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
toast.success(
$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
modelName: sanitizedModelTag
})
);
models.set(await getModels(localStorage.token));
} else {
toast.error('Download canceled');
}
delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL
});
}
};
onMount(async () => {
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
});
const cancelModelPullHandler = async (model: string) => {
const { reader, requestId } = $MODEL_DOWNLOAD_POOL[model];
if (reader) {
await reader.cancel();
await cancelOllamaRequest(localStorage.token, requestId);
delete $MODEL_DOWNLOAD_POOL[model];
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL
});
await deleteModel(localStorage.token, model);
toast.success(`${model} download has been canceled`);
}
};
</script>
<Select.Root
{items}
onOpenChange={async () => {
searchValue = '';
window.setTimeout(() => document.getElementById('model-search-input')?.focus(), 0);
}}
selected={items.find((item) => item.value === value) ?? ''}
onSelectedChange={(selectedItem) => {
value = selectedItem.value;
}}
>
<Select.Trigger class="relative w-full" aria-label={placeholder}>
<Select.Value
class="flex text-left px-0.5 outline-none bg-transparent truncate text-lg font-semibold placeholder-gray-400 focus:outline-none"
{placeholder}
/>
<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
</Select.Trigger>
<Select.Content
class=" z-40 w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50 outline-none"
transition={flyAndScale}
sideOffset={4}
>
<slot>
{#if searchEnabled}
<div class="flex items-center gap-2.5 px-5 mt-3.5 mb-3">
<Search className="size-4" strokeWidth="2.5" />
<input
id="model-search-input"
bind:value={searchValue}
class="w-full text-sm bg-transparent outline-none"
placeholder={searchPlaceholder}
/>
</div>
<hr class="border-gray-100 dark:border-gray-800" />
{/if}
<div class="px-3 my-2 max-h-72 overflow-y-auto">
{#each filteredItems as item}
<Select.Item
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
value={item.value}
label={item.label}
>
<div class="flex items-center gap-2">
<div class="line-clamp-1">
{item.label}
<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
>{item.info?.details?.parameter_size ?? ''}</span
>
</div>
<!-- {JSON.stringify(item.info)} -->
{#if item.info.external}
<Tooltip content={item.info?.source ?? 'External'}>
<div class=" mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-3"
>
<path
fill-rule="evenodd"
d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
clip-rule="evenodd"
/>
</svg>
</div>
</Tooltip>
{:else}
<Tooltip
content={`${
item.info?.details?.quantization_level
? item.info?.details?.quantization_level + ' '
: ''
}${item.info.size ? `(${(item.info.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}
>
<div class=" mr-2">
<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="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
</div>
</Tooltip>
{/if}
</div>
{#if value === item.value}
<div class="ml-auto">
<Check />
</div>
{/if}
</Select.Item>
{:else}
<div>
<div class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100">
No results found
</div>
</div>
{/each}
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
<button
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
pullModelHandler();
}}
>
Pull "{searchValue}" from Ollama.com
</button>
{/if}
{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
<div
class="flex w-full justify-between font-medium select-none rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
>
<div class="flex">
<div class="-ml-2 mr-2.5 translate-y-0.5">
<svg
class="size-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
<div class="flex flex-col self-start">
<div class="line-clamp-1">
Downloading "{model}" {'pullProgress' in $MODEL_DOWNLOAD_POOL[model]
? `(${$MODEL_DOWNLOAD_POOL[model].pullProgress}%)`
: ''}
</div>
{#if 'digest' in $MODEL_DOWNLOAD_POOL[model] && $MODEL_DOWNLOAD_POOL[model].digest}
<div class="-mt-1 h-fit text-[0.7rem] dark:text-gray-500 line-clamp-1">
{$MODEL_DOWNLOAD_POOL[model].digest}
</div>
{/if}
</div>
</div>
<div class="mr-2 translate-y-0.5">
<Tooltip content="Cancel">
<button
class="text-gray-800 dark:text-gray-100"
on:click={() => {
cancelModelPullHandler(model);
}}
>
<svg
class="w-4 h-4 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg>
</button>
</Tooltip>
</div>
</div>
{/each}
</div>
</slot>
</Select.Content>
</Select.Root>

View file

@ -6,8 +6,6 @@
import { compareVersion } from '$lib/utils';
import { onMount, getContext } from 'svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
let ollamaVersion = '';
@ -53,10 +51,8 @@
</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 class="flex gap-1">
<Tooltip content={WEBUI_VERSION === '0.1.117' ? "🪖 We're just getting started." : ''}>
<div>
v{WEBUI_VERSION}
</Tooltip>
<a
href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
@ -130,9 +126,7 @@
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
{#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')}
{$i18n.t('Created by')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://github.com/tjbck"

View file

@ -3,13 +3,11 @@
import { onMount, getContext } from 'svelte';
import { user } from '$lib/stores';
import { updateUserProfile, createAPIKey, getAPIKey } from '$lib/apis/auths';
import { updateUserProfile } from '$lib/apis/auths';
import UpdatePassword from './Account/UpdatePassword.svelte';
import { getGravatarUrl } from '$lib/apis/utils';
import { copyToClipboard } from '$lib/utils';
import Plus from '$lib/components/icons/Plus.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
@ -17,14 +15,8 @@
let profileImageUrl = '';
let name = '';
let showJWTToken = false;
let JWTTokenCopied = false;
let APIKey = '';
let showAPIKey = false;
let APIKeyCopied = false;
let profileImageInputElement: HTMLInputElement;
const submitHandler = async () => {
@ -41,23 +33,9 @@
return false;
};
const createAPIKeyHandler = async () => {
APIKey = await createAPIKey(localStorage.token);
if (APIKey) {
toast.success($i18n.t('API Key created.'));
} else {
toast.error($i18n.t('Failed to create API Key.'));
}
};
onMount(async () => {
onMount(() => {
name = $user.name;
profileImageUrl = $user.profile_image_url;
APIKey = await getAPIKey(localStorage.token).catch((error) => {
console.log(error);
return '';
});
});
</script>
@ -192,23 +170,22 @@
<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=" w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
</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"
class="w-full rounded-l-lg py-1.5 pl-4 text-sm 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"
class="dark:bg-gray-800 px-2 transition rounded-r-lg"
on:click={() => {
showJWTToken = !showJWTToken;
}}
@ -248,7 +225,7 @@
</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 hover:bg-gray-800 transition rounded-lg"
on:click={() => {
copyToClipboard(localStorage.token);
JWTTokenCopied = true;
@ -292,143 +269,6 @@
</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-800 outline-none"
type={showAPIKey ? 'text' : 'password'}
value={APIKey}
disabled
/>
<button
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800"
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>
<button
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 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-800transition 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-800 transition"
on:click={() => {
createAPIKeyHandler();
}}
>
<Plus strokeWidth="2" className=" size-3.5" />
Create new secret key</button
>
{/if}
</div>
</div>
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">

View file

@ -14,7 +14,7 @@
export let getModels: Function;
// General
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light', 'oled-dark'];
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
let selectedTheme = 'system';
let languages = [];
@ -91,17 +91,12 @@
});
const applyTheme = (_theme: string) => {
let themeToApply = _theme === 'oled-dark' ? 'dark' : _theme;
let themeToApply = _theme;
if (_theme === 'system') {
themeToApply = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
if (themeToApply === 'dark' && !_theme.includes('oled')) {
document.documentElement.style.setProperty('--color-gray-900', '#171717');
document.documentElement.style.setProperty('--color-gray-950', '#0d0d0d');
}
themes
.filter((e) => e !== themeToApply)
.forEach((e) => {
@ -120,11 +115,7 @@
const themeChangeHandler = (_theme: string) => {
theme.set(_theme);
localStorage.setItem('theme', _theme);
if (_theme.includes('oled')) {
document.documentElement.style.setProperty('--color-gray-900', '#000000');
document.documentElement.style.setProperty('--color-gray-950', '#000000');
document.documentElement.classList.add('dark');
}
applyTheme(_theme);
};
</script>
@ -145,7 +136,6 @@
>
<option value="system">⚙️ {$i18n.t('System')}</option>
<option value="dark">🌑 {$i18n.t('Dark')}</option>
<option value="oled-dark">🌃 {$i18n.t('OLED Dark')}</option>
<option value="light">☀️ {$i18n.t('Light')}</option>
<option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { getBackendConfig } from '$lib/apis';
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
import { config, models, settings, user } from '$lib/stores';
import { config, models, user } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
@ -14,7 +14,6 @@
let titleAutoGenerate = true;
let responseAutoCopy = false;
let titleAutoGenerateModel = '';
let titleAutoGenerateModelExternal = '';
let fullScreenMode = false;
let titleGenerationPrompt = '';
@ -34,12 +33,7 @@
const toggleTitleAutoGenerate = async () => {
titleAutoGenerate = !titleAutoGenerate;
saveSettings({
title: {
...$settings.title,
auto: titleAutoGenerate
}
});
saveSettings({ titleAutoGenerate: titleAutoGenerate });
};
const toggleResponseAutoCopy = async () => {
@ -71,13 +65,8 @@
}
saveSettings({
title: {
...$settings.title,
model: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
modelExternal:
titleAutoGenerateModelExternal !== '' ? titleAutoGenerateModelExternal : undefined,
prompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
}
titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
});
};
@ -88,18 +77,16 @@
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
titleAutoGenerate = settings?.title?.auto ?? true;
titleAutoGenerateModel = settings?.title?.model ?? '';
titleAutoGenerateModelExternal = settings?.title?.modelExternal ?? '';
titleGenerationPrompt =
settings?.title?.prompt ??
$i18n.t(
"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':"
) + ' {{prompt}}';
titleAutoGenerate = settings.titleAutoGenerate ?? true;
responseAutoCopy = settings.responseAutoCopy ?? false;
showUsername = settings.showUsername ?? false;
fullScreenMode = settings.fullScreenMode ?? false;
titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
titleGenerationPrompt =
settings.titleGenerationPrompt ??
$i18n.t(
"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':"
) + ' {{prompt}}';
});
</script>
@ -203,9 +190,8 @@
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Title Auto-Generation Model')}</div>
<div class="flex w-full gap-2 pr-2">
<div class="flex-1">
<div class=" text-xs mb-1">Local Models</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModel}
@ -221,24 +207,6 @@
{/each}
</select>
</div>
<div class="flex-1">
<div class=" text-xs mb-1">External Models</div>
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModelExternal}
placeholder={$i18n.t('Select a model')}
>
<option value="" selected>{$i18n.t('Current Model')}</option>
{#each $models as model}
{#if model.name !== 'hr'}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700">
{model.name}
</option>
{/if}
{/each}
</select>
</div>
</div>
<div class="mt-3 mr-2">

View file

@ -258,9 +258,6 @@
console.log(error);
}
}
} else {
const error = await fileResponse?.json();
toast.error(error?.detail ?? error);
}
if (uploaded) {

View file

@ -1,124 +1,19 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
import { chatId, modelfiles } from '$lib/stores';
import { copyToClipboard } from '$lib/utils';
import { getContext } from 'svelte';
import Modal from '../common/Modal.svelte';
import Link from '../icons/Link.svelte';
let chat = null;
const i18n = getContext('i18n');
const shareLocalChat = async () => {
const _chat = chat;
const sharedChat = await shareChatById(localStorage.token, $chatId);
const chatShareUrl = `${window.location.origin}/s/${sharedChat.id}`;
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
copyToClipboard(chatShareUrl);
chat = await getChatById(localStorage.token, $chatId);
};
const shareChat = async () => {
const _chat = chat.chat;
console.log('share', _chat);
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
// const url = 'http://localhost:5173';
const tab = await window.open(`${url}/chats/upload`, '_blank');
window.addEventListener(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(
JSON.stringify({
chat: _chat,
modelfiles: $modelfiles.filter((modelfile) =>
_chat.models.includes(modelfile.tagName)
)
}),
'*'
);
}
},
false
);
};
export let downloadChat: Function;
export let shareChat: Function;
export let show = false;
$: if (show) {
(async () => {
if ($chatId) {
chat = await getChatById(localStorage.token, $chatId);
} else {
chat = null;
console.log(chat);
}
})();
}
</script>
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
<div class=" text-lg font-medium self-center">{$i18n.t('Share Chat')}</div>
<button
class="self-center"
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<hr class=" dark:border-gray-800" />
{#if chat}
<Modal bind:show size="xs">
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
<div class=" text-sm dark:text-gray-300 mb-1">
{#if chat.share_id}
<a href="/s/{chat.share_id}" target="_blank"
>You have shared this chat <span class=" underline">before</span>.</a
>
Click here to
<button
class="underline"
on:click={async () => {
const res = await deleteSharedChatById(localStorage.token, $chatId);
if (res) {
chat = await getChatById(localStorage.token, $chatId);
}
}}>delete this link</button
> and create a new shared link.
{:else}
Messages you send after creating your link won't be shared. Users with the URL will be
able to view the shared chat.
{/if}
</div>
<div class="flex justify-end">
<div class="flex flex-col items-end space-x-1 mt-1.5">
<div class="flex gap-1">
<button
class=" self-center px-3.5 py-2 rounded-xl text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white"
class=" self-center px-8 py-1.5 w-full rounded-full text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white"
type="button"
on:click={() => {
shareChat();
@ -128,26 +23,19 @@
{$i18n.t('Share to OpenWebUI Community')}
</button>
<div class="flex justify-center space-x-1 mt-1.5">
<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
<button
class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
class=" self-center rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
type="button"
on:click={() => {
shareLocalChat();
downloadChat();
show = false;
}}
>
<Link />
{#if chat.share_id}
{$i18n.t('Update and Copy Link')}
{:else}
{$i18n.t('Copy Link')}
{/if}
{$i18n.t('Download as a File')}
</button>
</div>
</div>
</div>
</div>
{/if}
</div>
</Modal>

View file

@ -1,20 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte';
import Modal from '../common/Modal.svelte';
import Tags from '../common/Tags.svelte';
const i18n = getContext('i18n');
export let tags;
export let deleteTag: Function;
export let addTag: Function;
export let show = false;
</script>
<Modal bind:show size="xs">
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
<Tags {tags} {deleteTag} {addTag} />
</div>
</Modal>

View file

@ -12,9 +12,6 @@
export let value = '';
export let placeholder = 'Select a model';
export let searchEnabled = true;
export let searchPlaceholder = 'Search a model';
export let items = [
{ value: 'mango', label: 'Mango' },
{ value: 'watermelon', label: 'Watermelon' },
@ -52,25 +49,22 @@
transition={flyAndScale}
sideOffset={4}
>
<slot>
{#if searchEnabled}
<div class="flex items-center gap-2.5 px-5 mt-3.5 mb-3">
<Search className="size-4" strokeWidth="2.5" />
<input
bind:value={searchValue}
class="w-full text-sm bg-transparent outline-none"
placeholder={searchPlaceholder}
placeholder="Search a model"
/>
</div>
<hr class="border-gray-100 dark:border-gray-800" />
{/if}
<div class="px-3 my-2 max-h-80 overflow-y-auto">
{#each filteredItems as item}
<Select.Item
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 data-[highlighted]:bg-muted"
value={item.value}
label={item.label}
>
@ -83,13 +77,11 @@
{/if}
</Select.Item>
{:else}
<div>
<div class="block px-5 py-2 text-sm text-gray-700 dark:text-gray-100">
<span class="block px-5 py-2 text-sm text-gray-700 dark:text-gray-100">
No results found
</div>
</div>
</span>
{/each}
</div>
</slot>
</Select.Content>
<Select.Input name="favoriteFruit" />
</Select.Root>

View file

@ -1,25 +1,24 @@
<script lang="ts">
export let className: string = '';
export let className: string = 'text-white';
export let theme: 'blue' | 'white' | 'black' = 'white';
</script>
<div class="flex justify-center text-center {className}">
<svg class="size-5" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
<svg
class="animate-spin -ml-1 mr-3 h-5 w-5 {theme === 'blue'
? 'text-sky-600'
: theme === 'white'
? 'text-white'
: 'text-gray-600'} "
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>

View file

@ -8,7 +8,7 @@
export let addTag: Function;
</script>
<div class="flex flex-row flex-wrap gap-0.5 line-clamp-1">
<div class="flex flex-row space-x-0.5 line-clamp-1">
<TagList
{tags}
on:delete={(e) => {
@ -17,7 +17,6 @@
/>
<TagInput
label={tags.length == 0 ? 'Add Tags' : ''}
on:add={(e) => {
addTag(e.detail);
}}

View file

@ -1,31 +1,24 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte';
import { tags } from '$lib/stores';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let label = '';
let showTagInput = false;
let tagName = '';
const addTagHandler = async () => {
tagName = tagName.trim();
if (tagName !== '') {
dispatch('add', tagName);
tagName = '';
showTagInput = false;
} else {
toast.error('Invalid Tag');
}
};
</script>
<div class="flex space-x-1 pl-1.5">
{#if showTagInput}
<div class="flex items-center">
<button type="button" on:click={addTagHandler}>
<button
type="button"
on:click={() => {
dispatch('add', tagName);
tagName = '';
showTagInput = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@ -41,21 +34,12 @@
</button>
<input
bind:value={tagName}
class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[5.5rem]"
class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[8rem]"
placeholder={$i18n.t('Add a tag')}
list="tagOptions"
on:keydown={(event) => {
if (event.key === 'Enter') {
addTagHandler();
}
}}
/>
<datalist id="tagOptions">
{#each $tags as tag}
<option value={tag.name} />
{/each}
</datalist>
</div>
<!-- TODO: Tag Suggestions -->
{/if}
<button
@ -78,8 +62,4 @@
</svg>
</div>
</button>
{#if label && !showTagInput}
<span class="text-xs pl-1.5 self-center">{label}</span>
{/if}
</div>

View file

@ -7,7 +7,7 @@
{#each tags as tag}
<div
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-800 dark:text-white"
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-600 dark:text-white"
>
<div class=" text-[0.7rem] font-medium self-center line-clamp-1">
{tag.name}

View file

@ -29,6 +29,6 @@
});
</script>
<div bind:this={tooltipElement} aria-label={content} class="flex">
<div bind:this={tooltipElement} aria-label={content}>
<slot />
</div>

View file

@ -1,19 +0,0 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 15 12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
/>
</svg>

View file

@ -1,19 +0,0 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 12.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 18.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z"
/>
</svg>

View file

@ -1,16 +0,0 @@
<script lang="ts">
export let className = 'w-4 h-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
clip-rule="evenodd"
/>
</svg>

View file

@ -1,15 +0,0 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>

View file

@ -1,14 +1,14 @@
<script lang="ts">
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
import { slide } from 'svelte/transition';
import { getChatById } from '$lib/apis/chats';
import { WEBUI_NAME, chatId, modelfiles, settings } from '$lib/stores';
import ShareChatModal from '../chat/ShareChatModal.svelte';
import ModelSelector from '../chat/ModelSelector.svelte';
import Tooltip from '../common/Tooltip.svelte';
import Menu from './Navbar/Menu.svelte';
import TagInput from '../common/Tags/TagInput.svelte';
import Tags from '../common/Tags.svelte';
const i18n = getContext('i18n');
@ -16,108 +16,71 @@
export let title: string = $WEBUI_NAME;
export let shareEnabled: boolean = false;
export let chat;
export let selectedModels;
export let tags = [];
export let addTag: Function;
export let deleteTag: Function;
export let showModelSelector = true;
let showShareChatModal = false;
let showDownloadChatModal = false;
let tagName = '';
let showTagInput = false;
const shareChat = async () => {
const chat = (await getChatById(localStorage.token, $chatId)).chat;
console.log('share', chat);
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
// const url = 'http://localhost:5173';
const tab = await window.open(`${url}/chats/upload`, '_blank');
window.addEventListener(
'message',
(event) => {
if (event.origin !== url) return;
if (event.data === 'loaded') {
tab.postMessage(
JSON.stringify({
chat: chat,
modelfiles: $modelfiles.filter((modelfile) => chat.models.includes(modelfile.tagName))
}),
'*'
);
}
},
false
);
};
const downloadChat = async () => {
const chat = (await getChatById(localStorage.token, $chatId)).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`);
};
</script>
<ShareChatModal bind:show={showShareChatModal} />
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
<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"
class=" flex {$settings?.fullScreenMode ?? null
? 'max-w-full'
: 'max-w-3xl'} w-full mx-auto px-3"
>
<div class="flex items-center w-full max-w-full">
<div class="flex-1 overflow-hidden max-w-full">
{#if showModelSelector}
<ModelSelector bind:selectedModels />
{/if}
</div>
<div class="self-start flex flex-none items-center">
<div class="flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" />
{#if !shareEnabled}
<Tooltip content="Settings">
<button
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
id="open-settings-button"
on:click={async () => {
await showSettings.set(!$showSettings);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>
</button>
</Tooltip>
{:else}
<Menu
{chat}
{shareEnabled}
shareHandler={() => {
showShareChatModal = !showShareChatModal;
}}
downloadHandler={() => {
showDownloadChatModal = !showDownloadChatModal;
}}
{tags}
{deleteTag}
{addTag}
>
<button
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
>
<div class=" m-auto self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
/>
</svg>
</div>
</button>
</Menu>
{/if}
<Tooltip content="New Chat">
<div class="pr-2 self-start">
<button
id="new-chat-button"
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
on:click={() => {
initNewChat();
}}
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition"
on:click={initNewChat}
>
<div class=" m-auto self-center">
<svg
@ -135,7 +98,41 @@
</svg>
</div>
</button>
</Tooltip>
</div>
<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 space-x-2">
{#if shareEnabled}
<Tags {tags} {deleteTag} {addTag} />
<button
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg 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>
{/if}
</div>
</div>
</div>

View file

@ -1,223 +0,0 @@
<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 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
on:change={(e) => {
if (e.detail === false) {
onClose();
}
}}
>
<slot />
<div slot="content">
<DropdownMenu.Content
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 cursor-pointer dark:hover:bg-gray-850 rounded-md"
on:click={async () => {
await showSettings.set(!$showSettings);
}}
>
<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="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>
<div class="flex items-center">Settings</div>
</DropdownMenu.Item>
{#if shareEnabled}
<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={() => {
shareHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-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 class="flex items-center">Share</div>
</DropdownMenu.Item>
<!-- <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} />
</div>
<!-- <DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
on:click={() => {
tagHandler();
}}
>
<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="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z"
/>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
</svg>
<div class="flex items-center">Tag</div>
</DropdownMenu.Item> -->
{/if}
</DropdownMenu.Content>
</div>
</Dropdown>

View file

@ -107,7 +107,7 @@
bind:this={navElement}
class="h-screen max-h-[100dvh] min-h-screen {show
? 'lg:relative w-[260px]'
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition z-40 fixed top-0 left-0
"
>
<div
@ -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 hover:bg-gray-100 dark:hover:bg-gray-800 transition"
class="flex py-2.5 px-3.5 w-full 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 hover:bg-gray-100 dark:hover:bg-gray-800 transition"
class="flex py-2.5 px-3.5 w-full 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 hover:bg-gray-100 dark:hover:bg-gray-800 transition"
class="flex py-2.5 px-3.5 w-full 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 hover:bg-gray-100 dark:hover:bg-gray-800 transition"
class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition"
on:click={() => {
localStorage.removeItem('token');
location.href = '/auth';
@ -706,7 +706,7 @@
</div>
<div
class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
>
<Tooltip
placement="right"

View file

@ -22,7 +22,6 @@ export const SUPPORTED_FILE_TYPE = [
'text/plain',
'text/csv',
'text/xml',
'text/html',
'text/x-python',
'text/css',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
@ -51,8 +50,6 @@ export const SUPPORTED_FILE_EXTENSIONS = [
'h',
'c',
'cs',
'htm',
'html',
'sql',
'log',
'ini',

View file

@ -37,28 +37,23 @@ const createIsLoadingStore = (i18n: i18nType) => {
return isLoading;
};
export const initI18n = (defaultLocale: string) => {
let detectionOrder = defaultLocale
? ['querystring', 'localStorage']
: ['querystring', 'localStorage', 'navigator'];
let fallbackDefaultLocale = defaultLocale ? [defaultLocale] : ['en-US'];
const loadResource = (language: string, namespace: string) =>
import(`./locales/${language}/${namespace}.json`);
i18next
.use(resourcesToBackend(loadResource))
.use(
resourcesToBackend(
(language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`)
)
)
.use(LanguageDetector)
.init({
debug: false,
detection: {
order: detectionOrder,
order: ['querystring', 'localStorage', 'navigator'],
caches: ['localStorage'],
lookupQuerystring: 'lang',
lookupLocalStorage: 'locale'
},
fallbackLng: {
default: fallbackDefaultLocale
default: ['en-US']
},
ns: 'translation',
returnEmptyString: false,
@ -66,7 +61,6 @@ export const initI18n = (defaultLocale: string) => {
escapeValue: false // not needed for svelte as it escapes by default
}
});
};
const i18n = createI18nStore(i18next);
const isLoadingStore = createIsLoadingStore(i18next);

View file

@ -279,7 +279,7 @@
"Send a Message": "Изпращане на Съобщение",
"Send message": "Изпращане на съобщение",
"Server connection verified": "Server connection verified",
"Set as default": "Задай по подразбиране",
"Set as default": "Задай като по подразбиране",
"Set Default Model": "Задай Модел По Подразбиране",
"Set Image Size": "Задай Размер на Изображението",
"Set Steps": "Задай Стъпки",
@ -320,7 +320,7 @@
"Title": "Заглавие",
"Title Auto-Generation": "Автоматично Генериране на Заглавие",
"Title Generation Prompt": "Промпт за Генериране на Заглавие",
"to": "в",
"to": "до",
"To access the available model names for downloading,": "За да получите достъп до наличните имена на модели за изтегляне,",
"To access the GGUF models available for downloading,": "За да получите достъп до GGUF моделите, налични за изтегляне,",
"to chat input.": "към чат входа.",

View file

@ -1,128 +1,128 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
"(Beta)": "",
"(e.g. `sh webui.sh --api`)": "",
"(latest)": "",
"{{modelName}} is thinking...": "",
"{{webUIName}} Backend Required": "",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
"(latest)": "(latest)",
"{{modelName}} is thinking...": "{{modelName}} is thinking...",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Required",
"a user": "",
"About": "",
"Account": "",
"Action": "",
"Add a model": "",
"Add a model tag name": "",
"Add a short description about what this modelfile does": "",
"Add a short title for this prompt": "",
"Add a tag": "",
"Add Docs": "",
"Add Files": "",
"Add message": "",
"add tags": "",
"Adjusting these settings will apply changes universally to all users.": "",
"admin": "",
"Admin Panel": "",
"Admin Settings": "",
"Advanced Parameters": "",
"all": "",
"All Users": "",
"Allow": "",
"Allow Chat Deletion": "",
"alphanumeric characters and hyphens": "",
"Already have an account?": "",
"About": "About",
"Account": "Account",
"Action": "Action",
"Add a model": "Add a model",
"Add a model tag name": "Add a model tag name",
"Add a short description about what this modelfile does": "Add a short description about what this modelfile does",
"Add a short title for this prompt": "Add a short title for this prompt",
"Add a tag": "Add a tag",
"Add Docs": "Add Docs",
"Add Files": "Add Files",
"Add message": "Add message",
"add tags": "add tags",
"Adjusting these settings will apply changes universally to all users.": "Adjusting these settings will apply changes universally to all users.",
"admin": "admin",
"Admin Panel": "Admin Panel",
"Admin Settings": "Admin Settings",
"Advanced Parameters": "Advanced Parameters",
"all": "all",
"All Users": "All Users",
"Allow": "Allow",
"Allow Chat Deletion": "Allow Chat Deletion",
"alphanumeric characters and hyphens": "alphanumeric characters and hyphens",
"Already have an account?": "Already have an account?",
"an assistant": "",
"and": "",
"API Base URL": "",
"API Key": "",
"API RPM": "",
"are allowed - Activate this command by typing": "",
"and": "and",
"API Base URL": "API Base URL",
"API Key": "API Key",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "are allowed - Activate this command by typing",
"Are you sure?": "",
"Audio": "",
"Auto-playback response": "",
"Auto-send input after 3 sec.": "",
"AUTOMATIC1111 Base URL": "",
"Audio": "Audio",
"Auto-playback response": "Auto-playback response",
"Auto-send input after 3 sec.": "Auto-send input after 3 sec.",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "",
"available!": "",
"Back": "",
"Builder Mode": "",
"Cancel": "",
"Categories": "",
"Change Password": "",
"Chat": "",
"Chat History": "",
"Chat History is off for this browser.": "",
"Chats": "",
"Check Again": "",
"Check for updates": "",
"Checking for updates...": "",
"Choose a model before saving...": "",
"Chunk Overlap": "",
"Chunk Params": "",
"Chunk Size": "",
"Click here for help.": "",
"Click here to check other modelfiles.": "",
"available!": "available!",
"Back": "Back",
"Builder Mode": "Builder Mode",
"Cancel": "Cancel",
"Categories": "Categories",
"Change Password": "Change Password",
"Chat": "Chat",
"Chat History": "Chat History",
"Chat History is off for this browser.": "Chat History is off for this browser.",
"Chats": "Chats",
"Check Again": "Check Again",
"Check for updates": "Check for updates",
"Checking for updates...": "Checking for updates...",
"Choose a model before saving...": "Choose a model before saving...",
"Chunk Overlap": "Chunk Overlap",
"Chunk Params": "Chunk Params",
"Chunk Size": "Chunk Size",
"Click here for help.": "Click here for help.",
"Click here to check other modelfiles.": "Click here to check other modelfiles.",
"Click here to select": "",
"Click here to select documents.": "",
"click here.": "",
"Click on the user role button to change a user's role.": "",
"Close": "",
"Collection": "",
"Command": "",
"Confirm Password": "",
"Connections": "",
"Content": "",
"Context Length": "",
"Conversation Mode": "",
"Copy last code block": "",
"Copy last response": "",
"Copying to clipboard was successful!": "",
"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':": "",
"Create a modelfile": "",
"Create Account": "",
"Created at": "",
"Created by": "",
"Current Model": "",
"Current Password": "",
"Custom": "",
"Customize Ollama models for a specific purpose": "",
"Dark": "",
"Database": "",
"DD/MM/YYYY HH:mm": "",
"Default": "",
"click here.": "click here.",
"Click on the user role button to change a user's role.": "Click on the user role button to change a user's role.",
"Close": "Close",
"Collection": "Collection",
"Command": "Command",
"Confirm Password": "Confirm Password",
"Connections": "Connections",
"Content": "Content",
"Context Length": "Context Length",
"Conversation Mode": "Conversation Mode",
"Copy last code block": "Copy last code block",
"Copy last response": "Copy last response",
"Copying to clipboard was successful!": "Copying to clipboard was successful!",
"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':": "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':",
"Create a modelfile": "Create a modelfile",
"Create Account": "Create Account",
"Created at": "Created at",
"Created by": "Created by",
"Current Model": "Current Model",
"Current Password": "Current Password",
"Custom": "Custom",
"Customize Ollama models for a specific purpose": "Customize Ollama models for a specific purpose",
"Dark": "Dark",
"Database": "Database",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Default",
"Default (Automatic1111)": "",
"Default (Web API)": "",
"Default model updated": "",
"Default Prompt Suggestions": "",
"Default User Role": "",
"delete": "",
"Delete a model": "",
"Delete chat": "",
"Delete Chats": "",
"Deleted {{deleteModelTag}}": "",
"Deleted {tagName}": "",
"Description": "",
"Desktop Notifications": "",
"Disabled": "",
"Discover a modelfile": "",
"Discover a prompt": "",
"Discover, download, and explore custom prompts": "",
"Discover, download, and explore model presets": "",
"Display the username instead of You in the Chat": "",
"Document": "",
"Document Settings": "",
"Documents": "",
"does not make any external connections, and your data stays securely on your locally hosted server.": "",
"Don't Allow": "",
"Don't have an account?": "",
"Download as a File": "",
"Download Database": "",
"Drop any files here to add to the conversation": "",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
"Edit Doc": "",
"Edit User": "",
"Email": "",
"Enable Chat History": "",
"Enable New Sign Ups": "",
"Enabled": "",
"Default (Web API)": "Default (Web API)",
"Default model updated": "Default model updated",
"Default Prompt Suggestions": "Default Prompt Suggestions",
"Default User Role": "Default User Role",
"delete": "delete",
"Delete a model": "Delete a model",
"Delete chat": "Delete chat",
"Delete Chats": "Delete Chats",
"Deleted {{deleteModelTag}}": "Deleted {{deleteModelTag}}",
"Deleted {tagName}": "Deleted {tagName}",
"Description": "Description",
"Desktop Notifications": "Notification",
"Disabled": "Disabled",
"Discover a modelfile": "Discover a modelfile",
"Discover a prompt": "Discover a prompt",
"Discover, download, and explore custom prompts": "Discover, download, and explore custom prompts",
"Discover, download, and explore model presets": "Discover, download, and explore model presets",
"Display the username instead of You in the Chat": "Display the username instead of 'You' in the Chat",
"Document": "Document",
"Document Settings": "Document Settings",
"Documents": "Documents",
"does not make any external connections, and your data stays securely on your locally hosted server.": "does not make any external connections, and your data stays securely on your locally hosted server.",
"Don't Allow": "Don't Allow",
"Don't have an account?": "Don't have an account?",
"Download as a File": "Download as a File",
"Download Database": "Download Database",
"Drop any files here to add to the conversation": "Drop any files here to add to the conversation",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.",
"Edit Doc": "Edit Doc",
"Edit User": "Edit User",
"Email": "Email",
"Enable Chat History": "Enable Chat History",
"Enable New Sign Ups": "Enable New Sign Ups",
"Enabled": "Enabled",
"Enter {{role}} message here": "",
"Enter API Key": "",
"Enter Chunk Overlap": "",
@ -135,229 +135,229 @@
"Enter Max Tokens (litellm_params.max_tokens)": "",
"Enter model tag (e.g. {{modelTag}})": "",
"Enter Number of Steps (e.g. 50)": "",
"Enter stop sequence": "",
"Enter stop sequence": "Enter stop sequence",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter Your Email": "",
"Enter Your Full Name": "",
"Enter Your Password": "",
"Experimental": "",
"Export All Chats (All Users)": "",
"Export Chats": "",
"Export Documents Mapping": "",
"Export Modelfiles": "",
"Export Prompts": "",
"Failed to read clipboard contents": "",
"File Mode": "",
"File not found.": "",
"Focus chat input": "",
"Format your variables using square brackets like this:": "",
"From (Base Model)": "",
"Full Screen Mode": "",
"General": "",
"General Settings": "",
"Hello, {{name}}": "",
"Hide": "",
"Hide Additional Params": "",
"How can I help you today?": "",
"Image Generation (Experimental)": "",
"Enter Your Email": "Enter Your Email",
"Enter Your Full Name": "Enter Your Full Name",
"Enter Your Password": "Enter Your Password",
"Experimental": "Experimental",
"Export All Chats (All Users)": "Export All Chats (All Users)",
"Export Chats": "Export Chats",
"Export Documents Mapping": "Export Documents Mapping",
"Export Modelfiles": "Export Modelfiles",
"Export Prompts": "Export Prompts",
"Failed to read clipboard contents": "Failed to read clipboard contents",
"File Mode": "File Mode",
"File not found.": "File not found.",
"Focus chat input": "Focus chat input",
"Format your variables using square brackets like this:": "Format your variables using square brackets like this:",
"From (Base Model)": "From (Base Model)",
"Full Screen Mode": "Full Screen Mode",
"General": "General",
"General Settings": "General Settings",
"Hello, {{name}}": "Hello, {{name}}",
"Hide": "Hide",
"Hide Additional Params": "Hide Additional Params",
"How can I help you today?": "How can I help you today?",
"Image Generation (Experimental)": "Image Generation (Experimental)",
"Image Generation Engine": "",
"Image Settings": "",
"Images": "",
"Import Chats": "",
"Import Documents Mapping": "",
"Import Modelfiles": "",
"Import Prompts": "",
"Include `--api` flag when running stable-diffusion-webui": "",
"Interface": "",
"join our Discord for help.": "",
"JSON": "",
"JWT Expiration": "",
"JWT Token": "",
"Keep Alive": "",
"Keyboard shortcuts": "",
"Language": "",
"Light": "",
"Listening...": "",
"LLMs can make mistakes. Verify important information.": "",
"Made by OpenWebUI Community": "",
"Make sure to enclose them with": "",
"Manage LiteLLM Models": "",
"Manage Models": "",
"Manage Ollama Models": "",
"Max Tokens": "",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "",
"Mirostat": "",
"Mirostat Eta": "",
"Mirostat Tau": "",
"MMMM DD, YYYY": "",
"Model '{{modelName}}' has been successfully downloaded.": "",
"Model '{{modelTag}}' is already in queue for downloading.": "",
"Model {{modelId}} not found": "",
"Model {{modelName}} already exists.": "",
"Model Name": "",
"Model not selected": "",
"Model Tag Name": "",
"Model Whitelisting": "",
"Model(s) Whitelisted": "",
"Modelfile": "",
"Modelfile Advanced Settings": "",
"Modelfile Content": "",
"Modelfiles": "",
"Models": "",
"My Documents": "",
"My Modelfiles": "",
"My Prompts": "",
"Name": "",
"Name Tag": "",
"Name your modelfile": "",
"New Chat": "",
"New Password": "",
"Not sure what to add?": "",
"Not sure what to write? Switch to": "",
"Off": "",
"Okay, Let's Go!": "",
"Image Settings": "Image Settings",
"Images": "Images",
"Import Chats": "Import Chats",
"Import Documents Mapping": "Import Documents Mapping",
"Import Modelfiles": "Import Modelfiles",
"Import Prompts": "Import Prompts",
"Include `--api` flag when running stable-diffusion-webui": "Include `--api` flag when running stable-diffusion-webui",
"Interface": "Interface",
"join our Discord for help.": "join our Discord for help.",
"JSON": "JSON",
"JWT Expiration": "JWT Expiration",
"JWT Token": "JWT Token",
"Keep Alive": "Keep Alive",
"Keyboard shortcuts": "Keyboard shortcuts",
"Language": "Language",
"Light": "Light",
"Listening...": "Listening...",
"LLMs can make mistakes. Verify important information.": "LLMs can make mistakes. Verify important information.",
"Made by OpenWebUI Community": "Made by OpenWebUI Community",
"Make sure to enclose them with": "Make sure to enclose them with",
"Manage LiteLLM Models": "Manage LiteLLM Models",
"Manage Models": "Manage Models",
"Manage Ollama Models": "Manage Ollama Models",
"Max Tokens": "Max Tokens",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maximum of 3 models can be downloaded simultaneously. Please try again later.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.",
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
"Model {{modelId}} not found": "Model {{modelId}} not found",
"Model {{modelName}} already exists.": "Model {{modelName}} already exists.",
"Model Name": "Model Name",
"Model not selected": "Model not selected",
"Model Tag Name": "Model Tag Name",
"Model Whitelisting": "Model Whitelisting",
"Model(s) Whitelisted": "Model(s) Whitelisted",
"Modelfile": "Modelfile",
"Modelfile Advanced Settings": "Modelfile Advanced Settings",
"Modelfile Content": "Modelfile Content",
"Modelfiles": "Modelfiles",
"Models": "Models",
"My Documents": "My Documents",
"My Modelfiles": "My Modelfiles",
"My Prompts": "My Prompts",
"Name": "Name",
"Name Tag": "Name Tag",
"Name your modelfile": "Name your modelfile",
"New Chat": "New Chat",
"New Password": "New Password",
"Not sure what to add?": "Not sure what to add?",
"Not sure what to write? Switch to": "Not sure what to write? Switch to",
"Off": "Off",
"Okay, Let's Go!": "Okay, Let's Go!",
"Ollama Base URL": "",
"Ollama Version": "",
"On": "",
"Only": "",
"Only alphanumeric characters and hyphens are allowed in the command string.": "",
"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.": "",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
"Open": "",
"Open AI": "",
"Ollama Version": "Ollama Version",
"On": "On",
"Only": "Only",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Only alphanumeric characters and hyphens are allowed in the command string.",
"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.": "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.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Looks like the URL is invalid. Please double-check and try again.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.",
"Open": "Open",
"Open AI": "Open AI",
"Open AI (Dall-E)": "",
"Open new chat": "",
"OpenAI API": "",
"Open new chat": "Open new chat",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "",
"OpenAI API Key is required.": "",
"or": "",
"Parameters": "",
"Password": "",
"PDF Extract Images (OCR)": "",
"pending": "",
"Permission denied when accessing microphone: {{error}}": "",
"Playground": "",
"Profile": "",
"Prompt Content": "",
"Prompt suggestions": "",
"Prompts": "",
"Pull a model from Ollama.com": "",
"Pull Progress": "",
"Query Params": "",
"RAG Template": "",
"Raw Format": "",
"Record voice": "",
"Redirecting you to OpenWebUI Community": "",
"Release Notes": "",
"Repeat Last N": "",
"Repeat Penalty": "",
"Request Mode": "",
"Reset Vector Storage": "",
"Response AutoCopy to Clipboard": "",
"Role": "",
"Rosé Pine": "",
"Rosé Pine Dawn": "",
"Save": "",
"Save & Create": "",
"Save & Submit": "",
"Save & Update": "",
"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": "",
"Scan": "",
"Scan complete!": "",
"Scan for documents from {{path}}": "",
"Search": "",
"Search Documents": "",
"Search Prompts": "",
"See readme.md for instructions": "",
"See what's new": "",
"Seed": "",
"or": "or",
"Parameters": "Parameters",
"Password": "Password",
"PDF Extract Images (OCR)": "PDF Extract Images (OCR)",
"pending": "pending",
"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
"Playground": "Playground",
"Profile": "Profile",
"Prompt Content": "Prompt Content",
"Prompt suggestions": "Prompt suggestions",
"Prompts": "Prompts",
"Pull a model from Ollama.com": "Pull a model from Ollama.com",
"Pull Progress": "Pull Progress",
"Query Params": "Query Params",
"RAG Template": "RAG Template",
"Raw Format": "Raw Format",
"Record voice": "Record voice",
"Redirecting you to OpenWebUI Community": "Redirecting you to OpenWebUI Community",
"Release Notes": "Release Notes",
"Repeat Last N": "Repeat Last N",
"Repeat Penalty": "Repeat Penalty",
"Request Mode": "Request Mode",
"Reset Vector Storage": "Reset Vector Storage",
"Response AutoCopy to Clipboard": "Response AutoCopy to Clipboard",
"Role": "Role",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"Save": "Save",
"Save & Create": "Save & Create",
"Save & Submit": "Save & Submit",
"Save & Update": "Save & Update",
"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": "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",
"Scan": "Scan",
"Scan complete!": "Scan complete!",
"Scan for documents from {{path}}": "Scan for documents from {{path}}",
"Search": "Search",
"Search Documents": "Search Documents",
"Search Prompts": "Search Prompts",
"See readme.md for instructions": "See readme.md for instructions",
"See what's new": "See what's new",
"Seed": "Seed",
"Select a mode": "",
"Select a model": "",
"Select a model": "Select a model",
"Select an Ollama instance": "",
"Send a Message": "",
"Send message": "",
"Server connection verified": "",
"Set as default": "",
"Set Default Model": "",
"Set Image Size": "",
"Set Steps": "",
"Set Title Auto-Generation Model": "",
"Set Voice": "",
"Settings": "",
"Settings saved successfully!": "",
"Share to OpenWebUI Community": "",
"short-summary": "",
"Show": "",
"Show Additional Params": "",
"Show shortcuts": "",
"sidebar": "",
"Sign in": "",
"Sign Out": "",
"Sign up": "",
"Speech recognition error: {{error}}": "",
"Speech-to-Text Engine": "",
"SpeechRecognition API is not supported in this browser.": "",
"Stop Sequence": "",
"STT Settings": "",
"Submit": "",
"Success": "",
"Successfully updated.": "",
"Sync All": "",
"System": "",
"System Prompt": "",
"Tags": "",
"Temperature": "",
"Template": "",
"Text Completion": "",
"Text-to-Speech Engine": "",
"Tfs Z": "",
"Theme": "",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
"This setting does not sync across browsers or devices.": "",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
"Title": "",
"Title Auto-Generation": "",
"Title Generation Prompt": "",
"to": "",
"To access the available model names for downloading,": "",
"To access the GGUF models available for downloading,": "",
"to chat input.": "",
"Toggle settings": "",
"Toggle sidebar": "",
"Top K": "",
"Top P": "",
"Trouble accessing Ollama?": "",
"TTS Settings": "",
"Send a Message": "Send a Message",
"Send message": "Send message",
"Server connection verified": "Server connection verified",
"Set as default": "Set as default",
"Set Default Model": "Set Default Model",
"Set Image Size": "Set Image Size",
"Set Steps": "Set Steps",
"Set Title Auto-Generation Model": "Set Title Auto-Generation Model",
"Set Voice": "Set Voice",
"Settings": "Settings",
"Settings saved successfully!": "Settings saved successfully!",
"Share to OpenWebUI Community": "Share to OpenWebUI Community",
"short-summary": "short-summary",
"Show": "Show",
"Show Additional Params": "Show Additional Params",
"Show shortcuts": "Show shortcuts",
"sidebar": "sidebar",
"Sign in": "Sign in",
"Sign Out": "Sign Out",
"Sign up": "Sign up",
"Speech recognition error: {{error}}": "Speech recognition error: {{error}}",
"Speech-to-Text Engine": "Speech-to-Text Engine",
"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API is not supported in this browser.",
"Stop Sequence": "Stop Sequence",
"STT Settings": "STT Settings",
"Submit": "Submit",
"Success": "Success",
"Successfully updated.": "Successfully updated.",
"Sync All": "Sync All",
"System": "System",
"System Prompt": "System Prompt",
"Tags": "Tags",
"Temperature": "Temperature",
"Template": "Template",
"Text Completion": "Text Completion",
"Text-to-Speech Engine": "Text-to-Speech Engine",
"Tfs Z": "Tfs Z",
"Theme": "Theme",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "This ensures that your valuable conversations are securely saved to your backend database. Thank you!",
"This setting does not sync across browsers or devices.": "This setting does not sync across browsers or devices.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.",
"Title": "Title",
"Title Auto-Generation": "Title Auto-Generation",
"Title Generation Prompt": "Title Generation Prompt",
"to": "to",
"To access the available model names for downloading,": "To access the available model names for downloading,",
"To access the GGUF models available for downloading,": "To access the GGUF models available for downloading,",
"to chat input.": "to chat input.",
"Toggle settings": "Toggle settings",
"Toggle sidebar": "Toggle sidebar",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Trouble accessing Ollama?",
"TTS Settings": "TTS Settings",
"Type Hugging Face Resolve (Download) URL": "",
"Uh-oh! There was an issue connecting to {{provider}}.": "",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
"Update password": "",
"Upload a GGUF model": "",
"Upload files": "",
"Upload Progress": "",
"URL Mode": "",
"Use '#' in the prompt input to load and select your documents.": "",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! There was an issue connecting to {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Unknown File Type '{{file_type}}', but accepting and treating as plain text",
"Update password": "Update password",
"Upload a GGUF model": "Upload a GGUF model",
"Upload files": "Upload files",
"Upload Progress": "Upload Progress",
"URL Mode": "URL Mode",
"Use '#' in the prompt input to load and select your documents.": "Use '#' in the prompt input to load and select your documents.",
"Use Gravatar": "",
"user": "",
"User Permissions": "",
"Users": "",
"Utilize": "",
"Valid time units:": "",
"variable": "",
"variable to have them replaced with clipboard content.": "",
"Version": "",
"Web": "",
"WebUI Add-ons": "",
"WebUI Settings": "",
"WebUI will make requests to": "",
"Whats New in": "",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "",
"Whisper (Local)": "",
"Write a prompt suggestion (e.g. Who are you?)": "",
"Write a summary in 50 words that summarizes [topic or keyword].": "",
"You": "",
"You're a helpful assistant.": "",
"You're now logged in.": ""
"user": "user",
"User Permissions": "User Permissions",
"Users": "Users",
"Utilize": "Utilize",
"Valid time units:": "Valid time units:",
"variable": "variable",
"variable to have them replaced with clipboard content.": "variable to have them replaced with clipboard content.",
"Version": "Version",
"Web": "Web",
"WebUI Add-ons": "WebUI Add-ons",
"WebUI Settings": "WebUI Settings",
"WebUI will make requests to": "WebUI will make requests to",
"Whats New in": "Whats New in",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "When history is turned off, new chats on this browser won't appear in your history on any of your devices.",
"Whisper (Local)": "Whisper (Local)",
"Write a prompt suggestion (e.g. Who are you?)": "Write a prompt suggestion (e.g. Who are you?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Write a summary in 50 words that summarizes [topic or keyword].",
"You": "You",
"You're a helpful assistant.": "You're a helpful assistant.",
"You're now logged in.": "You're now logged in."
}

View file

@ -1,362 +0,0 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'초', '분', '시간', '일', '주' 또는 만료 없음 '-1'",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(예: `sh webui.sh --api`)",
"(latest)": "(latest)",
"{{modelName}} is thinking...": "{{modelName}} 이(가) 생각중입니다....",
"{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.",
"a user": "사용자",
"About": "소개",
"Account": "계정",
"Action": "액션",
"Add a model": "모델 추가",
"Add a model tag name": "모델 태그명 추가",
"Add a short description about what this modelfile does": "이 모델파일이 하는 일에 대한 간단한 설명 추가",
"Add a short title for this prompt": "이 프롬프트에 대한 간단한 제목 추가",
"Add a tag": "태그 추가",
"Add Docs": "문서 추가",
"Add Files": "파일 추가",
"Add message": "메시지 추가",
"add tags": "태그들 추가",
"Adjusting these settings will apply changes universally to all users.": "이 설정을 조정하면 모든 사용자에게 적용됩니다.",
"admin": "관리자",
"Admin Panel": "관리자 패널",
"Admin Settings": "관리자 설정",
"Advanced Parameters": "고급 매개변수",
"all": "모두",
"All Users": "모든 사용자",
"Allow": "허용",
"Allow Chat Deletion": "채팅 삭제 허용",
"alphanumeric characters and hyphens": "영문자,숫자 및 하이픈",
"Already have an account?": "이미 계정이 있으신가요?",
"an assistant": "어시스턴트",
"and": "그리고",
"API Base URL": "API 기본 URL",
"API Key": "API키",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "허용됩니다 - 이 명령을 활성화하려면 입력하세요.",
"Are you sure?": "확실합니까?",
"Audio": "오디오",
"Auto-playback response": "응답 자동 재생",
"Auto-send input after 3 sec.": "3초 후 입력 자동 전송",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL이 필요합니다.",
"available!": "사용 가능!",
"Back": "뒤로가기",
"Builder Mode": "빌더 모드",
"Cancel": "취소",
"Categories": "분류",
"Change Password": "비밀번호 변경",
"Chat": "채팅",
"Chat History": "채팅 기록",
"Chat History is off for this browser.": "이 브라우저에서 채팅 기록이 꺼져 있습니다.",
"Chats": "채팅",
"Check Again": "다시 확인",
"Check for updates": "업데이트 확인",
"Checking for updates...": "업데이트 확인중...",
"Choose a model before saving...": "저장하기 전에 모델을 선택하세요...",
"Chunk Overlap": "Chunk Overlap",
"Chunk Params": "Chunk Params",
"Chunk Size": "Chunk Size",
"Click here for help.": "도움말을 보려면 여기를 클릭하세요.",
"Click here to check other modelfiles.": "다른 모델파일을 확인하려면 여기를 클릭하세요.",
"Click here to select": "선택하려면 여기를 클릭하세요.",
"Click here to select documents.": "문서를 선택하려면 여기를 클릭하세요.",
"click here.": "여기를 클릭하세요.",
"Click on the user role button to change a user's role.": "사용자 역할 버튼을 클릭하여 사용자의 역할을 변경하세요.",
"Close": "닫기",
"Collection": "컬렉션",
"Command": "명령",
"Confirm Password": "비밀번호 확인",
"Connections": "연결",
"Content": "내용",
"Context Length": "내용 길이",
"Conversation Mode": "대화 모드",
"Copy last code block": "마지막 코드 블록 복사",
"Copy last response": "마지막 응답 복사",
"Copying to clipboard was successful!": "클립보드에 복사되었습니다!",
"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':": "다음 질문에 대한 제목으로 간결한 3-5 단어 문구를 만드되 3-5 단어 제한을 엄격히 준수하고 'title' 단어 사용을 피하세요:",
"Create a modelfile": "모델파일 만들기",
"Create Account": "계정 만들기",
"Created at": "생성일",
"Created by": "생성자",
"Current Model": "현재 모델",
"Current Password": "현재 비밀번호",
"Custom": "사용자 정의",
"Customize Ollama models for a specific purpose": "특정 목적으로 Ollama 모델 사용자 정의",
"Dark": "어두운",
"Database": "데이터베이스",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "기본값",
"Default (Automatic1111)": "기본값 (Automatic1111)",
"Default (Web API)": "기본값 (Web API)",
"Default model updated": "기본 모델이 업데이트되었습니다.",
"Default Prompt Suggestions": "기본 프롬프트 제안",
"Default User Role": "기본 사용자 역할",
"delete": "삭제",
"Delete a model": "모델 삭제",
"Delete chat": "채팅 삭제",
"Delete Chats": "채팅들 삭제",
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} 삭제됨",
"Deleted {tagName}": "{tagName} 삭제됨",
"Description": "설명",
"Desktop Notifications": "알림",
"Disabled": "비활성화",
"Discover a modelfile": "모델파일 검색",
"Discover a prompt": "프롬프트 검색",
"Discover, download, and explore custom prompts": "사용자 정의 프롬프트 검색, 다운로드 및 탐색",
"Discover, download, and explore model presets": "모델 사전 설정 검색, 다운로드 및 탐색",
"Display the username instead of You in the Chat": "채팅에서 'You' 대신 사용자 이름 표시",
"Document": "문서",
"Document Settings": "문서 설정",
"Documents": "문서들",
"does not make any external connections, and your data stays securely on your locally hosted server.": "어떠한 외부 연결도 하지 않으며, 데이터는 로컬에서 호스팅되는 서버에 안전하게 유지됩니다.",
"Don't Allow": "허용 안 함",
"Don't have an account?": "계정이 없으신가요?",
"Download as a File": "파일로 다운로드",
"Download Database": "데이터베이스 다운로드",
"Drop any files here to add to the conversation": "대화에 추가할 파일을 여기에 드롭하세요.",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "예: '30s','10m'. 유효한 시간 단위는 's', 'm', 'h'입니다.",
"Edit Doc": "문서 편집",
"Edit User": "사용자 편집",
"Email": "이메일",
"Enable Chat History": "채팅 기록 활성화",
"Enable New Sign Ups": "새 회원가입 활성화",
"Enabled": "활성화",
"Enter {{role}} message here": "여기에 {{role}} 메시지 입력",
"Enter API Key": "API 키 입력",
"Enter Chunk Overlap": "청크 오버랩 입력",
"Enter Chunk Size": "청크 크기 입력",
"Enter Image Size (e.g. 512x512)": "이미지 크기 입력(예: 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "LiteLLM API 기본 URL 입력(litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "LiteLLM API 키 입력(litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "LiteLLM API RPM 입력(litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "LiteLLM 모델 입력(litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "최대 토큰 수 입력(litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "모델 태그 입력(예: {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "단계 수 입력(예: 50)",
"Enter stop sequence": "중지 시퀀스 입력",
"Enter Top K": "Top K 입력",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL 입력(예: http://127.0.0.1:7860/)",
"Enter Your Email": "이메일 입력",
"Enter Your Full Name": "전체 이름 입력",
"Enter Your Password": "비밀번호 입력",
"Experimental": "실험적",
"Export All Chats (All Users)": "모든 채팅 내보내기 (모든 사용자)",
"Export Chats": "채팅 내보내기",
"Export Documents Mapping": "문서 매핑 내보내기",
"Export Modelfiles": "모델파일 내보내기",
"Export Prompts": "프롬프트 내보내기",
"Failed to read clipboard contents": "클립보드 내용을 읽는 데 실패했습니다.",
"File Mode": "파일 모드",
"File not found.": "파일을 찾을 수 없습니다.",
"Focus chat input": "채팅 입력 포커스",
"Format your variables using square brackets like this:": "이렇게 대괄호를 사용하여 변수를 형식화하세요:",
"From (Base Model)": "출처(기본 모델)",
"Full Screen Mode": "전체 화면 모드",
"General": "일반",
"General Settings": "일반 설정",
"Hello, {{name}}": "안녕하세요, {{name}}",
"Hide": "숨기기",
"Hide Additional Params": "추가 매개변수 숨기기",
"How can I help you today?": "오늘 어떻게 도와드릴까요?",
"Image Generation (Experimental)": "이미지 생성(실험적)",
"Image Generation Engine": "이미지 생성 엔진",
"Image Settings": "이미지 설정",
"Images": "이미지",
"Import Chats": "채팅 가져오기",
"Import Documents Mapping": "문서 매핑 가져오기",
"Import Modelfiles": "모델파일 가져오기",
"Import Prompts": "프롬프트 가져오기",
"Include --api flag when running stable-diffusion-webui": "stable-diffusion-webui를 실행할 때 --api 플래그 포함",
"Interface": "인터페이스",
"join our Discord for help.": "도움말을 보려면 Discord에 가입하세요.",
"JSON": "JSON",
"JWT Expiration": "JWT 만료",
"JWT Token": "JWT 토큰",
"Keep Alive": "계속 유지하기",
"Keyboard shortcuts": "키보드 단축키",
"Language": "언어",
"Light": "밝음",
"Listening...": "청취 중...",
"LLMs can make mistakes. Verify important information.": "LLM은 실수를 할 수 있습니다. 중요한 정보를 확인하세요.",
"Made by OpenWebUI Community": "OpenWebUI 커뮤니티에서 제작",
"Make sure to enclose them with": "다음으로 묶는 것을 잊지 마세요:",
"Manage LiteLLM Models": "LiteLLM 모델 관리",
"Manage Models": "모델 관리",
"Manage Ollama Models": "Ollama 모델 관리",
"Max Tokens": "최대 토큰 수",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "최대 3개의 모델을 동시에 다운로드할 수 있습니다. 나중에 다시 시도하세요.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"Model '{{modelName}}' has been successfully downloaded.": "모델 '{{modelName}}'이(가) 성공적으로 다운로드되었습니다.",
"Model '{{modelTag}}' is already in queue for downloading.": "모델 '{{modelTag}}'이(가) 이미 다운로드 대기열에 있습니다.",
"Model {{modelId}} not found": "모델 {{modelId}}를 찾을 수 없습니다.",
"Model {{modelName}} already exists.": "모델 {{modelName}}이(가) 이미 존재합니다.",
"Model Name": "모델 이름",
"Model not selected": "모델이 선택되지 않았습니다.",
"Model Tag Name": "모델 태그 이름",
"Model Whitelisting": "모델 허용 목록",
"Model(s) Whitelisted": "허용된 모델",
"Modelfile": "모델파일",
"Modelfile Advanced Settings": "모델파일 고급 설정",
"Modelfile Content": "모델파일 내용",
"Modelfiles": "모델파일",
"Models": "모델",
"My Documents": "내 문서",
"My Modelfiles": "내 모델파일",
"My Prompts": "내 프롬프트",
"Name": "이름",
"Name Tag": "이름 태그",
"Name your modelfile": "모델파일 이름 지정",
"New Chat": "새 채팅",
"New Password": "새 비밀번호",
"Not sure what to add?": "추가할 것이 궁금하세요?",
"Not sure what to write? Switch to": "무엇을 쓸지 모르겠나요? 전환하세요.",
"Off": "끄기",
"Okay, Let's Go!": "그렇습니다, 시작합시다!",
"Ollama Base URL": "Ollama 기본 URL",
"Ollama Version": "Ollama 버전",
"On": "켜기",
"Only": "오직",
"Only alphanumeric characters and hyphens are allowed in the command string.": "명령어 문자열에는 영문자, 숫자 및 하이픈만 허용됩니다.",
"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.": "이런! 잠시만 기다려 주세요! 파일이 아직 처리 중입니다. 완벽하게 준비하고 있습니다. 잠시만 기다려주시면 준비가 되면 알려드리겠습니다.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "이런! URL이 잘못된 것 같습니다. 다시 한번 확인하고 다시 시도해주세요.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "이런! 지원되지 않는 방식(프론트엔드 전용)을 사용하고 계십니다. 백엔드에서 WebUI를 제공해주세요.",
"Open": "열기",
"Open AI": "Open AI",
"Open AI (Dall-E)": "OpenAI (Dall-E)",
"Open new chat": "새 채팅 열기",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "OpenAI API 키",
"OpenAI API Key is required.": "OpenAI API 키가 필요합니다.",
"or": "또는",
"Parameters": "매개변수",
"Password": "비밀번호",
"PDF Extract Images (OCR)": "PDF에서 이미지 추출 (OCR)",
"pending": "보류 중",
"Permission denied when accessing microphone: {{error}}": "마이크 액세스가 거부되었습니다: {{error}}",
"Playground": "놀이터",
"Profile": "프로필",
"Prompt Content": "프롬프트 내용",
"Prompt suggestions": "프롬프트 제안",
"Prompts": "프롬프트",
"Pull a model from Ollama.com": "Ollama.com에서 모델 가져오기",
"Pull Progress": "가져오기 진행 상황",
"Query Params": "쿼리 매개변수",
"RAG Template": "RAG 템플릿",
"Raw Format": "Raw 형식",
"Record voice": "음성 녹음",
"Redirecting you to OpenWebUI Community": "OpenWebUI 커뮤니티로 리디렉션하는 중",
"Release Notes": "릴리스 노트",
"Repeat Last N": "마지막 N 반복",
"Repeat Penalty": "반복 패널티",
"Request Mode": "요청 모드",
"Reset Vector Storage": "벡터 스토리지 초기화",
"Response AutoCopy to Clipboard": "응답 자동 클립보드 복사",
"Role": "역할",
"Rosé Pine": "로제 파인",
"Rosé Pine Dawn": "로제 파인 던",
"Save": "저장",
"Save & Create": "저장 및 생성",
"Save & Submit": "저장 및 제출",
"Save & Update": "저장 및 업데이트",
"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": "브라우저의 저장소에 채팅 로그를 직접 저장하는 것은 더 이상 지원되지 않습니다. 아래 버튼을 클릭하여 채팅 로그를 다운로드하고 삭제하세요. 걱정 마세요. 백엔드를 통해 채팅 로그를 쉽게 다시 가져올 수 있습니다.",
"Scan": "스캔",
"Scan complete!": "스캔 완료!",
"Scan for documents from {{path}}": "{{path}}에서 문서 스캔",
"Search": "검색",
"Search Documents": "문서 검색",
"Search Prompts": "프롬프트 검색",
"See readme.md for instructions": "설명은 readme.md를 참조하세요.",
"See what's new": "새로운 기능 보기",
"Seed": "시드",
"Select a mode": "모드 선택",
"Select a model": "모델 선택",
"Select an Ollama instance": "Ollama 인스턴스 선택",
"Send a Message": "메시지 보내기",
"Send message": "메시지 보내기",
"Server connection verified": "서버 연결 확인됨",
"Set as default": "기본값으로 설정",
"Set Default Model": "기본 모델 설정",
"Set Image Size": "이미지 크기 설정",
"Set Steps": "단계 설정",
"Set Title Auto-Generation Model": "제목 자동 생성 모델 설정",
"Set Voice": "음성 설정",
"Settings": "설정",
"Settings saved successfully!": "설정이 성공적으로 저장되었습니다!",
"Share to OpenWebUI Community": "OpenWebUI 커뮤니티에 공유",
"short-summary": "간단한 요약",
"Show": "보이기",
"Show Additional Params": "추가 매개변수 보기",
"Show shortcuts": "단축키 보기",
"sidebar": "사이드바",
"Sign in": "로그인",
"Sign Out": "로그아웃",
"Sign up": "가입",
"Speech recognition error: {{error}}": "음성 인식 오류: {{error}}",
"Speech-to-Text Engine": "음성-텍스트 엔진",
"SpeechRecognition API is not supported in this browser.": "이 브라우저에서는 SpeechRecognition API를 지원하지 않습니다.",
"Stop Sequence": "중지 시퀀스",
"STT Settings": "STT 설정",
"Submit": "제출",
"Success": "성공",
"Successfully updated.": "성공적으로 업데이트되었습니다.",
"Sync All": "모두 동기화",
"System": "시스템",
"System Prompt": "시스템 프롬프트",
"Tags": "Tags",
"Temperature": "Temperature",
"Template": "Template",
"Text Completion": "텍스트 완성",
"Text-to-Speech Engine": "텍스트-음성 엔진",
"Tfs Z": "Tfs Z",
"Theme": "테마",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "이렇게 하면 소중한 대화 내용이 백엔드 데이터베이스에 안전하게 저장됩니다. 감사합니다!",
"This setting does not sync across browsers or devices.": "이 설정은 브라우저 또는 장치 간에 동기화되지 않습니다.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "팁: 각 대체 후 채팅 입력에서 탭 키를 눌러 여러 개의 변수 슬롯을 연속적으로 업데이트하세요.",
"Title": "제목",
"Title Auto-Generation": "제목 자동 생성",
"Title Generation Prompt": "제목 생성 프롬프트",
"to": "~까지",
"To access the available model names for downloading,": "다운로드 가능한 모델명을 확인하려면,",
"To access the GGUF models available for downloading,": "다운로드 가능한 GGUF 모델을 확인하려면,",
"to chat input.": "채팅 입력으로.",
"Toggle settings": "설정 전환",
"Toggle sidebar": "사이드바 전환",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Ollama에 접근하는 데 문제가 있나요?",
"TTS Settings": "TTS 설정",
"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (다운로드) URL 입력",
"Uh-oh! There was an issue connecting to {{provider}}.": "앗! {{provider}}에 연결하는 데 문제가 있었습니다.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "알 수 없는 파일 유형 '{{file_type}}', 하지만 일반 텍스트로 허용하고 처리합니다.",
"Update password": "비밀번호 업데이트",
"Upload a GGUF model": "GGUF 모델 업로드",
"Upload files": "파일 업로드",
"Upload Progress": "업로드 진행 상황",
"URL Mode": "URL 모드",
"Use '#' in the prompt input to load and select your documents.": "프롬프트 입력에서 '#'를 사용하여 문서를 로드하고 선택하세요.",
"Use Gravatar": "Gravatar 사용",
"user": "사용자",
"User Permissions": "사용자 권한",
"Users": "사용자",
"Utilize": "활용",
"Valid time units:": "유효한 시간 단위:",
"variable": "변수",
"variable to have them replaced with clipboard content.": "변수를 사용하여 클립보드 내용으로 바꾸세요.",
"Version": "버전",
"Web": "웹",
"WebUI Add-ons": "WebUI 애드온",
"WebUI Settings": "WebUI 설정",
"WebUI will make requests to": "WebUI가 요청할 대상:",
"What's New in": "새로운 기능",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "기록 기능이 꺼져 있으면 이 브라우저의 새 채팅이 다른 장치의 채팅 기록에 나타나지 않습니다.",
"Whisper (Local)": "위스퍼 (Local)",
"Write a prompt suggestion (e.g. Who are you?)": "프롬프트 제안 작성 (예: 당신은 누구인가요?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "[주제 또는 키워드]에 대한 50단어 요약문 작성.",
"You": "당신",
"You're a helpful assistant.": "당신은 유용한 어시스턴트입니다.",
"You're now logged in.": "로그인되었습니다."
}

View file

@ -39,10 +39,6 @@
"code": "ja-JP",
"title": "Japanese"
},
{
"code": "ko-KR",
"title": "Korean"
},
{
"code": "nl-NL",
"title": "Dutch (Netherlands)"
@ -51,18 +47,10 @@
"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"

View file

@ -5,7 +5,7 @@
"(latest)": "(nieuwste)",
"{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
"a user": "een gebruiker",
"a user": "",
"About": "Over",
"Account": "Account",
"Action": "Actie",
@ -35,12 +35,12 @@
"API Key": "API Key",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "zijn toegestaan - Activeer deze commando door te typen",
"Are you sure?": "Zeker weten?",
"Are you sure?": "",
"Audio": "Audio",
"Auto-playback response": "Automatisch afspelen van antwoord",
"Auto-send input after 3 sec.": "Automatisch verzenden van input na 3 sec.",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Basis URL is verplicht",
"AUTOMATIC1111 Base URL is required.": "",
"available!": "beschikbaar!",
"Back": "Terug",
"Builder Mode": "Bouwer Modus",
@ -60,8 +60,8 @@
"Chunk Size": "Chunk Grootte",
"Click here for help.": "Klik hier voor help.",
"Click here to check other modelfiles.": "Klik hier om andere modelfiles te controleren.",
"Click here to select": "Klik hier om te selecteren",
"Click here to select documents.": "Klik hier om documenten te selecteren",
"Click here to select": "",
"Click here to select documents.": "",
"click here.": "click here.",
"Click on the user role button to change a user's role.": "Klik op de gebruikersrol knop om de rol van een gebruiker te wijzigen.",
"Close": "Sluiten",
@ -88,7 +88,7 @@
"Database": "Database",
"DD/MM/YYYY HH:mm": "YYYY/MM/DD HH:mm",
"Default": "Standaard",
"Default (Automatic1111)": "Standaard (Automatic1111)",
"Default (Automatic1111)": "",
"Default (Web API)": "Standaard (Web API)",
"Default model updated": "Standaard model bijgewerkt",
"Default Prompt Suggestions": "Standaard Prompt Suggesties",
@ -123,21 +123,21 @@
"Enable Chat History": "Schakel Chat Geschiedenis in",
"Enable New Sign Ups": "Schakel Nieuwe Registraties in",
"Enabled": "Ingeschakeld",
"Enter {{role}} message here": "Voeg {{role}} bericht hier toe",
"Enter API Key": "Voeg API Key toe",
"Enter Chunk Overlap": "Voeg Chunk Overlap toe",
"Enter Chunk Size": "Voeg Chunk Size toe",
"Enter Image Size (e.g. 512x512)": "Voeg afbeelding formaat toe (Bijv. 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Voeg LiteLLM API Base URL toe (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Voeg LiteLLM API Sleutel toe (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Voeg LiteLLM API RPM toe (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "Voeg LiteLLM Model toe (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Voeg maximum aantal tokens toe (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Voeg model tag toe (Bijv. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Voeg aantal stappen toe (Bijv. 50)",
"Enter {{role}} message here": "",
"Enter API Key": "",
"Enter Chunk Overlap": "",
"Enter Chunk Size": "",
"Enter Image Size (e.g. 512x512)": "",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "",
"Enter LiteLLM API Key (litellm_params.api_key)": "",
"Enter LiteLLM API RPM (litellm_params.rpm)": "",
"Enter LiteLLM Model (litellm_params.model)": "",
"Enter Max Tokens (litellm_params.max_tokens)": "",
"Enter model tag (e.g. {{modelTag}})": "",
"Enter Number of Steps (e.g. 50)": "",
"Enter stop sequence": "Zet stop sequentie",
"Enter Top K": "Voeg Top K toe",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Zet URL (Bijv. http://127.0.0.1:7860/)",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter Your Email": "Voer je Email in",
"Enter Your Full Name": "Voer je Volledige Naam in",
"Enter Your Password": "Voer je Wachtwoord in",
@ -161,7 +161,7 @@
"Hide Additional Params": "Verberg Extra Params",
"How can I help you today?": "Hoe kan ik je vandaag helpen?",
"Image Generation (Experimental)": "Afbeelding Generatie (Experimenteel)",
"Image Generation Engine": "Afbeelding Generatie Engine",
"Image Generation Engine": "",
"Image Settings": "Afbeelding Instellingen",
"Images": "Afbeeldingen",
"Import Chats": "Importeer Chats",
@ -217,7 +217,7 @@
"Not sure what to write? Switch to": "Niet zeker wat te schrijven? Schakel over naar",
"Off": "Uit",
"Okay, Let's Go!": "Okay, Laten we gaan!",
"Ollama Base URL": "Ollama Basis URL",
"Ollama Base URL": "",
"Ollama Version": "Ollama Versie",
"On": "Aan",
"Only": "Alleen",
@ -227,11 +227,11 @@
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Je gebruikt een niet-ondersteunde methode (alleen frontend). Serveer de WebUI vanuit de backend.",
"Open": "Open",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open AI (Dall-E)": "",
"Open new chat": "Open nieuwe chat",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "OpenAI API Sleutel",
"OpenAI API Key is required.": "OpenAI API Sleutel is verplicht",
"OpenAI API Key": "",
"OpenAI API Key is required.": "",
"or": "of",
"Parameters": "Parameters",
"Password": "Wachtwoord",
@ -273,9 +273,9 @@
"See readme.md for instructions": "Zie readme.md voor instructies",
"See what's new": "Zie wat er nieuw is",
"Seed": "Seed",
"Select a mode": "Selecteer een modus",
"Select a mode": "",
"Select a model": "Selecteer een model",
"Select an Ollama instance": "Selecteer een Ollama instantie",
"Select an Ollama instance": "",
"Send a Message": "Stuur een Bericht",
"Send message": "Stuur bericht",
"Server connection verified": "Server verbinding geverifieerd",
@ -330,7 +330,7 @@
"Top P": "Top P",
"Trouble accessing Ollama?": "Problemen met toegang tot Ollama?",
"TTS Settings": "TTS instellingen",
"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
"Type Hugging Face Resolve (Download) URL": "",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Er was een probleem met verbinden met {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Onbekend Bestandstype '{{file_type}}', maar accepteren en behandelen als platte tekst",
"Update password": "Wijzig wachtwoord",
@ -339,7 +339,7 @@
"Upload Progress": "Upload Voortgang",
"URL Mode": "URL Modus",
"Use '#' in the prompt input to load and select your documents.": "Gebruik '#' in de prompt input om je documenten te laden en te selecteren.",
"Use Gravatar": "Gebruik Gravatar",
"Use Gravatar": "",
"user": "user",
"User Permissions": "Gebruikers Rechten",
"Users": "Gebruikers",

View file

@ -1,363 +0,0 @@
{
"'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",
"Desktop 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",
"Whats 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."
}

View file

@ -1,363 +0,0 @@
{
"'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",
"Desktop 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:",
"Whats 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."
}

View file

@ -7,9 +7,8 @@ export const config = writable(undefined);
export const user = writable(undefined);
// Frontend
export const MODEL_DOWNLOAD_POOL = writable({});
export const theme = writable('system');
export const chatId = writable('');
export const chats = writable([]);

View file

@ -1,55 +1,10 @@
import { v4 as uuidv4 } from 'uuid';
import sha256 from 'js-sha256';
import { getOllamaModels } from '$lib/apis/ollama';
import { getOpenAIModels } from '$lib/apis/openai';
import { getLiteLLMModels } from '$lib/apis/litellm';
export const getModels = async (token: string) => {
let models = await Promise.all([
await getOllamaModels(token).catch((error) => {
console.log(error);
return null;
}),
await getOpenAIModels(token).catch((error) => {
console.log(error);
return null;
}),
await getLiteLLMModels(token).catch((error) => {
console.log(error);
return null;
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
return models;
};
//////////////////////////
// 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('<', '&lt;')
.trim();
};
export const revertSanitizedResponseContent = (content: string) => {
return content.replaceAll('&lt;', '<');
};
export const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
export const splitStream = (splitOn) => {
let buffer = '';
return new TransformStream({

View file

@ -19,7 +19,7 @@
} from '$lib/stores';
import { copyToClipboard, splitStream } from '$lib/utils';
import { generateChatCompletion, cancelOllamaRequest } from '$lib/apis/ollama';
import { generateChatCompletion, cancelOllamaRequest, generateTitle } from '$lib/apis/ollama';
import {
addTagById,
createNewChat,
@ -30,14 +30,14 @@
updateChatById
} from '$lib/apis/chats';
import { queryCollection, queryDoc } from '$lib/apis/rag';
import { generateOpenAIChatCompletion, generateTitle } from '$lib/apis/openai';
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
import MessageInput from '$lib/components/chat/MessageInput.svelte';
import Messages from '$lib/components/chat/Messages.svelte';
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import { RAGTemplate } from '$lib/utils/rag';
import { LITELLM_API_BASE_URL, OLLAMA_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
import { WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
@ -48,7 +48,6 @@
let messagesContainerElement: HTMLDivElement;
let currentRequestId = null;
let showModelSelector = true;
let selectedModels = [''];
let selectedModelfile = null;
@ -512,13 +511,13 @@
if (messages.length == 2 && messages.at(1).content !== '') {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
await setChatTitle(_chatId, _title);
await generateChatTitle(_chatId, userPrompt);
}
};
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId];
scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
@ -529,8 +528,6 @@
console.log(docs);
console.log(model);
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
@ -583,16 +580,9 @@
max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
},
model?.source?.toLowerCase() === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
);
// Wait until history/message have been updated
await tick();
scrollToBottom();
if (res && res.ok) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
@ -706,8 +696,11 @@
if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
await setChatTitle(_chatId, _title);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
}
};
@ -761,46 +754,23 @@
}
};
const generateChatTitle = async (userPrompt) => {
if ($settings?.title?.auto ?? true) {
const model = $models.find((model) => model.id === selectedModels[0]);
const titleModelId =
model?.external ?? false
? $settings?.title?.modelExternal ?? selectedModels[0]
: $settings?.title?.model ?? selectedModels[0];
const titleModel = $models.find((model) => model.id === titleModelId);
console.log(titleModel);
const generateChatTitle = async (_chatId, userPrompt) => {
if ($settings.titleAutoGenerate ?? true) {
const title = await generateTitle(
localStorage.token,
$settings?.title?.prompt ??
$settings?.titleGenerationPrompt ??
$i18n.t(
"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':"
) + ' {{prompt}}',
titleModelId,
userPrompt,
titleModel?.external ?? false
? titleModel?.source?.toLowerCase() === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
: `${OLLAMA_API_BASE_URL}/v1`
$settings?.titleAutoGenerateModel ?? selectedModels[0],
userPrompt
);
return title;
if (title) {
await setChatTitle(_chatId, title);
}
} else {
return `${userPrompt}`;
}
};
const setChatTitle = async (_chatId, _title) => {
if (_chatId === $chatId) {
title = _title;
}
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
await chats.set(await getChatList(localStorage.token));
await setChatTitle(_chatId, `${userPrompt}`);
}
};
@ -831,6 +801,17 @@
_tags.set(await getAllChatTags(localStorage.token));
};
const setChatTitle = async (_chatId, _title) => {
if (_chatId === $chatId) {
title = _title;
}
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
await chats.set(await getChatList(localStorage.token));
}
};
</script>
<svelte:head>
@ -842,17 +823,7 @@
</svelte:head>
<div class="h-screen max-h-[100dvh] w-full flex flex-col">
<Navbar
{title}
bind:selectedModels
bind:showModelSelector
shareEnabled={messages.length > 0}
{chat}
{initNewChat}
{tags}
{addTag}
{deleteTag}
/>
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
<div class="flex flex-col flex-auto">
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
@ -864,7 +835,15 @@
messagesContainerElement.clientHeight + 5;
}}
>
<div class=" h-full w-full flex flex-col pt-2 pb-4">
<div
class="{$settings?.fullScreenMode ?? null
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
<ModelSelector bind:selectedModels />
</div>
<div class=" h-full w-full flex flex-col py-8">
<Messages
chatId={$chatId}
{selectedModels}

View file

@ -19,7 +19,7 @@
} from '$lib/stores';
import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils';
import { generateChatCompletion, cancelOllamaRequest } from '$lib/apis/ollama';
import { generateChatCompletion, generateTitle, cancelOllamaRequest } from '$lib/apis/ollama';
import {
addTagById,
createNewChat,
@ -31,19 +31,14 @@
updateChatById
} from '$lib/apis/chats';
import { queryCollection, queryDoc } from '$lib/apis/rag';
import { generateOpenAIChatCompletion, generateTitle } from '$lib/apis/openai';
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
import MessageInput from '$lib/components/chat/MessageInput.svelte';
import Messages from '$lib/components/chat/Messages.svelte';
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import { RAGTemplate } from '$lib/utils/rag';
import {
LITELLM_API_BASE_URL,
OPENAI_API_BASE_URL,
OLLAMA_API_BASE_URL,
WEBUI_BASE_URL
} from '$lib/constants';
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
@ -56,10 +51,8 @@
let currentRequestId = null;
// let chatId = $page.params.id;
let showModelSelector = true;
let selectedModels = [''];
let selectedModelfile = null;
$: selectedModelfile =
selectedModels.length === 1 &&
$modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0]).length > 0
@ -528,13 +521,13 @@
if (messages.length == 2 && messages.at(1).content !== '') {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
await setChatTitle(_chatId, _title);
await generateChatTitle(_chatId, userPrompt);
}
};
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId];
scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
@ -597,16 +590,9 @@
max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
},
model?.source?.toLowerCase() === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
);
// Wait until history/message have been updated
await tick();
scrollToBottom();
if (res && res.ok) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
@ -720,8 +706,11 @@
if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
const _title = await generateChatTitle(userPrompt);
await setChatTitle(_chatId, _title);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
}
};
@ -730,19 +719,6 @@
console.log('stopResponse');
};
const regenerateResponse = async () => {
console.log('regenerateResponse');
if (messages.length != 0 && messages.at(-1).done == true) {
messages.splice(messages.length - 1, 1);
messages = messages;
let userMessage = messages.at(-1);
let userPrompt = userMessage.content;
await sendPrompt(userPrompt, userMessage.id);
}
};
const continueGeneration = async () => {
console.log('continueGeneration');
const _chatId = JSON.parse(JSON.stringify($chatId));
@ -775,35 +751,36 @@
}
};
const generateChatTitle = async (userPrompt) => {
if ($settings?.title?.auto ?? true) {
const model = $models.find((model) => model.id === selectedModels[0]);
const regenerateResponse = async () => {
console.log('regenerateResponse');
if (messages.length != 0 && messages.at(-1).done == true) {
messages.splice(messages.length - 1, 1);
messages = messages;
const titleModelId =
model?.external ?? false
? $settings?.title?.modelExternal ?? selectedModels[0]
: $settings?.title?.model ?? selectedModels[0];
const titleModel = $models.find((model) => model.id === titleModelId);
let userMessage = messages.at(-1);
let userPrompt = userMessage.content;
console.log(titleModel);
await sendPrompt(userPrompt, userMessage.id);
}
};
const generateChatTitle = async (_chatId, userPrompt) => {
if ($settings.titleAutoGenerate ?? true) {
const title = await generateTitle(
localStorage.token,
$settings?.title?.prompt ??
$settings?.titleGenerationPrompt ??
$i18n.t(
"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':"
) + ' {{prompt}}',
titleModelId,
userPrompt,
titleModel?.external ?? false
? titleModel?.source?.toLowerCase() === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
: `${OLLAMA_API_BASE_URL}/v1`
$settings?.titleAutoGenerateModel ?? selectedModels[0],
userPrompt
);
return title;
if (title) {
await setChatTitle(_chatId, title);
}
} else {
return `${userPrompt}`;
await setChatTitle(_chatId, `${userPrompt}`);
}
};
@ -812,10 +789,8 @@
title = _title;
}
if ($settings.saveChatHistory ?? true) {
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
await chats.set(await getChatList(localStorage.token));
}
};
const getTags = async () => {
@ -865,9 +840,6 @@
<div class="min-h-screen max-h-screen w-full flex flex-col">
<Navbar
{title}
{chat}
bind:selectedModels
bind:showModelSelector
shareEnabled={messages.length > 0}
initNewChat={async () => {
if (currentRequestId !== null) {
@ -892,7 +864,15 @@
messagesContainerElement.clientHeight + 5;
}}
>
<div class=" h-full w-full flex flex-col py-4">
<div
class="{$settings?.fullScreenMode ?? null
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
<ModelSelector bind:selectedModels />
</div>
<div class=" h-full w-full flex flex-col py-8">
<Messages
chatId={$chatId}
{selectedModels}

View file

@ -18,7 +18,6 @@
import { splitStream } from '$lib/utils';
import ChatCompletion from '$lib/components/playground/ChatCompletion.svelte';
import Selector from '$lib/components/chat/ModelSelector/Selector.svelte';
const i18n = getContext('i18n');
@ -316,24 +315,27 @@
</div>
</div>
<div class="flex flex-col gap-1 px-1 w-full">
<div class="flex w-full">
<div class="overflow-hidden w-full">
<div class="max-w-full">
<Selector
placeholder={$i18n.t('Select a model')}
items={$models
.filter((model) => model.name !== 'hr')
.map((model) => ({
value: model.id,
label: model.name,
info: model
}))}
<div class=" flex gap-1 px-1">
<select
id="models"
class="outline-none bg-transparent text-sm font-medium rounded-lg w-full placeholder-gray-400"
bind:value={selectedModelId}
/>
</div>
</div>
</div>
>
<option class=" text-gray-800" value="" selected disabled
>{$i18n.t('Select a model')}</option
>
{#each $models as model}
{#if model.name === 'hr'}
<hr />
{:else}
<option value={model.id} class="text-gray-800 text-lg"
>{model.name +
`${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}</option
>
{/if}
{/each}
</select>
<!-- <button
class=" self-center dark:hover:text-gray-300"

View file

@ -11,7 +11,7 @@
import '../tailwind.css';
import 'tippy.js/dist/tippy.css';
import { WEBUI_BASE_URL } from '$lib/constants';
import i18n, { initI18n } from '$lib/i18n';
import i18n from '$lib/i18n';
setContext('i18n', i18n);
@ -25,11 +25,6 @@
if (backendConfig) {
// Save Backend Status to Store
await config.set(backendConfig);
if ($config.default_locale) {
initI18n($config.default_locale);
} else {
initI18n();
}
await WEBUI_NAME.set(backendConfig.name);
console.log(backendConfig);

View file

@ -1,7 +1,6 @@
<script>
import { goto } from '$app/navigation';
import { userSignIn, userSignUp } from '$lib/apis/auths';
import Spinner from '$lib/components/common/Spinner.svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, config, user } from '$lib/stores';
import { onMount, getContext } from 'svelte';
@ -57,9 +56,6 @@
await goto('/');
}
loaded = true;
if ($config?.trusted_header_auth ?? false) {
await signInHandler();
}
});
</script>
@ -94,23 +90,6 @@
</div> -->
<div class="w-full sm:max-w-lg px-4 min-h-screen flex flex-col">
{#if $config?.trusted_header_auth ?? false}
<div class=" my-auto pb-10 w-full">
<div
class="flex items-center justify-center gap-3 text-xl sm:text-2xl text-center font-bold dark:text-gray-200"
>
<div>
{$i18n.t('Signing in')}
{$i18n.t('to')}
{$WEBUI_NAME}
</div>
<div>
<Spinner />
</div>
</div>
</div>
{:else}
<div class=" my-auto pb-10 w-full">
<form
class=" flex flex-col justify-center bg-white py-6 sm:py-16 px-6 sm:px-16 rounded-2xl"
@ -205,7 +184,6 @@
</div>
</form>
</div>
{/if}
</div>
</div>
{/if}

View file

@ -1,180 +0,0 @@
<script lang="ts">
import { onMount, tick, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import dayjs from 'dayjs';
import { modelfiles, settings, chatId, WEBUI_NAME } from '$lib/stores';
import { convertMessagesToHistory } from '$lib/utils';
import { getChatByShareId } from '$lib/apis/chats';
import Messages from '$lib/components/chat/Messages.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
const i18n = getContext('i18n');
let loaded = false;
let autoScroll = true;
let processing = '';
let messagesContainerElement: HTMLDivElement;
// let chatId = $page.params.id;
let showModelSelector = false;
let selectedModels = [''];
let selectedModelfiles = {};
$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
const modelfile =
$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;
return {
...a,
...(modelfile && { [tagName]: modelfile })
};
}, {});
let chat = null;
let title = '';
let files = [];
let messages = [];
let history = {
messages: {},
currentId: null
};
$: if (history.currentId !== null) {
let _messages = [];
let currentMessage = history.messages[history.currentId];
while (currentMessage !== null) {
_messages.unshift({ ...currentMessage });
currentMessage =
currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
}
messages = _messages;
} else {
messages = [];
}
$: if ($page.params.id) {
(async () => {
if (await loadSharedChat()) {
await tick();
loaded = true;
window.setTimeout(() => scrollToBottom(), 0);
const chatInput = document.getElementById('chat-textarea');
chatInput?.focus();
} else {
await goto('/');
}
})();
}
//////////////////////////
// Web functions
//////////////////////////
const loadSharedChat = async () => {
await chatId.set($page.params.id);
chat = await getChatByShareId(localStorage.token, $chatId).catch(async (error) => {
await goto('/');
return null;
});
if (chat) {
const chatContent = chat.chat;
if (chatContent) {
console.log(chatContent);
selectedModels =
(chatContent?.models ?? undefined) !== undefined
? chatContent.models
: [chatContent.models ?? ''];
history =
(chatContent?.history ?? undefined) !== undefined
? chatContent.history
: convertMessagesToHistory(chatContent.messages);
title = chatContent.title;
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
await settings.set({
..._settings,
system: chatContent.system ?? _settings.system,
options: chatContent.options ?? _settings.options
});
autoScroll = true;
await tick();
if (messages.length > 0) {
history.messages[messages.at(-1).id].done = true;
}
await tick();
return true;
} else {
return null;
}
}
};
</script>
<svelte:head>
<title>
{title
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
: `${$WEBUI_NAME}`}
</title>
</svelte:head>
{#if loaded}
<div
class="min-h-screen max-h-screen w-full flex flex-col text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900"
>
<div class="flex flex-col flex-auto justify-center py-8">
<div class="px-3 w-full max-w-3xl mx-auto">
<div>
<div class=" text-3xl font-semibold line-clamp-1">
{title}
</div>
<div class=" mt-1 text-gray-400">
{dayjs(chat.chat.timestamp).format('MMMM D, YYYY')}
</div>
</div>
<hr class=" dark:border-gray-800 mt-6 mb-2" />
</div>
<div
class=" flex flex-col justify-center w-full flex-auto overflow-auto h-0"
id="messages-container"
>
<div class=" h-full w-full flex flex-col py-4">
<div class="py-2">
<Messages
chatId={$chatId}
readOnly={true}
{selectedModels}
{selectedModelfiles}
{processing}
bind:history
bind:messages
bind:autoScroll
bottomPadding={files.length > 0}
sendPrompt={() => {}}
continueGeneration={() => {}}
regenerateResponse={() => {}}
/>
</div>
</div>
</div>
</div>
</div>
{/if}

Some files were not shown because too many files have changed in this diff Show more