forked from open-webui/open-webui
commit
851754700a
41 changed files with 1462 additions and 294 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -5,6 +5,26 @@ 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.119] - 2024-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- **🌟 Enhanced RAG Embedding Support**: Ollama, and OpenAI models can now be used for RAG embedding model.
|
||||
- **🔄 Seamless Integration**: Copy 'ollama run <model name>' directly from Ollama page to easily select and pull models.
|
||||
- **🏷️ Tagging Feature**: Add tags to chats directly via the sidebar chat menu.
|
||||
- **📱 Mobile Accessibility**: Swipe left and right on mobile to effortlessly open and close the sidebar.
|
||||
- **🔍 Improved Navigation**: Admin panel now supports pagination for user list.
|
||||
- **🌍 Additional Language Support**: Added Polish language support.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🌍 Language Enhancements**: Vietnamese and Spanish translations have been improved.
|
||||
- **🔧 Helm Fixes**: Resolved issues with Helm trailing slash and manifest.json.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🐳 Docker Optimization**: Updated docker image build process to utilize 'uv' for significantly faster builds compared to 'pip3'.
|
||||
|
||||
## [0.1.118] - 2024-04-10
|
||||
|
||||
### Added
|
||||
|
|
|
@ -93,15 +93,16 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \
|
|||
# install python dependencies
|
||||
COPY ./backend/requirements.txt ./requirements.txt
|
||||
|
||||
RUN if [ "$USE_CUDA" = "true" ]; then \
|
||||
RUN pip3 install uv && \
|
||||
if [ "$USE_CUDA" = "true" ]; then \
|
||||
# If you use CUDA the whisper and embedding model will be downloaded on first use
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
|
||||
pip3 install -r requirements.txt --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
||||
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
else \
|
||||
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
|
||||
pip3 install -r requirements.txt --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])" && \
|
||||
python -c "import os; from chromadb.utils import embedding_functions; sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
fi
|
||||
|
|
|
@ -28,6 +28,7 @@ from config import (
|
|||
UPLOAD_DIR,
|
||||
WHISPER_MODEL,
|
||||
WHISPER_MODEL_DIR,
|
||||
WHISPER_MODEL_AUTO_UPDATE,
|
||||
DEVICE_TYPE,
|
||||
)
|
||||
|
||||
|
@ -69,12 +70,24 @@ def transcribe(
|
|||
f.write(contents)
|
||||
f.close()
|
||||
|
||||
model = WhisperModel(
|
||||
WHISPER_MODEL,
|
||||
device=whisper_device_type,
|
||||
compute_type="int8",
|
||||
download_root=WHISPER_MODEL_DIR,
|
||||
)
|
||||
whisper_kwargs = {
|
||||
"model_size_or_path": WHISPER_MODEL,
|
||||
"device": whisper_device_type,
|
||||
"compute_type": "int8",
|
||||
"download_root": WHISPER_MODEL_DIR,
|
||||
"local_files_only": not WHISPER_MODEL_AUTO_UPDATE,
|
||||
}
|
||||
|
||||
log.debug(f"whisper_kwargs: {whisper_kwargs}")
|
||||
|
||||
try:
|
||||
model = WhisperModel(**whisper_kwargs)
|
||||
except:
|
||||
log.warning(
|
||||
"WhisperModel initialization failed, attempting download with local_files_only=False"
|
||||
)
|
||||
whisper_kwargs["local_files_only"] = False
|
||||
model = WhisperModel(**whisper_kwargs)
|
||||
|
||||
segments, info = model.transcribe(file_path, beam_size=5)
|
||||
log.info(
|
||||
|
|
|
@ -29,7 +29,13 @@ import base64
|
|||
import json
|
||||
import logging
|
||||
|
||||
from config import SRC_LOG_LEVELS, CACHE_DIR, AUTOMATIC1111_BASE_URL, COMFYUI_BASE_URL
|
||||
from config import (
|
||||
SRC_LOG_LEVELS,
|
||||
CACHE_DIR,
|
||||
ENABLE_IMAGE_GENERATION,
|
||||
AUTOMATIC1111_BASE_URL,
|
||||
COMFYUI_BASE_URL,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -48,7 +54,7 @@ app.add_middleware(
|
|||
)
|
||||
|
||||
app.state.ENGINE = ""
|
||||
app.state.ENABLED = False
|
||||
app.state.ENABLED = ENABLE_IMAGE_GENERATION
|
||||
|
||||
app.state.OPENAI_API_KEY = ""
|
||||
app.state.MODEL = ""
|
||||
|
|
|
@ -612,8 +612,13 @@ async def generate_embeddings(
|
|||
user=Depends(get_current_user),
|
||||
):
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
@ -649,6 +654,60 @@ async def generate_embeddings(
|
|||
)
|
||||
|
||||
|
||||
def generate_ollama_embeddings(
|
||||
form_data: GenerateEmbeddingsForm,
|
||||
url_idx: Optional[int] = None,
|
||||
):
|
||||
|
||||
log.info(f"generate_ollama_embeddings {form_data}")
|
||||
|
||||
if url_idx == None:
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model),
|
||||
)
|
||||
|
||||
url = app.state.OLLAMA_BASE_URLS[url_idx]
|
||||
log.info(f"url: {url}")
|
||||
|
||||
try:
|
||||
r = requests.request(
|
||||
method="POST",
|
||||
url=f"{url}/api/embeddings",
|
||||
data=form_data.model_dump_json(exclude_none=True).encode(),
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
data = r.json()
|
||||
|
||||
log.info(f"generate_ollama_embeddings {data}")
|
||||
|
||||
if "embedding" in data:
|
||||
return data["embedding"]
|
||||
else:
|
||||
raise "Something went wrong :/"
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
error_detail = "Open WebUI: Server Connection Error"
|
||||
if r is not None:
|
||||
try:
|
||||
res = r.json()
|
||||
if "error" in res:
|
||||
error_detail = f"Ollama: {res['error']}"
|
||||
except:
|
||||
error_detail = f"Ollama: {e}"
|
||||
|
||||
raise error_detail
|
||||
|
||||
|
||||
class GenerateCompletionForm(BaseModel):
|
||||
model: str
|
||||
prompt: str
|
||||
|
@ -672,8 +731,13 @@ async def generate_completion(
|
|||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
@ -770,8 +834,13 @@ async def generate_chat_completion(
|
|||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
@ -874,8 +943,13 @@ async def generate_openai_chat_completion(
|
|||
):
|
||||
|
||||
if url_idx == None:
|
||||
if form_data.model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[form_data.model]["urls"])
|
||||
model = form_data.model
|
||||
|
||||
if ":" not in model:
|
||||
model = f"{model}:latest"
|
||||
|
||||
if model in app.state.MODELS:
|
||||
url_idx = random.choice(app.state.MODELS[model]["urls"])
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
|
|
@ -39,13 +39,22 @@ import uuid
|
|||
import json
|
||||
|
||||
|
||||
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
|
||||
|
||||
from apps.web.models.documents import (
|
||||
Documents,
|
||||
DocumentForm,
|
||||
DocumentResponse,
|
||||
)
|
||||
|
||||
from apps.rag.utils import query_doc, query_collection, get_embedding_model_path
|
||||
from apps.rag.utils import (
|
||||
query_doc,
|
||||
query_embeddings_doc,
|
||||
query_collection,
|
||||
query_embeddings_collection,
|
||||
get_embedding_model_path,
|
||||
generate_openai_embeddings,
|
||||
)
|
||||
|
||||
from utils.misc import (
|
||||
calculate_sha256,
|
||||
|
@ -58,6 +67,7 @@ from config import (
|
|||
SRC_LOG_LEVELS,
|
||||
UPLOAD_DIR,
|
||||
DOCS_DIR,
|
||||
RAG_EMBEDDING_ENGINE,
|
||||
RAG_EMBEDDING_MODEL,
|
||||
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
|
||||
DEVICE_TYPE,
|
||||
|
@ -74,16 +84,21 @@ log.setLevel(SRC_LOG_LEVELS["RAG"])
|
|||
|
||||
app = FastAPI()
|
||||
|
||||
app.state.PDF_EXTRACT_IMAGES = False
|
||||
app.state.CHUNK_SIZE = CHUNK_SIZE
|
||||
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
|
||||
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
|
||||
|
||||
|
||||
app.state.TOP_K = 4
|
||||
app.state.CHUNK_SIZE = CHUNK_SIZE
|
||||
app.state.CHUNK_OVERLAP = CHUNK_OVERLAP
|
||||
|
||||
|
||||
app.state.RAG_EMBEDDING_ENGINE = RAG_EMBEDDING_ENGINE
|
||||
app.state.RAG_EMBEDDING_MODEL = RAG_EMBEDDING_MODEL
|
||||
app.state.RAG_TEMPLATE = RAG_TEMPLATE
|
||||
|
||||
app.state.RAG_OPENAI_API_BASE_URL = "https://api.openai.com"
|
||||
app.state.RAG_OPENAI_API_KEY = ""
|
||||
|
||||
app.state.PDF_EXTRACT_IMAGES = False
|
||||
|
||||
|
||||
app.state.sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
|
@ -121,45 +136,72 @@ async def get_status():
|
|||
"chunk_size": app.state.CHUNK_SIZE,
|
||||
"chunk_overlap": app.state.CHUNK_OVERLAP,
|
||||
"template": app.state.RAG_TEMPLATE,
|
||||
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/embedding/model")
|
||||
async def get_embedding_model(user=Depends(get_admin_user)):
|
||||
@app.get("/embedding")
|
||||
async def get_embedding_config(user=Depends(get_admin_user)):
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"openai_config": {
|
||||
"url": app.state.RAG_OPENAI_API_BASE_URL,
|
||||
"key": app.state.RAG_OPENAI_API_KEY,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class OpenAIConfigForm(BaseModel):
|
||||
url: str
|
||||
key: str
|
||||
|
||||
|
||||
class EmbeddingModelUpdateForm(BaseModel):
|
||||
openai_config: Optional[OpenAIConfigForm] = None
|
||||
embedding_engine: str
|
||||
embedding_model: str
|
||||
|
||||
|
||||
@app.post("/embedding/model/update")
|
||||
async def update_embedding_model(
|
||||
@app.post("/embedding/update")
|
||||
async def update_embedding_config(
|
||||
form_data: EmbeddingModelUpdateForm, user=Depends(get_admin_user)
|
||||
):
|
||||
|
||||
log.info(
|
||||
f"Updating embedding model: {app.state.RAG_EMBEDDING_MODEL} to {form_data.embedding_model}"
|
||||
)
|
||||
|
||||
try:
|
||||
sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name=get_embedding_model_path(form_data.embedding_model, True),
|
||||
device=DEVICE_TYPE,
|
||||
)
|
||||
)
|
||||
app.state.RAG_EMBEDDING_ENGINE = form_data.embedding_engine
|
||||
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = sentence_transformer_ef
|
||||
if app.state.RAG_EMBEDDING_ENGINE in ["ollama", "openai"]:
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = None
|
||||
|
||||
if form_data.openai_config != None:
|
||||
app.state.RAG_OPENAI_API_BASE_URL = form_data.openai_config.url
|
||||
app.state.RAG_OPENAI_API_KEY = form_data.openai_config.key
|
||||
else:
|
||||
sentence_transformer_ef = (
|
||||
embedding_functions.SentenceTransformerEmbeddingFunction(
|
||||
model_name=get_embedding_model_path(
|
||||
form_data.embedding_model, True
|
||||
),
|
||||
device=DEVICE_TYPE,
|
||||
)
|
||||
)
|
||||
app.state.RAG_EMBEDDING_MODEL = form_data.embedding_model
|
||||
app.state.sentence_transformer_ef = sentence_transformer_ef
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"embedding_engine": app.state.RAG_EMBEDDING_ENGINE,
|
||||
"embedding_model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"openai_config": {
|
||||
"url": app.state.RAG_OPENAI_API_BASE_URL,
|
||||
"key": app.state.RAG_OPENAI_API_KEY,
|
||||
},
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
@ -252,12 +294,37 @@ def query_doc_handler(
|
|||
):
|
||||
|
||||
try:
|
||||
return query_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
return query_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
else:
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"prompt": form_data.query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=form_data.query,
|
||||
key=app.state.RAG_OPENAI_API_KEY,
|
||||
url=app.state.RAG_OPENAI_API_BASE_URL,
|
||||
)
|
||||
|
||||
return query_embeddings_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query_embeddings=query_embeddings,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
|
@ -277,12 +344,45 @@ def query_collection_handler(
|
|||
form_data: QueryCollectionsForm,
|
||||
user=Depends(get_current_user),
|
||||
):
|
||||
return query_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
try:
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
return query_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query=form_data.query,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
else:
|
||||
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": app.state.RAG_EMBEDDING_MODEL,
|
||||
"prompt": form_data.query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=form_data.query,
|
||||
key=app.state.RAG_OPENAI_API_KEY,
|
||||
url=app.state.RAG_OPENAI_API_BASE_URL,
|
||||
)
|
||||
|
||||
return query_embeddings_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
query_embeddings=query_embeddings,
|
||||
k=form_data.k if form_data.k else app.state.TOP_K,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
||||
)
|
||||
|
||||
|
||||
@app.post("/web")
|
||||
|
@ -317,9 +417,11 @@ def store_data_in_vector_db(data, collection_name, overwrite: bool = False) -> b
|
|||
chunk_overlap=app.state.CHUNK_OVERLAP,
|
||||
add_start_index=True,
|
||||
)
|
||||
|
||||
docs = text_splitter.split_documents(data)
|
||||
|
||||
if len(docs) > 0:
|
||||
log.info(f"store_data_in_vector_db {docs}")
|
||||
return store_docs_in_vector_db(docs, collection_name, overwrite), None
|
||||
else:
|
||||
raise ValueError(ERROR_MESSAGES.EMPTY_CONTENT)
|
||||
|
@ -338,6 +440,7 @@ def store_text_in_vector_db(
|
|||
|
||||
|
||||
def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> bool:
|
||||
log.info(f"store_docs_in_vector_db {docs} {collection_name}")
|
||||
|
||||
texts = [doc.page_content for doc in docs]
|
||||
metadatas = [doc.metadata for doc in docs]
|
||||
|
@ -349,18 +452,52 @@ def store_docs_in_vector_db(docs, collection_name, overwrite: bool = False) -> b
|
|||
log.info(f"deleting existing collection {collection_name}")
|
||||
CHROMA_CLIENT.delete_collection(name=collection_name)
|
||||
|
||||
collection = CHROMA_CLIENT.create_collection(
|
||||
name=collection_name,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "":
|
||||
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
collection = CHROMA_CLIENT.create_collection(
|
||||
name=collection_name,
|
||||
embedding_function=app.state.sentence_transformer_ef,
|
||||
)
|
||||
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
|
||||
else:
|
||||
collection = CHROMA_CLIENT.create_collection(name=collection_name)
|
||||
|
||||
if app.state.RAG_EMBEDDING_ENGINE == "ollama":
|
||||
embeddings = [
|
||||
generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{"model": app.state.RAG_EMBEDDING_MODEL, "prompt": text}
|
||||
)
|
||||
)
|
||||
for text in texts
|
||||
]
|
||||
elif app.state.RAG_EMBEDDING_ENGINE == "openai":
|
||||
embeddings = [
|
||||
generate_openai_embeddings(
|
||||
model=app.state.RAG_EMBEDDING_MODEL,
|
||||
text=text,
|
||||
key=app.state.RAG_OPENAI_API_KEY,
|
||||
url=app.state.RAG_OPENAI_API_BASE_URL,
|
||||
)
|
||||
for text in texts
|
||||
]
|
||||
|
||||
for batch in create_batches(
|
||||
api=CHROMA_CLIENT,
|
||||
ids=[str(uuid.uuid1()) for _ in texts],
|
||||
metadatas=metadatas,
|
||||
embeddings=embeddings,
|
||||
documents=texts,
|
||||
):
|
||||
collection.add(*batch)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
|
|
|
@ -2,10 +2,16 @@ import os
|
|||
import re
|
||||
import logging
|
||||
from typing import List
|
||||
import requests
|
||||
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
from apps.ollama.main import generate_ollama_embeddings, GenerateEmbeddingsForm
|
||||
|
||||
|
||||
from config import SRC_LOG_LEVELS, CHROMA_CLIENT
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
|
||||
|
@ -26,6 +32,24 @@ def query_doc(collection_name: str, query: str, k: int, embedding_function):
|
|||
raise e
|
||||
|
||||
|
||||
def query_embeddings_doc(collection_name: str, query_embeddings, k: int):
|
||||
try:
|
||||
# if you use docker use the model from the environment variable
|
||||
log.info(f"query_embeddings_doc {query_embeddings}")
|
||||
collection = CHROMA_CLIENT.get_collection(
|
||||
name=collection_name,
|
||||
)
|
||||
result = collection.query(
|
||||
query_embeddings=[query_embeddings],
|
||||
n_results=k,
|
||||
)
|
||||
|
||||
log.info(f"query_embeddings_doc:result {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
def merge_and_sort_query_results(query_results, k):
|
||||
# Initialize lists to store combined data
|
||||
combined_ids = []
|
||||
|
@ -96,14 +120,46 @@ def query_collection(
|
|||
return merge_and_sort_query_results(results, k)
|
||||
|
||||
|
||||
def query_embeddings_collection(collection_names: List[str], query_embeddings, k: int):
|
||||
|
||||
results = []
|
||||
log.info(f"query_embeddings_collection {query_embeddings}")
|
||||
|
||||
for collection_name in collection_names:
|
||||
try:
|
||||
collection = CHROMA_CLIENT.get_collection(name=collection_name)
|
||||
|
||||
result = collection.query(
|
||||
query_embeddings=[query_embeddings],
|
||||
n_results=k,
|
||||
)
|
||||
results.append(result)
|
||||
except:
|
||||
pass
|
||||
|
||||
return merge_and_sort_query_results(results, k)
|
||||
|
||||
|
||||
def rag_template(template: str, context: str, query: str):
|
||||
template = template.replace("[context]", context)
|
||||
template = template.replace("[query]", query)
|
||||
return template
|
||||
|
||||
|
||||
def rag_messages(docs, messages, template, k, embedding_function):
|
||||
log.debug(f"docs: {docs}")
|
||||
def rag_messages(
|
||||
docs,
|
||||
messages,
|
||||
template,
|
||||
k,
|
||||
embedding_engine,
|
||||
embedding_model,
|
||||
embedding_function,
|
||||
openai_key,
|
||||
openai_url,
|
||||
):
|
||||
log.debug(
|
||||
f"docs: {docs} {messages} {embedding_engine} {embedding_model} {embedding_function} {openai_key} {openai_url}"
|
||||
)
|
||||
|
||||
last_user_message_idx = None
|
||||
for i in range(len(messages) - 1, -1, -1):
|
||||
|
@ -136,22 +192,57 @@ def rag_messages(docs, messages, template, k, embedding_function):
|
|||
context = None
|
||||
|
||||
try:
|
||||
if doc["type"] == "collection":
|
||||
context = query_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
elif doc["type"] == "text":
|
||||
|
||||
if doc["type"] == "text":
|
||||
context = doc["content"]
|
||||
else:
|
||||
context = query_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
if embedding_engine == "":
|
||||
if doc["type"] == "collection":
|
||||
context = query_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
else:
|
||||
context = query_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query=query,
|
||||
k=k,
|
||||
embedding_function=embedding_function,
|
||||
)
|
||||
|
||||
else:
|
||||
if embedding_engine == "ollama":
|
||||
query_embeddings = generate_ollama_embeddings(
|
||||
GenerateEmbeddingsForm(
|
||||
**{
|
||||
"model": embedding_model,
|
||||
"prompt": query,
|
||||
}
|
||||
)
|
||||
)
|
||||
elif embedding_engine == "openai":
|
||||
query_embeddings = generate_openai_embeddings(
|
||||
model=embedding_model,
|
||||
text=query,
|
||||
key=openai_key,
|
||||
url=openai_url,
|
||||
)
|
||||
|
||||
if doc["type"] == "collection":
|
||||
context = query_embeddings_collection(
|
||||
collection_names=doc["collection_names"],
|
||||
query_embeddings=query_embeddings,
|
||||
k=k,
|
||||
)
|
||||
else:
|
||||
context = query_embeddings_doc(
|
||||
collection_name=doc["collection_name"],
|
||||
query_embeddings=query_embeddings,
|
||||
k=k,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
context = None
|
||||
|
@ -230,3 +321,26 @@ def get_embedding_model_path(
|
|||
except Exception as e:
|
||||
log.exception(f"Cannot determine embedding model snapshot path: {e}")
|
||||
return embedding_model
|
||||
|
||||
|
||||
def generate_openai_embeddings(
|
||||
model: str, text: str, key: str, url: str = "https://api.openai.com"
|
||||
):
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{url}/v1/embeddings",
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {key}",
|
||||
},
|
||||
json={"input": text, "model": model},
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
if "data" in data:
|
||||
return data["data"][0]["embedding"]
|
||||
else:
|
||||
raise "Something went wrong :/"
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
|
|
@ -18,6 +18,51 @@ from secrets import token_bytes
|
|||
from constants import ERROR_MESSAGES
|
||||
|
||||
|
||||
####################################
|
||||
# LOGGING
|
||||
####################################
|
||||
|
||||
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
||||
|
||||
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
|
||||
if GLOBAL_LOG_LEVEL in log_levels:
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
|
||||
else:
|
||||
GLOBAL_LOG_LEVEL = "INFO"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
||||
|
||||
log_sources = [
|
||||
"AUDIO",
|
||||
"COMFYUI",
|
||||
"CONFIG",
|
||||
"DB",
|
||||
"IMAGES",
|
||||
"LITELLM",
|
||||
"MAIN",
|
||||
"MODELS",
|
||||
"OLLAMA",
|
||||
"OPENAI",
|
||||
"RAG",
|
||||
"WEBHOOK",
|
||||
]
|
||||
|
||||
SRC_LOG_LEVELS = {}
|
||||
|
||||
for source in log_sources:
|
||||
log_env_var = source + "_LOG_LEVEL"
|
||||
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
|
||||
if SRC_LOG_LEVELS[source] not in log_levels:
|
||||
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
|
||||
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
|
||||
|
||||
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
|
||||
|
||||
####################################
|
||||
# Load .env file
|
||||
####################################
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
|
@ -122,47 +167,6 @@ STATIC_DIR = str(Path(os.getenv("STATIC_DIR", "./static")).resolve())
|
|||
|
||||
shutil.copyfile(f"{FRONTEND_BUILD_DIR}/favicon.png", f"{STATIC_DIR}/favicon.png")
|
||||
|
||||
####################################
|
||||
# LOGGING
|
||||
####################################
|
||||
log_levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
||||
|
||||
GLOBAL_LOG_LEVEL = os.environ.get("GLOBAL_LOG_LEVEL", "").upper()
|
||||
if GLOBAL_LOG_LEVEL in log_levels:
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL, force=True)
|
||||
else:
|
||||
GLOBAL_LOG_LEVEL = "INFO"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|
||||
|
||||
log_sources = [
|
||||
"AUDIO",
|
||||
"COMFYUI",
|
||||
"CONFIG",
|
||||
"DB",
|
||||
"IMAGES",
|
||||
"LITELLM",
|
||||
"MAIN",
|
||||
"MODELS",
|
||||
"OLLAMA",
|
||||
"OPENAI",
|
||||
"RAG",
|
||||
"WEBHOOK",
|
||||
]
|
||||
|
||||
SRC_LOG_LEVELS = {}
|
||||
|
||||
for source in log_sources:
|
||||
log_env_var = source + "_LOG_LEVEL"
|
||||
SRC_LOG_LEVELS[source] = os.environ.get(log_env_var, "").upper()
|
||||
if SRC_LOG_LEVELS[source] not in log_levels:
|
||||
SRC_LOG_LEVELS[source] = GLOBAL_LOG_LEVEL
|
||||
log.info(f"{log_env_var}: {SRC_LOG_LEVELS[source]}")
|
||||
|
||||
log.setLevel(SRC_LOG_LEVELS["CONFIG"])
|
||||
|
||||
|
||||
####################################
|
||||
# CUSTOM_NAME
|
||||
####################################
|
||||
|
@ -401,6 +405,9 @@ if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
|
|||
|
||||
CHROMA_DATA_PATH = f"{DATA_DIR}/vector_db"
|
||||
# this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (all-MiniLM-L6-v2)
|
||||
|
||||
RAG_EMBEDDING_ENGINE = os.environ.get("RAG_EMBEDDING_ENGINE", "")
|
||||
|
||||
RAG_EMBEDDING_MODEL = os.environ.get("RAG_EMBEDDING_MODEL", "all-MiniLM-L6-v2")
|
||||
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL}"),
|
||||
|
||||
|
@ -409,7 +416,7 @@ RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
|
|||
)
|
||||
|
||||
|
||||
# device type ebbeding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
|
||||
# device type embedding models - "cpu" (default), "cuda" (nvidia gpu required) or "mps" (apple silicon) - choosing this right can lead to better performance
|
||||
USE_CUDA = os.environ.get("USE_CUDA_DOCKER", "false")
|
||||
|
||||
if USE_CUDA.lower() == "true":
|
||||
|
@ -446,11 +453,17 @@ Query: [query]"""
|
|||
|
||||
WHISPER_MODEL = os.getenv("WHISPER_MODEL", "base")
|
||||
WHISPER_MODEL_DIR = os.getenv("WHISPER_MODEL_DIR", f"{CACHE_DIR}/whisper/models")
|
||||
WHISPER_MODEL_AUTO_UPDATE = (
|
||||
os.environ.get("WHISPER_MODEL_AUTO_UPDATE", "").lower() == "true"
|
||||
)
|
||||
|
||||
|
||||
####################################
|
||||
# Images
|
||||
####################################
|
||||
|
||||
ENABLE_IMAGE_GENERATION = (
|
||||
os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true"
|
||||
)
|
||||
AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
|
||||
COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "")
|
||||
|
|
|
@ -114,7 +114,11 @@ class RAGMiddleware(BaseHTTPMiddleware):
|
|||
data["messages"],
|
||||
rag_app.state.RAG_TEMPLATE,
|
||||
rag_app.state.TOP_K,
|
||||
rag_app.state.RAG_EMBEDDING_ENGINE,
|
||||
rag_app.state.RAG_EMBEDDING_MODEL,
|
||||
rag_app.state.sentence_transformer_ef,
|
||||
rag_app.state.RAG_OPENAI_API_KEY,
|
||||
rag_app.state.RAG_OPENAI_API_BASE_URL,
|
||||
)
|
||||
del data["docs"]
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ ollama
|
|||
{{- if .Values.ollama.externalHost }}
|
||||
{{- printf .Values.ollama.externalHost }}
|
||||
{{- else }}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d/" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- printf "http://%s.%s.svc.cluster.local:%d" (include "ollama.name" .) (.Release.Namespace) (.Values.ollama.service.port | int) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.118",
|
||||
"version": "0.1.119",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.1.118",
|
||||
"version": "0.1.119",
|
||||
"dependencies": {
|
||||
"@sveltejs/adapter-node": "^1.3.1",
|
||||
"async": "^3.2.5",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.1.118",
|
||||
"version": "0.1.119",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev --host",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<script>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { OLLAMA_API_BASE_URL } from '$lib/constants';
|
||||
import { promptTemplate } from '$lib/utils';
|
||||
|
||||
export const getOllamaUrls = async (token: string = '') => {
|
||||
let error = null;
|
||||
|
@ -144,7 +145,7 @@ export const generateTitle = async (
|
|||
) => {
|
||||
let error = null;
|
||||
|
||||
template = template.replace(/{{prompt}}/g, prompt);
|
||||
template = promptTemplate(template, prompt);
|
||||
|
||||
console.log(template);
|
||||
|
||||
|
@ -219,6 +220,32 @@ export const generatePrompt = async (token: string = '', model: string, conversa
|
|||
return res;
|
||||
};
|
||||
|
||||
export const generateEmbeddings = async (token: string = '', model: string, text: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${OLLAMA_API_BASE_URL}/api/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model,
|
||||
prompt: text
|
||||
})
|
||||
}).catch((err) => {
|
||||
error = err;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const generateTextCompletion = async (token: string = '', model: string, text: string) => {
|
||||
let error = null;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { OPENAI_API_BASE_URL } from '$lib/constants';
|
||||
import { promptTemplate } from '$lib/utils';
|
||||
|
||||
export const getOpenAIUrls = async (token: string = '') => {
|
||||
let error = null;
|
||||
|
@ -273,7 +274,7 @@ export const generateTitle = async (
|
|||
) => {
|
||||
let error = null;
|
||||
|
||||
template = template.replace(/{{prompt}}/g, prompt);
|
||||
template = promptTemplate(template, prompt);
|
||||
|
||||
console.log(template);
|
||||
|
||||
|
|
|
@ -346,10 +346,10 @@ export const resetVectorDB = async (token: string) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
export const getEmbeddingModel = async (token: string) => {
|
||||
export const getEmbeddingConfig = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model`, {
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -373,14 +373,21 @@ export const getEmbeddingModel = async (token: string) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
type OpenAIConfigForm = {
|
||||
key: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type EmbeddingModelUpdateForm = {
|
||||
openai_config?: OpenAIConfigForm;
|
||||
embedding_engine: string;
|
||||
embedding_model: string;
|
||||
};
|
||||
|
||||
export const updateEmbeddingModel = async (token: string, payload: EmbeddingModelUpdateForm) => {
|
||||
export const updateEmbeddingConfig = async (token: string, payload: EmbeddingModelUpdateForm) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/model/update`, {
|
||||
const res = await fetch(`${RAG_API_BASE_URL}/embedding/update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
localStorage.version = $config.version;
|
||||
show = false;
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import { synthesizeOpenAISpeech } from '$lib/apis/openai';
|
||||
import { imageGenerations } from '$lib/apis/images';
|
||||
import {
|
||||
approximateToHumanReadable,
|
||||
extractSentences,
|
||||
revertSanitizedResponseContent,
|
||||
sanitizeResponseContent
|
||||
|
@ -122,7 +123,10 @@
|
|||
eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
|
||||
eval_duration: ${
|
||||
Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
|
||||
}ms</span>`,
|
||||
}ms<br/>
|
||||
approximate_total: ${approximateToHumanReadable(
|
||||
message.info.total_duration
|
||||
)}</span>`,
|
||||
allowHTML: true
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
: items;
|
||||
|
||||
const pullModelHandler = async () => {
|
||||
const sanitizedModelTag = searchValue.trim();
|
||||
const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, '');
|
||||
|
||||
console.log($MODEL_DOWNLOAD_POOL);
|
||||
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
};
|
||||
|
||||
const pullModelHandler = async () => {
|
||||
const sanitizedModelTag = modelTag.trim();
|
||||
const sanitizedModelTag = modelTag.trim().replace(/^ollama\s+(run|pull)\s+/, '');
|
||||
if (modelDownloadStatus[sanitizedModelTag]) {
|
||||
toast.error(
|
||||
$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
|
||||
|
|
52
src/lib/components/chat/Tags.svelte
Normal file
52
src/lib/components/chat/Tags.svelte
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script>
|
||||
import {
|
||||
addTagById,
|
||||
deleteTagById,
|
||||
getAllChatTags,
|
||||
getTagsById,
|
||||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { tags as _tags } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import Tags from '../common/Tags.svelte';
|
||||
|
||||
export let chatId = '';
|
||||
let tags = [];
|
||||
|
||||
const getTags = async () => {
|
||||
return await getTagsById(localStorage.token, chatId).catch(async (error) => {
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
const addTag = async (tagName) => {
|
||||
const res = await addTagById(localStorage.token, chatId, tagName);
|
||||
tags = await getTags();
|
||||
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
tags: tags
|
||||
});
|
||||
|
||||
_tags.set(await getAllChatTags(localStorage.token));
|
||||
};
|
||||
|
||||
const deleteTag = async (tagName) => {
|
||||
const res = await deleteTagById(localStorage.token, chatId, tagName);
|
||||
tags = await getTags();
|
||||
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
tags: tags
|
||||
});
|
||||
|
||||
_tags.set(await getAllChatTags(localStorage.token));
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if (chatId) {
|
||||
tags = await getTags();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Tags {tags} {deleteTag} {addTag} />
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
export let show = false;
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root
|
||||
bind:open={show}
|
||||
onOpenChange={(state) => {
|
||||
dispatch('change', state);
|
||||
}}
|
||||
|
|
42
src/lib/components/common/Pagination.svelte
Normal file
42
src/lib/components/common/Pagination.svelte
Normal file
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts">
|
||||
import { Pagination } from 'bits-ui';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import ChevronLeft from '../icons/ChevronLeft.svelte';
|
||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||
|
||||
export let page = 0;
|
||||
export let count = 0;
|
||||
export let perPage = 20;
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<Pagination.Root bind:page {count} {perPage} let:pages>
|
||||
<div class="my-2 flex items-center">
|
||||
<Pagination.PrevButton
|
||||
class="mr-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
|
||||
>
|
||||
<ChevronLeft className="size-4" strokeWidth="2" />
|
||||
</Pagination.PrevButton>
|
||||
<div class="flex items-center gap-2.5">
|
||||
{#each pages as page (page.key)}
|
||||
{#if page.type === 'ellipsis'}
|
||||
<div class="text-sm font-medium text-foreground-alt">...</div>
|
||||
{:else}
|
||||
<Pagination.Page
|
||||
{page}
|
||||
class="inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 text-sm font-medium hover:bg-dark-10 active:scale-98 disabled:cursor-not-allowed disabled:opacity-50 hover:disabled:bg-transparent data-[selected]:bg-black data-[selected]:text-gray-100 data-[selected]:hover:bg-black dark:data-[selected]:bg-white dark:data-[selected]:text-gray-900 dark:data-[selected]:hover:bg-white"
|
||||
>
|
||||
{page.value}
|
||||
</Pagination.Page>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<Pagination.NextButton
|
||||
class="ml-[25px] inline-flex size-8 items-center justify-center rounded-[9px] bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 active:scale-98 disabled:cursor-not-allowed disabled:text-gray-400 dark:disabled:text-gray-700 hover:disabled:bg-transparent dark:hover:disabled:bg-transparent"
|
||||
>
|
||||
<ChevronRight className="size-4" strokeWidth="2" />
|
||||
</Pagination.NextButton>
|
||||
</div>
|
||||
</Pagination.Root>
|
||||
</div>
|
|
@ -16,7 +16,6 @@
|
|||
const i18n = getContext('i18n');
|
||||
|
||||
export let show = false;
|
||||
export let selectedDoc;
|
||||
let uploadDocInputElement: HTMLInputElement;
|
||||
let inputFiles;
|
||||
let tags = [];
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
scanDocs,
|
||||
updateQuerySettings,
|
||||
resetVectorDB,
|
||||
getEmbeddingModel,
|
||||
updateEmbeddingModel
|
||||
getEmbeddingConfig,
|
||||
updateEmbeddingConfig
|
||||
} from '$lib/apis/rag';
|
||||
|
||||
import { documents } from '$lib/stores';
|
||||
import { documents, models } from '$lib/stores';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
|
@ -26,6 +26,12 @@
|
|||
|
||||
let showResetConfirm = false;
|
||||
|
||||
let embeddingEngine = '';
|
||||
let embeddingModel = '';
|
||||
|
||||
let openAIKey = '';
|
||||
let openAIUrl = '';
|
||||
|
||||
let chunkSize = 0;
|
||||
let chunkOverlap = 0;
|
||||
let pdfExtractImages = true;
|
||||
|
@ -35,8 +41,6 @@
|
|||
k: 4
|
||||
};
|
||||
|
||||
let embeddingModel = '';
|
||||
|
||||
const scanHandler = async () => {
|
||||
scanDirLoading = true;
|
||||
const res = await scanDocs(localStorage.token);
|
||||
|
@ -49,7 +53,15 @@
|
|||
};
|
||||
|
||||
const embeddingModelUpdateHandler = async () => {
|
||||
if (embeddingModel.split('/').length - 1 > 1) {
|
||||
if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (embeddingEngine === 'ollama' && embeddingModel === '') {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
|
@ -58,14 +70,37 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (embeddingEngine === 'openai' && embeddingModel === '') {
|
||||
toast.error(
|
||||
$i18n.t(
|
||||
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((embeddingEngine === 'openai' && openAIKey === '') || openAIUrl === '') {
|
||||
toast.error($i18n.t('OpenAI URL/Key required.'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Update embedding model attempt:', embeddingModel);
|
||||
|
||||
updateEmbeddingModelLoading = true;
|
||||
const res = await updateEmbeddingModel(localStorage.token, {
|
||||
embedding_model: embeddingModel
|
||||
const res = await updateEmbeddingConfig(localStorage.token, {
|
||||
embedding_engine: embeddingEngine,
|
||||
embedding_model: embeddingModel,
|
||||
...(embeddingEngine === 'openai'
|
||||
? {
|
||||
openai_config: {
|
||||
key: openAIKey,
|
||||
url: openAIUrl
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}).catch(async (error) => {
|
||||
toast.error(error);
|
||||
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
|
||||
await setEmbeddingConfig();
|
||||
return null;
|
||||
});
|
||||
updateEmbeddingModelLoading = false;
|
||||
|
@ -73,7 +108,7 @@
|
|||
if (res) {
|
||||
console.log('embeddingModelUpdateHandler:', res);
|
||||
if (res.status === true) {
|
||||
toast.success($i18n.t('Model {{embedding_model}} update complete!', res), {
|
||||
toast.success($i18n.t('Embedding model set to "{{embedding_model}}"', res), {
|
||||
duration: 1000 * 10
|
||||
});
|
||||
}
|
||||
|
@ -91,6 +126,18 @@
|
|||
querySettings = await updateQuerySettings(localStorage.token, querySettings);
|
||||
};
|
||||
|
||||
const setEmbeddingConfig = async () => {
|
||||
const embeddingConfig = await getEmbeddingConfig(localStorage.token);
|
||||
|
||||
if (embeddingConfig) {
|
||||
embeddingEngine = embeddingConfig.embedding_engine;
|
||||
embeddingModel = embeddingConfig.embedding_model;
|
||||
|
||||
openAIKey = embeddingConfig.openai_config.key;
|
||||
openAIUrl = embeddingConfig.openai_config.url;
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const res = await getRAGConfig(localStorage.token);
|
||||
|
||||
|
@ -101,7 +148,7 @@
|
|||
chunkOverlap = res.chunk.chunk_overlap;
|
||||
}
|
||||
|
||||
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
|
||||
await setEmbeddingConfig();
|
||||
|
||||
querySettings = await getQuerySettings(localStorage.token);
|
||||
});
|
||||
|
@ -118,81 +165,212 @@
|
|||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
|
||||
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
|
||||
<div class="flex items-center relative">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
|
||||
bind:value={embeddingEngine}
|
||||
placeholder="Select an embedding model engine"
|
||||
on:change={(e) => {
|
||||
if (e.target.value === 'ollama') {
|
||||
embeddingModel = '';
|
||||
} else if (e.target.value === 'openai') {
|
||||
embeddingModel = 'text-embedding-3-small';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">{$i18n.t('Default (SentenceTransformer)')}</option>
|
||||
<option value="ollama">{$i18n.t('Ollama')}</option>
|
||||
<option value="openai">{$i18n.t('OpenAI')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
scanHandler();
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={scanDirLoading}
|
||||
>
|
||||
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
|
||||
|
||||
{#if scanDirLoading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style><path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/><path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700" />
|
||||
{#if embeddingEngine === 'openai'}
|
||||
<div class="mt-1 flex gap-2">
|
||||
<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={openAIUrl}
|
||||
required
|
||||
/>
|
||||
|
||||
<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={openAIKey}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<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('Update embedding model (e.g. {{model}})', {
|
||||
model: embeddingModel.slice(-40)
|
||||
})}
|
||||
bind:value={embeddingModel}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
{#if updateEmbeddingModelLoading}
|
||||
<div class="self-center">
|
||||
|
||||
{#if embeddingEngine === 'ollama'}
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
bind:value={embeddingModel}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
required
|
||||
>
|
||||
{#if !embeddingModel}
|
||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
||||
{/if}
|
||||
{#each $models.filter((m) => m.id && !m.external) as model}
|
||||
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
{#if updateEmbeddingModelLoading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style><path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/><path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 mr-2">
|
||||
<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('Update embedding model (e.g. {{model}})', {
|
||||
model: embeddingModel.slice(-40)
|
||||
})}
|
||||
bind:value={embeddingModel}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||
on:click={() => {
|
||||
embeddingModelUpdateHandler();
|
||||
}}
|
||||
disabled={updateEmbeddingModelLoading}
|
||||
>
|
||||
{#if updateEmbeddingModelLoading}
|
||||
<div class="self-center">
|
||||
<svg
|
||||
class=" w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><style>
|
||||
.spinner_ajPY {
|
||||
transform-origin: center;
|
||||
animation: spinner_AtaB 0.75s infinite linear;
|
||||
}
|
||||
@keyframes spinner_AtaB {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style><path
|
||||
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||
opacity=".25"
|
||||
/><path
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
class="spinner_ajPY"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t(
|
||||
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<div class=" flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
scanHandler();
|
||||
console.log('check');
|
||||
}}
|
||||
type="button"
|
||||
disabled={scanDirLoading}
|
||||
>
|
||||
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
|
||||
|
||||
{#if scanDirLoading}
|
||||
<div class="ml-3 self-center">
|
||||
<svg
|
||||
class=" w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -215,30 +393,10 @@
|
|||
/></svg
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
{$i18n.t(
|
||||
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700 my-3" />
|
||||
|
||||
<div class=" ">
|
||||
|
|
15
src/lib/components/icons/ChevronLeft.svelte
Normal file
15
src/lib/components/icons/ChevronLeft.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
15
src/lib/components/icons/ChevronRight.svelte
Normal file
15
src/lib/components/icons/ChevronRight.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
|
@ -19,10 +19,6 @@
|
|||
export let chat;
|
||||
export let selectedModels;
|
||||
|
||||
export let tags = [];
|
||||
export let addTag: Function;
|
||||
export let deleteTag: Function;
|
||||
|
||||
export let showModelSelector = true;
|
||||
|
||||
let showShareChatModal = false;
|
||||
|
@ -85,9 +81,6 @@
|
|||
downloadHandler={() => {
|
||||
showDownloadChatModal = !showDownloadChatModal;
|
||||
}}
|
||||
{tags}
|
||||
{deleteTag}
|
||||
{addTag}
|
||||
>
|
||||
<button
|
||||
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
import { flyAndScale } from '$lib/utils/transitions';
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tags from '$lib/components/common/Tags.svelte';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import Tags from '$lib/components/chat/Tags.svelte';
|
||||
|
||||
import { downloadChatAsPDF } from '$lib/apis/utils';
|
||||
|
||||
export let shareEnabled: boolean = false;
|
||||
|
@ -21,10 +21,6 @@
|
|||
// export let tagHandler: Function;
|
||||
|
||||
export let chat;
|
||||
export let tags;
|
||||
export let deleteTag: Function;
|
||||
export let addTag: Function;
|
||||
|
||||
export let onClose: Function = () => {};
|
||||
|
||||
const downloadTxt = async () => {
|
||||
|
@ -190,7 +186,7 @@
|
|||
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
|
||||
|
||||
<div class="flex p-1">
|
||||
<Tags {tags} {deleteTag} {addTag} />
|
||||
<Tags chatId={chat.id} />
|
||||
</div>
|
||||
|
||||
<!-- <DropdownMenu.Item
|
||||
|
|
|
@ -45,6 +45,39 @@
|
|||
show = true;
|
||||
}
|
||||
await chats.set(await getChatList(localStorage.token));
|
||||
|
||||
let touchstartX = 0;
|
||||
let touchendX = 0;
|
||||
|
||||
function checkDirection() {
|
||||
const screenWidth = window.innerWidth;
|
||||
const swipeDistance = Math.abs(touchendX - touchstartX);
|
||||
if (swipeDistance >= screenWidth / 4) {
|
||||
if (touchendX < touchstartX) {
|
||||
show = false;
|
||||
}
|
||||
if (touchendX > touchstartX) {
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onTouchStart = (e) => {
|
||||
touchstartX = e.changedTouches[0].screenX;
|
||||
};
|
||||
|
||||
const onTouchEnd = (e) => {
|
||||
touchendX = e.changedTouches[0].screenX;
|
||||
checkDirection();
|
||||
};
|
||||
|
||||
document.addEventListener('touchstart', onTouchStart);
|
||||
document.addEventListener('touchend', onTouchEnd);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('touchstart', onTouchStart);
|
||||
document.removeEventListener('touchend', onTouchEnd);
|
||||
};
|
||||
});
|
||||
|
||||
// Helper function to fetch and add chat content to each chat
|
||||
|
@ -513,6 +546,7 @@
|
|||
{:else}
|
||||
<div class="flex self-center space-x-1.5 z-10">
|
||||
<ChatMenu
|
||||
chatId={chat.id}
|
||||
renameHandler={() => {
|
||||
chatTitle = chat.title;
|
||||
chatTitleEditId = chat.id;
|
||||
|
@ -706,6 +740,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
id="sidebar-handle"
|
||||
class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
|
||||
>
|
||||
<Tooltip
|
||||
|
|
|
@ -6,14 +6,19 @@
|
|||
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
|
||||
import Pencil from '$lib/components/icons/Pencil.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Tags from '$lib/components/chat/Tags.svelte';
|
||||
|
||||
export let renameHandler: Function;
|
||||
export let deleteHandler: Function;
|
||||
|
||||
export let onClose: Function;
|
||||
|
||||
export let chatId = '';
|
||||
|
||||
let show = false;
|
||||
</script>
|
||||
|
||||
<Dropdown
|
||||
bind:show
|
||||
on:change={(e) => {
|
||||
if (e.detail === false) {
|
||||
onClose();
|
||||
|
@ -26,14 +31,14 @@
|
|||
|
||||
<div slot="content">
|
||||
<DropdownMenu.Content
|
||||
class="w-full max-w-[130px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
|
||||
class="w-full max-w-[150px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow"
|
||||
sideOffset={-2}
|
||||
side="bottom"
|
||||
align="start"
|
||||
transition={flyAndScale}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
renameHandler();
|
||||
}}
|
||||
|
@ -43,7 +48,7 @@
|
|||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer"
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
|
||||
on:click={() => {
|
||||
deleteHandler();
|
||||
}}
|
||||
|
@ -51,6 +56,12 @@
|
|||
<GarbageBin strokeWidth="2" />
|
||||
<div class="flex items-center">Delete</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-800 mt-2.5 mb-1.5" />
|
||||
|
||||
<div class="flex p-1">
|
||||
<Tags {chatId} />
|
||||
</div>
|
||||
</DropdownMenu.Content>
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
"Click here to select": "Presiona aquí para seleccionar",
|
||||
"Click here to select documents.": "Presiona aquí para seleccionar documentos",
|
||||
"click here.": "Presiona aquí.",
|
||||
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar el rol de un usuario.",
|
||||
"Click on the user role button to change a user's role.": "Presiona en el botón de roles del usuario para cambiar su rol.",
|
||||
"Close": "Cerrar",
|
||||
"Collection": "Colección",
|
||||
"Command": "Comando",
|
||||
|
@ -120,9 +120,10 @@
|
|||
"Edit Doc": "Editar Documento",
|
||||
"Edit User": "Editar Usuario",
|
||||
"Email": "Email",
|
||||
"Embedding model: {{embedding_model}}": "Modelo de Embedding: {{embedding_model}}",
|
||||
"Enable Chat History": "Activa el Historial de Chat",
|
||||
"Enable New Sign Ups": "Habilitar Nuevos Registros",
|
||||
"Enabled": "Habilitado",
|
||||
"Enabled": "Activado",
|
||||
"Enter {{role}} message here": "Introduzca el mensaje {{role}} aquí",
|
||||
"Enter API Key": "Ingrese la clave API",
|
||||
"Enter Chunk Overlap": "Ingresar superposición de fragmentos",
|
||||
|
@ -145,11 +146,12 @@
|
|||
"Export All Chats (All Users)": "Exportar todos los chats (Todos los usuarios)",
|
||||
"Export Chats": "Exportar Chats",
|
||||
"Export Documents Mapping": "Exportar el mapeo de documentos",
|
||||
"Export Modelfiles": "Exportal Modelfiles",
|
||||
"Export Modelfiles": "Exportar Modelfiles",
|
||||
"Export Prompts": "Exportar Prompts",
|
||||
"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
|
||||
"File Mode": "Modo de archivo",
|
||||
"File not found.": "Archivo no encontrado.",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Se detectó suplantación de huellas: No se pueden usar las iniciales como avatar. Por defecto se utiliza la imagen de perfil predeterminada.",
|
||||
"Focus chat input": "Enfoca la entrada del chat",
|
||||
"Format your variables using square brackets like this:": "Formatee sus variables usando corchetes así:",
|
||||
"From (Base Model)": "Desde (Modelo Base)",
|
||||
|
@ -193,8 +195,11 @@
|
|||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "El modelo '{{modelName}}' se ha descargado correctamente.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "El modelo '{{modelTag}}' ya está en cola para descargar.",
|
||||
"Model {{embedding_model}} update complete!": "¡La actualizacón del modelo {{embedding_model}} fué completada!",
|
||||
"Model {{embedding_model}} update failed or not required!": "¡La actualización del modelo {{embedding_model}} falló o no es requerida!",
|
||||
"Model {{modelId}} not found": "El modelo {{modelId}} no fue encontrado",
|
||||
"Model {{modelName}} already exists.": "El modelo {{modelName}} ya existe.",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Se detectó la ruta del sistema de archivos del modelo. Se requiere el nombre corto del modelo para la actualización, no se puede continuar.",
|
||||
"Model Name": "Nombre del modelo",
|
||||
"Model not selected": "Modelo no seleccionado",
|
||||
"Model Tag Name": "Nombre de la etiqueta del modelo",
|
||||
|
@ -215,11 +220,11 @@
|
|||
"New Password": "Nueva Contraseña",
|
||||
"Not sure what to add?": "¿No estás seguro de qué añadir?",
|
||||
"Not sure what to write? Switch to": "¿No estás seguro de qué escribir? Cambia a",
|
||||
"Off": "Apagado",
|
||||
"Off": "Desactivado",
|
||||
"Okay, Let's Go!": "Okay, Let's Go!",
|
||||
"Ollama Base URL": "URL base de Ollama",
|
||||
"Ollama Version": "Version de Ollama",
|
||||
"On": "Encendido",
|
||||
"On": "Activado",
|
||||
"Only": "Solamente",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "Sólo se permiten caracteres alfanuméricos y guiones en la cadena de comando.",
|
||||
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "¡Ups! ¡Agárrate fuerte! Tus archivos todavía están en el horno de procesamiento. Los estamos cocinando a la perfección. Tenga paciencia y le avisaremos una vez que estén listos.",
|
||||
|
@ -227,7 +232,7 @@
|
|||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "¡Ups! Estás utilizando un método no compatible (solo frontend). Sirve la WebUI desde el backend.",
|
||||
"Open": "Abrir",
|
||||
"Open AI": "Open AI",
|
||||
"Open AI (Dall-E)": "",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Abrir nuevo chat",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "Clave de OpenAI API",
|
||||
|
@ -243,7 +248,7 @@
|
|||
"Prompt Content": "Contenido del Prompt",
|
||||
"Prompt suggestions": "Sugerencias de Prompts",
|
||||
"Prompts": "Prompts",
|
||||
"Pull a model from Ollama.com": "Extraer un modelo de Ollama.com",
|
||||
"Pull a model from Ollama.com": "Halar un modelo de Ollama.com",
|
||||
"Pull Progress": "Progreso de extracción",
|
||||
"Query Params": "Parámetros de consulta",
|
||||
"RAG Template": "Plantilla de RAG",
|
||||
|
@ -256,7 +261,7 @@
|
|||
"Request Mode": "Modo de petición",
|
||||
"Reset Vector Storage": "Restablecer almacenamiento vectorial",
|
||||
"Response AutoCopy to Clipboard": "Copiar respuesta automáticamente al portapapeles",
|
||||
"Role": "personalizados",
|
||||
"Role": "Rol",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Guardar",
|
||||
|
@ -332,7 +337,10 @@
|
|||
"TTS Settings": "Configuración de TTS",
|
||||
"Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "¡UH oh! Hubo un problema al conectarse a {{provider}}.",
|
||||
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "Comprenda que actualizar o cambiar su modelo de embedding requiere restablecer la base de datos de vectores y volver a importar todos los documentos. ¡Usted ha sido advertido!",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo de archivo desconocido '{{file_type}}', pero se acepta y se trata como texto sin formato",
|
||||
"Update": "Actualizar",
|
||||
"Update embedding model {{embedding_model}}": "Actualizar modelo de embedding {{embedding_model}}",
|
||||
"Update password": "Actualiza contraseña",
|
||||
"Upload a GGUF model": "Sube un modelo GGUF",
|
||||
"Upload files": "Subir archivos",
|
||||
|
@ -340,6 +348,7 @@
|
|||
"URL Mode": "Modo de URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Utilice '#' en el prompt para cargar y seleccionar sus documentos.",
|
||||
"Use Gravatar": "Usar Gravatar",
|
||||
"Use Initials": "Usar Iniciales",
|
||||
"user": "usuario",
|
||||
"User Permissions": "Permisos de usuario",
|
||||
"Users": "Usuarios",
|
||||
|
@ -347,7 +356,7 @@
|
|||
"Valid time units:": "Unidades válidas de tiempo:",
|
||||
"variable": "variable",
|
||||
"variable to have them replaced with clipboard content.": "variable para reemplazarlos con el contenido del portapapeles.",
|
||||
"Version": "Version",
|
||||
"Version": "Versión",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "WebUI Add-ons",
|
||||
"WebUI Settings": "Configuración del WebUI",
|
||||
|
|
|
@ -52,13 +52,17 @@
|
|||
"title": "Dutch (Netherlands)"
|
||||
},
|
||||
{
|
||||
"code": "pt-PT",
|
||||
"title": "Portuguese (Portugal)"
|
||||
"code": "pl-PL",
|
||||
"title": "Polish"
|
||||
},
|
||||
{
|
||||
"code": "pt-BR",
|
||||
"title": "Portuguese (Brazil)"
|
||||
},
|
||||
{
|
||||
"code": "pt-PT",
|
||||
"title": "Portuguese (Portugal)"
|
||||
},
|
||||
{
|
||||
"code": "ru-RU",
|
||||
"title": "Russian (Russia)"
|
||||
|
|
372
src/lib/i18n/locales/pl-pl/translation.json
Normal file
372
src/lib/i18n/locales/pl-pl/translation.json
Normal file
|
@ -0,0 +1,372 @@
|
|||
{
|
||||
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' lub '-1' dla bez wygaśnięcia.",
|
||||
"(Beta)": "(Beta)",
|
||||
"(e.g. `sh webui.sh --api`)": "(np. `sh webui.sh --api`)",
|
||||
"(latest)": "(najnowszy)",
|
||||
"{{modelName}} is thinking...": "{{modelName}} myśli...",
|
||||
"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
|
||||
"a user": "użytkownik",
|
||||
"About": "O nas",
|
||||
"Account": "Konto",
|
||||
"Action": "Akcja",
|
||||
"Add a model": "Dodaj model",
|
||||
"Add a model tag name": "Dodaj nazwę tagu modelu",
|
||||
"Add a short description about what this modelfile does": "Dodaj krótki opis tego, co robi ten plik modelu",
|
||||
"Add a short title for this prompt": "Dodaj krótki tytuł tego polecenia",
|
||||
"Add a tag": "Dodaj tag",
|
||||
"Add Docs": "Dodaj dokumenty",
|
||||
"Add Files": "Dodaj pliki",
|
||||
"Add message": "Dodaj wiadomość",
|
||||
"add tags": "dodaj tagi",
|
||||
"Adjusting these settings will apply changes universally to all users.": "Dostosowanie tych ustawień spowoduje zastosowanie zmian uniwersalnie do wszystkich użytkowników.",
|
||||
"admin": "admin",
|
||||
"Admin Panel": "Panel administracyjny",
|
||||
"Admin Settings": "Ustawienia administratora",
|
||||
"Advanced Parameters": "Zaawansowane parametry",
|
||||
"all": "wszyscy",
|
||||
"All Users": "Wszyscy użytkownicy",
|
||||
"Allow": "Pozwól",
|
||||
"Allow Chat Deletion": "Pozwól na usuwanie czatu",
|
||||
"alphanumeric characters and hyphens": "znaki alfanumeryczne i myślniki",
|
||||
"Already have an account?": "Masz już konto?",
|
||||
"an assistant": "asystent",
|
||||
"and": "i",
|
||||
"API Base URL": "Podstawowy adres URL interfejsu API",
|
||||
"API Key": "Klucz API",
|
||||
"API RPM": "Pakiet API RPM",
|
||||
"are allowed - Activate this command by typing": "są dozwolone - Aktywuj to polecenie, wpisując",
|
||||
"Are you sure?": "Jesteś pewien?",
|
||||
"Audio": "Dźwięk",
|
||||
"Auto-playback response": "Odtwarzanie automatyczne odpowiedzi",
|
||||
"Auto-send input after 3 sec.": "Wysyłanie automatyczne po 3 sek.",
|
||||
"AUTOMATIC1111 Base URL": "Podstawowy adres URL AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Podstawowy adres URL AUTOMATIC1111 jest wymagany.",
|
||||
"available!": "dostępny!",
|
||||
"Back": "Wstecz",
|
||||
"Builder Mode": "Tryb budowniczego",
|
||||
"Cancel": "Anuluj",
|
||||
"Categories": "Kategorie",
|
||||
"Change Password": "Zmień hasło",
|
||||
"Chat": "Czat",
|
||||
"Chat History": "Historia czatu",
|
||||
"Chat History is off for this browser.": "Historia czatu jest wyłączona dla tej przeglądarki.",
|
||||
"Chats": "Czaty",
|
||||
"Check Again": "Sprawdź ponownie",
|
||||
"Check for updates": "Sprawdź aktualizacje",
|
||||
"Checking for updates...": "Sprawdzanie aktualizacji...",
|
||||
"Choose a model before saving...": "Wybierz model przed zapisaniem...",
|
||||
"Chunk Overlap": "Zachodzenie bloku",
|
||||
"Chunk Params": "Parametry bloku",
|
||||
"Chunk Size": "Rozmiar bloku",
|
||||
"Click here for help.": "Kliknij tutaj, aby uzyskać pomoc.",
|
||||
"Click here to check other modelfiles.": "Kliknij tutaj, aby sprawdzić inne pliki modelowe.",
|
||||
"Click here to select": "Kliknij tutaj, aby wybrać",
|
||||
"Click here to select documents.": "Kliknij tutaj, aby wybrać dokumenty.",
|
||||
"click here.": "kliknij tutaj.",
|
||||
"Click on the user role button to change a user's role.": "Kliknij przycisk roli użytkownika, aby zmienić rolę użytkownika.",
|
||||
"Close": "Zamknij",
|
||||
"Collection": "Kolekcja",
|
||||
"Command": "Polecenie",
|
||||
"Confirm Password": "Potwierdź hasło",
|
||||
"Connections": "Połączenia",
|
||||
"Content": "Zawartość",
|
||||
"Context Length": "Długość kontekstu",
|
||||
"Conversation Mode": "Tryb rozmowy",
|
||||
"Copy last code block": "Skopiuj ostatni blok kodu",
|
||||
"Copy last response": "Skopiuj ostatnią odpowiedź",
|
||||
"Copying to clipboard was successful!": "Kopiowanie do schowka zakończone powodzeniem!",
|
||||
"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':": "Utwórz zwięzłą frazę składającą się z 3-5 słów jako nagłówek dla następującego zapytania, ściśle przestrzegając limitu od 3 do 5 słów i unikając użycia słowa 'tytuł':",
|
||||
"Create a modelfile": "Utwórz plik modelu",
|
||||
"Create Account": "Utwórz konto",
|
||||
"Created at": "Utworzono o",
|
||||
"Created by": "Utworzono przez",
|
||||
"Current Model": "Bieżący model",
|
||||
"Current Password": "Bieżące hasło",
|
||||
"Custom": "Niestandardowy",
|
||||
"Customize Ollama models for a specific purpose": "Dostosuj modele Ollama do określonego celu",
|
||||
"Dark": "Ciemny",
|
||||
"Database": "Baza danych",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/RRRR GG:MM",
|
||||
"Default": "Domyślny",
|
||||
"Default (Automatic1111)": "Domyślny (Automatyczny1111)",
|
||||
"Default (Web API)": "Domyślny (Interfejs API)",
|
||||
"Default model updated": "Domyślny model zaktualizowany",
|
||||
"Default Prompt Suggestions": "Domyślne sugestie promptów",
|
||||
"Default User Role": "Domyślna rola użytkownika",
|
||||
"delete": "Usuń",
|
||||
"Delete a model": "Usuń model",
|
||||
"Delete chat": "Usuń czat",
|
||||
"Delete Chats": "Usuń czaty",
|
||||
"Deleted {{deleteModelTag}}": "Usunięto {{deleteModelTag}}",
|
||||
"Deleted {tagName}": "Usunięto {tagName}",
|
||||
"Description": "Opis",
|
||||
"Notifications": "Powiadomienia",
|
||||
"Disabled": "Wyłączone",
|
||||
"Discover a modelfile": "Odkryj plik modelu",
|
||||
"Discover a prompt": "Odkryj prompt",
|
||||
"Discover, download, and explore custom prompts": "Odkryj, pobierz i eksploruj niestandardowe prompty",
|
||||
"Discover, download, and explore model presets": "Odkryj, pobierz i eksploruj ustawienia modeli",
|
||||
"Display the username instead of You in the Chat": "Wyświetl nazwę użytkownika zamiast Ty w czacie",
|
||||
"Document": "Dokument",
|
||||
"Document Settings": "Ustawienia dokumentu",
|
||||
"Documents": "Dokumenty",
|
||||
"does not make any external connections, and your data stays securely on your locally hosted server.": "nie nawiązuje żadnych zewnętrznych połączeń, a Twoje dane pozostają bezpiecznie na Twoim lokalnie hostowanym serwerze.",
|
||||
"Don't Allow": "Nie zezwalaj",
|
||||
"Don't have an account?": "Nie masz konta?",
|
||||
"Download as a File": "Pobierz jako plik",
|
||||
"Download Database": "Pobierz bazę danych",
|
||||
"Drop any files here to add to the conversation": "Upuść pliki tutaj, aby dodać do rozmowy",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "np. '30s', '10m'. Poprawne jednostki czasu to 's', 'm', 'h'.",
|
||||
"Edit Doc": "Edytuj dokument",
|
||||
"Edit User": "Edytuj użytkownika",
|
||||
"Email": "Email",
|
||||
"Embedding model: {{embedding_model}}": "Osadzony model: {{embedding_model}}",
|
||||
"Enable Chat History": "Włącz historię czatu",
|
||||
"Enable New Sign Ups": "Włącz nowe rejestracje",
|
||||
"Enabled": "Włączone",
|
||||
"Enter {{role}} message here": "Wprowadź wiadomość {{role}} tutaj",
|
||||
"Enter API Key": "Wprowadź klucz API",
|
||||
"Enter Chunk Overlap": "Wprowadź zakchodzenie bloku",
|
||||
"Enter Chunk Size": "Wprowadź rozmiar bloku",
|
||||
"Enter Image Size (e.g. 512x512)": "Wprowadź rozmiar obrazu (np. 512x512)",
|
||||
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Wprowadź bazowy adres URL LiteLLM API (litellm_params.api_base)",
|
||||
"Enter LiteLLM API Key (litellm_params.api_key)": "Wprowadź klucz API LiteLLM (litellm_params.api_key)",
|
||||
"Enter LiteLLM API RPM (litellm_params.rpm)": "Wprowadź API LiteLLM RPM(litellm_params.rpm)",
|
||||
"Enter LiteLLM Model (litellm_params.model)": "Wprowadź model LiteLLM (litellm_params.model)",
|
||||
"Enter Max Tokens (litellm_params.max_tokens)": "Wprowadź maksymalną liczbę tokenów (litellm_params.max_tokens)",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Wprowadź tag modelu (np. {{modelTag}})",
|
||||
"Enter Number of Steps (e.g. 50)": "Wprowadź liczbę kroków (np. 50)",
|
||||
"Enter stop sequence": "Wprowadź sekwencję zatrzymania",
|
||||
"Enter Top K": "Wprowadź Top K",
|
||||
"Enter URL (e.g. http://127.0.0.1:7860/)": "Wprowadź adres URL (np. http://127.0.0.1:7860/)",
|
||||
"Enter Your Email": "Wprowadź swój adres email",
|
||||
"Enter Your Full Name": "Wprowadź swoje imię i nazwisko",
|
||||
"Enter Your Password": "Wprowadź swoje hasło",
|
||||
"Experimental": "Eksperymentalne",
|
||||
"Export All Chats (All Users)": "Eksportuj wszystkie czaty (wszyscy użytkownicy)",
|
||||
"Export Chats": "Eksportuj czaty",
|
||||
"Export Documents Mapping": "Eksportuj mapowanie dokumentów",
|
||||
"Export Modelfiles": "Eksportuj pliki modeli",
|
||||
"Export Prompts": "Eksportuj prompty",
|
||||
"Failed to read clipboard contents": "Nie udało się odczytać zawartości schowka",
|
||||
"File Mode": "Tryb pliku",
|
||||
"File not found.": "Plik nie został znaleziony.",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Wykryto podszywanie się pod odcisk palca: Nie można używać inicjałów jako awatara. Przechodzenie do domyślnego obrazu profilowego.",
|
||||
"Focus chat input": "Skoncentruj na czacie",
|
||||
"Format your variables using square brackets like this:": "Formatuj swoje zmienne, używając nawiasów kwadratowych, np.",
|
||||
"From (Base Model)": "Z (Model Podstawowy)",
|
||||
"Full Screen Mode": "Tryb pełnoekranowy",
|
||||
"General": "Ogólne",
|
||||
"General Settings": "Ogólne ustawienia",
|
||||
"Hello, {{name}}": "Witaj, {{nazwa}}",
|
||||
"Hide": "Ukryj",
|
||||
"Hide Additional Params": "Ukryj dodatkowe parametry",
|
||||
"How can I help you today?": "Jak mogę Ci dzisiaj pomóc?",
|
||||
"Image Generation (Experimental)": "Generowanie obrazu (eksperymentalne)",
|
||||
"Image Generation Engine": "Silnik generowania obrazu",
|
||||
"Image Settings": "Ustawienia obrazu",
|
||||
"Images": "Obrazy",
|
||||
"Import Chats": "Importuj rozmowy",
|
||||
"Import Documents Mapping": "Importuj mapowanie dokumentów",
|
||||
"Import Modelfiles": "Importuj pliki modeli",
|
||||
"Import Prompts": "Importuj prompty",
|
||||
"Include `--api` flag when running stable-diffusion-webui": "Dołącz flagę `--api` podczas uruchamiania stable-diffusion-webui",
|
||||
"Interface": "Interfejs",
|
||||
"join our Discord for help.": "Dołącz do naszego Discorda po pomoc.",
|
||||
"JSON": "JSON",
|
||||
"JWT Expiration": "Wygaśnięcie JWT",
|
||||
"JWT Token": "Token JWT",
|
||||
"Keep Alive": "Zachowaj łączność",
|
||||
"Keyboard shortcuts": "Skróty klawiszowe",
|
||||
"Language": "Język",
|
||||
"Light": "Jasny",
|
||||
"Listening...": "Nasłuchiwanie...",
|
||||
"LLMs can make mistakes. Verify important information.": "LLMy mogą popełniać błędy. Zweryfikuj ważne informacje.",
|
||||
"Made by OpenWebUI Community": "Stworzone przez społeczność OpenWebUI",
|
||||
"Make sure to enclose them with": "Upewnij się, że są one zamknięte w",
|
||||
"Manage LiteLLM Models": "Zarządzaj modelami LiteLLM",
|
||||
"Manage Models": "Zarządzaj modelami",
|
||||
"Manage Ollama Models": "Zarządzaj modelami Ollama",
|
||||
"Max Tokens": "Maksymalna liczba tokenów",
|
||||
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Maksymalnie 3 modele można pobierać jednocześnie. Spróbuj ponownie później.",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{nazwaModelu}}' został pomyślnie pobrany.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{nazwaModelu}}' jest już w kolejce do pobrania.",
|
||||
"Model {{embedding_model}} update complete!": "Aktualizacja modelu {{embedding_model}} zakończona pomyślnie!",
|
||||
"Model {{embedding_model}} update failed or not required!": "Model {{embedding_model}} aktualizacja nie powiodła się lub nie jest wymagana!",
|
||||
"Model {{modelId}} not found": "Model {{modelId}} nie został znaleziony",
|
||||
"Model {{modelName}} already exists.": "Model {{modelName}} już istnieje.",
|
||||
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Wykryto ścieżkę systemu plików modelu. Wymagana jest krótka nazwa modelu do aktualizacji, nie można kontynuować.",
|
||||
"Model Name": "Nazwa modelu",
|
||||
"Model not selected": "Model nie został wybrany",
|
||||
"Model Tag Name": "Nazwa tagu modelu",
|
||||
"Model Whitelisting": "Whitelisting modelu",
|
||||
"Model(s) Whitelisted": "Model(e) dodane do listy białej",
|
||||
"Modelfile": "Plik modelu",
|
||||
"Modelfile Advanced Settings": "Zaawansowane ustawienia pliku modelu",
|
||||
"Modelfile Content": "Zawartość pliku modelu",
|
||||
"Modelfiles": "Pliki modeli",
|
||||
"Models": "Modele",
|
||||
"My Documents": "Moje dokumenty",
|
||||
"My Modelfiles": "Moje pliki modeli",
|
||||
"My Prompts": "Moje prompty",
|
||||
"Name": "Nazwa",
|
||||
"Name Tag": "Etykieta nazwy",
|
||||
"Name your modelfile": "Nadaj nazwę swojemu plikowi modelu",
|
||||
"New Chat": "Nowy czat",
|
||||
"New Password": "Nowe hasło",
|
||||
"Not sure what to add?": "Nie wiesz, co dodać?",
|
||||
"Not sure what to write? Switch to": "Nie wiesz, co napisać? Przełącz się na",
|
||||
"Off": "Wyłączony",
|
||||
"Okay, Let's Go!": "Okej, zaczynamy!",
|
||||
"Ollama Base URL": "Adres bazowy URL Ollama",
|
||||
"Ollama Version": "Wersja Ollama",
|
||||
"On": "Włączony",
|
||||
"Only": "Tylko",
|
||||
"Only alphanumeric characters and hyphens are allowed in the command string.": "W poleceniu dozwolone są tylko znaki alfanumeryczne i myślniki.",
|
||||
"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.": "Ups! Trzymaj się! Twoje pliki są wciąż w procesie obróbki. Gotujemy je do perfekcji. Prosimy o cierpliwość, poinformujemy Cię, gdy będą gotowe.",
|
||||
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Wygląda na to, że URL jest nieprawidłowy. Sprawdź jeszcze raz i spróbuj ponownie.",
|
||||
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Używasz nieobsługiwaniej metody (tylko interfejs front-end). Proszę obsłużyć interfejs WebUI z poziomu backendu.",
|
||||
"Open": "Otwórz",
|
||||
"Open AI": "Open AI",
|
||||
"Open AI (Dall-E)": "Open AI (Dall-E)",
|
||||
"Open new chat": "Otwórz nowy czat",
|
||||
"OpenAI API": "OpenAI API",
|
||||
"OpenAI API Key": "Klucz API OpenAI",
|
||||
"OpenAI API Key is required.": "Klucz API OpenAI jest wymagany.",
|
||||
"or": "lub",
|
||||
"Parameters": "Parametry",
|
||||
"Password": "Hasło",
|
||||
"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
|
||||
"pending": "oczekujące",
|
||||
"Permission denied when accessing microphone: {{error}}": "Odmowa dostępu do mikrofonu: {{error}}",
|
||||
"Playground": "Plac zabaw",
|
||||
"Profile": "Profil",
|
||||
"Prompt Content": "Zawartość prompta",
|
||||
"Prompt suggestions": "Sugestie prompta",
|
||||
"Prompts": "Prompty",
|
||||
"Pull a model from Ollama.com": "Pobierz model z Ollama.com",
|
||||
"Pull Progress": "Postęp pobierania",
|
||||
"Query Params": "Parametry zapytania",
|
||||
"RAG Template": "Szablon RAG",
|
||||
"Raw Format": "Format bez obróbki",
|
||||
"Record voice": "Nagraj głos",
|
||||
"Redirecting you to OpenWebUI Community": "Przekierowujemy Cię do społeczności OpenWebUI",
|
||||
"Release Notes": "Notatki wydania",
|
||||
"Repeat Last N": "Powtórz ostatnie N",
|
||||
"Repeat Penalty": "Kara za powtórzenie",
|
||||
"Request Mode": "Tryb żądania",
|
||||
"Reset Vector Storage": "Resetuj przechowywanie wektorów",
|
||||
"Response AutoCopy to Clipboard": "Automatyczne kopiowanie odpowiedzi do schowka",
|
||||
"Role": "Rola",
|
||||
"Rosé Pine": "Rosé Pine",
|
||||
"Rosé Pine Dawn": "Rosé Pine Dawn",
|
||||
"Save": "Zapisz",
|
||||
"Save & Create": "Zapisz i utwórz",
|
||||
"Save & Submit": "Zapisz i wyślij",
|
||||
"Save & Update": "Zapisz i zaktualizuj",
|
||||
"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": "Bezpośrednie zapisywanie dzienników czatu w pamięci przeglądarki nie jest już obsługiwane. Prosimy o pobranie i usunięcie dzienników czatu, klikając poniższy przycisk. Nie martw się, możesz łatwo ponownie zaimportować dzienniki czatu do backendu za pomocą",
|
||||
"Scan": "Skanuj",
|
||||
"Scan complete!": "Skanowanie zakończone!",
|
||||
"Scan for documents from {{path}}": "Skanuj dokumenty z {{path}}",
|
||||
"Search": "Szukaj",
|
||||
"Search Documents": "Szukaj dokumentów",
|
||||
"Search Prompts": "Szukaj promptów",
|
||||
"See readme.md for instructions": "Zajrzyj do readme.md po instrukcje",
|
||||
"See what's new": "Zobacz co nowego",
|
||||
"Seed": "Seed",
|
||||
"Select a mode": "Wybierz tryb",
|
||||
"Select a model": "Wybierz model",
|
||||
"Select an Ollama instance": "Wybierz instancję Ollama",
|
||||
"Send a Message": "Wyślij Wiadomość",
|
||||
"Send message": "Wyślij wiadomość",
|
||||
"Server connection verified": "Połączenie z serwerem zweryfikowane",
|
||||
"Set as default": "Ustaw jako domyślne",
|
||||
"Set Default Model": "Ustaw domyślny model",
|
||||
"Set Image Size": "Ustaw rozmiar obrazu",
|
||||
"Set Steps": "Ustaw kroki",
|
||||
"Set Title Auto-Generation Model": "Ustaw model automatycznego generowania tytułów",
|
||||
"Set Voice": "Ustaw głos",
|
||||
"Settings": "Ustawienia",
|
||||
"Settings saved successfully!": "Ustawienia zapisane pomyślnie!",
|
||||
"Share to OpenWebUI Community": "Dziel się z społecznością OpenWebUI",
|
||||
"short-summary": "Krótkie podsumowanie",
|
||||
"Show": "Pokaż",
|
||||
"Show Additional Params": "Pokaż dodatkowe parametry",
|
||||
"Show shortcuts": "Pokaż skróty",
|
||||
"sidebar": "Panel boczny",
|
||||
"Sign in": "Zaloguj się",
|
||||
"Sign Out": "Wyloguj się",
|
||||
"Sign up": "Zarejestruj się",
|
||||
"Speech recognition error: {{error}}": "Błąd rozpoznawania mowy: {{error}}",
|
||||
"Speech-to-Text Engine": "Silnik mowy na tekst",
|
||||
"SpeechRecognition API is not supported in this browser.": "API Rozpoznawania Mowy nie jest obsługiwane w tej przeglądarce.",
|
||||
"Stop Sequence": "Zatrzymaj sekwencję",
|
||||
"STT Settings": "Ustawienia STT",
|
||||
"Submit": "Zatwierdź",
|
||||
"Success": "Sukces",
|
||||
"Successfully updated.": "Pomyślnie zaktualizowano.",
|
||||
"Sync All": "Synchronizuj wszystko",
|
||||
"System": "System",
|
||||
"System Prompt": "Prompt systemowy",
|
||||
"Tags": "Tagi",
|
||||
"Temperature": "Temperatura",
|
||||
"Template": "Szablon",
|
||||
"Text Completion": "Uzupełnienie tekstu",
|
||||
"Text-to-Speech Engine": "Silnik tekstu na mowę",
|
||||
"Tfs Z": "Tfs Z",
|
||||
"Theme": "Motyw",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zapewnia, że Twoje cenne rozmowy są bezpiecznie zapisywane w bazie danych backendowej. Dziękujemy!",
|
||||
"This setting does not sync across browsers or devices.": "To ustawienie nie synchronizuje się między przeglądarkami ani urządzeniami.",
|
||||
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Porada: Aktualizuj wiele zmiennych kolejno, naciskając klawisz tabulatora w polu wprowadzania czatu po każdej zmianie.",
|
||||
"Title": "Tytuł",
|
||||
"Title Auto-Generation": "Automatyczne generowanie tytułu",
|
||||
"Title Generation Prompt": "Prompt generowania tytułu",
|
||||
"to": "do",
|
||||
"To access the available model names for downloading,": "Aby uzyskać dostęp do dostępnych nazw modeli do pobrania,",
|
||||
"To access the GGUF models available for downloading,": "Aby uzyskać dostęp do dostępnych modeli GGUF do pobrania,",
|
||||
"to chat input.": "do pola wprowadzania czatu.",
|
||||
"Toggle settings": "Przełącz ustawienia",
|
||||
"Toggle sidebar": "Przełącz panel boczny",
|
||||
"Top K": "Najlepsze K",
|
||||
"Top P": "Najlepsze P",
|
||||
"Trouble accessing Ollama?": "Problemy z dostępem do Ollama?",
|
||||
"TTS Settings": "Ustawienia TTS",
|
||||
"Type Hugging Face Resolve (Download) URL": "Wprowadź adres URL do pobrania z Hugging Face",
|
||||
"Uh-oh! There was an issue connecting to {{provider}}.": "O nie! Wystąpił problem z połączeniem z {{provider}}.",
|
||||
"Understand that updating or changing your embedding model requires reset of the vector database and re-import of all documents. You have been warned!": "Zrozum, że aktualizacja lub zmiana modelu osadzania wymaga zresetowania bazy wektorów i ponownego zaimportowania wszystkich dokumentów. Zostałeś ostrzeżony!",
|
||||
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Nieznany typ pliku '{{file_type}}', ale akceptowany i traktowany jako zwykły tekst",
|
||||
"Update": "Aktualizacja",
|
||||
"Update embedding model {{embedding_model}}": "Aktualizuj modelu osadzania {{embedding_model}}",
|
||||
"Update password": "Aktualizacja hasła",
|
||||
"Upload a GGUF model": "Prześlij model GGUF",
|
||||
"Upload files": "Prześlij pliki",
|
||||
"Upload Progress": "Postęp przesyłania",
|
||||
"URL Mode": "Tryb adresu URL",
|
||||
"Use '#' in the prompt input to load and select your documents.": "Użyj '#' w polu wprowadzania polecenia, aby załadować i wybrać swoje dokumenty.",
|
||||
"Use Gravatar": "Użyj Gravatara",
|
||||
"Use Initials": "Użyj inicjałów",
|
||||
"user": "użytkownik",
|
||||
"User Permissions": "Uprawnienia użytkownika",
|
||||
"Users": "Użytkownicy",
|
||||
"Utilize": "Wykorzystaj",
|
||||
"Valid time units:": "Poprawne jednostki czasu:",
|
||||
"variable": "zmienna",
|
||||
"variable to have them replaced with clipboard content.": "zmienna która zostanie zastąpiona zawartością schowka.",
|
||||
"Version": "Wersja",
|
||||
"Web": "Sieć",
|
||||
"WebUI Add-ons": "Dodatki do interfejsu WebUI",
|
||||
"WebUI Settings": "Ustawienia interfejsu WebUI",
|
||||
"WebUI will make requests to": "Interfejs sieciowy będzie wysyłał żądania do",
|
||||
"What’s New in": "Co nowego w",
|
||||
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kiedy historia jest wyłączona, nowe rozmowy na tej przeglądarce nie będą widoczne w historii na żadnym z twoich urządzeń.",
|
||||
"Whisper (Local)": "Whisper (Lokalnie)",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Napisz sugestię do polecenia (np. Kim jesteś?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Napisz podsumowanie w 50 słowach, które podsumowuje [temat lub słowo kluczowe].",
|
||||
"You": "Ty",
|
||||
"You're a helpful assistant.": "Jesteś pomocnym asystentem.",
|
||||
"You're now logged in.": "Jesteś teraz zalogowany."
|
||||
}
|
|
@ -86,7 +86,7 @@
|
|||
"Customize Ollama models for a specific purpose": "Personalize os modelos Ollama para um propósito específico",
|
||||
"Dark": "Escuro",
|
||||
"Database": "Banco de dados",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/AAAA HH:mm",
|
||||
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
|
||||
"Default": "Padrão",
|
||||
"Default (Automatic1111)": "Padrão (Automatic1111)",
|
||||
"Default (Web API)": "Padrão (API Web)",
|
||||
|
|
|
@ -55,9 +55,9 @@
|
|||
"Check for updates": "Kiểm tra cập nhật",
|
||||
"Checking for updates...": "Đang kiểm tra cập nhật...",
|
||||
"Choose a model before saving...": "Chọn mô hình trước khi lưu...",
|
||||
"Chunk Overlap": "Kích thước chồng lấn (overlap)",
|
||||
"Chunk Overlap": "Chồng lấn (overlap)",
|
||||
"Chunk Params": "Cài đặt số lượng ký tự cho khối ký tự (chunk)",
|
||||
"Chunk Size": "Kích thức khối (size)",
|
||||
"Chunk Size": "Kích thước khối (size)",
|
||||
"Click here for help.": "Bấm vào đây để được trợ giúp.",
|
||||
"Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.",
|
||||
"Click here to select": "Bấm vào đây để chọn",
|
||||
|
@ -65,7 +65,7 @@
|
|||
"click here.": "bấm vào đây.",
|
||||
"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.",
|
||||
"Close": "Đóng",
|
||||
"Collection": "Bộ sưu tập",
|
||||
"Collection": "Tổng hợp mọi tài liệu",
|
||||
"Command": "Lệnh",
|
||||
"Confirm Password": "Xác nhận Mật khẩu",
|
||||
"Connections": "Kết nối",
|
||||
|
@ -76,7 +76,7 @@
|
|||
"Copy last response": "Sao chép phản hồi cuối cùng",
|
||||
"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!",
|
||||
"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':": "Tạo một cụm từ súc tích, 3-5 từ làm tiêu đề cho truy vấn sau, tuân thủ nghiêm ngặt giới hạn 3-5 từ và tránh sử dụng từ 'tiêu đề':",
|
||||
"Create a modelfile": "Tạo tệp mô tả mô hình",
|
||||
"Create a modelfile": "Tạo tệp mô tả cho mô hình",
|
||||
"Create Account": "Tạo Tài khoản",
|
||||
"Created at": "Được tạo vào lúc",
|
||||
"Created by": "Được tạo bởi",
|
||||
|
@ -347,7 +347,7 @@
|
|||
"Valid time units:": "Đơn vị thời gian hợp lệ:",
|
||||
"variable": "biến",
|
||||
"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.",
|
||||
"Version": "Phiên bản",
|
||||
"Version": "Version",
|
||||
"Web": "Web",
|
||||
"WebUI Add-ons": "Tiện ích WebUI",
|
||||
"WebUI Settings": "Cài đặt WebUI",
|
||||
|
|
|
@ -467,3 +467,52 @@ export const blobToFile = (blob, fileName) => {
|
|||
const file = new File([blob], fileName, { type: blob.type });
|
||||
return file;
|
||||
};
|
||||
|
||||
// promptTemplate replaces any occurrences of the following in the template with the prompt
|
||||
// {{prompt}} will be replaced with the prompt
|
||||
// {{prompt:start:<length>}} will be replaced with the first <length> characters of the prompt
|
||||
// {{prompt:end:<length>}} will be replaced with the last <length> characters of the prompt
|
||||
// Character length is used as we don't have the ability to tokenize the prompt
|
||||
export const promptTemplate = (template: string, prompt: string) => {
|
||||
template = template.replace(/{{prompt}}/g, prompt);
|
||||
|
||||
// Replace all instances of {{prompt:start:<length>}} with the first <length> characters of the prompt
|
||||
const startRegex = /{{prompt:start:(\d+)}}/g;
|
||||
let startMatch: RegExpMatchArray | null;
|
||||
while ((startMatch = startRegex.exec(template)) !== null) {
|
||||
const length = parseInt(startMatch[1]);
|
||||
template = template.replace(startMatch[0], prompt.substring(0, length));
|
||||
}
|
||||
|
||||
// Replace all instances of {{prompt:end:<length>}} with the last <length> characters of the prompt
|
||||
const endRegex = /{{prompt:end:(\d+)}}/g;
|
||||
let endMatch: RegExpMatchArray | null;
|
||||
while ((endMatch = endRegex.exec(template)) !== null) {
|
||||
const length = parseInt(endMatch[1]);
|
||||
template = template.replace(endMatch[0], prompt.substring(prompt.length - length));
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
export const approximateToHumanReadable = (nanoseconds: number) => {
|
||||
const seconds = Math.floor((nanoseconds / 1e9) % 60);
|
||||
const minutes = Math.floor((nanoseconds / 6e10) % 60);
|
||||
const hours = Math.floor((nanoseconds / 3.6e12) % 24);
|
||||
|
||||
const results: string[] = [];
|
||||
|
||||
if (seconds >= 0) {
|
||||
results.push(`${seconds}s`);
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
results.push(`${minutes}m`);
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
results.push(`${hours}h`);
|
||||
}
|
||||
|
||||
return results.reverse().join(' ');
|
||||
};
|
||||
|
|
|
@ -106,11 +106,6 @@
|
|||
// IndexedDB Not Found
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
await models.set(await getModels());
|
||||
await tick();
|
||||
|
||||
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
|
||||
|
||||
await modelfiles.set(await getModelfiles(localStorage.token));
|
||||
|
|
|
@ -849,9 +849,6 @@
|
|||
shareEnabled={messages.length > 0}
|
||||
{chat}
|
||||
{initNewChat}
|
||||
{tags}
|
||||
{addTag}
|
||||
{deleteTag}
|
||||
/>
|
||||
<div class="flex flex-col flex-auto">
|
||||
<div
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
|
||||
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
|
||||
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
|
||||
import Pagination from '$lib/components/common/Pagination.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -21,6 +22,8 @@
|
|||
let search = '';
|
||||
let selectedUser = null;
|
||||
|
||||
let page = 1;
|
||||
|
||||
let showSettingsModal = false;
|
||||
let showEditUserModal = false;
|
||||
|
||||
|
@ -159,15 +162,17 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users.filter((user) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
} else {
|
||||
let name = user.name.toLowerCase();
|
||||
const query = search.toLowerCase();
|
||||
return name.includes(query);
|
||||
}
|
||||
}) as user}
|
||||
{#each users
|
||||
.filter((user) => {
|
||||
if (search === '') {
|
||||
return true;
|
||||
} else {
|
||||
let name = user.name.toLowerCase();
|
||||
const query = search.toLowerCase();
|
||||
return name.includes(query);
|
||||
}
|
||||
})
|
||||
.slice((page - 1) * 20, page * 20) as user}
|
||||
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
|
||||
<td class="px-3 py-2 min-w-[7rem] w-28">
|
||||
<button
|
||||
|
@ -270,6 +275,8 @@
|
|||
<div class=" text-gray-500 text-xs mt-2 text-right">
|
||||
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
|
||||
</div>
|
||||
|
||||
<Pagination bind:page count={users.length} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -30,14 +30,12 @@
|
|||
getTagsById,
|
||||
updateChatById
|
||||
} from '$lib/apis/chats';
|
||||
import { queryCollection, queryDoc } from '$lib/apis/rag';
|
||||
import { generateOpenAIChatCompletion, generateTitle } from '$lib/apis/openai';
|
||||
|
||||
import MessageInput from '$lib/components/chat/MessageInput.svelte';
|
||||
import Messages from '$lib/components/chat/Messages.svelte';
|
||||
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
|
||||
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||
import { RAGTemplate } from '$lib/utils/rag';
|
||||
|
||||
import {
|
||||
LITELLM_API_BASE_URL,
|
||||
OPENAI_API_BASE_URL,
|
||||
|
@ -877,9 +875,6 @@
|
|||
|
||||
goto('/');
|
||||
}}
|
||||
{tags}
|
||||
{addTag}
|
||||
{deleteTag}
|
||||
/>
|
||||
<div class="flex flex-col flex-auto">
|
||||
<div
|
||||
|
|
Loading…
Reference in a new issue