forked from open-webui/open-webui
Compare commits
223 commits
921a1cf978
...
fd5bf819cc
Author | SHA1 | Date | |
---|---|---|---|
fd5bf819cc | |||
|
331fe04df7 | ||
|
aed4763bfe | ||
|
8d0f3fbde9 | ||
|
318d4d1ce1 | ||
|
5a787ab2c5 | ||
|
81dbc65853 | ||
|
d001d7afb1 | ||
|
183b84beae | ||
|
7d64ada43e | ||
|
802eb3beec | ||
|
f87805b46f | ||
|
7a7d1931d9 | ||
|
e61e1b079f | ||
|
7813c3316f | ||
|
46774aa5cd | ||
|
62392aa88a | ||
|
c32d41b47f | ||
|
9404b6135a | ||
|
8caad59cb4 | ||
|
21132420d8 | ||
|
c89070797c | ||
|
1f85354856 | ||
|
bfd066cc2f | ||
|
000bea84ae | ||
|
944efd2cd8 | ||
|
4a09342a71 | ||
|
aad13607c0 | ||
|
1509c2c2ee | ||
|
d94bc21ac8 | ||
|
947c392f72 | ||
|
15d24a6a11 | ||
|
a3d6343dbe | ||
|
14fcf4d267 | ||
|
83ad488e31 | ||
|
fa61e738c3 | ||
|
0aa4d305c7 | ||
|
1066096b33 | ||
|
398b2076cd | ||
|
fb516ea694 | ||
|
b35cc36e19 | ||
|
6d1ce99537 | ||
|
7c5f6d71b3 | ||
|
e49e04c56a | ||
|
da8646cae9 | ||
|
ba0523cd69 | ||
|
38c34b4444 | ||
|
b11af9ea0e | ||
|
56369fea3a | ||
|
24fb77759e | ||
|
f9106cf394 | ||
|
807a60cccf | ||
|
ac4675900e | ||
|
f7a1fc207a | ||
|
b9b1d64bd5 | ||
|
7e6a00b009 | ||
|
5aad328040 | ||
|
03a7f88dca | ||
|
f61869d90d | ||
|
95505bc4c1 | ||
|
eca59f759d | ||
|
9975cb17a9 | ||
|
0b823f90e6 | ||
|
c0fff4c69f | ||
|
180dd3966b | ||
|
e4d101e550 | ||
|
88c5b7f495 | ||
|
4e9ca31146 | ||
|
48e7376336 | ||
|
488301f150 | ||
|
287c95e72d | ||
|
af5a7a35c0 | ||
|
5faa05a117 | ||
|
e273a99153 | ||
|
151055590d | ||
|
7978adbf45 | ||
|
94976e5ed3 | ||
|
3a1d747677 | ||
|
7121465eda | ||
|
52e83891b4 | ||
|
456a78ada9 | ||
|
cd60dbe08c | ||
|
a0d932b986 | ||
|
10cad60566 | ||
|
c6296e5108 | ||
|
dd9cbd0ab9 | ||
|
865c268312 | ||
|
dcac8a773b | ||
|
96d75dfc1b | ||
|
49428eb7ef | ||
|
6c0e3972dc | ||
|
458c317736 | ||
|
98f68b8b08 | ||
|
b76e934140 | ||
|
0cdcc2deb6 | ||
|
58992d592c | ||
|
04e172deac | ||
|
a363c1f2f1 | ||
|
6425e794f6 | ||
|
f340178bb7 | ||
|
aa6c8b1bfd | ||
|
d1ca6b50d9 | ||
|
57530d87f6 | ||
|
2c030218f8 | ||
|
5558514ff1 | ||
|
83c7633acb | ||
|
6c3f2f8d38 | ||
|
d72653cdea | ||
|
6c96361402 | ||
|
554e56687c | ||
|
77b1edcd0f | ||
|
d667faea33 | ||
|
4cb2cc62d2 | ||
|
698bfcf87f | ||
|
edeff20e1d | ||
|
eda157e303 | ||
|
653a0ff02f | ||
|
8175a323e9 | ||
|
11850b75c8 | ||
|
562e40a7bd | ||
|
0e3b7a11e3 | ||
|
196f91d68c | ||
|
bfbfdae1c5 | ||
|
a6c154d839 | ||
|
7563619e26 | ||
|
1b5c92d66c | ||
|
2f23325c90 | ||
|
cef676429f | ||
|
a0935dec60 | ||
|
587a8c5992 | ||
|
4ddb7e124e | ||
|
68cfdd89c4 | ||
|
144c9059a3 | ||
|
ee38b3c84d | ||
|
86aa2ca6cb | ||
|
1d7ca44017 | ||
|
174c082c69 | ||
|
bbb63104ea | ||
|
43832d165f | ||
|
d630485051 | ||
|
784572590f | ||
|
90a03c331d | ||
|
b391ad12ae | ||
|
6c2eb814ed | ||
|
3b0cb7945f | ||
|
46a181042b | ||
|
5af8d0612a | ||
|
d4fabeee3c | ||
|
e47d6ce1f6 | ||
|
15f4153fda | ||
|
1371e66ed9 | ||
|
2fbb92aceb | ||
|
f96beeee8f | ||
|
0c367412c3 | ||
|
150152ddbd | ||
|
12287f8680 | ||
|
047c9fe82c | ||
|
ce20f05ef1 | ||
|
242311aee1 | ||
|
bbc34d9be5 | ||
|
09da5e71e2 | ||
|
74638bbd6e | ||
|
f34fe49bdd | ||
|
50089710f3 | ||
|
4bf38e5287 | ||
|
50f6addd6f | ||
|
c42cb438f9 | ||
|
b93bdfb222 | ||
|
c85a2e8cd4 | ||
|
46e3208cde | ||
|
c6bb0ded5e | ||
|
d187b1615a | ||
|
5e24e03fe5 | ||
|
2fcc4aec4b | ||
|
67865fc5ad | ||
|
9ad097d265 | ||
|
572eefe181 | ||
|
cf44209730 | ||
|
dfeadf9595 | ||
|
25c71d8ac2 | ||
|
19d2121252 | ||
|
c24d9d3bea | ||
|
976c714063 | ||
|
f87d5d3d9b | ||
|
29f13f34d3 | ||
|
7d45d2762f | ||
|
e5408995d8 | ||
|
d69dfb7c8b | ||
|
addee2f248 | ||
|
0c021e42f5 | ||
|
64e6003627 | ||
|
26121c5d88 | ||
|
d245b756a3 | ||
|
b4b56f9c85 | ||
|
81e928030f | ||
|
0b62bbb52e | ||
|
cb364f0ac7 | ||
|
d12a7247bc | ||
|
941bb7c603 | ||
|
3edc547389 | ||
|
427ca4e3f5 | ||
|
5eac5c54f8 | ||
|
a1fc2f4df0 | ||
|
3688955c77 | ||
|
6307adfba1 | ||
|
4f78acaa5c | ||
|
7f405c74cd | ||
|
5d6cee0c58 | ||
|
af366cf732 | ||
|
32cb5f3ceb | ||
|
eecc7f3529 | ||
|
8962d948cd | ||
|
c91a5d8b1f | ||
|
77f4ffddc1 | ||
|
784a6ec85e | ||
|
eb004ccfc2 | ||
|
c6f9e74477 | ||
|
302bd11b33 | ||
|
c19ea89a1d | ||
|
9560f9196d | ||
|
42bb1ec089 | ||
|
c91c95431d | ||
|
1d64d31275 |
111 changed files with 6483 additions and 2728 deletions
|
@ -10,3 +10,7 @@ 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"
|
||||
|
|
7
.github/workflows/docker-build.yaml
vendored
7
.github/workflows/docker-build.yaml
vendored
|
@ -12,16 +12,13 @@ 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: git.depeuter.dev
|
||||
REGISTRY: ghcr.io
|
||||
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
|
||||
|
@ -42,7 +39,7 @@ jobs:
|
|||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.CI_TOKEN }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker images
|
||||
id: meta
|
||||
|
|
32
.github/workflows/format-backend.yaml
vendored
32
.github/workflows/format-backend.yaml
vendored
|
@ -1,31 +1,39 @@
|
|||
name: Python CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
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:
|
||||
node-version:
|
||||
- latest
|
||||
python-version: [ '3.12.2' ]
|
||||
python-version: [3.11]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Python
|
||||
|
||||
- name: Set up 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 yapf
|
||||
pip install black
|
||||
|
||||
- name: Format backend
|
||||
run: bun run format:backend
|
||||
run: npm run format:backend
|
||||
|
||||
- name: Check for changes after format
|
||||
run: git diff --exit-code
|
||||
|
|
42
.github/workflows/format-build-frontend.yaml
vendored
42
.github/workflows/format-build-frontend.yaml
vendored
|
@ -1,22 +1,36 @@
|
|||
name: Bun CI
|
||||
name: Frontend Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Format & Build Frontend'
|
||||
env:
|
||||
PUBLIC_API_BASE_URL: ''
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
- 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
|
||||
|
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -5,6 +5,41 @@ 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
|
||||
|
|
|
@ -26,10 +26,15 @@ 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"
|
||||
|
|
2
Makefile
2
Makefile
|
@ -8,6 +8,8 @@ remove:
|
|||
|
||||
start:
|
||||
@docker-compose start
|
||||
startAndBuild:
|
||||
docker-compose up -d --build
|
||||
|
||||
stop:
|
||||
@docker-compose stop
|
||||
|
|
|
@ -22,7 +22,13 @@ 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"])
|
||||
|
|
|
@ -325,7 +325,7 @@ def save_url_image(url):
|
|||
|
||||
return image_id
|
||||
except Exception as e:
|
||||
print(f"Error saving image: {e}")
|
||||
log.exception(f"Error saving image: {e}")
|
||||
return None
|
||||
|
||||
|
||||
|
@ -397,7 +397,7 @@ def generate_image(
|
|||
user.id,
|
||||
app.state.COMFYUI_BASE_URL,
|
||||
)
|
||||
print(res)
|
||||
log.debug(f"res: {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)
|
||||
|
||||
print(images)
|
||||
log.debug(f"images: {images}")
|
||||
return images
|
||||
else:
|
||||
if form_data.model:
|
||||
|
|
|
@ -4,6 +4,12 @@ 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
|
||||
|
||||
|
@ -121,7 +127,7 @@ COMFYUI_DEFAULT_PROMPT = """
|
|||
|
||||
|
||||
def queue_prompt(prompt, client_id, base_url):
|
||||
print("queue_prompt")
|
||||
log.info("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)
|
||||
|
@ -129,7 +135,7 @@ def queue_prompt(prompt, client_id, base_url):
|
|||
|
||||
|
||||
def get_image(filename, subfolder, folder_type, base_url):
|
||||
print("get_image")
|
||||
log.info("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:
|
||||
|
@ -137,14 +143,14 @@ def get_image(filename, subfolder, folder_type, base_url):
|
|||
|
||||
|
||||
def get_image_url(filename, subfolder, folder_type, base_url):
|
||||
print("get_image")
|
||||
log.info("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):
|
||||
print("get_history")
|
||||
log.info("get_history")
|
||||
with urllib.request.urlopen(f"{base_url}/history/{prompt_id}") as response:
|
||||
return json.loads(response.read())
|
||||
|
||||
|
@ -212,15 +218,15 @@ def comfyui_generate_image(
|
|||
try:
|
||||
ws = websocket.WebSocket()
|
||||
ws.connect(f"ws://{host}/ws?clientId={client_id}")
|
||||
print("WebSocket connection established.")
|
||||
log.info("WebSocket connection established.")
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to WebSocket server: {e}")
|
||||
log.exception(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:
|
||||
print(f"Error while receiving images: {e}")
|
||||
log.exception(f"Error while receiving images: {e}")
|
||||
images = None
|
||||
|
||||
ws.close()
|
||||
|
|
|
@ -33,7 +33,13 @@ 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__)
|
||||
|
@ -75,6 +81,12 @@ 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}
|
||||
|
@ -266,7 +278,7 @@ async def pull_model(
|
|||
if request_id in REQUEST_POOL:
|
||||
yield chunk
|
||||
else:
|
||||
print("User: canceled request")
|
||||
log.warning("User: canceled request")
|
||||
break
|
||||
finally:
|
||||
if hasattr(r, "close"):
|
||||
|
@ -664,7 +676,7 @@ async def generate_completion(
|
|||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="error_detail",
|
||||
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
|
||||
)
|
||||
|
||||
url = app.state.OLLAMA_BASE_URLS[url_idx]
|
||||
|
@ -770,7 +782,11 @@ 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
|
||||
|
@ -1019,6 +1035,14 @@ 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]
|
||||
|
@ -1027,6 +1051,7 @@ 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),
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ from fastapi import (
|
|||
Form,
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import os, shutil, logging
|
||||
import os, shutil, logging, re
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
@ -21,6 +21,7 @@ from langchain_community.document_loaders import (
|
|||
TextLoader,
|
||||
PyPDFLoader,
|
||||
CSVLoader,
|
||||
BSHTMLLoader,
|
||||
Docx2txtLoader,
|
||||
UnstructuredEPubLoader,
|
||||
UnstructuredWordDocumentLoader,
|
||||
|
@ -114,6 +115,7 @@ class CollectionNameForm(BaseModel):
|
|||
class StoreWebForm(CollectionNameForm):
|
||||
url: str
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def get_status():
|
||||
return {
|
||||
|
@ -296,13 +298,18 @@ 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)
|
||||
return store_docs_in_vector_db(docs, collection_name, overwrite)
|
||||
|
||||
if len(docs) > 0:
|
||||
return store_docs_in_vector_db(docs, collection_name, overwrite), None
|
||||
else:
|
||||
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
|
||||
|
||||
|
||||
def store_text_in_vector_db(
|
||||
|
@ -318,6 +325,7 @@ 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]
|
||||
|
||||
|
@ -325,7 +333,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:
|
||||
print(f"deleting existing collection {collection_name}")
|
||||
log.info(f"deleting existing collection {collection_name}")
|
||||
CHROMA_CLIENT.delete_collection(name=collection_name)
|
||||
|
||||
collection = CHROMA_CLIENT.create_collection(
|
||||
|
@ -338,7 +346,7 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
|
|||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(e)
|
||||
log.exception(e)
|
||||
if e.__class__.__name__ == "UniqueConstraintError":
|
||||
return True
|
||||
|
||||
|
@ -402,6 +410,8 @@ 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":
|
||||
|
@ -438,8 +448,11 @@ def store_doc(
|
|||
|
||||
log.info(f"file.content_type: {file.content_type}")
|
||||
try:
|
||||
filename = file.filename
|
||||
unsanitized_filename = file.filename
|
||||
filename = os.path.basename(unsanitized_filename)
|
||||
|
||||
file_path = f"{UPLOAD_DIR}/{filename}"
|
||||
|
||||
contents = file.file.read()
|
||||
with open(file_path, "wb") as f:
|
||||
f.write(contents)
|
||||
|
@ -450,8 +463,10 @@ def store_doc(
|
|||
collection_name = calculate_sha256(f)[:63]
|
||||
f.close()
|
||||
|
||||
loader, known_type = get_loader(file.filename, file.content_type, file_path)
|
||||
loader, known_type = get_loader(filename, file.content_type, file_path)
|
||||
data = loader.load()
|
||||
|
||||
try:
|
||||
result = store_data_in_vector_db(data, collection_name)
|
||||
|
||||
if result:
|
||||
|
@ -461,10 +476,10 @@ def store_doc(
|
|||
"filename": filename,
|
||||
"known_type": known_type,
|
||||
}
|
||||
else:
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=ERROR_MESSAGES.DEFAULT(),
|
||||
detail=e,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
@ -529,6 +544,7 @@ def scan_docs_dir(user=Depends(get_admin_user)):
|
|||
)
|
||||
data = loader.load()
|
||||
|
||||
try:
|
||||
result = store_data_in_vector_db(data, collection_name)
|
||||
|
||||
if result:
|
||||
|
@ -561,6 +577,9 @@ def scan_docs_dir(user=Depends(get_admin_user)):
|
|||
}
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
|
|
@ -156,6 +156,8 @@ 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:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from peewee import *
|
||||
from peewee_migrate import Router
|
||||
from config import SRC_LOG_LEVELS, DATA_DIR
|
||||
import os
|
||||
import logging
|
||||
|
@ -16,4 +17,6 @@ else:
|
|||
|
||||
|
||||
DB = SqliteDatabase(f"{DATA_DIR}/webui.db")
|
||||
DB.connect()
|
||||
router = Router(DB, migrate_dir="apps/web/internal/migrations", logger=log)
|
||||
router.run()
|
||||
DB.connect(reuse_if_open=True)
|
||||
|
|
149
backend/apps/web/internal/migrations/001_initial_schema.py
Normal file
149
backend/apps/web/internal/migrations/001_initial_schema.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
"""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")
|
|
@ -0,0 +1,48 @@
|
|||
"""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")
|
48
backend/apps/web/internal/migrations/003_add_auth_api_key.py
Normal file
48
backend/apps/web/internal/migrations/003_add_auth_api_key.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""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")
|
21
backend/apps/web/internal/migrations/README.md
Normal file
21
backend/apps/web/internal/migrations/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# 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.
|
|
@ -20,6 +20,7 @@ from config import (
|
|||
ENABLE_SIGNUP,
|
||||
USER_PERMISSIONS,
|
||||
WEBHOOK_URL,
|
||||
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
@ -34,7 +35,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,
|
||||
|
|
|
@ -11,6 +11,7 @@ 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"])
|
||||
|
||||
|
@ -46,6 +47,10 @@ class Token(BaseModel):
|
|||
token_type: str
|
||||
|
||||
|
||||
class ApiKey(BaseModel):
|
||||
api_key: Optional[str] = None
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
|
@ -122,6 +127,28 @@ 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)
|
||||
|
|
|
@ -20,6 +20,7 @@ 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
|
||||
|
@ -31,6 +32,7 @@ class ChatModel(BaseModel):
|
|||
title: str
|
||||
chat: str
|
||||
timestamp: int # timestamp in epoch
|
||||
share_id: Optional[str] = None
|
||||
|
||||
|
||||
####################
|
||||
|
@ -52,6 +54,7 @@ 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):
|
||||
|
@ -95,6 +98,71 @@ 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]:
|
||||
|
@ -131,6 +199,13 @@ 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)
|
||||
|
@ -149,12 +224,15 @@ 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
|
||||
return True and self.delete_shared_chat_by_chat_id(id)
|
||||
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.
|
||||
|
||||
|
@ -162,5 +240,19 @@ 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)
|
||||
|
|
|
@ -13,6 +13,7 @@ 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"])
|
||||
|
||||
|
|
|
@ -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,7 +73,8 @@ 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())
|
||||
|
@ -87,29 +88,28 @@ 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),
|
||||
|
|
|
@ -52,8 +52,9 @@ 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,
|
||||
|
@ -61,7 +62,8 @@ class PromptsTable:
|
|||
"title": form_data.title,
|
||||
"content": form_data.content,
|
||||
"timestamp": int(time.time()),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
result = Prompt.create(**prompt.model_dump())
|
||||
|
@ -81,13 +83,14 @@ 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,
|
||||
|
|
|
@ -11,6 +11,7 @@ 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"])
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ class User(Model):
|
|||
role = CharField()
|
||||
profile_image_url = CharField()
|
||||
timestamp = DateField()
|
||||
api_key = CharField(null=True, unique=True)
|
||||
|
||||
class Meta:
|
||||
database = DB
|
||||
|
@ -32,6 +33,7 @@ class UserModel(BaseModel):
|
|||
role: str = "pending"
|
||||
profile_image_url: str = "/user.png"
|
||||
timestamp: int # timestamp in epoch
|
||||
api_key: Optional[str] = None
|
||||
|
||||
|
||||
####################
|
||||
|
@ -82,6 +84,13 @@ 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)
|
||||
|
@ -149,5 +158,21 @@ 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)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
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 Request
|
||||
from fastapi import Depends, HTTPException, status
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
import time
|
||||
import uuid
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from apps.web.models.auths import (
|
||||
SigninForm,
|
||||
|
@ -17,6 +14,7 @@ from apps.web.models.auths import (
|
|||
UserResponse,
|
||||
SigninResponse,
|
||||
Auths,
|
||||
ApiKey,
|
||||
)
|
||||
from apps.web.models.users import Users
|
||||
|
||||
|
@ -25,10 +23,12 @@ 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,6 +79,8 @@ 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)
|
||||
|
||||
|
@ -98,7 +100,22 @@ 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},
|
||||
|
@ -249,3 +266,40 @@ 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)
|
||||
|
|
|
@ -29,6 +29,7 @@ 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"])
|
||||
|
||||
|
@ -188,6 +189,78 @@ 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
|
||||
############################
|
||||
|
|
|
@ -10,7 +10,12 @@ 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
|
||||
|
||||
|
@ -43,7 +48,6 @@ 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,
|
||||
|
|
|
@ -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,17 +36,16 @@ async def get_modelfiles(skip: int = 0,
|
|||
|
||||
|
||||
@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,
|
||||
|
@ -60,17 +59,18 @@ async def create_new_modelfile(form_data: ModelfileForm,
|
|||
|
||||
|
||||
@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,8 +84,9 @@ async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm,
|
|||
|
||||
|
||||
@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 = {
|
||||
|
@ -94,14 +95,15 @@ async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm,
|
|||
}
|
||||
|
||||
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,
|
||||
|
@ -115,7 +117,8 @@ async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm,
|
|||
|
||||
|
||||
@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
|
||||
|
|
|
@ -16,6 +16,7 @@ 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"])
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
from fastapi import APIRouter, UploadFile, File, BackgroundTasks
|
||||
from fastapi import APIRouter, UploadFile, File, Response
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from starlette.responses import StreamingResponse, FileResponse
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
import requests
|
||||
import os
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
from fpdf import FPDF
|
||||
import markdown
|
||||
|
||||
|
||||
from utils.utils import get_admin_user
|
||||
|
@ -16,7 +13,7 @@ from utils.misc import calculate_sha256, get_gravatar_url
|
|||
|
||||
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR
|
||||
from constants import ERROR_MESSAGES
|
||||
|
||||
from typing import List
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -28,6 +25,70 @@ async def get_gravatar(
|
|||
return get_gravatar_url(email)
|
||||
|
||||
|
||||
class MarkdownForm(BaseModel):
|
||||
md: str
|
||||
|
||||
|
||||
@router.post("/markdown")
|
||||
async def get_html_from_markdown(
|
||||
form_data: MarkdownForm,
|
||||
):
|
||||
return {"html": markdown.markdown(form_data.md)}
|
||||
|
||||
|
||||
class ChatForm(BaseModel):
|
||||
title: str
|
||||
messages: List[dict]
|
||||
|
||||
|
||||
@router.post("/pdf")
|
||||
async def download_chat_as_pdf(
|
||||
form_data: ChatForm,
|
||||
):
|
||||
pdf = FPDF()
|
||||
pdf.add_page()
|
||||
|
||||
STATIC_DIR = "./static"
|
||||
FONTS_DIR = f"{STATIC_DIR}/fonts"
|
||||
|
||||
pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf")
|
||||
pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf")
|
||||
pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf")
|
||||
pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf")
|
||||
pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf")
|
||||
|
||||
pdf.set_font("NotoSans", size=12)
|
||||
pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP"])
|
||||
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
|
||||
# Adjust the effective page width for multi_cell
|
||||
effective_page_width = (
|
||||
pdf.w - 2 * pdf.l_margin - 10
|
||||
) # Subtracted an additional 10 for extra padding
|
||||
|
||||
# Add chat messages
|
||||
for message in form_data.messages:
|
||||
role = message["role"]
|
||||
content = message["content"]
|
||||
pdf.set_font("NotoSans", "B", size=14) # Bold for the role
|
||||
pdf.multi_cell(effective_page_width, 10, f"{role.upper()}", 0, "L")
|
||||
pdf.ln(1) # Extra space between messages
|
||||
|
||||
pdf.set_font("NotoSans", size=10) # Regular for content
|
||||
pdf.multi_cell(effective_page_width, 6, content, 0, "L")
|
||||
pdf.ln(1.5) # Extra space between messages
|
||||
|
||||
# Save the pdf with name .pdf
|
||||
pdf_bytes = pdf.output()
|
||||
|
||||
return Response(
|
||||
content=bytes(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f"attachment;filename=chat.pdf"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/db/download")
|
||||
async def download_db(user=Depends(get_admin_user)):
|
||||
|
||||
|
|
|
@ -25,7 +25,9 @@ try:
|
|||
except ImportError:
|
||||
log.warning("dotenv not installed, skipping...")
|
||||
|
||||
WEBUI_NAME = "Aura"
|
||||
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
|
||||
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
|
||||
|
||||
shutil.copyfile("../build/favicon.png", "./static/favicon.png")
|
||||
|
||||
####################################
|
||||
|
@ -116,7 +118,20 @@ else:
|
|||
log = logging.getLogger(__name__)
|
||||
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
||||
|
||||
log_sources = ["AUDIO", "CONFIG", "DB", "IMAGES", "LITELLM", "MAIN", "MODELS", "OLLAMA", "OPENAI", "RAG"]
|
||||
log_sources = [
|
||||
"AUDIO",
|
||||
"COMFYUI",
|
||||
"CONFIG",
|
||||
"DB",
|
||||
"IMAGES",
|
||||
"LITELLM",
|
||||
"MAIN",
|
||||
"MODELS",
|
||||
"OLLAMA",
|
||||
"OPENAI",
|
||||
"RAG",
|
||||
"WEBHOOK",
|
||||
]
|
||||
|
||||
SRC_LOG_LEVELS = {}
|
||||
|
||||
|
@ -135,13 +150,14 @@ 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:
|
||||
url = (
|
||||
WEBUI_FAVICON_URL = url = (
|
||||
f"https://api.openwebui.com{data['logo']}"
|
||||
if data["logo"][0] == "/"
|
||||
else data["logo"]
|
||||
|
@ -157,7 +173,9 @@ if CUSTOM_NAME:
|
|||
except Exception as e:
|
||||
log.exception(e)
|
||||
pass
|
||||
|
||||
else:
|
||||
if WEBUI_NAME != "Open WebUI":
|
||||
WEBUI_NAME += " (Open WebUI)"
|
||||
|
||||
####################################
|
||||
# DATA/FRONTEND BUILD DIR
|
||||
|
@ -238,7 +256,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 = (
|
||||
|
@ -251,6 +269,9 @@ 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
|
||||
|
@ -345,6 +366,9 @@ 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
|
||||
|
|
|
@ -20,6 +20,7 @@ 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."
|
||||
|
@ -36,6 +37,7 @@ 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 = (
|
||||
|
@ -58,5 +60,8 @@ 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="": f"OpenAI API was not found"
|
||||
OPENAI_NOT_FOUND = lambda name="": "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."
|
||||
|
|
|
@ -1,33 +1,22 @@
|
|||
{
|
||||
"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."
|
||||
}
|
||||
]
|
||||
|
|
|
@ -32,6 +32,7 @@ from utils.utils import get_admin_user
|
|||
from apps.rag.utils import rag_messages
|
||||
|
||||
from config import (
|
||||
CONFIG_DATA,
|
||||
WEBUI_NAME,
|
||||
ENV,
|
||||
VERSION,
|
||||
|
@ -49,6 +50,7 @@ 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:
|
||||
|
@ -60,6 +62,21 @@ 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
|
||||
|
@ -67,7 +84,6 @@ app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
|
|||
|
||||
app.state.WEBHOOK_URL = WEBHOOK_URL
|
||||
|
||||
|
||||
origins = ["*"]
|
||||
|
||||
|
||||
|
@ -88,7 +104,6 @@ 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"],
|
||||
|
@ -163,14 +178,22 @@ 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),
|
||||
}
|
||||
|
||||
|
||||
|
@ -191,7 +214,6 @@ 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
|
||||
|
||||
|
@ -201,6 +223,9 @@ 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,
|
||||
|
@ -231,7 +256,6 @@ 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,
|
||||
}
|
||||
|
@ -239,7 +263,7 @@ async def get_app_config():
|
|||
|
||||
@app.get("/api/changelog")
|
||||
async def get_app_changelog():
|
||||
return CHANGELOG
|
||||
return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
|
||||
|
||||
|
||||
@app.get("/api/version/updates")
|
||||
|
@ -259,6 +283,20 @@ async def get_app_latest_release_version():
|
|||
)
|
||||
|
||||
|
||||
@app.get("/manifest.json")
|
||||
async def get_manifest_json():
|
||||
return {
|
||||
"name": WEBUI_NAME,
|
||||
"short_name": WEBUI_NAME,
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#343541",
|
||||
"theme_color": "#343541",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [{"src": "/favicon.png", "type": "image/png", "sizes": "844x884"}],
|
||||
}
|
||||
|
||||
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
app.mount("/cache", StaticFiles(directory="data/cache"), name="cache")
|
||||
|
||||
|
|
|
@ -14,9 +14,12 @@ uuid
|
|||
requests
|
||||
aiohttp
|
||||
peewee
|
||||
peewee-migrate
|
||||
bcrypt
|
||||
|
||||
litellm==1.30.7
|
||||
boto3
|
||||
|
||||
argon2-cffi
|
||||
apscheduler
|
||||
google-generativeai
|
||||
|
@ -39,6 +42,8 @@ xlrd
|
|||
opencv-python-headless
|
||||
rapidocr-onnxruntime
|
||||
|
||||
fpdf2
|
||||
|
||||
faster-whisper
|
||||
|
||||
PyJWT
|
||||
|
|
BIN
backend/static/fonts/NotoSans-Bold.ttf
Normal file
BIN
backend/static/fonts/NotoSans-Bold.ttf
Normal file
Binary file not shown.
BIN
backend/static/fonts/NotoSans-Italic.ttf
Normal file
BIN
backend/static/fonts/NotoSans-Italic.ttf
Normal file
Binary file not shown.
BIN
backend/static/fonts/NotoSans-Regular.ttf
Normal file
BIN
backend/static/fonts/NotoSans-Regular.ttf
Normal file
Binary file not shown.
BIN
backend/static/fonts/NotoSansJP-Regular.ttf
Normal file
BIN
backend/static/fonts/NotoSansJP-Regular.ttf
Normal file
Binary file not shown.
BIN
backend/static/fonts/NotoSansKR-Regular.ttf
Normal file
BIN
backend/static/fonts/NotoSansKR-Regular.ttf
Normal file
Binary file not shown.
|
@ -1,6 +1,8 @@
|
|||
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
|
||||
|
@ -8,6 +10,7 @@ from passlib.context import CryptContext
|
|||
from datetime import datetime, timedelta
|
||||
import requests
|
||||
import jwt
|
||||
import uuid
|
||||
import logging
|
||||
import config
|
||||
|
||||
|
@ -58,6 +61,11 @@ 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(" ")
|
||||
|
@ -69,6 +77,10 @@ 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"])
|
||||
|
@ -85,6 +97,16 @@ 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(
|
||||
|
|
|
@ -1,20 +1,54 @@
|
|||
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 = {}
|
||||
|
||||
if "https://hooks.slack.com" in url:
|
||||
# Slack and Google Chat Webhooks
|
||||
if "https://hooks.slack.com" in url or "https://chat.googleapis.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:
|
||||
print(e)
|
||||
log.exception(e)
|
||||
return False
|
||||
|
|
|
@ -7,7 +7,7 @@ ollama
|
|||
{{- end -}}
|
||||
|
||||
{{- define "ollama.url" -}}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d/api" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "chart.name" -}}
|
||||
|
|
|
@ -88,7 +88,7 @@ spec:
|
|||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.ollama.persistence.size | quote }}
|
||||
storageClass: {{ .Values.ollama.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.ollama.persistence.storageClass }}
|
||||
{{- with .Values.ollama.persistence.selector }}
|
||||
selector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
|
|
|
@ -17,7 +17,7 @@ spec:
|
|||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.webui.persistence.size }}
|
||||
storageClass: {{ .Values.webui.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.webui.persistence.storageClass }}
|
||||
{{- with .Values.webui.persistence.selector }}
|
||||
selector:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
|
|
|
@ -35,4 +35,4 @@ spec:
|
|||
volumes:
|
||||
- name: webui-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: ollama-webui-pvc
|
||||
claimName: open-webui-pvc
|
|
@ -2,8 +2,8 @@ apiVersion: v1
|
|||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
labels:
|
||||
app: ollama-webui
|
||||
name: ollama-webui-pvc
|
||||
app: open-webui
|
||||
name: open-webui-pvc
|
||||
namespace: open-webui
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
|
|
200
package-lock.json
generated
200
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.114",
|
||||
"version": "0.1.117",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.1.114",
|
||||
"version": "0.1.117",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"async": "^3.2.5",
|
||||
|
@ -19,6 +19,7 @@
|
|||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"idb": "^7.1.1",
|
||||
"js-sha256": "^0.10.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^9.1.0",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
|
@ -1067,6 +1068,12 @@
|
|||
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
|
@ -1402,6 +1409,17 @@
|
|||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
},
|
||||
"node_modules/atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"bin": {
|
||||
"atob": "bin/atob.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.19",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
||||
|
@ -1459,6 +1477,15 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
@ -1666,6 +1693,17 @@
|
|||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/btoa": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
|
||||
"bin": {
|
||||
"btoa": "bin/btoa.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
|
@ -1758,6 +1796,31 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
|
||||
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/raf": "^3.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/canvg/node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
|
@ -1944,6 +2007,17 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.36.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
|
||||
"integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
|
@ -1964,6 +2038,15 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
|
@ -2156,6 +2239,12 @@
|
|||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.9.tgz",
|
||||
"integrity": "sha512-iHtnxYMotKgOTvxIqq677JsKHvCOkAFqj9x8Mek2zdeHW1XjuFKwjpmZeMaXQRQ8AbJZDbcRz/+r1QhwvFtmQg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
|
@ -2584,6 +2673,11 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
|
||||
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||
|
@ -3003,6 +3097,19 @@
|
|||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
|
@ -3403,10 +3510,27 @@
|
|||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz",
|
||||
"integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"atob": "^2.1.2",
|
||||
"btoa": "^1.2.1",
|
||||
"fflate": "^0.4.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.6",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^2.2.0",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.9",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz",
|
||||
"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==",
|
||||
"version": "0.16.10",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
|
||||
"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
|
@ -3971,6 +4095,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
|
||||
|
@ -4391,6 +4521,15 @@
|
|||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -4494,6 +4633,15 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rgbcolor": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
|
@ -4814,6 +4962,15 @@
|
|||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-composer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
|
||||
|
@ -5215,6 +5372,15 @@
|
|||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-pathdata": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symlink-or-copy": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz",
|
||||
|
@ -5353,6 +5519,15 @@
|
|||
"streamx": "^2.12.5"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
|
@ -5583,6 +5758,15 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
|
@ -5676,9 +5860,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.115",
|
||||
"version": "0.1.117",
|
||||
"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": "yapf --recursive backend -p -i",
|
||||
"format:backend": "black . --exclude \"/venv/\"",
|
||||
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write 'src/lib/i18n/**/*.{js,json}'"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -53,6 +53,7 @@
|
|||
"i18next-resources-to-backend": "^1.2.0",
|
||||
"idb": "^7.1.1",
|
||||
"js-sha256": "^0.10.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"katex": "^0.16.9",
|
||||
"marked": "^9.1.0",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
|
|
|
@ -78,3 +78,7 @@ select {
|
|||
/* for Chrome */
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.katex-mathml {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
<script>
|
||||
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||
(() => {
|
||||
if (
|
||||
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 (
|
||||
localStorage.theme === 'light' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: light)').matches)
|
||||
) {
|
||||
|
|
|
@ -318,3 +318,78 @@ 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;
|
||||
};
|
||||
|
|
|
@ -218,6 +218,102 @@ 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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -263,3 +263,53 @@ 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';
|
||||
};
|
||||
|
|
|
@ -22,6 +22,57 @@ export const getGravatarUrl = async (email: string) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const downloadChatAsPDF = async (chat: object) => {
|
||||
let error = null;
|
||||
|
||||
const blob = await fetch(`${WEBUI_API_BASE_URL}/utils/pdf`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: chat.title,
|
||||
messages: chat.messages
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.blob();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
return blob;
|
||||
};
|
||||
|
||||
export const getHTMLFromMarkdown = async (md: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/utils/markdown`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
md: md
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
return res.html;
|
||||
};
|
||||
|
||||
export const downloadDatabase = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
const i18n = getContext('i18n');
|
||||
|
||||
export let chatId = '';
|
||||
export let readOnly = false;
|
||||
export let sendPrompt: Function;
|
||||
export let continueGeneration: Function;
|
||||
export let regenerateResponse: Function;
|
||||
|
@ -317,6 +318,7 @@
|
|||
<UserMessage
|
||||
on:delete={() => messageDeleteHandler(message.id)}
|
||||
user={$user}
|
||||
{readOnly}
|
||||
{message}
|
||||
isFirstMessage={messageIdx === 0}
|
||||
siblings={message.parentId !== null
|
||||
|
@ -335,6 +337,7 @@
|
|||
modelfiles={selectedModelfiles}
|
||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
||||
isLastMessage={messageIdx + 1 === messages.length}
|
||||
{readOnly}
|
||||
{confirmEditResponseMessage}
|
||||
{showPreviousMessage}
|
||||
{showNextMessage}
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
import { config, settings } from '$lib/stores';
|
||||
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
||||
import { imageGenerations } from '$lib/apis/images';
|
||||
import { extractSentences } from '$lib/utils';
|
||||
import {
|
||||
extractSentences,
|
||||
revertSanitizedResponseContent,
|
||||
sanitizeResponseContent
|
||||
} from '$lib/utils';
|
||||
|
||||
import Name from './Name.svelte';
|
||||
import ProfileImage from './ProfileImage.svelte';
|
||||
|
@ -33,6 +37,8 @@
|
|||
|
||||
export let isLastMessage = true;
|
||||
|
||||
export let readOnly = false;
|
||||
|
||||
export let confirmEditResponseMessage: Function;
|
||||
export let showPreviousMessage: Function;
|
||||
export let showNextMessage: Function;
|
||||
|
@ -54,7 +60,7 @@
|
|||
let loadingSpeech = false;
|
||||
let generatingImage = false;
|
||||
|
||||
$: tokens = marked.lexer(message.content);
|
||||
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
|
@ -128,7 +134,7 @@
|
|||
// • auto-render specific keys, e.g.:
|
||||
delimiters: [
|
||||
{ left: '$$', right: '$$', display: false },
|
||||
{ left: '$', right: '$', display: false },
|
||||
{ left: '$ ', right: ' $', display: false },
|
||||
{ left: '\\(', right: '\\)', display: false },
|
||||
{ left: '\\[', right: '\\]', display: false },
|
||||
{ left: '[ ', right: ' ]', display: false }
|
||||
|
@ -403,8 +409,10 @@
|
|||
{:else}
|
||||
{#each tokens as token}
|
||||
{#if token.type === 'code'}
|
||||
<!-- {token.text} -->
|
||||
<CodeBlock lang={token.lang} code={token.text} />
|
||||
<CodeBlock
|
||||
lang={token.lang}
|
||||
code={revertSanitizedResponseContent(token.text)}
|
||||
/>
|
||||
{:else}
|
||||
{@html marked.parse(token.raw, {
|
||||
...defaults,
|
||||
|
@ -422,7 +430,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 -mt-1">
|
||||
<div class="flex self-center min-w-fit">
|
||||
<button
|
||||
class="self-center dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
|
@ -469,6 +477,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !readOnly}
|
||||
<Tooltip content="Edit" placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
|
@ -494,6 +503,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content="Copy" placement="bottom">
|
||||
<button
|
||||
|
@ -521,6 +531,7 @@
|
|||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if !readOnly}
|
||||
<Tooltip content="Good Response" placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
|
@ -574,6 +585,7 @@
|
|||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content="Read Aloud" placement="bottom">
|
||||
<button
|
||||
|
@ -656,7 +668,7 @@
|
|||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if $config.images}
|
||||
{#if $config.images && !readOnly}
|
||||
<Tooltip content="Generate Image" placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
|
@ -752,7 +764,7 @@
|
|||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if isLastMessage}
|
||||
{#if isLastMessage && !readOnly}
|
||||
<Tooltip content="Continue Response" placement="bottom">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let message;
|
||||
export let siblings;
|
||||
export let isFirstMessage: boolean;
|
||||
export let readOnly: boolean;
|
||||
|
||||
export let confirmEditMessage: Function;
|
||||
export let showPreviousMessage: Function;
|
||||
|
@ -203,7 +204,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 -mt-1">
|
||||
<div class="flex self-center">
|
||||
<button
|
||||
class="self-center dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
|
@ -250,6 +251,7 @@
|
|||
</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"
|
||||
|
@ -273,6 +275,7 @@
|
|||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content="Copy" placement="bottom">
|
||||
<button
|
||||
|
@ -298,7 +301,7 @@
|
|||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if !isFirstMessage}
|
||||
{#if !isFirstMessage && !readOnly}
|
||||
<Tooltip content="Delete" placement="bottom">
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<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 Select from '../common/Select.svelte';
|
||||
import Selector from './ModelSelector/Selector.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -33,19 +36,19 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col my-2 w-full">
|
||||
<div class="flex flex-col mt-0.5 w-full">
|
||||
{#each selectedModels as selectedModel, selectedModelIdx}
|
||||
<div class="flex w-full">
|
||||
<div class="overflow-hidden w-full">
|
||||
<div class="mr-2 max-w-full">
|
||||
<Select
|
||||
<div class="mr-0.5 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 + `${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`
|
||||
label: model.name,
|
||||
info: model
|
||||
}))}
|
||||
bind:value={selectedModel}
|
||||
/>
|
||||
|
@ -53,10 +56,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=" self-center {selectedModelIdx === 0
|
||||
? 'mr-3'
|
||||
: 'mr-7'} disabled:text-gray-600 disabled:hover:text-gray-600"
|
||||
class=" "
|
||||
{disabled}
|
||||
on:click={() => {
|
||||
selectedModels = [...selectedModels, ''];
|
||||
|
@ -73,12 +76,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);
|
||||
|
@ -96,41 +99,13 @@
|
|||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
|
||||
</svg>
|
||||
</button>
|
||||
{/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>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="text-left mt-1.5 ml-1 text-xs text-gray-500">
|
||||
<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
|
||||
<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
|
||||
</div>
|
||||
|
|
389
src/lib/components/chat/ModelSelector/Selector.svelte
Normal file
389
src/lib/components/chat/ModelSelector/Selector.svelte
Normal file
|
@ -0,0 +1,389 @@
|
|||
<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>
|
|
@ -6,6 +6,8 @@
|
|||
import { compareVersion } from '$lib/utils';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
let ollamaVersion = '';
|
||||
|
@ -51,8 +53,10 @@
|
|||
</div>
|
||||
<div class="flex w-full justify-between items-center">
|
||||
<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
|
||||
<div>
|
||||
<div class="flex gap-1">
|
||||
<Tooltip content={WEBUI_VERSION === '0.1.117' ? "🪖 We're just getting started." : ''}>
|
||||
v{WEBUI_VERSION}
|
||||
</Tooltip>
|
||||
|
||||
<a
|
||||
href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
|
||||
|
@ -126,7 +130,9 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t('Created by')}
|
||||
{#if !$WEBUI_NAME.includes('Open WebUI')}
|
||||
<span class=" text-gray-500 dark:text-gray-300 font-medium">{$WEBUI_NAME}</span> -
|
||||
{/if}{$i18n.t('Created by')}
|
||||
<a
|
||||
class=" text-gray-500 dark:text-gray-300 font-medium"
|
||||
href="https://github.com/tjbck"
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
import { onMount, getContext } from 'svelte';
|
||||
|
||||
import { user } from '$lib/stores';
|
||||
import { updateUserProfile } from '$lib/apis/auths';
|
||||
import { updateUserProfile, createAPIKey, getAPIKey } 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');
|
||||
|
||||
|
@ -15,8 +17,14 @@
|
|||
|
||||
let profileImageUrl = '';
|
||||
let name = '';
|
||||
|
||||
let showJWTToken = false;
|
||||
let JWTTokenCopied = false;
|
||||
|
||||
let APIKey = '';
|
||||
let showAPIKey = false;
|
||||
let APIKeyCopied = false;
|
||||
|
||||
let profileImageInputElement: HTMLInputElement;
|
||||
|
||||
const submitHandler = async () => {
|
||||
|
@ -33,9 +41,23 @@
|
|||
return false;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
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 () => {
|
||||
name = $user.name;
|
||||
profileImageUrl = $user.profile_image_url;
|
||||
|
||||
APIKey = await getAPIKey(localStorage.token).catch((error) => {
|
||||
console.log(error);
|
||||
return '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -170,22 +192,23 @@
|
|||
|
||||
<hr class=" dark:border-gray-700 my-4" />
|
||||
|
||||
<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 class="flex flex-col gap-4">
|
||||
<div class="justify-between w-full">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none"
|
||||
type={showJWTToken ? 'text' : 'password'}
|
||||
value={localStorage.token}
|
||||
disabled
|
||||
/>
|
||||
|
||||
<button
|
||||
class="dark:bg-gray-800 px-2 transition rounded-r-lg"
|
||||
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800"
|
||||
on:click={() => {
|
||||
showJWTToken = !showJWTToken;
|
||||
}}
|
||||
|
@ -225,7 +248,7 @@
|
|||
</div>
|
||||
|
||||
<button
|
||||
class="ml-1.5 px-1.5 py-1 hover:bg-gray-800 transition rounded-lg"
|
||||
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg"
|
||||
on:click={() => {
|
||||
copyToClipboard(localStorage.token);
|
||||
JWTTokenCopied = true;
|
||||
|
@ -269,6 +292,143 @@
|
|||
</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">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
export let getModels: Function;
|
||||
|
||||
// General
|
||||
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
|
||||
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light', 'oled-dark'];
|
||||
let selectedTheme = 'system';
|
||||
|
||||
let languages = [];
|
||||
|
@ -91,12 +91,17 @@
|
|||
});
|
||||
|
||||
const applyTheme = (_theme: string) => {
|
||||
let themeToApply = _theme;
|
||||
let themeToApply = _theme === 'oled-dark' ? 'dark' : _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) => {
|
||||
|
@ -115,7 +120,11 @@
|
|||
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>
|
||||
|
@ -136,6 +145,7 @@
|
|||
>
|
||||
<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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getBackendConfig } from '$lib/apis';
|
||||
import { setDefaultPromptSuggestions } from '$lib/apis/configs';
|
||||
import { config, models, user } from '$lib/stores';
|
||||
import { config, models, settings, user } from '$lib/stores';
|
||||
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
@ -14,6 +14,7 @@
|
|||
let titleAutoGenerate = true;
|
||||
let responseAutoCopy = false;
|
||||
let titleAutoGenerateModel = '';
|
||||
let titleAutoGenerateModelExternal = '';
|
||||
let fullScreenMode = false;
|
||||
let titleGenerationPrompt = '';
|
||||
|
||||
|
@ -33,7 +34,12 @@
|
|||
|
||||
const toggleTitleAutoGenerate = async () => {
|
||||
titleAutoGenerate = !titleAutoGenerate;
|
||||
saveSettings({ titleAutoGenerate: titleAutoGenerate });
|
||||
saveSettings({
|
||||
title: {
|
||||
...$settings.title,
|
||||
auto: titleAutoGenerate
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const toggleResponseAutoCopy = async () => {
|
||||
|
@ -65,8 +71,13 @@
|
|||
}
|
||||
|
||||
saveSettings({
|
||||
titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
|
||||
titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
|
||||
title: {
|
||||
...$settings.title,
|
||||
model: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
|
||||
modelExternal:
|
||||
titleAutoGenerateModelExternal !== '' ? titleAutoGenerateModelExternal : undefined,
|
||||
prompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -77,16 +88,18 @@
|
|||
|
||||
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
|
||||
|
||||
titleAutoGenerate = settings.titleAutoGenerate ?? true;
|
||||
responseAutoCopy = settings.responseAutoCopy ?? false;
|
||||
showUsername = settings.showUsername ?? false;
|
||||
fullScreenMode = settings.fullScreenMode ?? false;
|
||||
titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
|
||||
titleAutoGenerate = settings?.title?.auto ?? true;
|
||||
titleAutoGenerateModel = settings?.title?.model ?? '';
|
||||
titleAutoGenerateModelExternal = settings?.title?.modelExternal ?? '';
|
||||
titleGenerationPrompt =
|
||||
settings.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}}';
|
||||
|
||||
responseAutoCopy = settings.responseAutoCopy ?? false;
|
||||
showUsername = settings.showUsername ?? false;
|
||||
fullScreenMode = settings.fullScreenMode ?? false;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -190,8 +203,9 @@
|
|||
|
||||
<div>
|
||||
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Title Auto-Generation Model')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<div class="flex w-full gap-2 pr-2">
|
||||
<div class="flex-1">
|
||||
<div class=" text-xs mb-1">Local 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={titleAutoGenerateModel}
|
||||
|
@ -207,6 +221,24 @@
|
|||
{/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">
|
||||
|
|
|
@ -258,6 +258,9 @@
|
|||
console.log(error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const error = await fileResponse?.json();
|
||||
toast.error(error?.detail ?? error);
|
||||
}
|
||||
|
||||
if (uploaded) {
|
||||
|
|
|
@ -1,19 +1,124 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import Modal from '../common/Modal.svelte';
|
||||
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 Modal from '../common/Modal.svelte';
|
||||
import Link from '../icons/Link.svelte';
|
||||
|
||||
let chat = null;
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let downloadChat: Function;
|
||||
export let shareChat: Function;
|
||||
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 show = false;
|
||||
|
||||
$: if (show) {
|
||||
(async () => {
|
||||
if ($chatId) {
|
||||
chat = await getChatById(localStorage.token, $chatId);
|
||||
} else {
|
||||
chat = null;
|
||||
console.log(chat);
|
||||
}
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show size="xs">
|
||||
<div class="px-4 pt-4 pb-5 w-full flex flex-col justify-center">
|
||||
<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 px-8 py-1.5 w-full rounded-full text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white"
|
||||
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}
|
||||
<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"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
shareChat();
|
||||
|
@ -23,19 +128,26 @@
|
|||
{$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 rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
|
||||
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"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
downloadChat();
|
||||
shareLocalChat();
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Download as a File')}
|
||||
<Link />
|
||||
|
||||
{#if chat.share_id}
|
||||
{$i18n.t('Update and Copy Link')}
|
||||
{:else}
|
||||
{$i18n.t('Copy Link')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
20
src/lib/components/chat/TagChatModal.svelte
Normal file
20
src/lib/components/chat/TagChatModal.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<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>
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
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' },
|
||||
|
@ -49,22 +52,25 @@
|
|||
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="Search a model"
|
||||
placeholder={searchPlaceholder}
|
||||
/>
|
||||
</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 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 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
|
||||
value={item.value}
|
||||
label={item.label}
|
||||
>
|
||||
|
@ -77,11 +83,13 @@
|
|||
{/if}
|
||||
</Select.Item>
|
||||
{:else}
|
||||
<span class="block px-5 py-2 text-sm text-gray-700 dark:text-gray-100">
|
||||
<div>
|
||||
<div class="block px-5 py-2 text-sm text-gray-700 dark:text-gray-100">
|
||||
No results found
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</slot>
|
||||
</Select.Content>
|
||||
<Select.Input name="favoriteFruit" />
|
||||
</Select.Root>
|
|
@ -1,24 +1,25 @@
|
|||
<script lang="ts">
|
||||
export let className: string = 'text-white';
|
||||
export let theme: 'blue' | 'white' | 'black' = 'white';
|
||||
export let className: string = '';
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center text-center {className}">
|
||||
<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"
|
||||
<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
|
||||
>
|
||||
<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>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export let addTag: Function;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row space-x-0.5 line-clamp-1">
|
||||
<div class="flex flex-row flex-wrap gap-0.5 line-clamp-1">
|
||||
<TagList
|
||||
{tags}
|
||||
on:delete={(e) => {
|
||||
|
@ -17,6 +17,7 @@
|
|||
/>
|
||||
|
||||
<TagInput
|
||||
label={tags.length == 0 ? 'Add Tags' : ''}
|
||||
on:add={(e) => {
|
||||
addTag(e.detail);
|
||||
}}
|
||||
|
|
|
@ -1,24 +1,31 @@
|
|||
<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={() => {
|
||||
dispatch('add', tagName);
|
||||
tagName = '';
|
||||
showTagInput = false;
|
||||
}}
|
||||
>
|
||||
<button type="button" on:click={addTagHandler}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
|
@ -34,12 +41,21 @@
|
|||
</button>
|
||||
<input
|
||||
bind:value={tagName}
|
||||
class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[8rem]"
|
||||
class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[5.5rem]"
|
||||
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
|
||||
|
@ -62,4 +78,8 @@
|
|||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if label && !showTagInput}
|
||||
<span class="text-xs pl-1.5 self-center">{label}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -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-600 dark:text-white"
|
||||
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"
|
||||
>
|
||||
<div class=" text-[0.7rem] font-medium self-center line-clamp-1">
|
||||
{tag.name}
|
||||
|
|
|
@ -29,6 +29,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={tooltipElement} aria-label={content}>
|
||||
<div bind:this={tooltipElement} aria-label={content} class="flex">
|
||||
<slot />
|
||||
</div>
|
||||
|
|
19
src/lib/components/icons/ChevronUpDown.svelte
Normal file
19
src/lib/components/icons/ChevronUpDown.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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>
|
19
src/lib/components/icons/EllipsisVertical.svelte
Normal file
19
src/lib/components/icons/EllipsisVertical.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<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>
|
16
src/lib/components/icons/Link.svelte
Normal file
16
src/lib/components/icons/Link.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<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>
|
15
src/lib/components/icons/Plus.svelte
Normal file
15
src/lib/components/icons/Plus.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<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>
|
|
@ -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 { getChatById } from '$lib/apis/chats';
|
||||
import { WEBUI_NAME, chatId, modelfiles, settings } from '$lib/stores';
|
||||
import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
|
||||
|
||||
import { slide } from 'svelte/transition';
|
||||
import ShareChatModal from '../chat/ShareChatModal.svelte';
|
||||
import TagInput from '../common/Tags/TagInput.svelte';
|
||||
import Tags from '../common/Tags.svelte';
|
||||
import ModelSelector from '../chat/ModelSelector.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import Menu from './Navbar/Menu.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -16,71 +16,108 @@
|
|||
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 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`);
|
||||
};
|
||||
let showDownloadChatModal = false;
|
||||
</script>
|
||||
|
||||
<ShareChatModal bind:show={showShareChatModal} {downloadChat} {shareChat} />
|
||||
<ShareChatModal bind:show={showShareChatModal} />
|
||||
<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="pr-2 self-start">
|
||||
<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">
|
||||
<button
|
||||
id="new-chat-button"
|
||||
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-lg transition"
|
||||
on:click={initNewChat}
|
||||
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
|
||||
on:click={() => {
|
||||
initNewChat();
|
||||
}}
|
||||
>
|
||||
<div class=" m-auto self-center">
|
||||
<svg
|
||||
|
@ -98,41 +135,7 @@
|
|||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</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}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
223
src/lib/components/layout/Navbar/Menu.svelte
Normal file
223
src/lib/components/layout/Navbar/Menu.svelte
Normal file
|
@ -0,0 +1,223 @@
|
|||
<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>
|
|
@ -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 z-40 fixed 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 fixed z-50 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 dark:hover:bg-gray-800 transition"
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
goto('/admin');
|
||||
showDropdown = false;
|
||||
|
@ -607,7 +607,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition"
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
goto('/playground');
|
||||
showDropdown = false;
|
||||
|
@ -634,7 +634,7 @@
|
|||
{/if}
|
||||
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition"
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={async () => {
|
||||
await showSettings.set(true);
|
||||
showDropdown = false;
|
||||
|
@ -669,7 +669,7 @@
|
|||
|
||||
<div class="py-2 w-full">
|
||||
<button
|
||||
class="flex py-2.5 px-3.5 w-full dark:hover:bg-gray-800 transition"
|
||||
class="flex py-2.5 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('token');
|
||||
location.href = '/auth';
|
||||
|
@ -706,7 +706,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="fixed left-0 top-[50dvh] z-40 -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||
class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||
>
|
||||
<Tooltip
|
||||
placement="right"
|
||||
|
|
|
@ -22,6 +22,7 @@ export const SUPPORTED_FILE_TYPE = [
|
|||
'text/plain',
|
||||
'text/csv',
|
||||
'text/xml',
|
||||
'text/html',
|
||||
'text/x-python',
|
||||
'text/css',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
|
@ -50,6 +51,8 @@ export const SUPPORTED_FILE_EXTENSIONS = [
|
|||
'h',
|
||||
'c',
|
||||
'cs',
|
||||
'htm',
|
||||
'html',
|
||||
'sql',
|
||||
'log',
|
||||
'ini',
|
||||
|
|
|
@ -37,23 +37,28 @@ const createIsLoadingStore = (i18n: i18nType) => {
|
|||
return isLoading;
|
||||
};
|
||||
|
||||
i18next
|
||||
.use(
|
||||
resourcesToBackend(
|
||||
(language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`)
|
||||
)
|
||||
)
|
||||
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(LanguageDetector)
|
||||
.init({
|
||||
debug: false,
|
||||
detection: {
|
||||
order: ['querystring', 'localStorage', 'navigator'],
|
||||
order: detectionOrder,
|
||||
caches: ['localStorage'],
|
||||
lookupQuerystring: 'lang',
|
||||
lookupLocalStorage: 'locale'
|
||||
},
|
||||
fallbackLng: {
|
||||
default: ['en-US']
|
||||
default: fallbackDefaultLocale
|
||||
},
|
||||
ns: 'translation',
|
||||
returnEmptyString: false,
|
||||
|
@ -61,6 +66,7 @@ i18next
|
|||
escapeValue: false // not needed for svelte as it escapes by default
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const i18n = createI18nStore(i18next);
|
||||
const isLoadingStore = createIsLoadingStore(i18next);
|
||||
|
|
|
@ -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.": "към чат входа.",
|
||||
|
|
|
@ -1,128 +1,128 @@
|
|||
{
|
||||
"'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",
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "",
|
||||
"(Beta)": "",
|
||||
"(e.g. `sh webui.sh --api`)": "",
|
||||
"(latest)": "",
|
||||
"{{modelName}} is thinking...": "",
|
||||
"{{webUIName}} Backend Required": "",
|
||||
"a user": "",
|
||||
"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?",
|
||||
"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": "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",
|
||||
"and": "",
|
||||
"API Base URL": "",
|
||||
"API Key": "",
|
||||
"API RPM": "",
|
||||
"are allowed - Activate this command by typing": "",
|
||||
"Are you sure?": "",
|
||||
"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",
|
||||
"Audio": "",
|
||||
"Auto-playback response": "",
|
||||
"Auto-send input after 3 sec.": "",
|
||||
"AUTOMATIC1111 Base URL": "",
|
||||
"AUTOMATIC1111 Base URL is required.": "",
|
||||
"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.",
|
||||
"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.": "",
|
||||
"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.": "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",
|
||||
"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": "",
|
||||
"Default (Automatic1111)": "",
|
||||
"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",
|
||||
"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": "",
|
||||
"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 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)",
|
||||
"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)": "",
|
||||
"Image Generation Engine": "",
|
||||
"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!",
|
||||
"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!": "",
|
||||
"Ollama Base URL": "",
|
||||
"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",
|
||||
"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": "",
|
||||
"Open AI (Dall-E)": "",
|
||||
"Open new chat": "Open new chat",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"Open new chat": "",
|
||||
"OpenAI API": "",
|
||||
"OpenAI API Key": "",
|
||||
"OpenAI API Key is required.": "",
|
||||
"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",
|
||||
"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": "",
|
||||
"Select a mode": "",
|
||||
"Select a model": "Select a model",
|
||||
"Select a model": "",
|
||||
"Select an Ollama instance": "",
|
||||
"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",
|
||||
"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": "",
|
||||
"Type Hugging Face Resolve (Download) URL": "",
|
||||
"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.",
|
||||
"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.": "",
|
||||
"Use Gravatar": "",
|
||||
"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",
|
||||
"What’s New in": "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.": "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."
|
||||
"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": "",
|
||||
"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)": "",
|
||||
"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.": ""
|
||||
}
|
||||
|
|
|
@ -360,4 +360,4 @@
|
|||
"You": "Tu",
|
||||
"You're a helpful assistant.": "Sei un assistente utile.",
|
||||
"You're now logged in.": "Ora hai effettuato l'accesso."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -360,4 +360,4 @@
|
|||
"You": "あなた",
|
||||
"You're a helpful assistant.": "あなたは役に立つアシスタントです。",
|
||||
"You're now logged in.": "ログインしました。"
|
||||
}
|
||||
}
|
||||
|
|
362
src/lib/i18n/locales/ko-KR/translation.json
Normal file
362
src/lib/i18n/locales/ko-KR/translation.json
Normal file
|
@ -0,0 +1,362 @@
|
|||
{
|
||||
"'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.": "로그인되었습니다."
|
||||
}
|
|
@ -39,6 +39,10 @@
|
|||
"code": "ja-JP",
|
||||
"title": "Japanese"
|
||||
},
|
||||
{
|
||||
"code": "ko-KR",
|
||||
"title": "Korean"
|
||||
},
|
||||
{
|
||||
"code": "nl-NL",
|
||||
"title": "Dutch (Netherlands)"
|
||||
|
@ -47,10 +51,18 @@
|
|||
"code": "pt-PT",
|
||||
"title": "Portuguese (Portugal)"
|
||||
},
|
||||
{
|
||||
"code": "pt-BR",
|
||||
"title": "Portuguese (Brazil)"
|
||||
},
|
||||
{
|
||||
"code": "ru-RU",
|
||||
"title": "Russian (Russia)"
|
||||
},
|
||||
{
|
||||
"code": "tr-TR",
|
||||
"title": "Turkish"
|
||||
},
|
||||
{
|
||||
"code": "uk-UA",
|
||||
"title": "Ukrainian"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"(latest)": "(nieuwste)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} is aan het denken...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
|
||||
"a user": "",
|
||||
"a user": "een gebruiker",
|
||||
"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?": "",
|
||||
"Are you sure?": "Zeker weten?",
|
||||
"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 Base URL is required.": "AUTOMATIC1111 Basis URL is verplicht",
|
||||
"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": "",
|
||||
"Click here to select documents.": "",
|
||||
"Click here to select": "Klik hier om te selecteren",
|
||||
"Click here to select documents.": "Klik hier om documenten te selecteren",
|
||||
"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)": "",
|
||||
"Default (Automatic1111)": "Standaard (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": "",
|
||||
"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 {{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 stop sequence": "Zet stop sequentie",
|
||||
"Enter Top K": "",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
|
||||
"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 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": "",
|
||||
"Image Generation Engine": "Afbeelding Generatie 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 Base URL": "Ollama Basis 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 Key is required.": "",
|
||||
"OpenAI API Key": "OpenAI API Sleutel",
|
||||
"OpenAI API Key is required.": "OpenAI API Sleutel is verplicht",
|
||||
"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": "",
|
||||
"Select a mode": "Selecteer een modus",
|
||||
"Select a model": "Selecteer een model",
|
||||
"Select an Ollama instance": "",
|
||||
"Select an Ollama instance": "Selecteer een Ollama instantie",
|
||||
"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": "",
|
||||
"Use Gravatar": "Gebruik Gravatar",
|
||||
"user": "user",
|
||||
"User Permissions": "Gebruikers Rechten",
|
||||
"Users": "Gebruikers",
|
||||
|
|
363
src/lib/i18n/locales/pt-BR/translation.json
Normal file
363
src/lib/i18n/locales/pt-BR/translation.json
Normal file
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 's' ou '-1' para não expirar.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(por exemplo, `sh webui.sh --api`)",
|
||||
"(latest)": "(mais recente)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} está pensando...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
|
||||
"a user": "um usuário",
|
||||
"About": "Sobre",
|
||||
"Account": "Conta",
|
||||
"Action": "Ação",
|
||||
"Add a model": "Adicionar um modelo",
|
||||
"Add a model tag name": "Adicionar um nome de tag de modelo",
|
||||
"Add a short description about what this modelfile does": "Adicione uma breve descrição sobre o que este arquivo de modelo faz",
|
||||
"Add a short title for this prompt": "Adicione um título curto para este prompt",
|
||||
"Add a tag": "Adicionar uma tag",
|
||||
"Add Docs": "Adicionar Documentos",
|
||||
"Add Files": "Adicionar Arquivos",
|
||||
"Add message": "Adicionar mensagem",
|
||||
"add tags": "adicionar tags",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Ajustar essas configurações aplicará alterações universalmente a todos os usuários.",
|
||||
"admin": "administrador",
|
||||
"Admin Panel": "Painel do Administrador",
|
||||
"Admin Settings": "Configurações do Administrador",
|
||||
"Advanced Parameters": "Parâmetros Avançados",
|
||||
"all": "todos",
|
||||
"All Users": "Todos os Usuários",
|
||||
"Allow": "Permitir",
|
||||
"Allow Chat Deletion": "Permitir Exclusão de Bate-papo",
|
||||
"alphanumeric characters and hyphens": "caracteres alfanuméricos e hífens",
|
||||
"Already have an account?": "Já tem uma conta?",
|
||||
"an assistant": "um assistente",
|
||||
"and": "e",
|
||||
"API Base URL": "URL Base da API",
|
||||
"API Key": "Chave da API",
|
||||
"API RPM": "API RPM",
|
||||
"are allowed - Activate this command by typing": "são permitidos - Ative este comando digitando",
|
||||
"Are you sure?": "Tem certeza?",
|
||||
"Audio": "Áudio",
|
||||
"Auto-playback response": "Reprodução automática da resposta",
|
||||
"Auto-send input after 3 sec.": "Enviar entrada automaticamente após 3 segundos.",
|
||||
"AUTOMATIC1111 Base URL": "URL Base do AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "A URL Base do AUTOMATIC1111 é obrigatória.",
|
||||
"available!": "disponível!",
|
||||
"Back": "Voltar",
|
||||
"Builder Mode": "Modo de Construtor",
|
||||
"Cancel": "Cancelar",
|
||||
"Categories": "Categorias",
|
||||
"Change Password": "Alterar Senha",
|
||||
"Chat": "Bate-papo",
|
||||
"Chat History": "Histórico de Bate-papo",
|
||||
"Chat History is off for this browser.": "O histórico de bate-papo está desativado para este navegador.",
|
||||
"Chats": "Bate-papos",
|
||||
"Check Again": "Verifique novamente",
|
||||
"Check for updates": "Verificar atualizações",
|
||||
"Checking for updates...": "Verificando atualizações...",
|
||||
"Choose a model before saving...": "Escolha um modelo antes de salvar...",
|
||||
"Chunk Overlap": "Sobreposição de Fragmento",
|
||||
"Chunk Params": "Parâmetros de Fragmento",
|
||||
"Chunk Size": "Tamanho do Fragmento",
|
||||
"Click here for help.": "Clique aqui para obter ajuda.",
|
||||
"Click here to check other modelfiles.": "Clique aqui para verificar outros arquivos de modelo.",
|
||||
"Click here to select": "Clique aqui para selecionar",
|
||||
"Click here to select documents.": "Clique aqui para selecionar documentos.",
|
||||
"click here.": "clique aqui.",
|
||||
"Click on the user role button to change a user's role.": "Clique no botão de função do usuário para alterar a função de um usuário.",
|
||||
"Close": "Fechar",
|
||||
"Collection": "Coleção",
|
||||
"Command": "Comando",
|
||||
"Confirm Password": "Confirmar Senha",
|
||||
"Connections": "Conexões",
|
||||
"Content": "Conteúdo",
|
||||
"Context Length": "Comprimento do Contexto",
|
||||
"Conversation Mode": "Modo de Conversa",
|
||||
"Copy last code block": "Copiar último bloco de código",
|
||||
"Copy last response": "Copiar última resposta",
|
||||
"Copying to clipboard was successful!": "Cópia para a área de transferência bem-sucedida!",
|
||||
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crie uma frase concisa de 3 a 5 palavras como cabeçalho para a seguinte consulta, aderindo estritamente ao limite de 3 a 5 palavras e evitando o uso da palavra 'título':",
|
||||
"Create a modelfile": "Criar um arquivo de modelo",
|
||||
"Create Account": "Criar Conta",
|
||||
"Created at": "Criado em",
|
||||
"Created by": "Criado por",
|
||||
"Current Model": "Modelo Atual",
|
||||
"Current Password": "Senha Atual",
|
||||
"Custom": "Personalizado",
|
||||
"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
|
||||
"Dark": "Escuro",
|
||||
"Database": "Banco de dados",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm",
|
||||
"Default": "Padrão",
|
||||
"Default (Automatic1111)": "Padrão (Automatic1111)",
|
||||
"Default (Web API)": "Padrão (API Web)",
|
||||
"Default model updated": "Modelo padrão atualizado",
|
||||
"Default Prompt Suggestions": "Sugestões de Prompt Padrão",
|
||||
"Default User Role": "Função de Usuário Padrão",
|
||||
"delete": "excluir",
|
||||
"Delete a model": "Excluir um modelo",
|
||||
"Delete chat": "Excluir bate-papo",
|
||||
"Delete Chats": "Excluir Bate-papos",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} excluído",
|
||||
"Deleted {tagName}": "{tagName} excluído",
|
||||
"Description": "Descrição",
|
||||
"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",
|
||||
"What’s New in": "O que há de novo em",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Quando o histórico está desativado, novos bate-papos neste navegador não aparecerão em seu histórico em nenhum dos seus dispositivos.",
|
||||
"Whisper (Local)": "Whisper (Local)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Escreva uma sugestão de prompt (por exemplo, Quem é você?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Escreva um resumo em 50 palavras que resuma [tópico ou palavra-chave].",
|
||||
"You": "Você",
|
||||
"You're a helpful assistant.": "Você é um assistente útil.",
|
||||
"You're now logged in.": "Você está conectado agora."
|
||||
}
|
363
src/lib/i18n/locales/tr-TR/translation.json
Normal file
363
src/lib/i18n/locales/tr-TR/translation.json
Normal file
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' veya süresiz için '-1'.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(örn. `sh webui.sh --api`)",
|
||||
"(latest)": "(en son)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} düşünüyor...",
|
||||
"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
|
||||
"a user": "bir kullanıcı",
|
||||
"About": "Hakkında",
|
||||
"Account": "Hesap",
|
||||
"Action": "Eylem",
|
||||
"Add a model": "Bir model ekleyin",
|
||||
"Add a model tag name": "Bir model etiket adı ekleyin",
|
||||
"Add a short description about what this modelfile does": "Bu model dosyasının ne yaptığı hakkında kısa bir açıklama ekleyin",
|
||||
"Add a short title for this prompt": "Bu prompt için kısa bir başlık ekleyin",
|
||||
"Add a tag": "Bir etiket ekleyin",
|
||||
"Add Docs": "Dökümanlar Ekle",
|
||||
"Add Files": "Dosyalar Ekle",
|
||||
"Add message": "Mesaj ekle",
|
||||
"add tags": "etiketler ekle",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Bu ayarları ayarlamak değişiklikleri tüm kullanıcılara evrensel olarak uygular.",
|
||||
"admin": "yönetici",
|
||||
"Admin Panel": "Yönetici Paneli",
|
||||
"Admin Settings": "Yönetici Ayarları",
|
||||
"Advanced Parameters": "Gelişmiş Parametreler",
|
||||
"all": "tümü",
|
||||
"All Users": "Tüm Kullanıcılar",
|
||||
"Allow": "İzin ver",
|
||||
"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
|
||||
"alphanumeric characters and hyphens": "alfanumerik karakterler ve tireler",
|
||||
"Already have an account?": "Zaten bir hesabınız mı var?",
|
||||
"an assistant": "bir asistan",
|
||||
"and": "ve",
|
||||
"API Base URL": "API Temel URL",
|
||||
"API Key": "API Anahtarı",
|
||||
"API RPM": "API RPM",
|
||||
"are allowed - Activate this command by typing": "izin verilir - Bu komutu yazarak etkinleştirin",
|
||||
"Are you sure?": "Emin misiniz?",
|
||||
"Audio": "Ses",
|
||||
"Auto-playback response": "Yanıtı otomatik oynatma",
|
||||
"Auto-send input after 3 sec.": "3 saniye sonra otomatik olarak gönder",
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Temel URL gereklidir.",
|
||||
"available!": "mevcut!",
|
||||
"Back": "Geri",
|
||||
"Builder Mode": "Oluşturucu Modu",
|
||||
"Cancel": "İptal",
|
||||
"Categories": "Kategoriler",
|
||||
"Change Password": "Parola Değiştir",
|
||||
"Chat": "Sohbet",
|
||||
"Chat History": "Sohbet Geçmişi",
|
||||
"Chat History is off for this browser.": "Bu tarayıcı için sohbet geçmişi kapalı.",
|
||||
"Chats": "Sohbetler",
|
||||
"Check Again": "Tekrar Kontrol Et",
|
||||
"Check for updates": "Güncellemeleri kontrol et",
|
||||
"Checking for updates...": "Güncellemeler kontrol ediliyor...",
|
||||
"Choose a model before saving...": "Kaydetmeden önce bir model seçin...",
|
||||
"Chunk Overlap": "Chunk Çakışması",
|
||||
"Chunk Params": "Chunk Parametreleri",
|
||||
"Chunk Size": "Chunk Boyutu",
|
||||
"Click here for help.": "Yardım için buraya tıklayın.",
|
||||
"Click here to check other modelfiles.": "Diğer model dosyalarını kontrol etmek için buraya tıklayın.",
|
||||
"Click here to select": "Seçmek için buraya tıklayın",
|
||||
"Click here to select documents.": "Belgeleri seçmek için buraya tıklayın.",
|
||||
"click here.": "buraya tıklayın.",
|
||||
"Click on the user role button to change a user's role.": "Bir kullanıcının rolünü değiştirmek için kullanıcı rolü düğmesine tıklayın.",
|
||||
"Close": "Kapat",
|
||||
"Collection": "Koleksiyon",
|
||||
"Command": "Komut",
|
||||
"Confirm Password": "Parolayı Onayla",
|
||||
"Connections": "Bağlantılar",
|
||||
"Content": "İçerik",
|
||||
"Context Length": "Bağlam Uzunluğu",
|
||||
"Conversation Mode": "Sohbet Modu",
|
||||
"Copy last code block": "Son kod bloğunu kopyala",
|
||||
"Copy last response": "Son yanıtı kopyala",
|
||||
"Copying to clipboard was successful!": "Panoya kopyalama başarılı!",
|
||||
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Aşağıdaki sorgu için başlık olarak 3-5 kelimelik kısa ve öz bir ifade oluşturun, 3-5 kelime sınırına kesinlikle uyun ve 'başlık' kelimesini kullanmaktan kaçının:",
|
||||
"Create a modelfile": "Bir model dosyası oluştur",
|
||||
"Create Account": "Hesap Oluştur",
|
||||
"Created at": "Oluşturulma tarihi",
|
||||
"Created by": "Oluşturan",
|
||||
"Current Model": "Mevcut Model",
|
||||
"Current Password": "Mevcut Parola",
|
||||
"Custom": "Özel",
|
||||
"Customize Ollama models for a specific purpose": "Ollama modellerini belirli bir amaç için özelleştirin",
|
||||
"Dark": "Koyu",
|
||||
"Database": "Veritabanı",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "Varsayılan",
|
||||
"Default (Automatic1111)": "Varsayılan (Automatic1111)",
|
||||
"Default (Web API)": "Varsayılan (Web API)",
|
||||
"Default model updated": "Varsayılan model güncellendi",
|
||||
"Default Prompt Suggestions": "Varsayılan Prompt Önerileri",
|
||||
"Default User Role": "Varsayılan Kullanıcı Rolü",
|
||||
"delete": "sil",
|
||||
"Delete a model": "Bir modeli sil",
|
||||
"Delete chat": "Sohbeti sil",
|
||||
"Delete Chats": "Sohbetleri Sil",
|
||||
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} silindi",
|
||||
"Deleted {tagName}": "{tagName} silindi",
|
||||
"Description": "Açıklama",
|
||||
"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:",
|
||||
"What’s New in": "Yenilikler:",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Geçmiş kapatıldığında, bu tarayıcıdaki yeni sohbetler hiçbir cihazınızdaki geçmişinizde görünmez.",
|
||||
"Whisper (Local)": "Whisper (Yerel)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Bir prompt önerisi yazın (örn. Sen kimsin?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
|
||||
"You": "Siz",
|
||||
"You're a helpful assistant.": "Sen yardımcı bir asistansın.",
|
||||
"You're now logged in.": "Şimdi oturum açtınız."
|
||||
}
|
|
@ -7,8 +7,9 @@ export const config = writable(undefined);
|
|||
export const user = writable(undefined);
|
||||
|
||||
// Frontend
|
||||
export const theme = writable('system');
|
||||
export const MODEL_DOWNLOAD_POOL = writable({});
|
||||
|
||||
export const theme = writable('system');
|
||||
export const chatId = writable('');
|
||||
|
||||
export const chats = writable([]);
|
||||
|
|
|
@ -1,10 +1,55 @@
|
|||
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('<', '<')
|
||||
.trim();
|
||||
};
|
||||
|
||||
export const revertSanitizedResponseContent = (content: string) => {
|
||||
return content.replaceAll('<', '<');
|
||||
};
|
||||
|
||||
export const capitalizeFirstLetter = (string) => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
export const splitStream = (splitOn) => {
|
||||
let buffer = '';
|
||||
return new TransformStream({
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
} from '$lib/stores';
|
||||
import { copyToClipboard, splitStream } from '$lib/utils';
|
||||
|
||||
import { generateChatCompletion, cancelOllamaRequest, generateTitle } from '$lib/apis/ollama';
|
||||
import { generateChatCompletion, cancelOllamaRequest } 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 } from '$lib/apis/openai';
|
||||
import { generateOpenAIChatCompletion, generateTitle } 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 } from '$lib/constants';
|
||||
import { LITELLM_API_BASE_URL, OLLAMA_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
@ -48,6 +48,7 @@
|
|||
let messagesContainerElement: HTMLDivElement;
|
||||
let currentRequestId = null;
|
||||
|
||||
let showModelSelector = true;
|
||||
let selectedModels = [''];
|
||||
|
||||
let selectedModelfile = null;
|
||||
|
@ -511,13 +512,13 @@
|
|||
|
||||
if (messages.length == 2 && messages.at(1).content !== '') {
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
await generateChatTitle(_chatId, userPrompt);
|
||||
const _title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, _title);
|
||||
}
|
||||
};
|
||||
|
||||
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
|
||||
const responseMessage = history.messages[responseMessageId];
|
||||
scrollToBottom();
|
||||
|
||||
const docs = messages
|
||||
.filter((message) => message?.files ?? null)
|
||||
|
@ -528,6 +529,8 @@
|
|||
|
||||
console.log(docs);
|
||||
|
||||
console.log(model);
|
||||
|
||||
const res = await generateOpenAIChatCompletion(
|
||||
localStorage.token,
|
||||
{
|
||||
|
@ -580,9 +583,16 @@
|
|||
max_tokens: $settings?.options?.num_predict ?? undefined,
|
||||
docs: docs.length > 0 ? docs : undefined
|
||||
},
|
||||
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
|
||||
model?.source?.toLowerCase() === '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())
|
||||
|
@ -696,11 +706,8 @@
|
|||
if (messages.length == 2) {
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
|
||||
if ($settings?.titleAutoGenerateModel) {
|
||||
await generateChatTitle(_chatId, userPrompt);
|
||||
} else {
|
||||
await setChatTitle(_chatId, userPrompt);
|
||||
}
|
||||
const _title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, _title);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -754,23 +761,46 @@
|
|||
}
|
||||
};
|
||||
|
||||
const generateChatTitle = async (_chatId, userPrompt) => {
|
||||
if ($settings.titleAutoGenerate ?? true) {
|
||||
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 title = await generateTitle(
|
||||
localStorage.token,
|
||||
$settings?.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}}',
|
||||
$settings?.titleAutoGenerateModel ?? selectedModels[0],
|
||||
userPrompt
|
||||
titleModelId,
|
||||
userPrompt,
|
||||
titleModel?.external ?? false
|
||||
? titleModel?.source?.toLowerCase() === 'litellm'
|
||||
? `${LITELLM_API_BASE_URL}/v1`
|
||||
: `${OPENAI_API_BASE_URL}`
|
||||
: `${OLLAMA_API_BASE_URL}/v1`
|
||||
);
|
||||
|
||||
if (title) {
|
||||
await setChatTitle(_chatId, title);
|
||||
}
|
||||
return title;
|
||||
} else {
|
||||
await setChatTitle(_chatId, `${userPrompt}`);
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -801,17 +831,6 @@
|
|||
|
||||
_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>
|
||||
|
@ -823,7 +842,17 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="h-screen max-h-[100dvh] w-full flex flex-col">
|
||||
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
|
||||
<Navbar
|
||||
{title}
|
||||
bind:selectedModels
|
||||
bind:showModelSelector
|
||||
shareEnabled={messages.length > 0}
|
||||
{chat}
|
||||
{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"
|
||||
|
@ -835,15 +864,7 @@
|
|||
messagesContainerElement.clientHeight + 5;
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<div class=" h-full w-full flex flex-col pt-2 pb-4">
|
||||
<Messages
|
||||
chatId={$chatId}
|
||||
{selectedModels}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
} from '$lib/stores';
|
||||
import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils';
|
||||
|
||||
import { generateChatCompletion, generateTitle, cancelOllamaRequest } from '$lib/apis/ollama';
|
||||
import { generateChatCompletion, cancelOllamaRequest } from '$lib/apis/ollama';
|
||||
import {
|
||||
addTagById,
|
||||
createNewChat,
|
||||
|
@ -31,14 +31,19 @@
|
|||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { queryCollection, queryDoc } from '$lib/apis/rag';
|
||||
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
|
||||
import { generateOpenAIChatCompletion, generateTitle } 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, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import {
|
||||
LITELLM_API_BASE_URL,
|
||||
OPENAI_API_BASE_URL,
|
||||
OLLAMA_API_BASE_URL,
|
||||
WEBUI_BASE_URL
|
||||
} from '$lib/constants';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -51,8 +56,10 @@
|
|||
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
|
||||
|
@ -521,13 +528,13 @@
|
|||
|
||||
if (messages.length == 2 && messages.at(1).content !== '') {
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
await generateChatTitle(_chatId, userPrompt);
|
||||
const _title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, _title);
|
||||
}
|
||||
};
|
||||
|
||||
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
|
||||
const responseMessage = history.messages[responseMessageId];
|
||||
scrollToBottom();
|
||||
|
||||
const docs = messages
|
||||
.filter((message) => message?.files ?? null)
|
||||
|
@ -590,9 +597,16 @@
|
|||
max_tokens: $settings?.options?.num_predict ?? undefined,
|
||||
docs: docs.length > 0 ? docs : undefined
|
||||
},
|
||||
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
|
||||
model?.source?.toLowerCase() === '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,11 +720,8 @@
|
|||
if (messages.length == 2) {
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
|
||||
if ($settings?.titleAutoGenerateModel) {
|
||||
await generateChatTitle(_chatId, userPrompt);
|
||||
} else {
|
||||
await setChatTitle(_chatId, userPrompt);
|
||||
}
|
||||
const _title = await generateChatTitle(userPrompt);
|
||||
await setChatTitle(_chatId, _title);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -719,6 +730,19 @@
|
|||
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));
|
||||
|
@ -751,36 +775,35 @@
|
|||
}
|
||||
};
|
||||
|
||||
const regenerateResponse = async () => {
|
||||
console.log('regenerateResponse');
|
||||
if (messages.length != 0 && messages.at(-1).done == true) {
|
||||
messages.splice(messages.length - 1, 1);
|
||||
messages = messages;
|
||||
const generateChatTitle = async (userPrompt) => {
|
||||
if ($settings?.title?.auto ?? true) {
|
||||
const model = $models.find((model) => model.id === selectedModels[0]);
|
||||
|
||||
let userMessage = messages.at(-1);
|
||||
let userPrompt = userMessage.content;
|
||||
const titleModelId =
|
||||
model?.external ?? false
|
||||
? $settings?.title?.modelExternal ?? selectedModels[0]
|
||||
: $settings?.title?.model ?? selectedModels[0];
|
||||
const titleModel = $models.find((model) => model.id === titleModelId);
|
||||
|
||||
await sendPrompt(userPrompt, userMessage.id);
|
||||
}
|
||||
};
|
||||
|
||||
const generateChatTitle = async (_chatId, userPrompt) => {
|
||||
if ($settings.titleAutoGenerate ?? true) {
|
||||
console.log(titleModel);
|
||||
const title = await generateTitle(
|
||||
localStorage.token,
|
||||
$settings?.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}}',
|
||||
$settings?.titleAutoGenerateModel ?? selectedModels[0],
|
||||
userPrompt
|
||||
titleModelId,
|
||||
userPrompt,
|
||||
titleModel?.external ?? false
|
||||
? titleModel?.source?.toLowerCase() === 'litellm'
|
||||
? `${LITELLM_API_BASE_URL}/v1`
|
||||
: `${OPENAI_API_BASE_URL}`
|
||||
: `${OLLAMA_API_BASE_URL}/v1`
|
||||
);
|
||||
|
||||
if (title) {
|
||||
await setChatTitle(_chatId, title);
|
||||
}
|
||||
return title;
|
||||
} else {
|
||||
await setChatTitle(_chatId, `${userPrompt}`);
|
||||
return `${userPrompt}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -789,8 +812,10 @@
|
|||
title = _title;
|
||||
}
|
||||
|
||||
if ($settings.saveChatHistory ?? true) {
|
||||
chat = await updateChatById(localStorage.token, _chatId, { title: _title });
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
}
|
||||
};
|
||||
|
||||
const getTags = async () => {
|
||||
|
@ -840,6 +865,9 @@
|
|||
<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) {
|
||||
|
@ -864,15 +892,7 @@
|
|||
messagesContainerElement.clientHeight + 5;
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<div class=" h-full w-full flex flex-col py-4">
|
||||
<Messages
|
||||
chatId={$chatId}
|
||||
{selectedModels}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
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');
|
||||
|
||||
|
@ -315,27 +316,24 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
<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
|
||||
}))}
|
||||
bind:value={selectedModelId}
|
||||
>
|
||||
<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>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
class=" self-center dark:hover:text-gray-300"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import '../tailwind.css';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import i18n from '$lib/i18n';
|
||||
import i18n, { initI18n } from '$lib/i18n';
|
||||
|
||||
setContext('i18n', i18n);
|
||||
|
||||
|
@ -25,6 +25,11 @@
|
|||
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);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue