main #2

Merged
tdpeuter merged 129 commits from open-webui/open-webui:main into main 2024-03-20 21:29:12 +01:00
41 changed files with 1104 additions and 814 deletions
Showing only changes of commit 0d127ffef1 - Show all commits

View file

@ -7,7 +7,6 @@ node_modules
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
__pycache__

View file

@ -1,6 +1,6 @@
# Ollama URL for the backend to connect
# The path '/ollama/api' will be redirected to the specified backend URL
OLLAMA_API_BASE_URL='http://localhost:11434/api'
# The path '/ollama' will be redirected to the specified backend URL
OLLAMA_BASE_URL='http://localhost:11434'
OPENAI_API_BASE_URL=''
OPENAI_API_KEY=''

View file

@ -26,17 +26,27 @@ jobs:
VERSION=$(jq -r '.version' package.json)
echo "::set-output name=version::$VERSION"
- name: Extract latest CHANGELOG entry
id: changelog
run: |
CHANGELOG_CONTENT=$(awk '/^## \[/{n++} n==1' CHANGELOG.md)
echo "CHANGELOG_CONTENT<<EOF"
echo "$CHANGELOG_CONTENT"
echo "EOF"
echo "::set-output name=content::${CHANGELOG_CONTENT}"
- name: Create GitHub release
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const changelog = `${{ steps.changelog.outputs.content }}`;
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${{ steps.get_version.outputs.version }}`,
name: `v${{ steps.get_version.outputs.version }}`,
body: 'Automatically created new release',
body: changelog,
})
console.log(`Created release ${release.data.html_url}`)

View file

@ -5,6 +5,38 @@ 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.110] - 2024-03-06
### Added
- **🌐 Multiple OpenAI Servers Support**: Enjoy seamless integration with multiple OpenAI-compatible APIs, now supported natively.
### Fixed
- **🔍 OCR Issue**: Resolved PDF parsing issue caused by OCR malfunction.
- **🚫 RAG Issue**: Fixed the RAG functionality, ensuring it operates smoothly.
- **📄 "Add Docs" Model Button**: Addressed the non-functional behavior of the "Add Docs" model button.
## [0.1.109] - 2024-03-06
### Added
- **🔄 Multiple Ollama Servers Support**: Enjoy enhanced scalability and performance with support for multiple Ollama servers in a single WebUI. Load balancing features are now available, providing improved efficiency (#788, #278).
- **🔧 Support for Claude 3 and Gemini**: Responding to user requests, we've expanded our toolset to include Claude 3 and Gemini, offering a wider range of functionalities within our platform (#1064).
- **🔍 OCR Functionality for PDF Loader**: We've augmented our PDF loader with Optical Character Recognition (OCR) capabilities. Now, extract text from scanned documents and images within PDFs, broadening the scope of content processing (#1050).
### Fixed
- **🛠️ RAG Collection**: Implemented a dynamic mechanism to recreate RAG collections, ensuring users have up-to-date and accurate data (#1031).
- **📝 User Agent Headers**: Fixed issue of RAG web requests being sent with empty user_agent headers, reducing rejections from certain websites. Realistic headers are now utilized for these requests (#1024).
- **⏹️ Playground Cancel Functionality**: Introducing a new "Cancel" option for stopping Ollama generation in the Playground, enhancing user control and usability (#1006).
- **🔤 Typographical Error in 'ASSISTANT' Field**: Corrected a typographical error in the 'ASSISTANT' field within the GGUF model upload template for accuracy and consistency (#1061).
### Changed
- **🔄 Refactored Message Deletion Logic**: Streamlined message deletion process for improved efficiency and user experience, simplifying interactions within the platform (#1004).
- **⚠️ Deprecation of `OLLAMA_API_BASE_URL`**: Deprecated `OLLAMA_API_BASE_URL` environment variable; recommend using `OLLAMA_BASE_URL` instead. Refer to our documentation for further details.
## [0.1.108] - 2024-03-02
### Added

View file

@ -20,7 +20,7 @@ FROM python:3.11-slim-bookworm as base
ENV ENV=prod
ENV PORT ""
ENV OLLAMA_API_BASE_URL "/ollama/api"
ENV OLLAMA_BASE_URL "/ollama"
ENV OPENAI_API_BASE_URL ""
ENV OPENAI_API_KEY ""
@ -53,6 +53,8 @@ WORKDIR /app/backend
# install python dependencies
COPY ./backend/requirements.txt ./requirements.txt
RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
RUN pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir
RUN pip3 install -r requirements.txt --no-cache-dir

View file

@ -95,10 +95,10 @@ Don't forget to explore our sibling project, [Open WebUI Community](https://open
- **If Ollama is on a Different Server**, use this command:
- To connect to Ollama on another server, change the `OLLAMA_API_BASE_URL` to the server's URL:
- To connect to Ollama on another server, change the `OLLAMA_BASE_URL` to the server's URL:
```bash
docker run -d -p 3000:8080 -e OLLAMA_API_BASE_URL=https://example.com/api -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=https://example.com -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
- After installation, you can access Open WebUI at [http://localhost:3000](http://localhost:3000). Enjoy! 😄
@ -110,7 +110,7 @@ If you're experiencing connection issues, its often due to the WebUI docker c
**Example Docker Command**:
```bash
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api --name open-webui --restart always ghcr.io/open-webui/open-webui:main
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
### Other Installation Methods

View file

@ -4,7 +4,7 @@
The Open WebUI system is designed to streamline interactions between the client (your browser) and the Ollama API. At the heart of this design is a backend reverse proxy, enhancing security and resolving CORS issues.
- **How it Works**: The Open WebUI is designed to interact with the Ollama API through a specific route. When a request is made from the WebUI to Ollama, it is not directly sent to the Ollama API. Initially, the request is sent to the Open WebUI backend via `/ollama/api` route. From there, the backend is responsible for forwarding the request to the Ollama API. This forwarding is accomplished by using the route specified in the `OLLAMA_API_BASE_URL` environment variable. Therefore, a request made to `/ollama/api` in the WebUI is effectively the same as making a request to `OLLAMA_API_BASE_URL` in the backend. For instance, a request to `/ollama/api/tags` in the WebUI is equivalent to `OLLAMA_API_BASE_URL/tags` in the backend.
- **How it Works**: The Open WebUI is designed to interact with the Ollama API through a specific route. When a request is made from the WebUI to Ollama, it is not directly sent to the Ollama API. Initially, the request is sent to the Open WebUI backend via `/ollama` route. From there, the backend is responsible for forwarding the request to the Ollama API. This forwarding is accomplished by using the route specified in the `OLLAMA_BASE_URL` environment variable. Therefore, a request made to `/ollama` in the WebUI is effectively the same as making a request to `OLLAMA_BASE_URL` in the backend. For instance, a request to `/ollama/api/tags` in the WebUI is equivalent to `OLLAMA_BASE_URL/api/tags` in the backend.
- **Security Benefits**: This design prevents direct exposure of the Ollama API to the frontend, safeguarding against potential CORS (Cross-Origin Resource Sharing) issues and unauthorized access. Requiring authentication to access the Ollama API further enhances this security layer.
@ -15,7 +15,7 @@ If you're experiencing connection issues, its often due to the WebUI docker c
**Example Docker Command**:
```bash
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_API_BASE_URL=http://127.0.0.1:11434/api --name open-webui --restart always ghcr.io/open-webui/open-webui:main
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
```
### General Connection Errors
@ -25,8 +25,8 @@ docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_API_BASE_
**Troubleshooting Steps**:
1. **Verify Ollama URL Format**:
- When running the Web UI container, ensure the `OLLAMA_API_BASE_URL` is correctly set, including the `/api` suffix. (e.g., `http://192.168.1.1:11434/api` for different host setups).
- When running the Web UI container, ensure the `OLLAMA_BASE_URL` is correctly set. (e.g., `http://192.168.1.1:11434` for different host setups).
- In the Open WebUI, navigate to "Settings" > "General".
- Confirm that the Ollama Server URL is correctly set to `[OLLAMA URL]/api` (e.g., `http://localhost:11434/api`), including the `/api` suffix.
- Confirm that the Ollama Server URL is correctly set to `[OLLAMA URL]` (e.g., `http://localhost:11434`).
By following these enhanced troubleshooting steps, connection issues should be effectively resolved. For further assistance or queries, feel free to reach out to us on our community Discord.

View file

@ -15,7 +15,7 @@ import asyncio
from apps.web.models.users import Users
from constants import ERROR_MESSAGES
from utils.utils import decode_token, get_current_user, get_admin_user
from config import OLLAMA_BASE_URL, WEBUI_AUTH
from config import OLLAMA_BASE_URLS
from typing import Optional, List, Union
@ -29,8 +29,7 @@ app.add_middleware(
allow_headers=["*"],
)
app.state.OLLAMA_BASE_URL = OLLAMA_BASE_URL
app.state.OLLAMA_BASE_URLS = [OLLAMA_BASE_URL]
app.state.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
app.state.MODELS = {}
@ -223,7 +222,7 @@ async def pull_model(
r = requests.request(
method="POST",
url=f"{url}/api/pull",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
stream=True,
)
@ -295,7 +294,7 @@ async def push_model(
r = requests.request(
method="POST",
url=f"{url}/api/push",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
@ -357,7 +356,7 @@ async def create_model(
r = requests.request(
method="POST",
url=f"{url}/api/create",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
stream=True,
)
@ -420,7 +419,7 @@ async def copy_model(
r = requests.request(
method="POST",
url=f"{url}/api/copy",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
@ -467,7 +466,7 @@ async def delete_model(
r = requests.request(
method="DELETE",
url=f"{url}/api/delete",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
@ -507,7 +506,7 @@ async def show_model_info(form_data: ModelNameForm, user=Depends(get_current_use
r = requests.request(
method="POST",
url=f"{url}/api/show",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
@ -559,7 +558,7 @@ async def generate_embeddings(
r = requests.request(
method="POST",
url=f"{url}/api/embeddings",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
)
r.raise_for_status()
@ -645,7 +644,7 @@ async def generate_completion(
r = requests.request(
method="POST",
url=f"{url}/api/generate",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
stream=True,
)
@ -715,7 +714,7 @@ async def generate_chat_completion(
r = None
print(form_data.model_dump_json(exclude_none=True))
print(form_data.model_dump_json(exclude_none=True).encode())
def get_request():
nonlocal form_data
@ -745,7 +744,7 @@ async def generate_chat_completion(
r = requests.request(
method="POST",
url=f"{url}/api/chat",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
stream=True,
)
@ -757,6 +756,7 @@ async def generate_chat_completion(
headers=dict(r.headers),
)
except Exception as e:
print(e)
raise e
try:
@ -844,7 +844,7 @@ async def generate_openai_chat_completion(
r = requests.request(
method="POST",
url=f"{url}/v1/chat/completions",
data=form_data.model_dump_json(exclude_none=True),
data=form_data.model_dump_json(exclude_none=True).encode(),
stream=True,
)

View file

@ -3,7 +3,10 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
import requests
import aiohttp
import asyncio
import json
from pydantic import BaseModel
@ -15,7 +18,9 @@ from utils.utils import (
get_verified_user,
get_admin_user,
)
from config import OPENAI_API_BASE_URL, OPENAI_API_KEY, CACHE_DIR
from config import OPENAI_API_BASE_URLS, OPENAI_API_KEYS, CACHE_DIR
from typing import List, Optional
import hashlib
from pathlib import Path
@ -29,116 +34,207 @@ app.add_middleware(
allow_headers=["*"],
)
app.state.OPENAI_API_BASE_URL = OPENAI_API_BASE_URL
app.state.OPENAI_API_KEY = OPENAI_API_KEY
app.state.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
app.state.OPENAI_API_KEYS = OPENAI_API_KEYS
app.state.MODELS = {}
class UrlUpdateForm(BaseModel):
url: str
@app.middleware("http")
async def check_url(request: Request, call_next):
if len(app.state.MODELS) == 0:
await get_all_models()
else:
pass
response = await call_next(request)
return response
class KeyUpdateForm(BaseModel):
key: str
class UrlsUpdateForm(BaseModel):
urls: List[str]
@app.get("/url")
async def get_openai_url(user=Depends(get_admin_user)):
return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL}
class KeysUpdateForm(BaseModel):
keys: List[str]
@app.post("/url/update")
async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_admin_user)):
app.state.OPENAI_API_BASE_URL = form_data.url
return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL}
@app.get("/urls")
async def get_openai_urls(user=Depends(get_admin_user)):
return {"OPENAI_API_BASE_URLS": app.state.OPENAI_API_BASE_URLS}
@app.get("/key")
async def get_openai_key(user=Depends(get_admin_user)):
return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY}
@app.post("/urls/update")
async def update_openai_urls(form_data: UrlsUpdateForm, user=Depends(get_admin_user)):
app.state.OPENAI_API_BASE_URLS = form_data.urls
return {"OPENAI_API_BASE_URLS": app.state.OPENAI_API_BASE_URLS}
@app.post("/key/update")
async def update_openai_key(form_data: KeyUpdateForm, user=Depends(get_admin_user)):
app.state.OPENAI_API_KEY = form_data.key
return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY}
@app.get("/keys")
async def get_openai_keys(user=Depends(get_admin_user)):
return {"OPENAI_API_KEYS": app.state.OPENAI_API_KEYS}
@app.post("/keys/update")
async def update_openai_key(form_data: KeysUpdateForm, user=Depends(get_admin_user)):
app.state.OPENAI_API_KEYS = form_data.keys
return {"OPENAI_API_KEYS": app.state.OPENAI_API_KEYS}
@app.post("/audio/speech")
async def speech(request: Request, user=Depends(get_verified_user)):
target_url = f"{app.state.OPENAI_API_BASE_URL}/audio/speech"
if app.state.OPENAI_API_KEY == "":
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
body = await request.body()
name = hashlib.sha256(body).hexdigest()
SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
# Check if the file already exists in the cache
if file_path.is_file():
return FileResponse(file_path)
headers = {}
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
headers["Content-Type"] = "application/json"
idx = None
try:
print("openai")
r = requests.post(
url=target_url,
data=body,
headers=headers,
stream=True,
idx = app.state.OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
body = await request.body()
name = hashlib.sha256(body).hexdigest()
SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")
# Check if the file already exists in the cache
if file_path.is_file():
return FileResponse(file_path)
headers = {}
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEYS[idx]}"
headers["Content-Type"] = "application/json"
try:
r = requests.post(
url=f"{app.state.OPENAI_API_BASE_URLS[idx]}/audio/speech",
data=body,
headers=headers,
stream=True,
)
r.raise_for_status()
# Save the streaming content to a file
with open(file_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
with open(file_body_path, "w") as f:
json.dump(json.loads(body.decode("utf-8")), f)
# Return the saved file
return FileResponse(file_path)
except Exception as e:
print(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']}"
except:
error_detail = f"External: {e}"
raise HTTPException(status_code=r.status_code, detail=error_detail)
except ValueError:
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.OPENAI_NOT_FOUND)
async def fetch_url(url, key):
try:
headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
return await response.json()
except Exception as e:
# Handle connection error here
print(f"Connection error: {e}")
return None
def merge_models_lists(model_lists):
merged_list = []
for idx, models in enumerate(model_lists):
merged_list.extend(
[
{**model, "urlIdx": idx}
for model in models
if "api.openai.com" not in app.state.OPENAI_API_BASE_URLS[idx]
or "gpt" in model["id"]
]
)
r.raise_for_status()
return merged_list
# Save the streaming content to a file
with open(file_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
with open(file_body_path, "w") as f:
json.dump(json.loads(body.decode("utf-8")), f)
async def get_all_models():
print("get_all_models")
tasks = [
fetch_url(f"{url}/models", app.state.OPENAI_API_KEYS[idx])
for idx, url in enumerate(app.state.OPENAI_API_BASE_URLS)
]
responses = await asyncio.gather(*tasks)
responses = list(filter(lambda x: x is not None and "error" not in x, responses))
models = {
"data": merge_models_lists(
list(map(lambda response: response["data"], responses))
)
}
app.state.MODELS = {model["id"]: model for model in models["data"]}
# Return the saved file
return FileResponse(file_path)
return models
except Exception as e:
print(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']}"
except:
error_detail = f"External: {e}"
raise HTTPException(status_code=r.status_code, detail=error_detail)
# , user=Depends(get_current_user)
@app.get("/models")
@app.get("/models/{url_idx}")
async def get_models(url_idx: Optional[int] = None):
if url_idx == None:
return await get_all_models()
else:
url = app.state.OPENAI_API_BASE_URLS[url_idx]
try:
r = requests.request(method="GET", url=f"{url}/models")
r.raise_for_status()
response_data = r.json()
if "api.openai.com" in url:
response_data["data"] = list(
filter(lambda model: "gpt" in model["id"], response_data["data"])
)
return response_data
except Exception as e:
print(e)
error_detail = "Open WebUI: Server Connection Error"
if r is not None:
try:
res = r.json()
if "error" in res:
error_detail = f"External: {res['error']}"
except:
error_detail = f"External: {e}"
raise HTTPException(
status_code=r.status_code if r else 500,
detail=error_detail,
)
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
target_url = f"{app.state.OPENAI_API_BASE_URL}/{path}"
print(target_url, app.state.OPENAI_API_KEY)
if app.state.OPENAI_API_KEY == "":
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
idx = 0
body = await request.body()
# TODO: Remove below after gpt-4-vision fix from Open AI
# Try to decode the body of the request from bytes to a UTF-8 string (Require add max_token to fix gpt-4-vision)
try:
body = body.decode("utf-8")
body = json.loads(body)
idx = app.state.MODELS[body.get("model")]["urlIdx"]
# Check if the model is "gpt-4-vision-preview" and set "max_tokens" to 4000
# This is a workaround until OpenAI fixes the issue with this model
if body.get("model") == "gpt-4-vision-preview":
@ -158,8 +254,16 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
except json.JSONDecodeError as e:
print("Error loading request body into a dictionary:", e)
url = app.state.OPENAI_API_BASE_URLS[idx]
key = app.state.OPENAI_API_KEYS[idx]
target_url = f"{url}/{path}"
if key == "":
raise HTTPException(status_code=401, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND)
headers = {}
headers["Authorization"] = f"Bearer {app.state.OPENAI_API_KEY}"
headers["Authorization"] = f"Bearer {key}"
headers["Content-Type"] = "application/json"
try:
@ -181,21 +285,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
headers=dict(r.headers),
)
else:
# For non-SSE, read the response and return it
# response_data = (
# r.json()
# if r.headers.get("Content-Type", "")
# == "application/json"
# else r.text
# )
response_data = r.json()
if "api.openai.com" in app.state.OPENAI_API_BASE_URL and path == "models":
response_data["data"] = list(
filter(lambda model: "gpt" in model["id"], response_data["data"])
)
return response_data
except Exception as e:
print(e)

View file

@ -425,7 +425,7 @@ def get_loader(filename: str, file_content_type: str, file_path: str):
]
if file_ext == "pdf":
loader = PyPDFLoader(file_path)
loader = PyPDFLoader(file_path, extract_images=True)
elif file_ext == "csv":
loader = CSVLoader(file_path)
elif file_ext == "rst":

View file

@ -14,7 +14,7 @@ import json
from utils.utils import get_admin_user
from utils.misc import calculate_sha256, get_gravatar_url
from config import OLLAMA_API_BASE_URL, DATA_DIR, UPLOAD_DIR
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR
from constants import ERROR_MESSAGES
@ -75,7 +75,7 @@ async def download_file_stream(url, file_path, file_name, chunk_size=1024 * 1024
hashed = calculate_sha256(file)
file.seek(0)
url = f"{OLLAMA_API_BASE_URL}/blobs/sha256:{hashed}"
url = f"{OLLAMA_BASE_URLS[0]}/blobs/sha256:{hashed}"
response = requests.post(url, data=file)
if response.ok:
@ -147,7 +147,7 @@ def upload(file: UploadFile = File(...)):
hashed = calculate_sha256(f)
f.seek(0)
url = f"{OLLAMA_API_BASE_URL}/blobs/sha256:{hashed}"
url = f"{OLLAMA_BASE_URLS[0]}/blobs/sha256:{hashed}"
response = requests.post(url, data=f)
if response.ok:

View file

@ -200,27 +200,32 @@ if not os.path.exists(LITELLM_CONFIG_PATH):
####################################
# OLLAMA_API_BASE_URL
# OLLAMA_BASE_URL
####################################
OLLAMA_API_BASE_URL = os.environ.get(
"OLLAMA_API_BASE_URL", "http://localhost:11434/api"
)
if ENV == "prod":
if OLLAMA_API_BASE_URL == "/ollama/api":
OLLAMA_API_BASE_URL = "http://host.docker.internal:11434/api"
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
if OLLAMA_BASE_URL == "":
if ENV == "prod":
if OLLAMA_BASE_URL == "/ollama":
OLLAMA_BASE_URL = "http://host.docker.internal:11434"
if OLLAMA_BASE_URL == "" and OLLAMA_API_BASE_URL != "":
OLLAMA_BASE_URL = (
OLLAMA_API_BASE_URL[:-4]
if OLLAMA_API_BASE_URL.endswith("/api")
else OLLAMA_API_BASE_URL
)
OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
####################################
# OPENAI_API
@ -229,9 +234,25 @@ if OLLAMA_BASE_URL == "":
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
if OPENAI_API_KEY == "":
OPENAI_API_KEY = "none"
if OPENAI_API_BASE_URL == "":
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "")
OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY
OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "")
OPENAI_API_BASE_URLS = (
OPENAI_API_BASE_URLS if OPENAI_API_BASE_URLS != "" else OPENAI_API_BASE_URL
)
OPENAI_API_BASE_URLS = [url.strip() for url in OPENAI_API_BASE_URL.split(";")]
####################################
# WEBUI

View file

@ -41,6 +41,7 @@ class ERROR_MESSAGES(str, Enum):
NOT_FOUND = "We could not find what you're looking for :/"
USER_NOT_FOUND = "We could not find what you're looking for :/"
API_KEY_NOT_FOUND = "Oops! It looks like there's a hiccup. The API key is missing. Please make sure to provide a valid API key to access this feature."
MALICIOUS = "Unusual activities detected, please try again in a few minutes."
PANDOC_NOT_INSTALLED = "Pandoc is not installed on the server. Please contact your administrator for assistance."
@ -50,3 +51,4 @@ 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"

View file

@ -35,6 +35,9 @@ openpyxl
pyxlsb
xlrd
opencv-python-headless
rapidocr-onnxruntime
faster-whisper
PyJWT

View file

@ -14,7 +14,7 @@ services:
build:
context: .
args:
OLLAMA_API_BASE_URL: '/ollama/api'
OLLAMA_BASE_URL: '/ollama'
dockerfile: Dockerfile
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
@ -25,7 +25,7 @@ services:
ports:
- ${OPEN_WEBUI_PORT-3000}:8080
environment:
- 'OLLAMA_API_BASE_URL=http://ollama:11434/api'
- 'OLLAMA_BASE_URL=http://ollama:11434'
- 'WEBUI_SECRET_KEY='
extra_hosts:
- host.docker.internal:host-gateway

38
i18next-parser.config.ts Normal file
View file

@ -0,0 +1,38 @@
// i18next-parser.config.ts
import { getLanguages } from './src/lib/i18n/index.ts';
const getLangCodes = async () => {
const languages = await getLanguages();
return languages.map((l) => l.code);
};
export default {
contextSeparator: '_',
createOldCatalogs: false,
defaultNamespace: 'translation',
defaultValue: '',
indentation: 2,
keepRemoved: false,
keySeparator: false,
lexers: {
svelte: ['JavascriptLexer'],
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
ts: ['JavascriptLexer'],
default: ['JavascriptLexer']
},
lineEnding: 'auto',
locales: await getLangCodes(),
namespaceSeparator: false,
output: 'src/lib/i18n/locales/$LOCALE/$NAMESPACE.json',
pluralSeparator: '_',
input: 'src/**/*.{js,svelte}',
sort: true,
verbose: true,
failOnWarnings: false,
failOnUpdate: false,
customValueTemplate: null,
resetDefaultValueLocale: null,
i18nextOptions: null,
yamlOptions: null
};

View file

@ -40,7 +40,7 @@ spec:
- name: data
mountPath: /app/backend/data
env:
- name: OLLAMA_API_BASE_URL
- name: OLLAMA_BASE_URL
value: {{ include "ollama.url" . | quote }}
tty: true
{{- with .Values.webui.nodeSelector }}

View file

@ -26,8 +26,8 @@ spec:
cpu: "1000m"
memory: "1Gi"
env:
- name: OLLAMA_API_BASE_URL
value: "http://ollama-service.open-webui.svc.cluster.local:11434/api"
- name: OLLAMA_BASE_URL
value: "http://ollama-service.open-webui.svc.cluster.local:11434"
tty: true
volumeMounts:
- name: webui-volume

View file

@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.1.108",
"version": "0.1.110",
"private": true,
"scripts": {
"dev": "vite dev --host",
@ -27,6 +27,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"i18next-parser": "^8.13.0",
"postcss": "^8.4.31",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",

View file

@ -43,6 +43,10 @@ ol > li {
font-weight: 400;
}
li p {
display: inline;
}
::-webkit-scrollbar-thumb {
--tw-border-opacity: 1;
background-color: rgba(217, 217, 227, 0.8);

View file

@ -1,9 +1,9 @@
import { OPENAI_API_BASE_URL } from '$lib/constants';
export const getOpenAIUrl = async (token: string = '') => {
export const getOpenAIUrls = async (token: string = '') => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/url`, {
const res = await fetch(`${OPENAI_API_BASE_URL}/urls`, {
method: 'GET',
headers: {
Accept: 'application/json',
@ -29,13 +29,13 @@ export const getOpenAIUrl = async (token: string = '') => {
throw error;
}
return res.OPENAI_API_BASE_URL;
return res.OPENAI_API_BASE_URLS;
};
export const updateOpenAIUrl = async (token: string = '', url: string) => {
export const updateOpenAIUrls = async (token: string = '', urls: string[]) => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/url/update`, {
const res = await fetch(`${OPENAI_API_BASE_URL}/urls/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
@ -43,7 +43,7 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
url: url
urls: urls
})
})
.then(async (res) => {
@ -64,13 +64,13 @@ export const updateOpenAIUrl = async (token: string = '', url: string) => {
throw error;
}
return res.OPENAI_API_BASE_URL;
return res.OPENAI_API_BASE_URLS;
};
export const getOpenAIKey = async (token: string = '') => {
export const getOpenAIKeys = async (token: string = '') => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/key`, {
const res = await fetch(`${OPENAI_API_BASE_URL}/keys`, {
method: 'GET',
headers: {
Accept: 'application/json',
@ -96,13 +96,13 @@ export const getOpenAIKey = async (token: string = '') => {
throw error;
}
return res.OPENAI_API_KEY;
return res.OPENAI_API_KEYS;
};
export const updateOpenAIKey = async (token: string = '', key: string) => {
export const updateOpenAIKeys = async (token: string = '', keys: string[]) => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/key/update`, {
const res = await fetch(`${OPENAI_API_BASE_URL}/keys/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
@ -110,7 +110,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
key: key
keys: keys
})
})
.then(async (res) => {
@ -131,7 +131,7 @@ export const updateOpenAIKey = async (token: string = '', key: string) => {
throw error;
}
return res.OPENAI_API_KEY;
return res.OPENAI_API_KEYS;
};
export const getOpenAIModels = async (token: string = '') => {

View file

@ -225,33 +225,80 @@
}, 100);
};
// TODO: change delete behaviour
// const deleteMessageAndDescendants = async (messageId: string) => {
// if (history.messages[messageId]) {
// history.messages[messageId].deleted = true;
// for (const childId of history.messages[messageId].childrenIds) {
// await deleteMessageAndDescendants(childId);
// }
// }
// };
// const triggerDeleteMessageRecursive = async (messageId: string) => {
// await deleteMessageAndDescendants(messageId);
// await updateChatById(localStorage.token, chatId, { history });
// await chats.set(await getChatList(localStorage.token));
// };
const messageDeleteHandler = async (messageId) => {
if (history.messages[messageId]) {
history.messages[messageId].deleted = true;
for (const childId of history.messages[messageId].childrenIds) {
history.messages[childId].deleted = true;
const messageToDelete = history.messages[messageId];
const messageParentId = messageToDelete.parentId;
const messageChildrenIds = messageToDelete.childrenIds ?? [];
const hasSibling = messageChildrenIds.some(
(childId) => history.messages[childId]?.childrenIds?.length > 0
);
messageChildrenIds.forEach((childId) => {
const child = history.messages[childId];
if (child && child.childrenIds) {
if (child.childrenIds.length === 0 && !hasSibling) {
// if last prompt/response pair
history.messages[messageParentId].childrenIds = [];
history.currentId = messageParentId;
} else {
child.childrenIds.forEach((grandChildId) => {
if (history.messages[grandChildId]) {
history.messages[grandChildId].parentId = messageParentId;
history.messages[messageParentId].childrenIds.push(grandChildId);
}
});
}
}
}
await updateChatById(localStorage.token, chatId, { history });
// remove response
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== childId);
});
// remove prompt
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== messageId);
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
};
// const messageDeleteHandler = async (messageId) => {
// const message = history.messages[messageId];
// const parentId = message.parentId;
// const childrenIds = message.childrenIds ?? [];
// const grandchildrenIds = [];
// // Iterate through childrenIds to find grandchildrenIds
// for (const childId of childrenIds) {
// const childMessage = history.messages[childId];
// const grandChildrenIds = childMessage.childrenIds ?? [];
// for (const grandchildId of grandchildrenIds) {
// const childMessage = history.messages[grandchildId];
// childMessage.parentId = parentId;
// }
// grandchildrenIds.push(...grandChildrenIds);
// }
// history.messages[parentId].childrenIds.push(...grandchildrenIds);
// history.messages[parentId].childrenIds = history.messages[parentId].childrenIds.filter(
// (id) => id !== messageId
// );
// // Select latest message
// let currentMessageId = grandchildrenIds.at(-1);
// if (currentMessageId) {
// let messageChildrenIds = history.messages[currentMessageId].childrenIds;
// while (messageChildrenIds.length !== 0) {
// currentMessageId = messageChildrenIds.at(-1);
// messageChildrenIds = history.messages[currentMessageId].childrenIds;
// }
// history.currentId = currentMessageId;
// }
// await updateChatById(localStorage.token, chatId, { messages, history });
// };
</script>
{#if messages.length == 0}
@ -260,57 +307,55 @@
<div class=" pb-10">
{#key chatId}
{#each messages as message, messageIdx}
{#if !message.deleted}
<div class=" w-full">
<div
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
? 'max-w-full'
: 'max-w-3xl'} mx-auto rounded-lg group"
>
{#if message.role === 'user'}
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
user={$user}
{message}
isFirstMessage={messageIdx === 0}
siblings={message.parentId !== null
? history.messages[message.parentId]?.childrenIds ?? []
: Object.values(history.messages)
.filter((message) => message.parentId === null)
.map((message) => message.id) ?? []}
{confirmEditMessage}
{showPreviousMessage}
{showNextMessage}
{copyToClipboard}
/>
{:else}
<ResponseMessage
{message}
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
{copyToClipboard}
{continueGeneration}
{regenerateResponse}
on:save={async (e) => {
console.log('save', e);
<div class=" w-full">
<div
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
? 'max-w-full'
: 'max-w-3xl'} mx-auto rounded-lg group"
>
{#if message.role === 'user'}
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
user={$user}
{message}
isFirstMessage={messageIdx === 0}
siblings={message.parentId !== null
? history.messages[message.parentId]?.childrenIds ?? []
: Object.values(history.messages)
.filter((message) => message.parentId === null)
.map((message) => message.id) ?? []}
{confirmEditMessage}
{showPreviousMessage}
{showNextMessage}
{copyToClipboard}
/>
{:else}
<ResponseMessage
{message}
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
{copyToClipboard}
{continueGeneration}
{regenerateResponse}
on:save={async (e) => {
console.log('save', e);
const message = e.detail;
history.messages[message.id] = message;
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
}}
/>
{/if}
</div>
const message = e.detail;
history.messages[message.id] = message;
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
}}
/>
{/if}
</div>
{/if}
</div>
{/each}
{#if bottomPadding}

View file

@ -24,6 +24,7 @@
import CodeBlock from './CodeBlock.svelte';
import Image from '$lib/components/common/Image.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '$lib/components/common/Tooltip.svelte';
export let modelfiles = [];
export let message;
@ -346,6 +347,7 @@
class=" bg-transparent outline-none w-full resize-none"
bind:value={editedContent}
on:input={(e) => {
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
}}
/>
@ -464,189 +466,125 @@
</div>
{/if}
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
editMessageHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button"
on:click={() => {
copyToClipboard(message.content);
}}
>
<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="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
/>
</svg>
</button>
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, 1);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
/></svg
>
</button>
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, -1);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
/></svg
>
</button>
<button
id="speak-button-{message.id}"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
if (!loadingSpeech) {
toggleSpeakMessage(message);
}
}}
>
{#if loadingSpeech}
<svg
class=" w-4 h-4"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_S1WN {
animation: spinner_MGfb 0.8s linear infinite;
animation-delay: -0.8s;
}
.spinner_Km9P {
animation-delay: -0.65s;
}
.spinner_JApP {
animation-delay: -0.5s;
}
@keyframes spinner_MGfb {
93.75%,
100% {
opacity: 0.2;
}
}
</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle
class="spinner_S1WN spinner_Km9P"
cx="12"
cy="12"
r="3"
/><circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" /></svg
>
{:else if speaking}
<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="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"
/>
</svg>
{:else}
<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="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"
/>
</svg>
{/if}
</button>
{#if $config.images}
<Tooltip content="Edit" placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
if (!generatingImage) {
generateImage(message);
editMessageHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
</Tooltip>
<Tooltip content="Copy" placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button"
on:click={() => {
copyToClipboard(message.content);
}}
>
<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="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
/>
</svg>
</button>
</Tooltip>
<Tooltip content="Good Response" placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, 1);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
/></svg
>
</button>
</Tooltip>
<Tooltip content="Bad Response" placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, -1);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
/></svg
>
</button>
</Tooltip>
<Tooltip content="Read Aloud" placement="bottom">
<button
id="speak-button-{message.id}"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
if (!loadingSpeech) {
toggleSpeakMessage(message);
}
}}
>
{#if generatingImage}
{#if loadingSpeech}
<svg
class=" w-4 h-4"
fill="currentColor"
@ -681,6 +619,21 @@
r="3"
/></svg
>
{:else if speaking}
<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="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
@ -693,93 +646,166 @@
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"
/>
</svg>
{/if}
</button>
</Tooltip>
{#if $config.images}
<Tooltip content="Generate Image" placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
if (!generatingImage) {
generateImage(message);
}
}}
>
{#if generatingImage}
<svg
class=" w-4 h-4"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_S1WN {
animation: spinner_MGfb 0.8s linear infinite;
animation-delay: -0.8s;
}
.spinner_Km9P {
animation-delay: -0.65s;
}
.spinner_JApP {
animation-delay: -0.5s;
}
@keyframes spinner_MGfb {
93.75%,
100% {
opacity: 0.2;
}
}
</style><circle class="spinner_S1WN" cx="4" cy="12" r="3" /><circle
class="spinner_S1WN spinner_Km9P"
cx="12"
cy="12"
r="3"
/><circle
class="spinner_S1WN spinner_JApP"
cx="20"
cy="12"
r="3"
/></svg
>
{:else}
<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="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
/>
</svg>
{/if}
</button>
</Tooltip>
{/if}
{#if message.info}
<button
class=" {isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
on:click={() => {
console.log(message);
}}
id="info-{message.id}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
<Tooltip content="Generation Info" placement="bottom">
<button
class=" {isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
on:click={() => {
console.log(message);
}}
id="info-{message.id}"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
/>
</svg>
</button>
<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.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
/>
</svg>
</button>
</Tooltip>
{/if}
{#if isLastMessage}
<button
type="button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
continueGeneration();
}}
>
<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"
<Tooltip content="Continue Response" placement="bottom">
<button
type="button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
continueGeneration();
}}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"
/>
</svg>
</button>
<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="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"
/>
</svg>
</button>
</Tooltip>
<button
type="button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={regenerateResponse}
>
<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"
<Tooltip content="Regenerate" placement="bottom">
<button
type="button"
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={regenerateResponse}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</button>
</Tooltip>
{/if}
</div>
{/if}

View file

@ -5,6 +5,7 @@
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
import { modelfiles, settings } from '$lib/stores';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
@ -171,7 +172,8 @@
class=" bg-transparent outline-none w-full resize-none"
bind:value={editedContent}
on:input={(e) => {
messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
}}
/>
@ -248,55 +250,11 @@
</div>
{/if}
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
on:click={() => {
editMessageHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
copyToClipboard(message.content);
}}
>
<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="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
/>
</svg>
</button>
{#if !isFirstMessage}
<Tooltip content="Edit" placement="bottom">
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
on:click={() => {
deleteMessageHandler();
editMessageHandler();
}}
>
<svg
@ -310,10 +268,60 @@
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
</Tooltip>
<Tooltip content="Copy" placement="bottom">
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
copyToClipboard(message.content);
}}
>
<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="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
/>
</svg>
</button>
</Tooltip>
{#if !isFirstMessage}
<Tooltip content="Delete" placement="bottom">
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
deleteMessageHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</Tooltip>
{/if}
</div>
</div>

View file

@ -116,7 +116,7 @@
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
/>
</svg> -->
<span class="ml-2 self-center">{$i18n.t(' JSON ')}</span>
<span class="ml-2 self-center">{$i18n.t('JSON')}</span>
{/if}
</button>
</div>

View file

@ -4,7 +4,12 @@
const dispatch = createEventDispatcher();
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
import {
getOpenAIKeys,
getOpenAIUrls,
updateOpenAIKeys,
updateOpenAIUrls
} from '$lib/apis/openai';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
@ -18,12 +23,14 @@
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_KEYS = [''];
let OPENAI_API_BASE_URLS = [''];
let showOpenAI = false;
let showLiteLLM = false;
const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL);
OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
await models.set(await getModels());
};
@ -45,8 +52,8 @@
onMount(async () => {
if ($user.role === 'admin') {
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
}
});
</script>
@ -73,37 +80,74 @@
</div>
{#if showOpenAI}
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Key')}</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
placeholder={$i18n.t('Enter OpenAI API Key')}
bind:value={OPENAI_API_KEY}
autocomplete="off"
/>
</div>
</div>
</div>
<div class="flex flex-col gap-1">
{#each OPENAI_API_BASE_URLS as url, idx}
<div class="flex w-full gap-2">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={url}
autocomplete="off"
/>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
placeholder="Enter OpenAI API Base URL"
bind:value={OPENAI_API_BASE_URL}
autocomplete="off"
/>
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('API Key')}
bind:value={OPENAI_API_KEYS[idx]}
autocomplete="off"
/>
</div>
<div class="self-center flex items-center">
{#if idx === 0}
<button
class="px-1"
on:click={() => {
OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</button>
{:else}
<button
class="px-1"
on:click={() => {
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
(url, urlIdx) => idx !== urlIdx
);
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
</svg>
</button>
{/if}
</div>
</div>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
WebUI will make requests to <span class=" text-gray-200"
>'{OPENAI_API_BASE_URL}/chat'</span
>
</div>
<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span>
</div>
{/each}
</div>
{/if}
</div>
@ -193,7 +237,7 @@
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('Trouble accessing Ollama?')}
<a
class=" text-gray-300 font-medium"
class=" text-gray-300 font-medium underline"
href="https://github.com/open-webui/open-webui#troubleshooting"
target="_blank"
>

View file

@ -149,7 +149,7 @@
<br />
{$i18n.t('You can help us translate the WebUI.')}
<a
class=" text-gray-300 font-medium"
class=" text-gray-300 font-medium underline"
href="https://github.com/open-webui/open-webui/blob/main/docs/CONTRIBUTING.md#-translations-and-internationalization"
target="_blank"
>

View file

@ -56,7 +56,7 @@
let modelUploadMode = 'file';
let modelInputFile = '';
let modelFileUrl = '';
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`;
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
let modelFileDigest = '';
let uploadProgress = null;
@ -517,7 +517,7 @@
{#if !deleteModelTag}
<option value="" disabled selected>Select a model</option>
{/if}
{#each $models.filter((m) => m.size != null) as model}
{#each $models.filter((m) => m.size != null && (selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
>
@ -599,7 +599,7 @@
on:change={() => {
console.log(modelInputFile);
}}
accept=".gguf"
accept=".gguf,.safetensors"
required
hidden
/>

View file

@ -140,7 +140,9 @@
<button
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
type="button"
on:click={uploadDocInputElement.click}
on:click={() => {
uploadDocInputElement.click();
}}
>
{#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.

View file

@ -90,8 +90,3 @@ export const SUPPORTED_FILE_EXTENSIONS = [
// This feature, akin to $env/static/private, exclusively incorporates environment variables
// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
// Consequently, these variables can be securely exposed to client-side code.
// Example of the .env configuration:
// OLLAMA_API_BASE_URL="http://localhost:11434/api"
// # Public
// PUBLIC_API_BASE_URL=$OLLAMA_API_BASE_URL

View file

@ -4,7 +4,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import type { i18n as i18nType } from 'i18next';
import { writable } from 'svelte/store';
const createI18nStore = (i18n: i18n) => {
const createI18nStore = (i18n: i18nType) => {
const i18nWritable = writable(i18n);
i18n.on('initialized', () => {
@ -20,7 +20,7 @@ const createI18nStore = (i18n: i18n) => {
return i18nWritable;
};
const createIsLoadingStore = (i18n: i18n) => {
const createIsLoadingStore = (i18n: i18nType) => {
const isLoading = writable(false);
// if loaded resources are empty || {}, set loading to true
@ -39,11 +39,13 @@ const createIsLoadingStore = (i18n: i18n) => {
i18next
.use(
resourcesToBackend((language, namespace) => import(`./locales/${language}/${namespace}.json`))
resourcesToBackend(
(language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`)
)
)
.use(LanguageDetector)
.init({
debug: true,
debug: false,
detection: {
order: ['querystring', 'localStorage', 'navigator'],
caches: ['localStorage'],

View file

@ -18,10 +18,9 @@
"Add message": "Nachricht eingeben",
"add tags": "Tags hinzufügen",
"Adjusting these settings will apply changes universally to all users.": "Das Anpassen dieser Einstellungen wirkt sich universell auf alle Benutzer aus.",
"admin": "Administrator",
"Admin": "",
"Admin Panel": "Admin Panel",
"Admin Settings": "Admin Einstellungen",
"Advanced": "Angepasst",
"Advanced Model Params": "Erweiterte Modell Parameter",
"Advanced Parameters": "Erweiterte Parameter",
"all": "Alle",
@ -35,7 +34,6 @@
"API Key": "API Key",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "sind erlaubt - Aktiviere diesen Befehl, indem du",
"assistant": "Assistent",
"Audio": "Audio",
"Auto-playback response": "Automatische Wiedergabe der Antwort",
"Auto-send input after 3 sec.": "Automatisches Senden der Eingabe nach 3 Sek",
@ -98,7 +96,7 @@
"Deleted {tagName}": "{tagName} gelöscht",
"Description": "Beschreibung",
"Desktop Notifications": "Desktop-Benachrichtigungen",
"Didn't find your language?": "Deine Sprache nicht gefunden?",
"Didn't find your language?": "Deine Sprache nicht vorhanden?",
"Disabled": "Deaktiviert",
"Discover a modelfile": "Eine Modelfiles entdecken",
"Discover a prompt": "Einen Prompt entdecken",
@ -121,21 +119,17 @@
"Enable Chat History": "Chat-Verlauf aktivieren",
"Enable New Sign Ups": "Neue Anmeldungen aktivieren",
"Enabled": "Aktiviert",
"Enter a user message here": "Gebe hier eine Benutzernachricht ein",
"Enter an assistant message here": "Gebe hier eine Assistentennachricht ein",
"Enter OpenAI API Key": "OpenAI-API-Key eingeben",
"Enter stop sequence": "Stop-Sequenz eingeben",
"Enter Your Email": "Geben Deine E-Mail-Adresse ein",
"Enter Your Full Name": "Gebe Deinen vollständigen Namen ein",
"Enter Your Password": "Gebe Dein Passwort ein",
"Experimental": "Experimentell",
"Export All Chats (All Users)": "Alle Chats exportieren (alle Benutzer)",
"Export Chats": "Chats exportieren",
"Export Documents Mapping": "Dokumentenmapping exportieren",
"Export Modelfiles": "Modelfiles exportieren",
"Export Prompts": "Prompts exportieren",
"Failed to read clipboard contents": "Fehler beim Lesen des Zwischenablageninhalts",
"File Mode": "Dateimodus",
"File not found.": "Datei nicht gefunden.",
"Focus chat input": "Chat-Eingabe fokussieren",
"Format your variables using square brackets like this:": "Formatiere Deine Variablen mit eckigen Klammern wie folgt:",
@ -187,7 +181,6 @@
"Model Tag Name": "Modell-Tag-Name",
"Modelfile": "Modelfiles",
"Modelfile Advanced Settings": "Erweiterte Modelfileseinstellungen",
"Modelfile Content": "Modelfilesinhalt",
"Modelfiles": "Modelfiles",
"Models": "Modelle",
"My Documents": "Meine Dokumente",
@ -195,19 +188,20 @@
"My Prompts": "Meine Prompts",
"Name": "Name",
"Name Tag": "Namens-Tag",
"Name your Modelfile": "Benenne Dein Modelfile",
"Name your modelfile": "",
"New Chat": "Neuer Chat",
"New Password": "Neues Passwort",
"Not sure what to add?": "Nicht sicher, was hinzugefügt werden soll?",
"Not sure what to write? Switch to": "Nicht sicher, was Du schreiben sollst? Wechsel zu",
"Off": "Aus",
"Okay, Let's Go!": "Okay, los geht's!",
"Ollama API URL": "Ollama-API-URL",
"Ollama Version": "Ollama-Version",
"On": "Ein",
"Only": "Nur",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Nur alphanumerische Zeichen und Bindestriche sind im Befehlsstring erlaubt.",
"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.": "Hoppla! Warte noch einen Moment! Die Dateien sind noch im der Verarbeitung. Bitte habe etwas Geduld und wir informieren Dich, sobald sie bereit sind.",
"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": "Öffne",
"Open AI": "Open AI",
"Open new chat": "Neuen Chat öffnen",
@ -215,7 +209,7 @@
"or": "oder",
"Parameters": "Parameter",
"Password": "Passwort",
"pending": "ausstehend",
"Pending": "",
"Permission denied when accessing microphone: {{error}}": "Zugriff auf das Mikrofon verweigert: {{error}}",
"Playground": "Playground",
"Profile": "Profil",
@ -277,6 +271,7 @@
"STT Settings": "STT-Einstellungen",
"Submit": "Senden",
"Success": "Erfolg",
"Successfully updated": "",
"Successfully updated.": "Erfolgreich aktualisiert.",
"Sync All": "Alles synchronisieren",
"System": "System",
@ -290,12 +285,12 @@
"Theme": "Design",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dadurch werden Deine wertvollen Unterhaltungen sicher in der Backend-Datenbank gespeichert. Vielen Dank!",
"This setting does not sync across browsers or devices.": "Diese Einstellung wird nicht zwischen Browsern oder Geräten synchronisiert.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
"Title": "Titel",
"Title Auto-Generation": "Automatische Titelgenerierung",
"Title Generation Prompt": "Prompt für Titelgenerierung",
"to": "für",
"To access the available model names for downloading,": "Um auf die verfügbaren Modellnamen zum Herunterladen zuzugreifen,",
"to chat input.": "im Chat eingeben.",
"Toggle settings": "Einstellungen umschalten",
"Toggle sidebar": "Seitenleiste umschalten",
"Top K": "Top K",
@ -303,12 +298,10 @@
"Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?",
"TTS Settings": "TTS-Einstellungen",
"Uh-oh! There was an issue connecting to {{provider}}.": "Ups! Es gab ein Problem bei der Verbindung mit {{provider}}.",
"Upload a GGUF model": "Ein GGUF-Modell hochladen",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
"Upload files": "Dateien hochladen",
"Upload Progress": "Upload-Fortschritt",
"URL Mode": "URL-Modus",
"Use '#' in the prompt input to load and select your documents.": "Verwende '#' in der Prompt-Eingabe, um Deine Dokumente zu laden und auszuwählen.",
"user": "Benutzer",
"User": "",
"User Permissions": "Benutzerberechtigungen",
"Users": "Benutzer",
"Utilize": "Nutze die",
@ -322,8 +315,9 @@
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Wenn die Historie ausgeschaltet ist, werden neue Chats nicht in Deiner Historie auf Deine Geräte angezeigt.",
"Whisper (Local)": "Whisper (Lokal)",
"Write a prompt suggestion (e.g. Who are you?)": "Gebe einen Prompt-Vorschlag ein (z.B. Wer bist du?)",
"Write a summary in 50 words that summarizes [topic or keyword]": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
"Write a summary in 50 words that summarizes [topic or keyword].": "Schreibe eine kurze Zusammenfassung in 50 Wörtern, die [Thema oder Schlüsselwort] zusammenfasst.",
"You": "Du",
"You can help us translate the WebUI.": "Du kannst uns bei der Übersetzung der WebUI helfen.",
"You're a helpful assistant.": "Du bist ein hilfreicher Assistent."
"You're a helpful assistant.": "Du bist ein hilfreicher Assistent.",
"You're now logged in.": "Du bist nun eingeloggt.",
"You can help us translate the WebUI.": "Du kannst uns bei der Übersetzung der WebUI helfen."
}

View file

@ -18,10 +18,9 @@
"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": "",
"Admin Panel": "Admin Panel",
"Admin Settings": "Admin Settings",
"Advanced": "Advanced",
"Advanced Model Params": "Advanced Model Params",
"Advanced Parameters": "Advanced Parameters",
"all": "all",
@ -35,7 +34,6 @@
"API Key": "API Key",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "are allowed - Activate this command by typing",
"assistant": "Assistant",
"Audio": "Audio",
"Auto-playback response": "Auto-playback response",
"Auto-send input after 3 sec.": "Auto-send input after 3 sec.",
@ -121,21 +119,17 @@
"Enable Chat History": "Enable Chat History",
"Enable New Sign Ups": "Enable New Sign Ups",
"Enabled": "Enabled",
"Enter a user message here": "Enter a user message here",
"Enter an assistant message here": "Enter an assistant message here",
"Enter OpenAI API Key": "Enter OpenAI API Key",
"Enter stop sequence": "Enter stop sequence",
"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:",
@ -187,7 +181,6 @@
"Model Tag Name": "Model Tag Name",
"Modelfile": "Modelfile",
"Modelfile Advanced Settings": "Modelfile Advanced Settings",
"Modelfile Content": "Modelfile Content",
"Modelfiles": "Modelfiles",
"Models": "Models",
"My Documents": "My Documents",
@ -195,19 +188,20 @@
"My Prompts": "My Prompts",
"Name": "Name",
"Name Tag": "Name Tag",
"Name your Modelfile": "Name your Modelfile",
"Name your modelfile": "",
"New Chat": "New Chat",
"New Password": "New Password",
"Not sure what to add?": "Not sure what to add?",
"Not sure what to write? Switch to": "Not sure what to write? Switch to",
"Off": "Off",
"Okay, Let's Go!": "Okay, Let's Go!",
"Ollama API URL": "Ollama API 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! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
"Open": "Open",
"Open AI": "Open AI",
"Open new chat": "Open new chat",
@ -215,7 +209,7 @@
"or": "or",
"Parameters": "Parameters",
"Password": "Password",
"pending": "Pending",
"Pending": "",
"Permission denied when accessing microphone: {{error}}": "Permission denied when accessing microphone: {{error}}",
"Playground": "Playground",
"Profile": "Profile",
@ -277,6 +271,7 @@
"STT Settings": "STT Settings",
"Submit": "Submit",
"Success": "Success",
"Successfully updated": "",
"Successfully updated.": "Successfully updated.",
"Sync All": "Sync All",
"System": "System",
@ -290,12 +285,12 @@
"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.": "",
"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 chat input.": "to chat input.",
"Toggle settings": "Toggle settings",
"Toggle sidebar": "Toggle sidebar",
"Top K": "Top K",
@ -303,12 +298,10 @@
"Trouble accessing Ollama?": "Trouble accessing Ollama?",
"TTS Settings": "TTS Settings",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! There was an issue connecting to {{provider}}.",
"Upload a GGUF model": "Upload a GGUF model",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
"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.",
"user": "User",
"User": "",
"User Permissions": "User Permissions",
"Users": "Users",
"Utilize": "Utilize",
@ -322,8 +315,9 @@
"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 can help us translate the WebUI.": "You can help us translate the WebUI.",
"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."
"You're now logged in.": "You're now logged in.",
"You can help us translate the WebUI.": "You can help us translate the WebUI."
}

View file

@ -1,42 +1,65 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' یا '-1' برای غیر فعال کردن انقضا.",
"(Beta)": "(بتا)",
"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
"{{item}} not provided": "{{item}} ارائه نشده است",
"{{modelName}} is thinking...": "{{modelName}} در حال فکر کردن است...",
"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
"About": "درباره",
"Account": "حساب کاربری",
"Action": "عمل",
"Add a model": "اضافه کردن یک مدل",
"Add a model tag name": "اضافه کردن یک نام تگ برای مدل",
"Add a short description about what this modelfile does": "توضیح کوتاهی در مورد کاری که این فایل\u200cمدل انجام می دهد اضافه کنید",
"Add a short title for this prompt": "یک عنوان کوتاه برای این درخواست اضافه کنید",
"Add Docs": "اضافه کردن اسناد",
"Add Files": "اضافه کردن فایل‌ها",
"Add Files": "اضافه کردن فایل\u200cها",
"Add LiteLLM Model": "اضافه کردن مدل LiteLLM",
"add tags": "اضافه کردن تگ‌ها",
"admin": "مدیریت",
"Add message": "اضافه کردن پیغام",
"add tags": "اضافه کردن تگ\u200cها",
"Adjusting these settings will apply changes universally to all users.": "با تنظیم این تنظیمات، تغییرات به طور کلی برای همه کاربران اعمال می شود.",
"Admin": "",
"Admin Panel": "پنل مدیریت",
"Admin Settings": "تنظیمات مدیریت",
"Advanced Model Params": "پارامترهای پیشرفته مدل",
"Advanced Parameters": "پارامترهای پیشرفته",
"all": "همه",
"All Users": "همه کاربران",
"Allow": "اجازه دادن",
"Allow Chat Deletion": "اجازه حذف گپ",
"alphanumeric characters and hyphens": "حروف الفبایی و خط فاصله",
"Already have an account?": "از قبل حساب کاربری دارید؟",
"and": "و",
"API Base URL": "API Base URL",
"API Key": "API Key",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "مجاز هستند - این دستور را با تایپ کردن این فعال کنید:",
"Audio": "صدا",
"AUTOMATIC1111 Base URL": "پایه URL AUTOMATIC1111 ",
"Auto-playback response": "پخش خودکار پاسخ ",
"Auto-send input after 3 sec.": "به طور خودکار ورودی را پس از 3 ثانیه ارسال کن.",
"AUTOMATIC1111 Base URL": "پایه URL AUTOMATIC1111 ",
"available!": "در دسترس!",
"Back": "بازگشت",
"Builder Mode": "حالت سازنده",
"Cancel": "لغو",
"Categories": "دسته‌بندی‌ها",
"Categories": "دسته\u200cبندی\u200cها",
"Change Password": "تغییر رمز عبور",
"Chat History": "تاریخچه‌ی گفتگو",
"Chat": "گپ",
"Chat History": "تاریخچه\u200cی گفتگو",
"Chat History is off for this browser.": "سابقه گپ برای این مرورگر خاموش است.",
"Chats": "گپها",
"Chats": "گپ\u200cها",
"Check Again": "چک مجدد",
"Check for updates": "بررسی به\u200cروزرسانی",
"Checking for updates...": "در حال بررسی برای به\u200cروزرسانی..",
"Choose a model before saving...": "قبل از ذخیره یک مدل را انتخاب کنید...",
"Chunk Overlap": "همپوشانی تکه",
"Chunk Params": "پارامترهای تکه",
"Chunk Size": "اندازه تکه",
"Click here for help": "برای کمک اینجا را کلیک کنید",
"Click here to check other modelfiles.": "برای بررسی سایر فایل\u200cهای مدل اینجا را کلیک کنید.",
"click here.": "اینجا کلیک کنید.",
"Click here to check other modelfiles.": "برای بررسی سایر فایل‌های مدل اینجا را کلیک کنید.",
"Click on the user role button to change a user's role.": "برای تغییر نقش کاربر، روی دکمه نقش کاربر کلیک کنید.",
"Close": "بسته",
"Collection": "مجموعه",
"Command": "دستور",
"Confirm Password": "تایید رمز عبور",
@ -44,68 +67,86 @@
"Content": "محتوا",
"Context Length": "طول زمینه",
"Conversation Mode": "حالت مکالمه",
"Copying to clipboard was successful!": "کپی کردن در کلیپ بورد با موفقیت انجام شد!",
"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": "DD/MM/YYYY HH:mm",
"Default": "پیشفرض",
"Default model updated": "مدل پیشفرض به‌روزرسانی شد",
"Default (Web API)": "پیشفرض (Web API)",
"Default model updated": "مدل پیشفرض به\u200cروزرسانی شد",
"Default Prompt Suggestions": "پیشنهادات پرامپت پیش فرض",
"Default User Role": "نقش کاربر پیش فرض",
"Default (Web API)": "پیشفرض (Web API)",
"delete": "حذف",
"Delete a model": "حذف یک مدل",
"Delete chat": "حذف گپ",
"Delete Chats": "حذف گپها",
"Delete Chats": "حذف گپ\u200cها",
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} پاک شد",
"delete": "حذف",
"Deleted {tagName}": "{tagName} حذف شد",
"Description": "توضیحات",
"Desktop Notifications": "اعلان",
"Disabled": "غیرفعال",
"Discover a modelfile": "فایل مدل را کشف کنید",
"Discover a prompt": "یک اعلان را کشف کنید",
"Discover, download, and explore custom prompts": "پرامپتهای سفارشی را کشف، دانلود و کاوش کنید",
"Discover, download, and explore custom prompts": "پرامپت\u200cهای سفارشی را کشف، دانلود و کاوش کنید",
"Discover, download, and explore model presets": "پیش تنظیمات مدل را کشف، دانلود و کاوش کنید",
"Display the username instead of You in the Chat": "نمایش نام کاربری به جای «شما» در چت",
"Document": "سند",
"Documents": "اسناد",
"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'. واحد\u200cهای زمانی معتبر 's', 'm', 'h' هستند.",
"Edit Doc": "ویرایش سند",
"Edit User": "ویرایش کاربر",
"Email": "ایمیل",
"Enable Chat History": "تاریخچه چت را فعال کنید",
"Enable New Sign Ups": "فعال کردن ثبت نام\u200cهای جدید",
"Enabled": "فعال",
"Enable New Sign Ups": "فعال کردن ثبت نام‌های جدید",
"Experimental": "آزمایشی",
"Export All Chats (All Users)": "اکسپورت از همه گپ‌ها(همه کاربران)",
"Export Chats": "اکسپورت از گپ‌ها",
"Enter OpenAI API Key": "کلید OpenAI API را وارد کنید",
"Enter stop sequence": "توالی توقف را وارد کنید",
"Enter Your Email": "ایمیل خود را وارد کنید",
"Enter Your Full Name": "نام کامل خود را وارد کنید",
"Enter Your Password": "رمز عبور خود را وارد کنید",
"Export All Chats (All Users)": "اکسپورت از همه گپ\u200cها(همه کاربران)",
"Export Chats": "اکسپورت از گپ\u200cها",
"Export Documents Mapping": "اکسپورت از نگاشت اسناد",
"Export Modelfiles": "اکسپورت از فایل‌های مدل",
"Export Prompts": "اکسپورت از پرامپت‌ها",
"Export Modelfiles": "اکسپورت از فایل\u200cهای مدل",
"Export Prompts": "اکسپورت از پرامپت\u200cها",
"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": "پنهان",
"How can I help you today?": "امروز چطور می توانم کمک تان کنم؟",
"Image Generation (Experimental)": "تولید تصویر (آزمایشی)",
"Image Settings": "تنظیمات تصویر",
"Images": "تصاویر",
"Import Chats": "ایمپورت گپها",
"Import Chats": "ایمپورت گپ\u200cها",
"Import Documents Mapping": "ایمپورت نگاشت اسناد",
"Import Modelfiles": "ایمپورت فایل‌های مدل",
"Import Prompts": "ایمپورت پرامپت‌ها",
"Import Modelfiles": "ایمپورت فایل\u200cهای مدل",
"Import Prompts": "ایمپورت پرامپت\u200cها",
"Include `--api` flag when running stable-diffusion-webui": "فلگ `--api` را هنکام اجرای stable-diffusion-webui استفاده کنید.",
"Interface": "رابط",
"{{item}} not provided": "{{item}} ارائه نشده است",
"join our Discord for help.": "برای کمک به دیسکورد ما بپیوندید.",
"JSON": "JSON",
"JWT Expiration": "JWT انقضای",
@ -118,80 +159,90 @@
"LiteLLM API Base URL": "LiteLLM API Base URL",
"LiteLLM API Key": "LiteLLM API Key",
"LiteLLM API RPM": "LiteLLM API RPM",
"LLMs can make mistakes. Verify important information.": "مدل‌های زبانی بزرگ می‌توانند اشتباه کنند. اطلاعات مهم را راستی‌آزمایی کنید.",
"LLMs can make mistakes. Verify important information.": "مدل\u200cهای زبانی بزرگ می\u200cتوانند اشتباه کنند. اطلاعات مهم را راستی\u200cآزمایی کنید.",
"Made by OpenWebUI Community": "ساخته شده توسط OpenWebUI Community",
"Make sure to enclose them with": "مطمئن شوید که آنها را با این محصور کنید:",
"Manage LiteLLM Models": "Manage LiteLLM Models",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "حداکثر 3 مدل را می توان به طور همزمان دانلود کرد. لطفاً بعداً دوباره امتحان کنید.",
"Manage Ollama Models": "مدیریت مدل\u200cهای اولاما",
"Max Tokens": "حداکثر توکن",
"Mirostat Eta": "Mirostat Eta",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "حداکثر 3 مدل را می توان به طور همزمان دانلود کرد. لطفاً بعداً دوباره امتحان کنید.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "مدل '{{modelName}}' با موفقیت دانلود شد.",
"Modelfile Advanced Settings": "تنظیمات پیشرفته فایل‌مدل",
"Modelfile Content": "محتویات فایل‌مدل",
"Modelfile": "فایل مدل",
"Modelfiles": "فایل‌های مدل",
"Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.",
"Model {{modelId}} not found": "مدل {{modelId}} یافت نشد",
"Model {{modelName}} already exists.": "مدل {{modelName}} در حال حاضر وجود دارد.",
"Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.",
"{{modelName}} is thinking...": "{{modelName}} در حال فکر کردن است...",
"Model Name": "نام مدل",
"Model not selected": "مدل انتخاب نشده",
"Models": "مدل‌ها",
"Model Tag Name": "نام تگ مدل",
"Modelfile": "فایل مدل",
"Modelfile Advanced Settings": "تنظیمات پیشرفته فایل\u200cمدل",
"Modelfiles": "فایل\u200cهای مدل",
"Models": "مدل\u200cها",
"My Documents": "اسناد من",
"My Modelfiles": "فایل‌های مدل من",
"My Prompts": "پرامپت‌های من",
"My Modelfiles": "فایل\u200cهای مدل من",
"My Prompts": "پرامپت\u200cهای من",
"Name": "نام",
"Name Tag": "نام تگ",
"Name your modelfile": "",
"New Chat": "گپ جدید",
"New Password": "رمز عبور جدید",
"Desktop Notifications": "اعلان",
"Not sure what to add?": "مطمئن نیستید چه چیزی را اضافه کنید؟",
"Not sure what to write? Switch to": "مطمئن نیستید چه بنویسید؟ تغییر به",
"Off": "خاموش",
"Okay, Let's Go!": "باشه، بزن بریم!",
"Ollama API URL": "اولاما API URL",
"Ollama Version": "نسخه Ollama",
"Only alphanumeric characters and hyphens are allowed in the command string.": "فقط کاراکترهای الفبایی و خط فاصله در رشته فرمان مجاز هستند.",
"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.": "اوه! فایل های شما هنوز در فر پردازش هستند. ما آنها را کامل می پزیم. لطفا صبور باشید، به محض آماده شدن به شما اطلاع خواهیم داد.",
"OpenAI API": "OpenAI API",
"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",
"Open new chat": "باز کردن گپ جدید",
"OpenAI API": "OpenAI API",
"or": "روشن",
"Parameters": "پارامترها",
"Password": "رمز عبور",
"pending": "Pending",
"Pending": "",
"Permission denied when accessing microphone: {{error}}": "هنگام دسترسی به میکروفون، اجازه داده نشد: {{error}}",
"Playground": "زمین بازی",
"Profile": "پروفایل",
"Prompt Content": "محتویات پرامپت",
"Prompts": "پرامپت‌ها",
"Prompt suggestions": "پیشنهادات پرامپت",
"Prompts": "پرامپت\u200cها",
"Pull a model from Ollama.com": "دریافت یک مدل از Ollama.com",
"Pull Progress": "پیشرفت دریافت",
"RAG Template": "RAG الگوی",
"Raw Format": "فرمت خام",
"Record voice": "ضبط صدا",
"Redirecting you to OpenWebUI Community": "در حال هدایت به OpenWebUI Community",
"Release Notes": "یادداشتهای انتشار",
"Release Notes": "یادداشت\u200cهای انتشار",
"Repeat Last N": "Repeat Last N",
"Repeat Penalty": "Repeat Penalty",
"Request Mode": "حالت درخواست",
"Reset Vector Storage": "بازنشانی ذخیره سازی برداری",
"Response AutoCopy to Clipboard": "کپی خودکار پاسخ به کلیپ بورد",
"Role": "نقش",
"Ros\u00e9 Pine Dawn": "Ros\u00e9 Pine Dawn",
"Ros\u00e9 Pine": "Ros\u00e9 Pine",
"Save & Create": "ذخیره و ایجاد",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "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": "ذخیره گزارش‌های چت مستقیماً در حافظه مرورگر شما دیگر پشتیبانی نمی‌شود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
"Save & Update": "ذخیره و به\u200cروزرسانی",
"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": "ذخیره گزارش\u200cهای چت مستقیماً در حافظه مرورگر شما دیگر پشتیبانی نمی\u200cشود. لطفاً با کلیک بر روی دکمه زیر، چند لحظه برای دانلود و حذف گزارش های چت خود وقت بگذارید. نگران نباشید، شما به راحتی می توانید گزارش های چت خود را از طریق بکند دوباره وارد کنید",
"Scan": "اسکن",
"Scan complete!": "اسکن کامل شد!",
"Scan for documents from {{path}}": "اسکن اسناد از {{path}}",
"Scan": "اسکن",
"Search": "جستجو",
"Search Documents": "جستجوی اسناد",
"Search Prompts": "جستجوی پرامپت\u200cها",
"See readme.md for instructions": "برای مشاهده دستورالعمل\u200cها به readme.md مراجعه کنید",
"See what's new": "ببینید موارد جدید چه بوده",
"Seed": "Seed",
"See readme.md for instructions": "برای مشاهده دستورالعمل‌ها به readme.md مراجعه کنید",
"Select a model": "انتخاب یک مدل",
"Send a Messsage": "ارسال یک پیام",
"Send message": "ارسال پیام",
@ -199,129 +250,71 @@
"Set as default": "تنظیم به عنوان پیشفرض",
"Set Default Model": "تنظیم مدل پیش فرض",
"Set Image Size": "تنظیم اندازه تصویر",
"Set Steps": "تنظیم گام‌ها",
"Settings": "تنظیمات",
"Set Steps": "تنظیم گام\u200cها",
"Set Title Auto-Generation Model": "تنظیم مدل تولید خودکار عنوان",
"Set Voice": "تنظیم صدا",
"Settings": "تنظیمات",
"Share to OpenWebUI Community": "اشتراک گذاری با OpenWebUI Community",
"short-summary": "خلاصه کوتاه",
"Show": "نمایش",
"Show shortcuts": "نمایش میانبرها",
"sidebar": "نوار کناری",
"Sign in": "ورود",
"Sign Out": "خروج",
"SpeechRecognition API is not supported in this browser.": "API تشخیص گفتار در این مرورگر پشتیبانی نمی شود.",
"Sign up": "ثبت نام",
"Speech recognition error: {{error}}": "خطای تشخیص گفتار: {{error}}",
"Speech-to-Text Engine": "موتور گفتار به متن",
"SpeechRecognition API is not supported in this browser.": "API تشخیص گفتار در این مرورگر پشتیبانی نمی شود.",
"Stop Sequence": "توالی توقف",
"STT Settings": "STT تنظیمات",
"Successfully updated.": "با موفقیت به روز شد",
"Submit": "ارسال",
"Success": "موفقیت",
"Successfully updated": "",
"Successfully updated.": "با موفقیت به روز شد",
"Sync All": "همگام سازی همه",
"System": "سیستم",
"System Prompt": "پرامپت سیستم",
"Tags": "تگها",
"Tags": "تگ\u200cها",
"Temperature": "دما",
"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.": "این تنظیم در مرورگرها یا دستگاه\u200cها همگام\u200cسازی نمی\u200cشود.",
"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": "پرامپت تولید عنوان",
"Title": "عنوان",
"to": "به",
"To access the available model names for downloading,": "برای دسترسی به نام مدل های موجود برای دانلود،",
"Toggle settings": "نمایش/عدم نمایش تنظیمات",
"Toggle sidebar": "نمایش/عدم نمایش نوار کناری",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "در دسترسی به اولاما مشکل دارید؟",
"TTS Settings": "تنظیمات TTS",
"Uh-oh! There was an issue connecting to {{provider}}.": "اوه اوه! مشکلی در اتصال به {{provider}} وجود داشت.",
"Upload a GGUF model": "بارگذاری یک مدل GGUF",
"Upload files": "بارگذاری فایلها",
"Upload Progress": "میزان پیشرفت بارگذاری",
"URL Mode": "حالت URL",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
"Upload files": "بارگذاری فایل\u200cها",
"Use '#' in the prompt input to load and select your documents.": "در پرامپت از '#' برای لود و انتخاب اسناد خود استفاده کنید.",
"User": "",
"User Permissions": "مجوزهای کاربر",
"Users": "کاربران",
"user": "کاربر",
"WebUI Add-ons": "WebUI افزونه‌های",
"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
"WebUI Settings": "تنظیمات WebUI",
"Web": "وب",
"Whisper (Local)": "ویسپر (محلی)",
"You're now logged in.": "شما اکنون وارد شده‌اید.",
"Hide": "پنهان",
"Show": "نمایش",
"Enter stop sequence": "توالی توقف را وارد کنید",
"Enter OpenAI API Key": "کلید OpenAI API را وارد کنید",
"Current Model": "مدل فعلی",
"Display the username instead of You in the Chat": "نمایش نام کاربری به جای «شما» در چت",
"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':": "یک عبارت مختصر و ۳ تا ۵ کلمه ای را به عنوان سرفصل برای پرس و جو زیر ایجاد کنید، به شدت محدودیت ۳-۵ کلمه را رعایت کنید و از استفاده از کلمه 'عنوان' خودداری کنید:",
"Search": "جستجو",
"Search Documents": "جستجوی اسناد",
"Use '#' in the prompt input to load and select your documents.": "در پرامپت از '#' برای لود و انتخاب اسناد خود استفاده کنید.",
"All Users": "همه کاربران",
"Click on the user role button to change a user's role.": "برای تغییر نقش کاربر، روی دکمه نقش کاربر کلیک کنید.",
"Manage Ollama Models": "مدیریت مدل‌های اولاما",
"Advanced": "پیشرفته",
"Click here for help": "برای کمک اینجا را کلیک کنید",
"Not sure what to add?": "مطمئن نیستید چه چیزی را اضافه کنید؟",
"Adjusting these settings will apply changes universally to all users.": "با تنظیم این تنظیمات، تغییرات به طور کلی برای همه کاربران اعمال می شود.",
"Trouble accessing Ollama?": "در دسترسی به اولاما مشکل دارید؟",
"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
"Include `--api` flag when running stable-diffusion-webui": "فلگ `--api` را هنکام اجرای stable-diffusion-webui استفاده کنید.",
"This setting does not sync across browsers or devices.": "این تنظیم در مرورگرها یا دستگاه‌ها همگام‌سازی نمی‌شود.",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "وقتی سابقه خاموش است، چت‌های جدید در این مرورگر در سابقه شما در هیچ یک از دستگاه‌هایتان ظاهر نمی‌شوند.",
"Enable Chat History": "تاریخچه چت را فعال کنید",
"Search Prompts": "جستجوی پرامپت‌ها",
"available!": "در دسترس!",
"Created by": "ایجاد شده توسط",
"Check for updates": "بررسی به‌روزرسانی",
"See what's new": "ببینید موارد جدید چه بوده",
"Checking for updates...": "در حال بررسی برای به‌روزرسانی..",
"Whats New in": "موارد جدید در",
"Enter Your Email": "ایمیل خود را وارد کنید",
"Enter Your Password": "رمز عبور خود را وارد کنید",
"Enter Your Full Name": "نام کامل خود را وارد کنید",
"Sign in": "ورود",
"Sign up": "ثبت نام",
"Create Account": "ساخت حساب کاربری",
"Don't have an account?": "حساب کاربری ندارید؟",
"Already have an account?": "از قبل حساب کاربری دارید؟",
"to": "به",
"does not make any external connections, and your data stays securely on your locally hosted server.": "هیچ اتصال خارجی ایجاد نمی کند و داده های شما به طور ایمن در سرور میزبان محلی شما باقی می ماند.",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Created at": "ایجاد شده در",
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' یا '-1' برای غیر فعال کردن انقضا.",
"Valid time units:": "واحدهای زمانی معتبر:",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "به طور مثال '30s','10m'. واحد‌های زمانی معتبر 's', 'm', 'h' هستند.",
"Name your Modelfile": "فایل‌مدل خود را نام‌گذاری کنید",
"Add a model tag name": "اضافه کردن یک نام تگ برای مدل",
"Add a short description about what this modelfile does": "توضیح کوتاهی در مورد کاری که این فایل‌مدل انجام می دهد اضافه کنید",
"Write a prompt suggestion (e.g. Who are you?)": "یک پیشنهاد پرامپت بنویسید (مثلاً شما کی هستید؟)",
"Only": "فقط",
"alphanumeric characters and hyphens": "حروف الفبایی و خط فاصله",
"are allowed - Activate this command by typing": "مجاز هستند - این دستور را با تایپ کردن این فعال کنید:",
"to chat input.": "در ورودی گپ",
"Write a summary in 50 words that summarizes [topic or keyword]": "خلاصه ای در 50 کلمه بنویسید که [موضوع یا کلمه کلیدی] را خلاصه کند.",
"variable": "متغیر",
"Format your variables using square brackets like this:": "متغیرهای خود را با استفاده از براکت مربع به شکل زیر قالب بندی کنید:",
"Make sure to enclose them with": "مطمئن شوید که آنها را با این محصور کنید:",
"and": "و",
"Utilize": "استفاده کنید",
"Valid time units:": "واحدهای زمانی معتبر:",
"variable": "متغیر",
"variable to have them replaced with clipboard content.": "متغیر برای جایگزینی آنها با محتوای کلیپ بورد.",
"short-summary": "خلاصه کوتاه",
"Add a short title for this prompt": "یک عنوان کوتاه برای این درخواست اضافه کنید",
"Open": "باز",
"Close": "بسته",
"sidebar": "نوار کناری",
"Playground": "زمین بازی",
"Submit": "ارسال",
"(Beta)": "(بتا)",
"Text Completion": "تکمیل متن",
"Chat": "گپ",
"Web": "وب",
"WebUI Add-ons": "WebUI افزونه\u200cهای",
"WebUI Settings": "تنظیمات WebUI",
"Whats New in": "موارد جدید در",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "وقتی سابقه خاموش است، چت\u200cهای جدید در این مرورگر در سابقه شما در هیچ یک از دستگاه\u200cهایتان ظاهر نمی\u200cشوند.",
"Whisper (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.": "تو یک دستیار سودمند هستی.",
"System": "سیستم",
"assistant": "دستیار",
"Add message": "اضافه کردن پیغام",
"Enter a user message here": "در اینجا یک پیام کاربر را وارد کنید",
"Enter an assistant message here": "در اینجا یک پیام دستیار را وارد کنید",
"Drop any files here to add to the conversation": "هر فایلی را اینجا رها کنید تا به مکالمه اضافه شود",
"You": "شما"
"You're now logged in.": "شما اکنون وارد شده\u200cاید."
}

View file

@ -18,10 +18,9 @@
"Add message": "Ajouter un message",
"add tags": "ajouter des tags",
"Adjusting these settings will apply changes universally to all users.": "L'ajustement de ces paramètres appliquera les changements à tous les utilisateurs.",
"admin": "Administrateur",
"Admin": "",
"Admin Panel": "Panneau d'administration",
"Admin Settings": "Paramètres d'administration",
"Advanced": "Avancé",
"Advanced Model Params": "Paramètres avancés du modèle",
"Advanced Parameters": "Paramètres avancés",
"all": "tous",
@ -35,7 +34,6 @@
"API Key": "Clé API",
"API RPM": "RPM API",
"are allowed - Activate this command by typing": "sont autorisés - Activez cette commande en tapant",
"assistant": "Assistant",
"Audio": "Audio",
"Auto-playback response": "Réponse en lecture automatique",
"Auto-send input after 3 sec.": "Envoyer automatiquement l'entrée après 3 sec.",
@ -72,6 +70,7 @@
"Copy last code block": "Copier le dernier bloc de code",
"Copy last response": "Copier la dernière réponse",
"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
"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':": "Créez une phrase concise de 3-5 mots comme en-tête pour la requête suivante, en respectant strictement la limite de 3-5 mots et en évitant l'utilisation du mot 'titre' :",
"Create a modelfile": "Créer un fichier de modèle",
"Create Account": "Créer un compte",
"Created at": "Créé le",
@ -87,7 +86,6 @@
"Default (Web API)": "Par défaut (API Web)",
"Default model updated": "Modèle par défaut mis à jour",
"Default Prompt Suggestions": "Suggestions de prompt par défaut",
"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':": "Créez une phrase concise de 3-5 mots comme en-tête pour la requête suivante, en respectant strictement la limite de 3-5 mots et en évitant l'utilisation du mot 'titre' :",
"Default User Role": "Rôle d'utilisateur par défaut",
"delete": "supprimer",
"Delete a model": "Supprimer un modèle",
@ -119,21 +117,17 @@
"Enable Chat History": "Activer l'historique du chat",
"Enable New Sign Ups": "Activer les nouvelles inscriptions",
"Enabled": "Activé",
"Enter a user message here": "Entrez un message d'utilisateur ici",
"Enter an assistant message here": "Entrez un message d'assistant ici",
"Enter OpenAI API Key": "Entrez la clé API OpenAI",
"Enter stop sequence": "Entrez la séquence d'arrêt",
"Enter Your Email": "Entrez votre email",
"Enter Your Full Name": "Entrez votre nom complet",
"Enter Your Password": "Entrez votre mot de passe",
"Experimental": "Expérimental",
"Export All Chats (All Users)": "Exporter tous les chats (tous les utilisateurs)",
"Export Chats": "Exporter les chats",
"Export Documents Mapping": "Exporter la correspondance des documents",
"Export Modelfiles": "Exporter les fichiers de modèle",
"Export Prompts": "Exporter les prompts",
"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
"File Mode": "Mode fichier",
"File not found.": "Fichier non trouvé.",
"Focus chat input": "Concentrer sur l'entrée du chat",
"Format your variables using square brackets like this:": "Formatez vos variables en utilisant des crochets comme ceci :",
@ -185,7 +179,6 @@
"Model Tag Name": "Nom de tag du modèle",
"Modelfile": "Fichier de modèle",
"Modelfile Advanced Settings": "Paramètres avancés du fichier de modèle",
"Modelfile Content": "Contenu du fichier de modèle",
"Modelfiles": "Fichiers de modèle",
"Models": "Modèles",
"My Documents": "Mes documents",
@ -193,19 +186,20 @@
"My Prompts": "Mes prompts",
"Name": "Nom",
"Name Tag": "Tag de nom",
"Name your Modelfile": "Nommez votre fichier de modèle",
"Name your modelfile": "",
"New Chat": "Nouveau chat",
"New Password": "Nouveau mot de passe",
"Not sure what to add?": "Vous ne savez pas quoi ajouter ?",
"Not sure what to write? Switch to": "Vous ne savez pas quoi écrire ? Basculer vers",
"Off": "Désactivé",
"Okay, Let's Go!": "D'accord, allons-y !",
"Ollama API URL": "URL de l'API Ollama",
"Ollama Version": "Version Ollama",
"On": "Activé",
"Only": "Seulement",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Seuls les caractères alphanumériques et les tirets sont autorisés dans la chaîne de commande.",
"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.": "Oups ! Tenez bon ! Vos fichiers sont encore dans le four. Nous les cuisinons à la perfection. Soyez patient et nous vous informerons dès qu'ils seront prêts.",
"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": "Ouvrir",
"Open AI": "Open AI",
"Open new chat": "Ouvrir un nouveau chat",
@ -213,7 +207,7 @@
"or": "ou",
"Parameters": "Paramètres",
"Password": "Mot de passe",
"pending": "en attente",
"Pending": "",
"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
"Playground": "Aire de jeu",
"Profile": "Profil",
@ -275,6 +269,7 @@
"STT Settings": "Paramètres STT",
"Submit": "Soumettre",
"Success": "Succès",
"Successfully updated": "",
"Successfully updated.": "Mis à jour avec succès.",
"Sync All": "Synchroniser tout",
"System": "Système",
@ -288,12 +283,12 @@
"Theme": "Thème",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Cela garantit que vos précieuses conversations sont en sécurité dans votre base de données. Merci !",
"This setting does not sync across browsers or devices.": "Ce paramètre ne se synchronise pas entre les navigateurs ou les appareils.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "",
"Title": "Titre",
"Title Auto-Generation": "Génération automatique de titre",
"Title Generation Prompt": "Prompt de génération de titre",
"to": "à",
"To access the available model names for downloading,": "Pour accéder aux noms de modèles disponibles pour le téléchargement,",
"to chat input.": "à l'entrée du chat.",
"Toggle settings": "Basculer les paramètres",
"Toggle sidebar": "Basculer la barre latérale",
"Top K": "Top K",
@ -301,12 +296,10 @@
"Trouble accessing Ollama?": "Problèmes d'accès à Ollama ?",
"TTS Settings": "Paramètres TTS",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh ! Il y a eu un problème de connexion à {{provider}}.",
"Upload a GGUF model": "Téléverser un modèle GGUF",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "",
"Upload files": "Téléverser des fichiers",
"Upload Progress": "Progression du téléversement",
"URL Mode": "Mode URL",
"Use '#' in the prompt input to load and select your documents.": "Utilisez '#' dans l'entrée du prompt pour charger et sélectionner vos documents.",
"user": "Utilisateur",
"User": "",
"User Permissions": "Permissions d'utilisateur",
"Users": "Utilisateurs",
"Utilize": "Utiliser",
@ -320,7 +313,8 @@
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouveaux chats sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
"Whisper (Local)": "Chuchotement (Local)",
"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
"Write a summary in 50 words that summarizes [topic or keyword]": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
"You": "",
"You're a helpful assistant.": "Vous êtes un assistant utile",
"You're now logged in.": "Vous êtes maintenant connecté."
}

View file

@ -1,5 +1,6 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' для відсутності терміну дії.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
"{{item}} not provided": "{{item}} не надано",
"{{modelName}} is thinking...": "{{modelName}} думає...",
@ -14,12 +15,12 @@
"Add Docs": "Додати документи",
"Add Files": "Додати файли",
"Add LiteLLM Model": "Додати модель LiteLLM",
"Add message": "Додати повідомлення",
"add tags": "додати теги",
"Adjusting these settings will apply changes universally to all users.": "Корегування цих налаштувань застосовуватиме зміни для всіх користувачів.",
"admin": "адміністратор",
"Admin": "Адмін",
"Admin Panel": "Панель адміністратора",
"Admin Settings": "Налаштування адміністратора",
"Advanced": "Розширені",
"Advanced Model Params": "Розширені параметри моделі",
"Advanced Parameters": "Розширені параметри",
"all": "всі",
@ -43,6 +44,7 @@
"Cancel": "Скасувати",
"Categories": "Категорії",
"Change Password": "Змінити пароль",
"Chat": "Чат",
"Chat History": "Історія чату",
"Chat History is off for this browser.": "Історія чату вимкнена для цього браузера.",
"Chats": "Чати",
@ -68,6 +70,7 @@
"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 слів обмеження та уникаючи використання слова 'заголовок':",
"Create a modelfile": "Створити modelfile",
"Create Account": "Створити обліковий запис",
"Created at": "Створено",
@ -83,7 +86,6 @@
"Default (Web API)": "За замовчуванням (Web API)",
"Default model updated": "Модель за замовчуванням оновлено",
"Default Prompt Suggestions": "Запропоновані запити за замовчуванням",
"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 слів обмеження та уникаючи використання слова 'заголовок':",
"Default User Role": "Роль користувача за замовчуванням",
"delete": "видалити",
"Delete a model": "Видалити модель",
@ -107,6 +109,7 @@
"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'. Дійсні одиниці часу: 'с', 'хв', 'г'.",
"Edit Doc": "Редагувати документ",
"Edit User": "Редагувати користувача",
@ -119,15 +122,13 @@
"Enter Your Email": "Введіть вашу електронну пошту",
"Enter Your Full Name": "Введіть ваше повне ім'я",
"Enter Your Password": "Введіть ваш пароль",
"Experimental": "Експериментальний",
"Export All Chats (All Users)": "Експортувати всі чати (всі користувачі)",
"Export Chats": "Експортувати чати",
"Export Documents Mapping": "Експортувати відображення документів",
"Export Modelfiles": "Експортувати modelfiles",
"Export Prompts": "Експортувати запити",
"Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну",
"File Mode": "Режим файлу",
"File not found.`": "Файл не знайдено.`",
"File not found.": "Файл не знайдено.",
"Focus chat input": "Фокус вводу чату",
"Format your variables using square brackets like this:": "Форматуйте свої змінні квадратними дужками так:",
"From (Base Model)": "Від (базова модель)",
@ -169,36 +170,36 @@
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM ДД, РРРР",
"Model '{{modelName}}' has been successfully downloaded.`": "Модель '{{modelName}}' успішно завантажено.`",
"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": "Ім'я тегу моделі",
"Modelfile": "Modelfile",
"Modelfile": "Файли моделі",
"Modelfile Advanced Settings": "Розширені налаштування modelfile",
"Modelfile Content": "Зміст modelfile",
"Modelfiles": "Modelfiles",
"Modelfiles": "Файли моделей",
"Models": "Моделі",
"My Documents": "Мої документи",
"My Modelfiles": "Мої modelfiles",
"My Modelfiles": "Мої файли моделей",
"My Prompts": "Мої запити",
"Name": "Назва",
"Name Tag": "Назва тегу",
"Name your Modelfile": "Назвіть свій modelfile",
"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 API URL": "URL API Ollama",
"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 new chat": "Відкрити новий чат",
@ -206,8 +207,9 @@
"or": "або",
"Parameters": "Параметри",
"Password": "Пароль",
"pending": "очікування",
"Pending": "На розгляді",
"Permission denied when accessing microphone: {{error}}": "Доступ до мікрофона заборонено: {{error}}",
"Playground": "Майданчик",
"Profile": "Профіль",
"Prompt Content": "Зміст запиту",
"Prompt suggestions": "Швидкі запити",
@ -265,38 +267,39 @@
"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API не підтримується в цьому браузері.",
"Stop Sequence": "Символ зупинки",
"STT Settings": "Налаштування STT",
"Submit": "Надіслати",
"Success": "Успіх",
"Successfully updated": "Успішно оновлено",
"Successfully updated.": "Успішно оновлено.",
"Sync All": "Синхронізувати все",
"System": "Система",
"System Prompt": "Системний запит",
"Tags": "Теги",
"Temperature": "Температура",
"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 chat input.": "до введення чату.",
"Toggle settings": "Переключити налаштування",
"Toggle sidebar": "Переключити бокову панель",
"Top K": "Вершина K",
"Top P": "Вершина P",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Проблеми з доступом до Ollama?",
"TTS Settings": "Налаштування TTS",
"Uh-oh! There was an issue connecting to {{provider}}.": "Ой! Виникла проблема при підключенні до {{provider}}.",
"Upload a GGUF model": "Завантажити модель GGUF",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Невідомий тип файлу '{{file_type}}', але приймається та обробляється як звичайний текст",
"Upload files": "Завантажити файли",
"Upload Progress": "Прогрес завантаження",
"URL Mode": "Режим URL",
"Use '#' in the prompt input to load and select your documents.": "Використовуйте '#' у введенні запиту для завантаження та вибору ваших документів.",
"user": ористувач",
"User": "Користувач",
"User Permissions": "Дозволи користувача",
"Users": "Користувачі",
"Utilize": "Використовувати",
@ -308,19 +311,10 @@
"WebUI Settings": "Налаштування WebUI",
"Whats New in": "Що нового в",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Коли історія вимкнена, нові чати в цьому браузері не будуть відображатися в історії на жодному з ваших пристроїв.",
"Whisper (Local)": "Шепіт (локально)",
"Whisper (Local)": "Whisper (локально)",
"Write a prompt suggestion (e.g. Who are you?)": "Напишіть запит (напр. Хто ти?)",
"Write a summary in 50 words that summarizes [topic or keyword]": "Напишіть стислий зміст у 50 слів, який узагальнює [тему або ключове слово]",
"You're now logged in.": "Ви увійшли в систему.",
"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
"You": "Ви",
"Playground": "Майданчик",
"Add message": "Додати повідомлення",
"Enter a user message here": "Введіть тут повідомлення користувача",
"Enter an assistant message here": "Введіть тут повідомлення асистента",
"Drop any files here to add to the conversation": "Перетягніть сюди файли, щоб додати до розмови",
"You're a helpful assistant.": "Ви корисний асистент.",
"assistant": "асистент",
"Submit": "Надіслати",
"Chat": "Чат",
"Text Completion": "Завершення тексту"
"You're now logged in.": "Ви увійшли в систему."
}

View file

@ -99,14 +99,11 @@
if (localDBChats.length === 0) {
await deleteDB('Chats');
}
console.log('localdb', localDBChats);
}
console.log(DB);
} catch (error) {
// IndexedDB Not Found
console.log('IDB Not Found');
}
console.log();

View file

@ -344,7 +344,7 @@
content: $settings.system
}
: undefined,
...messages.filter((message) => !message.deleted)
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
@ -558,7 +558,7 @@
content: $settings.system
}
: undefined,
...messages.filter((message) => !message.deleted)
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({

View file

@ -354,7 +354,7 @@
content: $settings.system
}
: undefined,
...messages.filter((message) => !message.deleted)
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
@ -568,7 +568,7 @@
content: $settings.system
}
: undefined,
...messages.filter((message) => !message.deleted)
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({

View file

@ -562,9 +562,9 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
}}
>
{#if advanced}
<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if}
</button>
</div>

View file

@ -181,7 +181,7 @@
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder={$i18n.t(
'Write a summary in 50 words that summarizes [topic or keyword]'
'Write a summary in 50 words that summarizes [topic or keyword].'
)}
rows="6"
bind:value={content}